Merge pull request #4573 from RalfJung/rustup

Rustup
This commit is contained in:
Ralf Jung 2025-09-08 10:27:05 +00:00 committed by GitHub
commit 3430751d7e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
320 changed files with 6104 additions and 4125 deletions

View file

@ -336,7 +336,7 @@ dependencies = [
"curl",
"indexmap",
"serde",
"toml 0.7.8",
"toml 0.8.23",
]
[[package]]
@ -4022,6 +4022,7 @@ dependencies = [
name = "rustc_lint"
version = "0.0.0"
dependencies = [
"bitflags",
"rustc_abi",
"rustc_ast",
"rustc_ast_pretty",
@ -4072,7 +4073,6 @@ name = "rustc_log"
version = "0.0.0"
dependencies = [
"tracing",
"tracing-core",
"tracing-subscriber",
"tracing-tree",
]
@ -5541,9 +5541,9 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.30"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
dependencies = [
"once_cell",
"valuable",

View file

@ -503,7 +503,16 @@ pub(crate) fn apply_computed_concrete_opaque_types<'tcx>(
let mut errors = Vec::new();
for &(key, hidden_type) in opaque_types {
let Some(expected) = get_concrete_opaque_type(concrete_opaque_types, key.def_id) else {
assert!(tcx.use_typing_mode_borrowck(), "non-defining use in defining scope");
if !tcx.use_typing_mode_borrowck() {
if let ty::Alias(ty::Opaque, alias_ty) = hidden_type.ty.kind()
&& alias_ty.def_id == key.def_id.to_def_id()
&& alias_ty.args == key.args
{
continue;
} else {
unreachable!("non-defining use in defining scope");
}
}
errors.push(DeferredOpaqueTypeError::NonDefiningUseInDefiningScope {
span: hidden_type.span,
opaque_type_key: key,

View file

@ -121,7 +121,7 @@ rm tests/ui/abi/large-byval-align.rs # exceeds implementation limit of Cranelift
# ============================================================
rm -r tests/run-make/remap-path-prefix-dwarf # requires llvm-dwarfdump
rm -r tests/run-make/strip # same
rm -r tests/run-make/compiler-builtins # Expects lib/rustlib/src/rust to contains the standard library source
rm -r tests/run-make-cargo/compiler-builtins # Expects lib/rustlib/src/rust to contains the standard library source
rm -r tests/run-make/translation # same
rm -r tests/run-make/missing-unstable-trait-bound # This disables support for unstable features, but running cg_clif needs some unstable features
rm -r tests/run-make/const-trait-stable-toolchain # same
@ -166,5 +166,5 @@ index 073116933bd..c3e4578204d 100644
EOF
echo "[TEST] rustc test suite"
./x.py test --stage 0 --test-args=--no-capture tests/{codegen-units,run-make,ui,incremental}
./x.py test --stage 0 --test-args=--no-capture tests/{codegen-units,run-make,run-make-cargo,ui,incremental}
popd

View file

@ -12,7 +12,9 @@ use cranelift_object::{ObjectBuilder, ObjectModule};
use rustc_codegen_ssa::assert_module_sources::CguReuse;
use rustc_codegen_ssa::back::link::ensure_removed;
use rustc_codegen_ssa::base::determine_cgu_reuse;
use rustc_codegen_ssa::{CodegenResults, CompiledModule, CrateInfo, errors as ssa_errors};
use rustc_codegen_ssa::{
CodegenResults, CompiledModule, CrateInfo, ModuleKind, errors as ssa_errors,
};
use rustc_data_structures::profiling::SelfProfilerRef;
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
use rustc_data_structures::sync::{IntoDynSyncSend, par_map};
@ -361,6 +363,7 @@ fn emit_cgu(
invocation_temp,
prof,
product.object,
ModuleKind::Regular,
name.clone(),
producer,
)?;
@ -369,6 +372,7 @@ fn emit_cgu(
module_regular,
module_global_asm: global_asm_object_file.map(|global_asm_object_file| CompiledModule {
name: format!("{name}.asm"),
kind: ModuleKind::Regular,
object: Some(global_asm_object_file),
dwarf_object: None,
bytecode: None,
@ -385,6 +389,7 @@ fn emit_module(
invocation_temp: Option<&str>,
prof: &SelfProfilerRef,
mut object: cranelift_object::object::write::Object<'_>,
kind: ModuleKind,
name: String,
producer_str: &str,
) -> Result<CompiledModule, String> {
@ -425,6 +430,7 @@ fn emit_module(
Ok(CompiledModule {
name,
kind,
object: Some(tmp_file),
dwarf_object: None,
bytecode: None,
@ -479,6 +485,7 @@ fn reuse_workproduct_for_cgu(
Ok(ModuleCodegenResult {
module_regular: CompiledModule {
name: cgu.name().to_string(),
kind: ModuleKind::Regular,
object: Some(obj_out_regular),
dwarf_object: None,
bytecode: None,
@ -488,6 +495,7 @@ fn reuse_workproduct_for_cgu(
},
module_global_asm: source_file_global_asm.map(|source_file| CompiledModule {
name: cgu.name().to_string(),
kind: ModuleKind::Regular,
object: Some(obj_out_global_asm),
dwarf_object: None,
bytecode: None,
@ -643,6 +651,7 @@ fn emit_allocator_module(tcx: TyCtxt<'_>) -> Option<CompiledModule> {
tcx.sess.invocation_temp.as_deref(),
&tcx.sess.prof,
product.object,
ModuleKind::Allocator,
"allocator_shim".to_owned(),
&crate::debuginfo::producer(tcx.sess),
) {

View file

@ -1083,11 +1083,12 @@ where
fn test_rustc(env: &Env, args: &TestArg) -> Result<(), String> {
test_rustc_inner(env, args, |_| Ok(false), false, "run-make")?;
test_rustc_inner(env, args, |_| Ok(false), false, "run-make-cargo")?;
test_rustc_inner(env, args, |_| Ok(false), false, "ui")
}
fn test_failing_rustc(env: &Env, args: &TestArg) -> Result<(), String> {
let result1 = test_rustc_inner(
let run_make_result = test_rustc_inner(
env,
args,
retain_files_callback("tests/failing-run-make-tests.txt", "run-make"),
@ -1095,7 +1096,15 @@ fn test_failing_rustc(env: &Env, args: &TestArg) -> Result<(), String> {
"run-make",
);
let result2 = test_rustc_inner(
let run_make_cargo_result = test_rustc_inner(
env,
args,
retain_files_callback("tests/failing-run-make-tests.txt", "run-make-cargo"),
false,
"run-make",
);
let ui_result = test_rustc_inner(
env,
args,
retain_files_callback("tests/failing-ui-tests.txt", "ui"),
@ -1103,7 +1112,7 @@ fn test_failing_rustc(env: &Env, args: &TestArg) -> Result<(), String> {
"ui",
);
result1.and(result2)
run_make_result.and(run_make_cargo_result).and(ui_result)
}
fn test_successful_rustc(env: &Env, args: &TestArg) -> Result<(), String> {
@ -1120,6 +1129,13 @@ fn test_successful_rustc(env: &Env, args: &TestArg) -> Result<(), String> {
remove_files_callback("tests/failing-run-make-tests.txt", "run-make"),
false,
"run-make",
)?;
test_rustc_inner(
env,
args,
remove_files_callback("tests/failing-run-make-tests.txt", "run-make-cargo"),
false,
"run-make-cargo",
)
}

View file

@ -305,12 +305,9 @@ pub(crate) fn run_thin(
)
}
pub(crate) fn prepare_thin(
module: ModuleCodegen<GccContext>,
_emit_summary: bool,
) -> (String, ThinBuffer) {
pub(crate) fn prepare_thin(module: ModuleCodegen<GccContext>) -> (String, ThinBuffer) {
let name = module.name;
//let buffer = ThinBuffer::new(module.module_llvm.context, true, emit_summary);
//let buffer = ThinBuffer::new(module.module_llvm.context, true);
let buffer = ThinBuffer::new(&module.module_llvm.context);
(name, buffer)
}
@ -650,10 +647,6 @@ impl ThinBufferMethods for ThinBuffer {
fn data(&self) -> &[u8] {
&[]
}
fn thin_link_data(&self) -> &[u8] {
unimplemented!();
}
}
pub struct ThinData; //(Arc<TempDir>);

View file

@ -408,11 +408,8 @@ impl WriteBackendMethods for GccCodegenBackend {
back::write::codegen(cgcx, module, config)
}
fn prepare_thin(
module: ModuleCodegen<Self::Module>,
emit_summary: bool,
) -> (String, Self::ThinBuffer) {
back::lto::prepare_thin(module, emit_summary)
fn prepare_thin(module: ModuleCodegen<Self::Module>) -> (String, Self::ThinBuffer) {
back::lto::prepare_thin(module)
}
fn serialize_module(_module: ModuleCodegen<Self::Module>) -> (String, Self::ModuleBuffer) {

View file

@ -296,6 +296,19 @@ pub(crate) fn tune_cpu_attr<'ll>(cx: &CodegenCx<'ll, '_>) -> Option<&'ll Attribu
.map(|tune_cpu| llvm::CreateAttrStringValue(cx.llcx, "tune-cpu", tune_cpu))
}
/// Get the `target-features` LLVM attribute.
pub(crate) fn target_features_attr<'ll>(
cx: &CodegenCx<'ll, '_>,
function_features: Vec<String>,
) -> Option<&'ll Attribute> {
let global_features = cx.tcx.global_backend_features(()).iter().map(String::as_str);
let function_features = function_features.iter().map(String::as_str);
let target_features =
global_features.chain(function_features).intersperse(",").collect::<String>();
(!target_features.is_empty())
.then(|| llvm::CreateAttrStringValue(cx.llcx, "target-features", &target_features))
}
/// Get the `NonLazyBind` LLVM attribute,
/// if the codegen options allow skipping the PLT.
pub(crate) fn non_lazy_bind_attr<'ll>(cx: &CodegenCx<'ll, '_>) -> Option<&'ll Attribute> {
@ -523,14 +536,7 @@ pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>(
}
}
let global_features = cx.tcx.global_backend_features(()).iter().map(|s| s.as_str());
let function_features = function_features.iter().map(|s| s.as_str());
let target_features: String =
global_features.chain(function_features).intersperse(",").collect();
if !target_features.is_empty() {
to_add.push(llvm::CreateAttrStringValue(cx.llcx, "target-features", &target_features));
}
to_add.extend(target_features_attr(cx, function_features));
attributes::apply_to_llfn(llfn, Function, &to_add);
}

View file

@ -11,7 +11,7 @@ use object::{Object, ObjectSection};
use rustc_codegen_ssa::back::lto::{SerializedModule, ThinModule, ThinShared};
use rustc_codegen_ssa::back::write::{CodegenContext, FatLtoInput};
use rustc_codegen_ssa::traits::*;
use rustc_codegen_ssa::{ModuleCodegen, looks_like_rust_object_file};
use rustc_codegen_ssa::{ModuleCodegen, ModuleKind, looks_like_rust_object_file};
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::memmap::Mmap;
use rustc_errors::DiagCtxtHandle;
@ -185,12 +185,9 @@ pub(crate) fn run_thin(
thin_lto(cgcx, dcx, modules, upstream_modules, cached_modules, &symbols_below_threshold)
}
pub(crate) fn prepare_thin(
module: ModuleCodegen<ModuleLlvm>,
emit_summary: bool,
) -> (String, ThinBuffer) {
pub(crate) fn prepare_thin(module: ModuleCodegen<ModuleLlvm>) -> (String, ThinBuffer) {
let name = module.name;
let buffer = ThinBuffer::new(module.module_llvm.llmod(), true, emit_summary);
let buffer = ThinBuffer::new(module.module_llvm.llmod(), true);
(name, buffer)
}
@ -225,9 +222,15 @@ fn fat_lto(
// All the other modules will be serialized and reparsed into the new
// context, so this hopefully avoids serializing and parsing the largest
// codegen unit.
//
// Additionally use a regular module as the base here to ensure that various
// file copy operations in the backend work correctly. The only other kind
// of module here should be an allocator one, and if your crate is smaller
// than the allocator module then the size doesn't really matter anyway.
let costliest_module = in_memory
.iter()
.enumerate()
.filter(|&(_, module)| module.kind == ModuleKind::Regular)
.map(|(i, module)| {
let cost = unsafe { llvm::LLVMRustModuleCost(module.module_llvm.llmod()) };
(cost, i)
@ -681,9 +684,9 @@ unsafe impl Send for ThinBuffer {}
unsafe impl Sync for ThinBuffer {}
impl ThinBuffer {
pub(crate) fn new(m: &llvm::Module, is_thin: bool, emit_summary: bool) -> ThinBuffer {
pub(crate) fn new(m: &llvm::Module, is_thin: bool) -> ThinBuffer {
unsafe {
let buffer = llvm::LLVMRustThinLTOBufferCreate(m, is_thin, emit_summary);
let buffer = llvm::LLVMRustThinLTOBufferCreate(m, is_thin);
ThinBuffer(buffer)
}
}
@ -692,6 +695,14 @@ impl ThinBuffer {
let mut ptr = NonNull::new(ptr).unwrap();
ThinBuffer(unsafe { ptr.as_mut() })
}
pub(crate) fn thin_link_data(&self) -> &[u8] {
unsafe {
let ptr = llvm::LLVMRustThinLTOBufferThinLinkDataPtr(self.0) as *const _;
let len = llvm::LLVMRustThinLTOBufferThinLinkDataLen(self.0);
slice::from_raw_parts(ptr, len)
}
}
}
impl ThinBufferMethods for ThinBuffer {
@ -702,14 +713,6 @@ impl ThinBufferMethods for ThinBuffer {
slice::from_raw_parts(ptr, len)
}
}
fn thin_link_data(&self) -> &[u8] {
unsafe {
let ptr = llvm::LLVMRustThinLTOBufferThinLinkDataPtr(self.0) as *const _;
let len = llvm::LLVMRustThinLTOBufferThinLinkDataLen(self.0);
slice::from_raw_parts(ptr, len)
}
}
}
impl Drop for ThinBuffer {

View file

@ -837,7 +837,7 @@ pub(crate) fn codegen(
"LLVM_module_codegen_make_bitcode",
&*module.name,
);
ThinBuffer::new(llmod, config.emit_thin_lto, false)
ThinBuffer::new(llmod, config.emit_thin_lto)
};
let data = thin.data();
let _timer = cgcx

View file

@ -193,7 +193,7 @@ fn gen_define_handling<'ll>(
// reference) types.
let num_ptr_types = types
.iter()
.map(|&x| matches!(cx.type_kind(x), rustc_codegen_ssa::common::TypeKind::Pointer))
.filter(|&x| matches!(cx.type_kind(x), rustc_codegen_ssa::common::TypeKind::Pointer))
.count();
// We do not know their size anymore at this level, so hardcode a placeholder.

View file

@ -853,7 +853,7 @@ impl<'ll, 'tcx> MiscCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> {
fn declare_c_main(&self, fn_type: Self::Type) -> Option<Self::Function> {
let entry_name = self.sess().target.entry_name.as_ref();
if self.get_declared_value(entry_name).is_none() {
Some(self.declare_entry_fn(
let llfn = self.declare_entry_fn(
entry_name,
llvm::CallConv::from_conv(
self.sess().target.entry_abi,
@ -861,7 +861,13 @@ impl<'ll, 'tcx> MiscCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> {
),
llvm::UnnamedAddr::Global,
fn_type,
))
);
attributes::apply_to_llfn(
llfn,
llvm::AttributePlace::Function,
attributes::target_features_attr(self, vec![]).as_slice(),
);
Some(llfn)
} else {
// If the symbol already exists, it is an error: for example, the user wrote
// #[no_mangle] extern "C" fn main(..) {..}

View file

@ -211,11 +211,8 @@ impl WriteBackendMethods for LlvmCodegenBackend {
) -> CompiledModule {
back::write::codegen(cgcx, module, config)
}
fn prepare_thin(
module: ModuleCodegen<Self::Module>,
emit_summary: bool,
) -> (String, Self::ThinBuffer) {
back::lto::prepare_thin(module, emit_summary)
fn prepare_thin(module: ModuleCodegen<Self::Module>) -> (String, Self::ThinBuffer) {
back::lto::prepare_thin(module)
}
fn serialize_module(module: ModuleCodegen<Self::Module>) -> (String, Self::ModuleBuffer) {
(module.name, back::lto::ModuleBuffer::new(module.module_llvm.llmod()))

View file

@ -2602,7 +2602,6 @@ unsafe extern "C" {
pub(crate) fn LLVMRustThinLTOBufferCreate(
M: &Module,
is_thin: bool,
emit_summary: bool,
) -> &'static mut ThinLTOBuffer;
pub(crate) fn LLVMRustThinLTOBufferFree(M: &'static mut ThinLTOBuffer);
pub(crate) fn LLVMRustThinLTOBufferPtr(M: &ThinLTOBuffer) -> *const c_char;

View file

@ -1,7 +1,6 @@
use std::ffi::CString;
use std::sync::Arc;
use rustc_ast::expand::allocator::AllocatorKind;
use rustc_data_structures::memmap::Mmap;
use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
use rustc_middle::middle::exported_symbols::{ExportedSymbol, SymbolExportInfo, SymbolExportLevel};
@ -96,19 +95,6 @@ pub(super) fn exported_symbols_for_lto(
.filter_map(|&(s, info): &(ExportedSymbol<'_>, SymbolExportInfo)| {
if info.level.is_below_threshold(export_threshold) || info.used {
Some(symbol_name_for_instance_in_crate(tcx, s, cnum))
} else if export_threshold == SymbolExportLevel::C
&& info.rustc_std_internal_symbol
&& let Some(AllocatorKind::Default) = allocator_kind_for_codegen(tcx)
{
// Export the __rdl_* exports for usage by the allocator shim when not using
// #[global_allocator]. Most of the conditions above are only used to avoid
// unnecessary expensive symbol_name_for_instance_in_crate calls.
let sym = symbol_name_for_instance_in_crate(tcx, s, cnum);
if sym.contains("__rdl_") || sym.contains("__rg_oom") {
Some(sym)
} else {
None
}
} else {
None
}

View file

@ -334,6 +334,7 @@ pub struct CodegenContext<B: WriteBackendMethods> {
pub output_filenames: Arc<OutputFilenames>,
pub invocation_temp: Option<String>,
pub module_config: Arc<ModuleConfig>,
pub allocator_config: Arc<ModuleConfig>,
pub tm_factory: TargetMachineFactoryFn<B>,
pub msvc_imps_needed: bool,
pub is_pe_coff: bool,
@ -489,7 +490,7 @@ fn copy_all_cgu_workproducts_to_incr_comp_cache_dir(
let _timer = sess.timer("copy_all_cgu_workproducts_to_incr_comp_cache_dir");
for module in &compiled_modules.modules {
for module in compiled_modules.modules.iter().filter(|m| m.kind == ModuleKind::Regular) {
let mut files = Vec::new();
if let Some(object_file_path) = &module.object {
files.push((OutputType::Object.extension(), object_file_path.as_path()));
@ -794,12 +795,19 @@ pub(crate) fn compute_per_cgu_lto_type(
sess_lto: &Lto,
opts: &config::Options,
sess_crate_types: &[CrateType],
module_kind: ModuleKind,
) -> ComputedLtoType {
// If the linker does LTO, we don't have to do it. Note that we
// keep doing full LTO, if it is requested, as not to break the
// assumption that the output will be a single module.
let linker_does_lto = opts.cg.linker_plugin_lto.enabled();
// When we're automatically doing ThinLTO for multi-codegen-unit
// builds we don't actually want to LTO the allocator module if
// it shows up. This is due to various linker shenanigans that
// we'll encounter later.
let is_allocator = module_kind == ModuleKind::Allocator;
// We ignore a request for full crate graph LTO if the crate type
// is only an rlib, as there is no full crate graph to process,
// that'll happen later.
@ -811,7 +819,7 @@ pub(crate) fn compute_per_cgu_lto_type(
let is_rlib = matches!(sess_crate_types, [CrateType::Rlib]);
match sess_lto {
Lto::ThinLocal if !linker_does_lto => ComputedLtoType::Thin,
Lto::ThinLocal if !linker_does_lto && !is_allocator => ComputedLtoType::Thin,
Lto::Thin if !linker_does_lto && !is_rlib => ComputedLtoType::Thin,
Lto::Fat if !is_rlib => ComputedLtoType::Fat,
_ => ComputedLtoType::No,
@ -822,21 +830,28 @@ fn execute_optimize_work_item<B: ExtraBackendMethods>(
cgcx: &CodegenContext<B>,
mut module: ModuleCodegen<B::Module>,
) -> WorkItemResult<B> {
let _timer = cgcx.prof.generic_activity_with_arg("codegen_module_optimize", &*module.name);
let dcx = cgcx.create_dcx();
let dcx = dcx.handle();
B::optimize(cgcx, dcx, &mut module, &cgcx.module_config);
let module_config = match module.kind {
ModuleKind::Regular => &cgcx.module_config,
ModuleKind::Allocator => &cgcx.allocator_config,
};
B::optimize(cgcx, dcx, &mut module, module_config);
// After we've done the initial round of optimizations we need to
// decide whether to synchronously codegen this module or ship it
// back to the coordinator thread for further LTO processing (which
// has to wait for all the initial modules to be optimized).
let lto_type = compute_per_cgu_lto_type(&cgcx.lto, &cgcx.opts, &cgcx.crate_types);
let lto_type = compute_per_cgu_lto_type(&cgcx.lto, &cgcx.opts, &cgcx.crate_types, module.kind);
// If we're doing some form of incremental LTO then we need to be sure to
// save our module to disk first.
let bitcode = if cgcx.module_config.emit_pre_lto_bc {
let bitcode = if module_config.emit_pre_lto_bc {
let filename = pre_lto_bitcode_filename(&module.name);
cgcx.incr_comp_session_dir.as_ref().map(|path| path.join(&filename))
} else {
@ -845,11 +860,11 @@ fn execute_optimize_work_item<B: ExtraBackendMethods>(
match lto_type {
ComputedLtoType::No => {
let module = B::codegen(cgcx, module, &cgcx.module_config);
let module = B::codegen(cgcx, module, module_config);
WorkItemResult::Finished(module)
}
ComputedLtoType::Thin => {
let (name, thin_buffer) = B::prepare_thin(module, false);
let (name, thin_buffer) = B::prepare_thin(module);
if let Some(path) = bitcode {
fs::write(&path, thin_buffer.data()).unwrap_or_else(|e| {
panic!("Error writing pre-lto-bitcode file `{}`: {}", path.display(), e);
@ -877,6 +892,10 @@ fn execute_copy_from_cache_work_item<B: ExtraBackendMethods>(
cgcx: &CodegenContext<B>,
module: CachedModuleCodegen,
) -> WorkItemResult<B> {
let _timer = cgcx
.prof
.generic_activity_with_arg("codegen_copy_artifacts_from_incr_cache", &*module.name);
let incr_comp_session_dir = cgcx.incr_comp_session_dir.as_ref().unwrap();
let mut links_from_incr_cache = Vec::new();
@ -947,6 +966,7 @@ fn execute_copy_from_cache_work_item<B: ExtraBackendMethods>(
WorkItemResult::Finished(CompiledModule {
links_from_incr_cache,
kind: ModuleKind::Regular,
name: module.name,
object,
dwarf_object,
@ -963,6 +983,8 @@ fn execute_fat_lto_work_item<B: ExtraBackendMethods>(
mut needs_fat_lto: Vec<FatLtoInput<B>>,
import_only_modules: Vec<(SerializedModule<B::ModuleBuffer>, WorkProduct)>,
) -> WorkItemResult<B> {
let _timer = cgcx.prof.generic_activity_with_arg("codegen_module_perform_lto", "everything");
for (module, wp) in import_only_modules {
needs_fat_lto.push(FatLtoInput::Serialized { name: wp.cgu_name, buffer: module })
}
@ -981,6 +1003,8 @@ fn execute_thin_lto_work_item<B: ExtraBackendMethods>(
cgcx: &CodegenContext<B>,
module: lto::ThinModule<B>,
) -> WorkItemResult<B> {
let _timer = cgcx.prof.generic_activity_with_arg("codegen_module_perform_lto", module.name());
let module = B::optimize_thin(cgcx, module);
let module = B::codegen(cgcx, module, &cgcx.module_config);
WorkItemResult::Finished(module)
@ -1133,6 +1157,7 @@ fn start_executing_work<B: ExtraBackendMethods>(
diag_emitter: shared_emitter.clone(),
output_filenames: Arc::clone(tcx.output_filenames(())),
module_config: regular_config,
allocator_config,
tm_factory: backend.target_machine_factory(tcx.sess, ol, backend_features),
msvc_imps_needed: msvc_imps_needed(tcx),
is_pe_coff: tcx.sess.target.is_like_windows,
@ -1147,11 +1172,6 @@ fn start_executing_work<B: ExtraBackendMethods>(
invocation_temp: sess.invocation_temp.clone(),
};
let compiled_allocator_module = allocator_module.map(|mut allocator_module| {
B::optimize(&cgcx, tcx.sess.dcx(), &mut allocator_module, &allocator_config);
B::codegen(&cgcx, allocator_module, &allocator_config)
});
// This is the "main loop" of parallel work happening for parallel codegen.
// It's here that we manage parallelism, schedule work, and work with
// messages coming from clients.
@ -1331,6 +1351,17 @@ fn start_executing_work<B: ExtraBackendMethods>(
let mut llvm_start_time: Option<VerboseTimingGuard<'_>> = None;
let compiled_allocator_module = allocator_module.and_then(|allocator_module| {
match execute_optimize_work_item(&cgcx, allocator_module) {
WorkItemResult::Finished(compiled_module) => return Some(compiled_module),
WorkItemResult::NeedsFatLto(fat_lto_input) => needs_fat_lto.push(fat_lto_input),
WorkItemResult::NeedsThinLto(name, thin_buffer) => {
needs_thin_lto.push((name, thin_buffer))
}
}
None
});
// Run the message loop while there's still anything that needs message
// processing. Note that as soon as codegen is aborted we simply want to
// wait for all existing work to finish, so many of the conditions here
@ -1693,38 +1724,21 @@ fn spawn_work<'a, B: ExtraBackendMethods>(
B::spawn_named_thread(cgcx.time_trace, work.short_description(), move || {
let result = std::panic::catch_unwind(AssertUnwindSafe(|| match work {
WorkItem::Optimize(m) => {
let _timer =
cgcx.prof.generic_activity_with_arg("codegen_module_optimize", &*m.name);
execute_optimize_work_item(&cgcx, m)
}
WorkItem::CopyPostLtoArtifacts(m) => {
let _timer = cgcx
.prof
.generic_activity_with_arg("codegen_copy_artifacts_from_incr_cache", &*m.name);
execute_copy_from_cache_work_item(&cgcx, m)
}
WorkItem::Optimize(m) => execute_optimize_work_item(&cgcx, m),
WorkItem::CopyPostLtoArtifacts(m) => execute_copy_from_cache_work_item(&cgcx, m),
WorkItem::FatLto {
exported_symbols_for_lto,
each_linked_rlib_for_lto,
needs_fat_lto,
import_only_modules,
} => {
let _timer =
cgcx.prof.generic_activity_with_arg("codegen_module_perform_lto", "everything");
execute_fat_lto_work_item(
&cgcx,
&exported_symbols_for_lto,
&each_linked_rlib_for_lto,
needs_fat_lto,
import_only_modules,
)
}
WorkItem::ThinLto(m) => {
let _timer =
cgcx.prof.generic_activity_with_arg("codegen_module_perform_lto", m.name());
execute_thin_lto_work_item(&cgcx, m)
}
} => execute_fat_lto_work_item(
&cgcx,
&exported_symbols_for_lto,
&each_linked_rlib_for_lto,
needs_fat_lto,
import_only_modules,
),
WorkItem::ThinLto(m) => execute_thin_lto_work_item(&cgcx, m),
}));
let msg = match result {

View file

@ -46,7 +46,9 @@ use crate::meth::load_vtable;
use crate::mir::operand::OperandValue;
use crate::mir::place::PlaceRef;
use crate::traits::*;
use crate::{CachedModuleCodegen, CodegenLintLevels, CrateInfo, ModuleCodegen, errors, meth, mir};
use crate::{
CachedModuleCodegen, CodegenLintLevels, CrateInfo, ModuleCodegen, ModuleKind, errors, meth, mir,
};
pub(crate) fn bin_op_to_icmp_predicate(op: BinOp, signed: bool) -> IntPredicate {
match (op, signed) {
@ -1124,7 +1126,12 @@ pub fn determine_cgu_reuse<'tcx>(tcx: TyCtxt<'tcx>, cgu: &CodegenUnit<'tcx>) ->
// We can re-use either the pre- or the post-thinlto state. If no LTO is
// being performed then we can use post-LTO artifacts, otherwise we must
// reuse pre-LTO artifacts
match compute_per_cgu_lto_type(&tcx.sess.lto(), &tcx.sess.opts, tcx.crate_types()) {
match compute_per_cgu_lto_type(
&tcx.sess.lto(),
&tcx.sess.opts,
tcx.crate_types(),
ModuleKind::Regular,
) {
ComputedLtoType::No => CguReuse::PostLto,
_ => CguReuse::PreLto,
}

View file

@ -119,7 +119,8 @@ impl<M> ModuleCodegen<M> {
});
CompiledModule {
name: self.name.clone(),
name: self.name,
kind: self.kind,
object,
dwarf_object,
bytecode,
@ -133,6 +134,7 @@ impl<M> ModuleCodegen<M> {
#[derive(Debug, Encodable, Decodable)]
pub struct CompiledModule {
pub name: String,
pub kind: ModuleKind,
pub object: Option<PathBuf>,
pub dwarf_object: Option<PathBuf>,
pub bytecode: Option<PathBuf>,

View file

@ -1,3 +1,4 @@
use itertools::Itertools as _;
use rustc_abi::{self as abi, FIRST_VARIANT};
use rustc_middle::ty::adjustment::PointerCoercion;
use rustc_middle::ty::layout::{HasTyCtxt, HasTypingEnv, LayoutOf, TyAndLayout};
@ -111,14 +112,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
let size = bx.const_usize(dest.layout.size.bytes());
// Use llvm.memset.p0i8.* to initialize all same byte arrays
if let Some(int) = bx.cx().const_to_opt_u128(v, false) {
let bytes = &int.to_le_bytes()[..cg_elem.layout.size.bytes_usize()];
let first = bytes[0];
if bytes[1..].iter().all(|&b| b == first) {
let fill = bx.cx().const_u8(first);
bx.memset(start, fill, size, dest.val.align, MemFlags::empty());
return true;
}
if let Some(int) = bx.cx().const_to_opt_u128(v, false)
&& let bytes = &int.to_le_bytes()[..cg_elem.layout.size.bytes_usize()]
&& let Ok(&byte) = bytes.iter().all_equal_value()
{
let fill = bx.cx().const_u8(byte);
bx.memset(start, fill, size, dest.val.align, MemFlags::empty());
return true;
}
// Use llvm.memset.p0i8.* to initialize byte arrays
@ -130,13 +130,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
false
};
match cg_elem.val {
OperandValue::Immediate(v) => {
if try_init_all_same(bx, v) {
return;
}
}
_ => (),
if let OperandValue::Immediate(v) = cg_elem.val
&& try_init_all_same(bx, v)
{
return;
}
let count = self

View file

@ -50,16 +50,12 @@ pub trait WriteBackendMethods: Clone + 'static {
module: ModuleCodegen<Self::Module>,
config: &ModuleConfig,
) -> CompiledModule;
fn prepare_thin(
module: ModuleCodegen<Self::Module>,
want_summary: bool,
) -> (String, Self::ThinBuffer);
fn prepare_thin(module: ModuleCodegen<Self::Module>) -> (String, Self::ThinBuffer);
fn serialize_module(module: ModuleCodegen<Self::Module>) -> (String, Self::ModuleBuffer);
}
pub trait ThinBufferMethods: Send + Sync {
fn data(&self) -> &[u8];
fn thin_link_data(&self) -> &[u8];
}
pub trait ModuleBufferMethods: Send + Sync {

View file

@ -945,11 +945,6 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
None,
"Span must not be empty and have no suggestion",
);
debug_assert_eq!(
parts.array_windows().find(|[a, b]| a.span.overlaps(b.span)),
None,
"suggestion must not have overlapping parts",
);
self.push_suggestion(CodeSuggestion {
substitutions: vec![Substitution { parts }],

View file

@ -2354,7 +2354,6 @@ impl HumanEmitter {
.sum();
let underline_start = (span_start_pos + start) as isize + offset;
let underline_end = (span_start_pos + start + sub_len) as isize + offset;
assert!(underline_start >= 0 && underline_end >= 0);
let padding: usize = max_line_num_len + 3;
for p in underline_start..underline_end {
if let DisplaySuggestion::Underline = show_code_change

View file

@ -381,6 +381,17 @@ impl CodeSuggestion {
// Assumption: all spans are in the same file, and all spans
// are disjoint. Sort in ascending order.
substitution.parts.sort_by_key(|part| part.span.lo());
// Verify the assumption that all spans are disjoint
assert_eq!(
substitution.parts.array_windows().find(|[a, b]| a.span.overlaps(b.span)),
None,
"all spans must be disjoint",
);
// Account for cases where we are suggesting the same code that's already
// there. This shouldn't happen often, but in some cases for multipart
// suggestions it's much easier to handle it here than in the origin.
substitution.parts.retain(|p| is_different(sm, &p.snippet, p.span));
// Find the bounding span.
let lo = substitution.parts.iter().map(|part| part.span.lo()).min()?;
@ -470,16 +481,12 @@ impl CodeSuggestion {
_ => 1,
})
.sum();
if !is_different(sm, &part.snippet, part.span) {
// Account for cases where we are suggesting the same code that's already
// there. This shouldn't happen often, but in some cases for multipart
// suggestions it's much easier to handle it here than in the origin.
} else {
line_highlight.push(SubstitutionHighlight {
start: (cur_lo.col.0 as isize + acc) as usize,
end: (cur_lo.col.0 as isize + acc + len) as usize,
});
}
line_highlight.push(SubstitutionHighlight {
start: (cur_lo.col.0 as isize + acc) as usize,
end: (cur_lo.col.0 as isize + acc + len) as usize,
});
buf.push_str(&part.snippet);
let cur_hi = sm.lookup_char_pos(part.span.hi());
// Account for the difference between the width of the current code and the

View file

@ -1897,7 +1897,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
fcx.suggest_semicolon_at_end(cond_expr.span, &mut err);
}
}
};
}
// If this is due to an explicit `return`, suggest adding a return type.
if let Some((fn_id, fn_decl)) = fcx.get_fn_decl(block_or_return_id)

View file

@ -2585,12 +2585,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.filter(|item| item.is_fn() && !item.is_method())
.filter_map(|item| {
// Only assoc fns that return `Self`
let fn_sig = self.tcx.fn_sig(item.def_id).skip_binder();
let ret_ty = fn_sig.output();
let ret_ty = self.tcx.normalize_erasing_late_bound_regions(
self.typing_env(self.param_env),
ret_ty,
);
let fn_sig = self
.tcx
.fn_sig(item.def_id)
.instantiate(self.tcx, self.fresh_args_for_item(span, item.def_id));
let ret_ty = self.tcx.instantiate_bound_regions_with_erased(fn_sig.output());
if !self.can_eq(self.param_env, ret_ty, adt_ty) {
return None;
}

View file

@ -1912,7 +1912,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
hir::StmtKind::Expr(ref expr) => {
// Check with expected type of `()`.
self.check_expr_has_type_or_error(expr, self.tcx.types.unit, |err| {
if expr.can_have_side_effects() {
if self.is_next_stmt_expr_continuation(stmt.hir_id)
&& let hir::ExprKind::Match(..) | hir::ExprKind::If(..) = expr.kind
{
// We have something like `match () { _ => true } && true`. Suggest
// wrapping in parentheses. We find the statement or expression
// following the `match` (`&& true`) and see if it is something that
// can reasonably be interpreted as a binop following an expression.
err.multipart_suggestion(
"parentheses are required to parse this as an expression",
vec![
(expr.span.shrink_to_lo(), "(".to_string()),
(expr.span.shrink_to_hi(), ")".to_string()),
],
Applicability::MachineApplicable,
);
} else if expr.can_have_side_effects() {
self.suggest_semicolon_at_end(expr.span, err);
}
});

View file

@ -1,3 +1,4 @@
// ignore-tidy-filelength
use core::cmp::min;
use core::iter;
@ -766,53 +767,118 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
needs_block: bool,
parent_is_closure: bool,
) {
if expected.is_unit() {
// `BlockTailExpression` only relevant if the tail expr would be
// useful on its own.
match expression.kind {
ExprKind::Call(..)
| ExprKind::MethodCall(..)
| ExprKind::Loop(..)
| ExprKind::If(..)
| ExprKind::Match(..)
| ExprKind::Block(..)
if expression.can_have_side_effects()
// If the expression is from an external macro, then do not suggest
// adding a semicolon, because there's nowhere to put it.
// See issue #81943.
&& !expression.span.in_external_macro(self.tcx.sess.source_map()) =>
if !expected.is_unit() {
return;
}
// `BlockTailExpression` only relevant if the tail expr would be
// useful on its own.
match expression.kind {
ExprKind::Call(..)
| ExprKind::MethodCall(..)
| ExprKind::Loop(..)
| ExprKind::If(..)
| ExprKind::Match(..)
| ExprKind::Block(..)
if expression.can_have_side_effects()
// If the expression is from an external macro, then do not suggest
// adding a semicolon, because there's nowhere to put it.
// See issue #81943.
&& !expression.span.in_external_macro(self.tcx.sess.source_map()) =>
{
if needs_block {
err.multipart_suggestion(
"consider using a semicolon here",
vec![
(expression.span.shrink_to_lo(), "{ ".to_owned()),
(expression.span.shrink_to_hi(), "; }".to_owned()),
],
Applicability::MachineApplicable,
);
} else if let hir::Node::Block(block) = self.tcx.parent_hir_node(expression.hir_id)
&& let hir::Node::Expr(expr) = self.tcx.parent_hir_node(block.hir_id)
&& let hir::Node::Expr(if_expr) = self.tcx.parent_hir_node(expr.hir_id)
&& let hir::ExprKind::If(_cond, _then, Some(_else)) = if_expr.kind
&& let hir::Node::Stmt(stmt) = self.tcx.parent_hir_node(if_expr.hir_id)
&& let hir::StmtKind::Expr(_) = stmt.kind
&& self.is_next_stmt_expr_continuation(stmt.hir_id)
{
if needs_block {
err.multipart_suggestion(
"consider using a semicolon here",
vec![
(expression.span.shrink_to_lo(), "{ ".to_owned()),
(expression.span.shrink_to_hi(), "; }".to_owned()),
],
Applicability::MachineApplicable,
);
} else {
err.span_suggestion(
expression.span.shrink_to_hi(),
"consider using a semicolon here",
";",
Applicability::MachineApplicable,
);
}
}
ExprKind::Path(..) | ExprKind::Lit(_)
if parent_is_closure
&& !expression.span.in_external_macro(self.tcx.sess.source_map()) =>
{
err.span_suggestion_verbose(
expression.span.shrink_to_lo(),
"consider ignoring the value",
"_ = ",
err.multipart_suggestion(
"parentheses are required to parse this as an expression",
vec![
(stmt.span.shrink_to_lo(), "(".to_string()),
(stmt.span.shrink_to_hi(), ")".to_string()),
],
Applicability::MachineApplicable,
);
} else {
err.span_suggestion(
expression.span.shrink_to_hi(),
"consider using a semicolon here",
";",
Applicability::MachineApplicable,
);
}
_ => (),
}
ExprKind::Path(..) | ExprKind::Lit(_)
if parent_is_closure
&& !expression.span.in_external_macro(self.tcx.sess.source_map()) =>
{
err.span_suggestion_verbose(
expression.span.shrink_to_lo(),
"consider ignoring the value",
"_ = ",
Applicability::MachineApplicable,
);
}
_ => {
if let hir::Node::Block(block) = self.tcx.parent_hir_node(expression.hir_id)
&& let hir::Node::Expr(expr) = self.tcx.parent_hir_node(block.hir_id)
&& let hir::Node::Expr(if_expr) = self.tcx.parent_hir_node(expr.hir_id)
&& let hir::ExprKind::If(_cond, _then, Some(_else)) = if_expr.kind
&& let hir::Node::Stmt(stmt) = self.tcx.parent_hir_node(if_expr.hir_id)
&& let hir::StmtKind::Expr(_) = stmt.kind
&& self.is_next_stmt_expr_continuation(stmt.hir_id)
{
// The error is pointing at an arm of an if-expression, and we want to get the
// `Span` of the whole if-expression for the suggestion. This only works for a
// single level of nesting, which is fine.
// We have something like `if true { false } else { true } && true`. Suggest
// wrapping in parentheses. We find the statement or expression following the
// `if` (`&& true`) and see if it is something that can reasonably be
// interpreted as a binop following an expression.
err.multipart_suggestion(
"parentheses are required to parse this as an expression",
vec![
(stmt.span.shrink_to_lo(), "(".to_string()),
(stmt.span.shrink_to_hi(), ")".to_string()),
],
Applicability::MachineApplicable,
);
}
}
}
}
pub(crate) fn is_next_stmt_expr_continuation(&self, hir_id: HirId) -> bool {
if let hir::Node::Block(b) = self.tcx.parent_hir_node(hir_id)
&& let mut stmts = b.stmts.iter().skip_while(|s| s.hir_id != hir_id)
&& let Some(_) = stmts.next() // The statement the statement that was passed in
&& let Some(next) = match (stmts.next(), b.expr) { // The following statement
(Some(next), _) => match next.kind {
hir::StmtKind::Expr(next) | hir::StmtKind::Semi(next) => Some(next),
_ => None,
},
(None, Some(next)) => Some(next),
_ => None,
}
&& let hir::ExprKind::AddrOf(..) // prev_stmt && next
| hir::ExprKind::Unary(..) // prev_stmt * next
| hir::ExprKind::Err(_) = next.kind
// prev_stmt + next
{
true
} else {
false
}
}

View file

@ -140,6 +140,30 @@ impl<I: Idx> IntervalSet<I> {
result
}
/// Specialized version of `insert` when we know that the inserted point is *after* any
/// contained.
pub fn append(&mut self, point: I) {
let point = point.index() as u32;
if let Some((_, last_end)) = self.map.last_mut() {
assert!(*last_end <= point);
if point == *last_end {
// The point is already in the set.
} else if point == *last_end + 1 {
*last_end = point;
} else {
self.map.push((point, point));
}
} else {
self.map.push((point, point));
}
debug_assert!(
self.check_invariants(),
"wrong intervals after append {point:?} to {self:?}"
);
}
pub fn contains(&self, needle: I) -> bool {
let needle = needle.index() as u32;
let Some(last) = self.map.partition_point(|r| r.0 <= needle).checked_sub(1) else {
@ -176,6 +200,32 @@ impl<I: Idx> IntervalSet<I> {
})
}
pub fn disjoint(&self, other: &IntervalSet<I>) -> bool
where
I: Step,
{
let helper = move || {
let mut self_iter = self.iter_intervals();
let mut other_iter = other.iter_intervals();
let mut self_current = self_iter.next()?;
let mut other_current = other_iter.next()?;
loop {
if self_current.end <= other_current.start {
self_current = self_iter.next()?;
continue;
}
if other_current.end <= self_current.start {
other_current = other_iter.next()?;
continue;
}
return Some(false);
}
};
helper().unwrap_or(true)
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
@ -325,6 +375,10 @@ impl<R: Idx, C: Step + Idx> SparseIntervalMatrix<R, C> {
self.ensure_row(row).insert(point)
}
pub fn append(&mut self, row: R, point: C) {
self.ensure_row(row).append(point)
}
pub fn contains(&self, row: R, point: C) -> bool {
self.row(row).is_some_and(|r| r.contains(point))
}

View file

@ -1,4 +1,4 @@
///! Definition of `InferCtxtLike` from the librarified type layer.
//! Definition of `InferCtxtLike` from the librarified type layer.
use rustc_hir::def_id::DefId;
use rustc_middle::traits::ObligationCause;
use rustc_middle::ty::relate::RelateResult;

View file

@ -5,6 +5,7 @@ edition = "2024"
[dependencies]
# tidy-alphabetical-start
bitflags = "2.4.1"
rustc_abi = { path = "../rustc_abi" }
rustc_ast = { path = "../rustc_ast" }
rustc_ast_pretty = { path = "../rustc_ast_pretty" }

View file

@ -136,7 +136,6 @@ impl ClashingExternDeclarations {
ty::TypingEnv::non_body_analysis(tcx, this_fi.owner_id),
existing_decl_ty,
this_decl_ty,
types::CItemKind::Declaration,
) {
let orig = name_of_extern_decl(tcx, existing_did);
@ -214,10 +213,9 @@ fn structurally_same_type<'tcx>(
typing_env: ty::TypingEnv<'tcx>,
a: Ty<'tcx>,
b: Ty<'tcx>,
ckind: types::CItemKind,
) -> bool {
let mut seen_types = UnordSet::default();
let result = structurally_same_type_impl(&mut seen_types, tcx, typing_env, a, b, ckind);
let result = structurally_same_type_impl(&mut seen_types, tcx, typing_env, a, b);
if cfg!(debug_assertions) && result {
// Sanity-check: must have same ABI, size and alignment.
// `extern` blocks cannot be generic, so we'll always get a layout here.
@ -236,7 +234,6 @@ fn structurally_same_type_impl<'tcx>(
typing_env: ty::TypingEnv<'tcx>,
a: Ty<'tcx>,
b: Ty<'tcx>,
ckind: types::CItemKind,
) -> bool {
debug!("structurally_same_type_impl(tcx, a = {:?}, b = {:?})", a, b);
@ -307,7 +304,6 @@ fn structurally_same_type_impl<'tcx>(
typing_env,
tcx.type_of(a_did).instantiate(tcx, a_gen_args),
tcx.type_of(b_did).instantiate(tcx, b_gen_args),
ckind,
)
},
)
@ -315,25 +311,19 @@ fn structurally_same_type_impl<'tcx>(
(ty::Array(a_ty, a_len), ty::Array(b_ty, b_len)) => {
// For arrays, we also check the length.
a_len == b_len
&& structurally_same_type_impl(
seen_types, tcx, typing_env, *a_ty, *b_ty, ckind,
)
&& structurally_same_type_impl(seen_types, tcx, typing_env, *a_ty, *b_ty)
}
(ty::Slice(a_ty), ty::Slice(b_ty)) => {
structurally_same_type_impl(seen_types, tcx, typing_env, *a_ty, *b_ty, ckind)
structurally_same_type_impl(seen_types, tcx, typing_env, *a_ty, *b_ty)
}
(ty::RawPtr(a_ty, a_mutbl), ty::RawPtr(b_ty, b_mutbl)) => {
a_mutbl == b_mutbl
&& structurally_same_type_impl(
seen_types, tcx, typing_env, *a_ty, *b_ty, ckind,
)
&& structurally_same_type_impl(seen_types, tcx, typing_env, *a_ty, *b_ty)
}
(ty::Ref(_a_region, a_ty, a_mut), ty::Ref(_b_region, b_ty, b_mut)) => {
// For structural sameness, we don't need the region to be same.
a_mut == b_mut
&& structurally_same_type_impl(
seen_types, tcx, typing_env, *a_ty, *b_ty, ckind,
)
&& structurally_same_type_impl(seen_types, tcx, typing_env, *a_ty, *b_ty)
}
(ty::FnDef(..), ty::FnDef(..)) => {
let a_poly_sig = a.fn_sig(tcx);
@ -347,7 +337,7 @@ fn structurally_same_type_impl<'tcx>(
(a_sig.abi, a_sig.safety, a_sig.c_variadic)
== (b_sig.abi, b_sig.safety, b_sig.c_variadic)
&& a_sig.inputs().iter().eq_by(b_sig.inputs().iter(), |a, b| {
structurally_same_type_impl(seen_types, tcx, typing_env, *a, *b, ckind)
structurally_same_type_impl(seen_types, tcx, typing_env, *a, *b)
})
&& structurally_same_type_impl(
seen_types,
@ -355,7 +345,6 @@ fn structurally_same_type_impl<'tcx>(
typing_env,
a_sig.output(),
b_sig.output(),
ckind,
)
}
(ty::Tuple(..), ty::Tuple(..)) => {
@ -383,14 +372,14 @@ fn structurally_same_type_impl<'tcx>(
// An Adt and a primitive or pointer type. This can be FFI-safe if non-null
// enum layout optimisation is being applied.
(ty::Adt(..) | ty::Pat(..), _) if is_primitive_or_pointer(b) => {
if let Some(a_inner) = types::repr_nullable_ptr(tcx, typing_env, a, ckind) {
if let Some(a_inner) = types::repr_nullable_ptr(tcx, typing_env, a) {
a_inner == b
} else {
false
}
}
(_, ty::Adt(..) | ty::Pat(..)) if is_primitive_or_pointer(a) => {
if let Some(b_inner) = types::repr_nullable_ptr(tcx, typing_env, b, ckind) {
if let Some(b_inner) = types::repr_nullable_ptr(tcx, typing_env, b) {
b_inner == a
} else {
false

View file

@ -194,8 +194,7 @@ late_lint_methods!(
DefaultCouldBeDerived: DefaultCouldBeDerived::default(),
DerefIntoDynSupertrait: DerefIntoDynSupertrait,
DropForgetUseless: DropForgetUseless,
ImproperCTypesDeclarations: ImproperCTypesDeclarations,
ImproperCTypesDefinitions: ImproperCTypesDefinitions,
ImproperCTypesLint: ImproperCTypesLint,
InvalidFromUtf8: InvalidFromUtf8,
VariantSizeDifferences: VariantSizeDifferences,
PathStatements: PathStatements,

View file

@ -1,35 +1,28 @@
use std::iter;
use std::ops::ControlFlow;
use rustc_abi::{BackendRepr, TagEncoding, VariantIdx, Variants, WrappingRange};
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::DiagMessage;
use rustc_hir::intravisit::VisitorExt;
use rustc_hir::{AmbigArg, Expr, ExprKind, HirId, LangItem};
use rustc_abi::{BackendRepr, TagEncoding, Variants, WrappingRange};
use rustc_hir::{Expr, ExprKind, HirId, LangItem};
use rustc_middle::bug;
use rustc_middle::ty::layout::{LayoutOf, SizeSkeleton};
use rustc_middle::ty::{
self, Adt, AdtKind, GenericArgsRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable,
TypeVisitableExt,
};
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt};
use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass};
use rustc_span::def_id::LocalDefId;
use rustc_span::{Span, Symbol, sym};
use tracing::debug;
use {rustc_ast as ast, rustc_hir as hir};
mod improper_ctypes;
mod improper_ctypes; // these filed do the implementation for ImproperCTypesDefinitions,ImproperCTypesDeclarations
pub(crate) use improper_ctypes::ImproperCTypesLint;
use crate::lints::{
AmbiguousWidePointerComparisons, AmbiguousWidePointerComparisonsAddrMetadataSuggestion,
AmbiguousWidePointerComparisonsAddrSuggestion, AmbiguousWidePointerComparisonsCastSuggestion,
AmbiguousWidePointerComparisonsExpectSuggestion, AtomicOrderingFence, AtomicOrderingLoad,
AtomicOrderingStore, ImproperCTypes, InvalidAtomicOrderingDiag, InvalidNanComparisons,
AtomicOrderingStore, InvalidAtomicOrderingDiag, InvalidNanComparisons,
InvalidNanComparisonsSuggestion, UnpredictableFunctionPointerComparisons,
UnpredictableFunctionPointerComparisonsSuggestion, UnusedComparisons, UsesPowerAlignment,
UnpredictableFunctionPointerComparisonsSuggestion, UnusedComparisons,
VariantSizeDifferencesDiag,
};
use crate::{LateContext, LateLintPass, LintContext, fluent_generated as fluent};
use crate::{LateContext, LateLintPass, LintContext};
mod literal;
@ -690,144 +683,6 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
}
}
declare_lint! {
/// The `improper_ctypes` lint detects incorrect use of types in foreign
/// modules.
///
/// ### Example
///
/// ```rust
/// unsafe extern "C" {
/// static STATIC: String;
/// }
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// The compiler has several checks to verify that types used in `extern`
/// blocks are safe and follow certain rules to ensure proper
/// compatibility with the foreign interfaces. This lint is issued when it
/// detects a probable mistake in a definition. The lint usually should
/// provide a description of the issue, along with possibly a hint on how
/// to resolve it.
IMPROPER_CTYPES,
Warn,
"proper use of libc types in foreign modules"
}
declare_lint_pass!(ImproperCTypesDeclarations => [IMPROPER_CTYPES]);
declare_lint! {
/// The `improper_ctypes_definitions` lint detects incorrect use of
/// [`extern` function] definitions.
///
/// [`extern` function]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier
///
/// ### Example
///
/// ```rust
/// # #![allow(unused)]
/// pub extern "C" fn str_type(p: &str) { }
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// There are many parameter and return types that may be specified in an
/// `extern` function that are not compatible with the given ABI. This
/// lint is an alert that these types should not be used. The lint usually
/// should provide a description of the issue, along with possibly a hint
/// on how to resolve it.
IMPROPER_CTYPES_DEFINITIONS,
Warn,
"proper use of libc types in foreign item definitions"
}
declare_lint! {
/// The `uses_power_alignment` lint detects specific `repr(C)`
/// aggregates on AIX.
/// In its platform C ABI, AIX uses the "power" (as in PowerPC) alignment
/// rule (detailed in https://www.ibm.com/docs/en/xl-c-and-cpp-aix/16.1?topic=data-using-alignment-modes#alignment),
/// which can also be set for XLC by `#pragma align(power)` or
/// `-qalign=power`. Aggregates with a floating-point type as the
/// recursively first field (as in "at offset 0") modify the layout of
/// *subsequent* fields of the associated structs to use an alignment value
/// where the floating-point type is aligned on a 4-byte boundary.
///
/// Effectively, subsequent floating-point fields act as-if they are `repr(packed(4))`. This
/// would be unsound to do in a `repr(C)` type without all the restrictions that come with
/// `repr(packed)`. Rust instead chooses a layout that maintains soundness of Rust code, at the
/// expense of incompatibility with C code.
///
/// ### Example
///
/// ```rust,ignore (fails on non-powerpc64-ibm-aix)
/// #[repr(C)]
/// pub struct Floats {
/// a: f64,
/// b: u8,
/// c: f64,
/// }
/// ```
///
/// This will produce:
///
/// ```text
/// warning: repr(C) does not follow the power alignment rule. This may affect platform C ABI compatibility for this type
/// --> <source>:5:3
/// |
/// 5 | c: f64,
/// | ^^^^^^
/// |
/// = note: `#[warn(uses_power_alignment)]` on by default
/// ```
///
/// ### Explanation
///
/// The power alignment rule specifies that the above struct has the
/// following alignment:
/// - offset_of!(Floats, a) == 0
/// - offset_of!(Floats, b) == 8
/// - offset_of!(Floats, c) == 12
///
/// However, Rust currently aligns `c` at `offset_of!(Floats, c) == 16`.
/// Using offset 12 would be unsound since `f64` generally must be 8-aligned on this target.
/// Thus, a warning is produced for the above struct.
USES_POWER_ALIGNMENT,
Warn,
"Structs do not follow the power alignment rule under repr(C)"
}
declare_lint_pass!(ImproperCTypesDefinitions => [IMPROPER_CTYPES_DEFINITIONS, USES_POWER_ALIGNMENT]);
#[derive(Clone, Copy)]
pub(crate) enum CItemKind {
Declaration,
Definition,
}
struct ImproperCTypesVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
mode: CItemKind,
}
/// Accumulator for recursive ffi type checking
struct CTypesVisitorState<'tcx> {
cache: FxHashSet<Ty<'tcx>>,
/// The original type being checked, before we recursed
/// to any other types it contains.
base_ty: Ty<'tcx>,
}
enum FfiResult<'tcx> {
FfiSafe,
FfiPhantom(Ty<'tcx>),
FfiUnsafe { ty: Ty<'tcx>, reason: DiagMessage, help: Option<DiagMessage> },
}
pub(crate) fn nonnull_optimization_guaranteed<'tcx>(
tcx: TyCtxt<'tcx>,
def: ty::AdtDef<'tcx>,
@ -855,14 +710,13 @@ fn ty_is_known_nonnull<'tcx>(
tcx: TyCtxt<'tcx>,
typing_env: ty::TypingEnv<'tcx>,
ty: Ty<'tcx>,
mode: CItemKind,
) -> bool {
let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty);
match ty.kind() {
ty::FnPtr(..) => true,
ty::Ref(..) => true,
ty::Adt(def, _) if def.is_box() && matches!(mode, CItemKind::Definition) => true,
ty::Adt(def, _) if def.is_box() => true,
ty::Adt(def, args) if def.repr().transparent() && !def.is_union() => {
let marked_non_null = nonnull_optimization_guaranteed(tcx, *def);
@ -878,10 +732,10 @@ fn ty_is_known_nonnull<'tcx>(
def.variants()
.iter()
.filter_map(|variant| transparent_newtype_field(tcx, variant))
.any(|field| ty_is_known_nonnull(tcx, typing_env, field.ty(tcx, args), mode))
.any(|field| ty_is_known_nonnull(tcx, typing_env, field.ty(tcx, args)))
}
ty::Pat(base, pat) => {
ty_is_known_nonnull(tcx, typing_env, *base, mode)
ty_is_known_nonnull(tcx, typing_env, *base)
|| pat_ty_is_known_nonnull(tcx, typing_env, *pat)
}
_ => false,
@ -992,7 +846,6 @@ pub(crate) fn repr_nullable_ptr<'tcx>(
tcx: TyCtxt<'tcx>,
typing_env: ty::TypingEnv<'tcx>,
ty: Ty<'tcx>,
ckind: CItemKind,
) -> Option<Ty<'tcx>> {
debug!("is_repr_nullable_ptr(tcx, ty = {:?})", ty);
match ty.kind() {
@ -1017,7 +870,7 @@ pub(crate) fn repr_nullable_ptr<'tcx>(
_ => return None,
};
if !ty_is_known_nonnull(tcx, typing_env, field_ty, ckind) {
if !ty_is_known_nonnull(tcx, typing_env, field_ty) {
return None;
}
@ -1076,710 +929,6 @@ fn get_nullable_type_from_pat<'tcx>(
}
}
impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
/// Check if the type is array and emit an unsafe type lint.
fn check_for_array_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool {
if let ty::Array(..) = ty.kind() {
self.emit_ffi_unsafe_type_lint(
ty,
sp,
fluent::lint_improper_ctypes_array_reason,
Some(fluent::lint_improper_ctypes_array_help),
);
true
} else {
false
}
}
/// Checks if the given field's type is "ffi-safe".
fn check_field_type_for_ffi(
&self,
acc: &mut CTypesVisitorState<'tcx>,
field: &ty::FieldDef,
args: GenericArgsRef<'tcx>,
) -> FfiResult<'tcx> {
let field_ty = field.ty(self.cx.tcx, args);
let field_ty = self
.cx
.tcx
.try_normalize_erasing_regions(self.cx.typing_env(), field_ty)
.unwrap_or(field_ty);
self.check_type_for_ffi(acc, field_ty)
}
/// Checks if the given `VariantDef`'s field types are "ffi-safe".
fn check_variant_for_ffi(
&self,
acc: &mut CTypesVisitorState<'tcx>,
ty: Ty<'tcx>,
def: ty::AdtDef<'tcx>,
variant: &ty::VariantDef,
args: GenericArgsRef<'tcx>,
) -> FfiResult<'tcx> {
use FfiResult::*;
let transparent_with_all_zst_fields = if def.repr().transparent() {
if let Some(field) = transparent_newtype_field(self.cx.tcx, variant) {
// Transparent newtypes have at most one non-ZST field which needs to be checked..
match self.check_field_type_for_ffi(acc, field, args) {
FfiUnsafe { ty, .. } if ty.is_unit() => (),
r => return r,
}
false
} else {
// ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
// `PhantomData`).
true
}
} else {
false
};
// We can't completely trust `repr(C)` markings, so make sure the fields are actually safe.
let mut all_phantom = !variant.fields.is_empty();
for field in &variant.fields {
all_phantom &= match self.check_field_type_for_ffi(acc, field, args) {
FfiSafe => false,
// `()` fields are FFI-safe!
FfiUnsafe { ty, .. } if ty.is_unit() => false,
FfiPhantom(..) => true,
r @ FfiUnsafe { .. } => return r,
}
}
if all_phantom {
FfiPhantom(ty)
} else if transparent_with_all_zst_fields {
FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_struct_zst, help: None }
} else {
FfiSafe
}
}
/// Checks if the given type is "ffi-safe" (has a stable, well-defined
/// representation which can be exported to C code).
fn check_type_for_ffi(
&self,
acc: &mut CTypesVisitorState<'tcx>,
ty: Ty<'tcx>,
) -> FfiResult<'tcx> {
use FfiResult::*;
let tcx = self.cx.tcx;
// Protect against infinite recursion, for example
// `struct S(*mut S);`.
// FIXME: A recursion limit is necessary as well, for irregular
// recursive types.
if !acc.cache.insert(ty) {
return FfiSafe;
}
match *ty.kind() {
ty::Adt(def, args) => {
if let Some(boxed) = ty.boxed_ty()
&& matches!(self.mode, CItemKind::Definition)
{
if boxed.is_sized(tcx, self.cx.typing_env()) {
return FfiSafe;
} else {
return FfiUnsafe {
ty,
reason: fluent::lint_improper_ctypes_box,
help: None,
};
}
}
if def.is_phantom_data() {
return FfiPhantom(ty);
}
match def.adt_kind() {
AdtKind::Struct | AdtKind::Union => {
if let Some(sym::cstring_type | sym::cstr_type) =
tcx.get_diagnostic_name(def.did())
&& !acc.base_ty.is_mutable_ptr()
{
return FfiUnsafe {
ty,
reason: fluent::lint_improper_ctypes_cstr_reason,
help: Some(fluent::lint_improper_ctypes_cstr_help),
};
}
if !def.repr().c() && !def.repr().transparent() {
return FfiUnsafe {
ty,
reason: if def.is_struct() {
fluent::lint_improper_ctypes_struct_layout_reason
} else {
fluent::lint_improper_ctypes_union_layout_reason
},
help: if def.is_struct() {
Some(fluent::lint_improper_ctypes_struct_layout_help)
} else {
Some(fluent::lint_improper_ctypes_union_layout_help)
},
};
}
if def.non_enum_variant().field_list_has_applicable_non_exhaustive() {
return FfiUnsafe {
ty,
reason: if def.is_struct() {
fluent::lint_improper_ctypes_struct_non_exhaustive
} else {
fluent::lint_improper_ctypes_union_non_exhaustive
},
help: None,
};
}
if def.non_enum_variant().fields.is_empty() {
return FfiUnsafe {
ty,
reason: if def.is_struct() {
fluent::lint_improper_ctypes_struct_fieldless_reason
} else {
fluent::lint_improper_ctypes_union_fieldless_reason
},
help: if def.is_struct() {
Some(fluent::lint_improper_ctypes_struct_fieldless_help)
} else {
Some(fluent::lint_improper_ctypes_union_fieldless_help)
},
};
}
self.check_variant_for_ffi(acc, ty, def, def.non_enum_variant(), args)
}
AdtKind::Enum => {
if def.variants().is_empty() {
// Empty enums are okay... although sort of useless.
return FfiSafe;
}
// Check for a repr() attribute to specify the size of the
// discriminant.
if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none()
{
// Special-case types like `Option<extern fn()>` and `Result<extern fn(), ()>`
if let Some(ty) =
repr_nullable_ptr(self.cx.tcx, self.cx.typing_env(), ty, self.mode)
{
return self.check_type_for_ffi(acc, ty);
}
return FfiUnsafe {
ty,
reason: fluent::lint_improper_ctypes_enum_repr_reason,
help: Some(fluent::lint_improper_ctypes_enum_repr_help),
};
}
use improper_ctypes::check_non_exhaustive_variant;
let non_exhaustive = def.variant_list_has_applicable_non_exhaustive();
// Check the contained variants.
let ret = def.variants().iter().try_for_each(|variant| {
check_non_exhaustive_variant(non_exhaustive, variant)
.map_break(|reason| FfiUnsafe { ty, reason, help: None })?;
match self.check_variant_for_ffi(acc, ty, def, variant, args) {
FfiSafe => ControlFlow::Continue(()),
r => ControlFlow::Break(r),
}
});
if let ControlFlow::Break(result) = ret {
return result;
}
FfiSafe
}
}
}
ty::Char => FfiUnsafe {
ty,
reason: fluent::lint_improper_ctypes_char_reason,
help: Some(fluent::lint_improper_ctypes_char_help),
},
// It's just extra invariants on the type that you need to uphold,
// but only the base type is relevant for being representable in FFI.
ty::Pat(base, ..) => self.check_type_for_ffi(acc, base),
// Primitive types with a stable representation.
ty::Bool | ty::Int(..) | ty::Uint(..) | ty::Float(..) | ty::Never => FfiSafe,
ty::Slice(_) => FfiUnsafe {
ty,
reason: fluent::lint_improper_ctypes_slice_reason,
help: Some(fluent::lint_improper_ctypes_slice_help),
},
ty::Dynamic(..) => {
FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_dyn, help: None }
}
ty::Str => FfiUnsafe {
ty,
reason: fluent::lint_improper_ctypes_str_reason,
help: Some(fluent::lint_improper_ctypes_str_help),
},
ty::Tuple(..) => FfiUnsafe {
ty,
reason: fluent::lint_improper_ctypes_tuple_reason,
help: Some(fluent::lint_improper_ctypes_tuple_help),
},
ty::RawPtr(ty, _) | ty::Ref(_, ty, _)
if {
matches!(self.mode, CItemKind::Definition)
&& ty.is_sized(self.cx.tcx, self.cx.typing_env())
} =>
{
FfiSafe
}
ty::RawPtr(ty, _)
if match ty.kind() {
ty::Tuple(tuple) => tuple.is_empty(),
_ => false,
} =>
{
FfiSafe
}
ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => self.check_type_for_ffi(acc, ty),
ty::Array(inner_ty, _) => self.check_type_for_ffi(acc, inner_ty),
ty::FnPtr(sig_tys, hdr) => {
let sig = sig_tys.with(hdr);
if sig.abi().is_rustic_abi() {
return FfiUnsafe {
ty,
reason: fluent::lint_improper_ctypes_fnptr_reason,
help: Some(fluent::lint_improper_ctypes_fnptr_help),
};
}
let sig = tcx.instantiate_bound_regions_with_erased(sig);
for arg in sig.inputs() {
match self.check_type_for_ffi(acc, *arg) {
FfiSafe => {}
r => return r,
}
}
let ret_ty = sig.output();
if ret_ty.is_unit() {
return FfiSafe;
}
self.check_type_for_ffi(acc, ret_ty)
}
ty::Foreign(..) => FfiSafe,
// While opaque types are checked for earlier, if a projection in a struct field
// normalizes to an opaque type, then it will reach this branch.
ty::Alias(ty::Opaque, ..) => {
FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_opaque, help: None }
}
// `extern "C" fn` functions can have type parameters, which may or may not be FFI-safe,
// so they are currently ignored for the purposes of this lint.
ty::Param(..) | ty::Alias(ty::Projection | ty::Inherent, ..)
if matches!(self.mode, CItemKind::Definition) =>
{
FfiSafe
}
ty::UnsafeBinder(_) => todo!("FIXME(unsafe_binder)"),
ty::Param(..)
| ty::Alias(ty::Projection | ty::Inherent | ty::Free, ..)
| ty::Infer(..)
| ty::Bound(..)
| ty::Error(_)
| ty::Closure(..)
| ty::CoroutineClosure(..)
| ty::Coroutine(..)
| ty::CoroutineWitness(..)
| ty::Placeholder(..)
| ty::FnDef(..) => bug!("unexpected type in foreign function: {:?}", ty),
}
}
fn emit_ffi_unsafe_type_lint(
&mut self,
ty: Ty<'tcx>,
sp: Span,
note: DiagMessage,
help: Option<DiagMessage>,
) {
let lint = match self.mode {
CItemKind::Declaration => IMPROPER_CTYPES,
CItemKind::Definition => IMPROPER_CTYPES_DEFINITIONS,
};
let desc = match self.mode {
CItemKind::Declaration => "block",
CItemKind::Definition => "fn",
};
let span_note = if let ty::Adt(def, _) = ty.kind()
&& let Some(sp) = self.cx.tcx.hir_span_if_local(def.did())
{
Some(sp)
} else {
None
};
self.cx.emit_span_lint(
lint,
sp,
ImproperCTypes { ty, desc, label: sp, help, note, span_note },
);
}
fn check_for_opaque_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool {
struct ProhibitOpaqueTypes;
impl<'tcx> ty::TypeVisitor<TyCtxt<'tcx>> for ProhibitOpaqueTypes {
type Result = ControlFlow<Ty<'tcx>>;
fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
if !ty.has_opaque_types() {
return ControlFlow::Continue(());
}
if let ty::Alias(ty::Opaque, ..) = ty.kind() {
ControlFlow::Break(ty)
} else {
ty.super_visit_with(self)
}
}
}
if let Some(ty) = self
.cx
.tcx
.try_normalize_erasing_regions(self.cx.typing_env(), ty)
.unwrap_or(ty)
.visit_with(&mut ProhibitOpaqueTypes)
.break_value()
{
self.emit_ffi_unsafe_type_lint(ty, sp, fluent::lint_improper_ctypes_opaque, None);
true
} else {
false
}
}
fn check_type_for_ffi_and_report_errors(
&mut self,
sp: Span,
ty: Ty<'tcx>,
is_static: bool,
is_return_type: bool,
) {
if self.check_for_opaque_ty(sp, ty) {
// We've already emitted an error due to an opaque type.
return;
}
let ty = self.cx.tcx.try_normalize_erasing_regions(self.cx.typing_env(), ty).unwrap_or(ty);
// C doesn't really support passing arrays by value - the only way to pass an array by value
// is through a struct. So, first test that the top level isn't an array, and then
// recursively check the types inside.
if !is_static && self.check_for_array_ty(sp, ty) {
return;
}
// Don't report FFI errors for unit return types. This check exists here, and not in
// the caller (where it would make more sense) so that normalization has definitely
// happened.
if is_return_type && ty.is_unit() {
return;
}
let mut acc = CTypesVisitorState { cache: FxHashSet::default(), base_ty: ty };
match self.check_type_for_ffi(&mut acc, ty) {
FfiResult::FfiSafe => {}
FfiResult::FfiPhantom(ty) => {
self.emit_ffi_unsafe_type_lint(
ty,
sp,
fluent::lint_improper_ctypes_only_phantomdata,
None,
);
}
FfiResult::FfiUnsafe { ty, reason, help } => {
self.emit_ffi_unsafe_type_lint(ty, sp, reason, help);
}
}
}
/// Check if a function's argument types and result type are "ffi-safe".
///
/// For a external ABI function, argument types and the result type are walked to find fn-ptr
/// types that have external ABIs, as these still need checked.
fn check_fn(&mut self, def_id: LocalDefId, decl: &'tcx hir::FnDecl<'_>) {
let sig = self.cx.tcx.fn_sig(def_id).instantiate_identity();
let sig = self.cx.tcx.instantiate_bound_regions_with_erased(sig);
for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) {
for (fn_ptr_ty, span) in self.find_fn_ptr_ty_with_external_abi(input_hir, *input_ty) {
self.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, false, false);
}
}
if let hir::FnRetTy::Return(ret_hir) = decl.output {
for (fn_ptr_ty, span) in self.find_fn_ptr_ty_with_external_abi(ret_hir, sig.output()) {
self.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, false, true);
}
}
}
/// Check if a function's argument types and result type are "ffi-safe".
fn check_foreign_fn(&mut self, def_id: LocalDefId, decl: &'tcx hir::FnDecl<'_>) {
let sig = self.cx.tcx.fn_sig(def_id).instantiate_identity();
let sig = self.cx.tcx.instantiate_bound_regions_with_erased(sig);
for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) {
self.check_type_for_ffi_and_report_errors(input_hir.span, *input_ty, false, false);
}
if let hir::FnRetTy::Return(ret_hir) = decl.output {
self.check_type_for_ffi_and_report_errors(ret_hir.span, sig.output(), false, true);
}
}
fn check_foreign_static(&mut self, id: hir::OwnerId, span: Span) {
let ty = self.cx.tcx.type_of(id).instantiate_identity();
self.check_type_for_ffi_and_report_errors(span, ty, true, false);
}
/// Find any fn-ptr types with external ABIs in `ty`.
///
/// For example, `Option<extern "C" fn()>` returns `extern "C" fn()`
fn find_fn_ptr_ty_with_external_abi(
&self,
hir_ty: &hir::Ty<'tcx>,
ty: Ty<'tcx>,
) -> Vec<(Ty<'tcx>, Span)> {
struct FnPtrFinder<'tcx> {
spans: Vec<Span>,
tys: Vec<Ty<'tcx>>,
}
impl<'tcx> hir::intravisit::Visitor<'_> for FnPtrFinder<'tcx> {
fn visit_ty(&mut self, ty: &'_ hir::Ty<'_, AmbigArg>) {
debug!(?ty);
if let hir::TyKind::FnPtr(hir::FnPtrTy { abi, .. }) = ty.kind
&& !abi.is_rustic_abi()
{
self.spans.push(ty.span);
}
hir::intravisit::walk_ty(self, ty)
}
}
impl<'tcx> ty::TypeVisitor<TyCtxt<'tcx>> for FnPtrFinder<'tcx> {
type Result = ();
fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
if let ty::FnPtr(_, hdr) = ty.kind()
&& !hdr.abi.is_rustic_abi()
{
self.tys.push(ty);
}
ty.super_visit_with(self)
}
}
let mut visitor = FnPtrFinder { spans: Vec::new(), tys: Vec::new() };
ty.visit_with(&mut visitor);
visitor.visit_ty_unambig(hir_ty);
iter::zip(visitor.tys.drain(..), visitor.spans.drain(..)).collect()
}
}
impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDeclarations {
fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, it: &hir::ForeignItem<'tcx>) {
let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Declaration };
let abi = cx.tcx.hir_get_foreign_abi(it.hir_id());
match it.kind {
hir::ForeignItemKind::Fn(sig, _, _) => {
if abi.is_rustic_abi() {
vis.check_fn(it.owner_id.def_id, sig.decl)
} else {
vis.check_foreign_fn(it.owner_id.def_id, sig.decl);
}
}
hir::ForeignItemKind::Static(ty, _, _) if !abi.is_rustic_abi() => {
vis.check_foreign_static(it.owner_id, ty.span);
}
hir::ForeignItemKind::Static(..) | hir::ForeignItemKind::Type => (),
}
}
}
impl ImproperCTypesDefinitions {
fn check_ty_maybe_containing_foreign_fnptr<'tcx>(
&mut self,
cx: &LateContext<'tcx>,
hir_ty: &'tcx hir::Ty<'_>,
ty: Ty<'tcx>,
) {
let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition };
for (fn_ptr_ty, span) in vis.find_fn_ptr_ty_with_external_abi(hir_ty, ty) {
vis.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, true, false);
}
}
fn check_arg_for_power_alignment<'tcx>(
&mut self,
cx: &LateContext<'tcx>,
ty: Ty<'tcx>,
) -> bool {
assert!(cx.tcx.sess.target.os == "aix");
// Structs (under repr(C)) follow the power alignment rule if:
// - the first field of the struct is a floating-point type that
// is greater than 4-bytes, or
// - the first field of the struct is an aggregate whose
// recursively first field is a floating-point type greater than
// 4 bytes.
if ty.is_floating_point() && ty.primitive_size(cx.tcx).bytes() > 4 {
return true;
} else if let Adt(adt_def, _) = ty.kind()
&& adt_def.is_struct()
&& adt_def.repr().c()
&& !adt_def.repr().packed()
&& adt_def.repr().align.is_none()
{
let struct_variant = adt_def.variant(VariantIdx::ZERO);
// Within a nested struct, all fields are examined to correctly
// report if any fields after the nested struct within the
// original struct are misaligned.
for struct_field in &struct_variant.fields {
let field_ty = cx.tcx.type_of(struct_field.did).instantiate_identity();
if self.check_arg_for_power_alignment(cx, field_ty) {
return true;
}
}
}
return false;
}
fn check_struct_for_power_alignment<'tcx>(
&mut self,
cx: &LateContext<'tcx>,
item: &'tcx hir::Item<'tcx>,
) {
let adt_def = cx.tcx.adt_def(item.owner_id.to_def_id());
// repr(C) structs also with packed or aligned representation
// should be ignored.
if adt_def.repr().c()
&& !adt_def.repr().packed()
&& adt_def.repr().align.is_none()
&& cx.tcx.sess.target.os == "aix"
&& !adt_def.all_fields().next().is_none()
{
let struct_variant_data = item.expect_struct().2;
for field_def in struct_variant_data.fields().iter().skip(1) {
// Struct fields (after the first field) are checked for the
// power alignment rule, as fields after the first are likely
// to be the fields that are misaligned.
let def_id = field_def.def_id;
let ty = cx.tcx.type_of(def_id).instantiate_identity();
if self.check_arg_for_power_alignment(cx, ty) {
cx.emit_span_lint(USES_POWER_ALIGNMENT, field_def.span, UsesPowerAlignment);
}
}
}
}
}
/// `ImproperCTypesDefinitions` checks items outside of foreign items (e.g. stuff that isn't in
/// `extern "C" { }` blocks):
///
/// - `extern "<abi>" fn` definitions are checked in the same way as the
/// `ImproperCtypesDeclarations` visitor checks functions if `<abi>` is external (e.g. "C").
/// - All other items which contain types (e.g. other functions, struct definitions, etc) are
/// checked for extern fn-ptrs with external ABIs.
impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
match item.kind {
hir::ItemKind::Static(_, _, ty, _)
| hir::ItemKind::Const(_, _, ty, _)
| hir::ItemKind::TyAlias(_, _, ty) => {
self.check_ty_maybe_containing_foreign_fnptr(
cx,
ty,
cx.tcx.type_of(item.owner_id).instantiate_identity(),
);
}
// See `check_fn`..
hir::ItemKind::Fn { .. } => {}
// Structs are checked based on if they follow the power alignment
// rule (under repr(C)).
hir::ItemKind::Struct(..) => {
self.check_struct_for_power_alignment(cx, item);
}
// See `check_field_def`..
hir::ItemKind::Union(..) | hir::ItemKind::Enum(..) => {}
// Doesn't define something that can contain a external type to be checked.
hir::ItemKind::Impl(..)
| hir::ItemKind::TraitAlias(..)
| hir::ItemKind::Trait(..)
| hir::ItemKind::GlobalAsm { .. }
| hir::ItemKind::ForeignMod { .. }
| hir::ItemKind::Mod(..)
| hir::ItemKind::Macro(..)
| hir::ItemKind::Use(..)
| hir::ItemKind::ExternCrate(..) => {}
}
}
fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::FieldDef<'tcx>) {
self.check_ty_maybe_containing_foreign_fnptr(
cx,
field.ty,
cx.tcx.type_of(field.def_id).instantiate_identity(),
);
}
fn check_fn(
&mut self,
cx: &LateContext<'tcx>,
kind: hir::intravisit::FnKind<'tcx>,
decl: &'tcx hir::FnDecl<'_>,
_: &'tcx hir::Body<'_>,
_: Span,
id: LocalDefId,
) {
use hir::intravisit::FnKind;
let abi = match kind {
FnKind::ItemFn(_, _, header, ..) => header.abi,
FnKind::Method(_, sig, ..) => sig.header.abi,
_ => return,
};
let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition };
if abi.is_rustic_abi() {
vis.check_fn(id, decl);
} else {
vis.check_foreign_fn(id, decl);
}
}
}
declare_lint_pass!(VariantSizeDifferences => [VARIANT_SIZE_DIFFERENCES]);
impl<'tcx> LateLintPass<'tcx> for VariantSizeDifferences {

File diff suppressed because it is too large Load diff

View file

@ -174,10 +174,10 @@ fn main() {
// Prevent critical warnings when we're compiling from rust-lang/rust CI,
// except on MSVC, as the compiler throws warnings that are only reported
// for this platform. See https://github.com/rust-lang/rust/pull/145031#issuecomment-3162677202
// FIXME(llvm22): It looks like the specific problem code has been removed
// in https://github.com/llvm/llvm-project/commit/e8fc808bf8e78a3c80d1f8e293a92677b92366dd,
// retry msvc once we bump our LLVM version.
// for this platform. See https://github.com/rust-lang/rust/pull/145031#issuecomment-3162677202.
// Moreover, LLVM generally guarantees warning-freedom only when building with Clang, as other
// compilers have too many false positives. This is typically the case for MSVC, which throws
// many false-positive warnings. We keep it excluded, for these reasons.
if std::env::var_os("CI").is_some() && !target.contains("msvc") {
cfg.warnings_into_errors(true);
}

View file

@ -1568,12 +1568,11 @@ extern "C" bool LLVMRustPrepareThinLTOImport(const LLVMRustThinLTOData *Data,
return true;
}
extern "C" LLVMRustThinLTOBuffer *
LLVMRustThinLTOBufferCreate(LLVMModuleRef M, bool is_thin, bool emit_summary) {
extern "C" LLVMRustThinLTOBuffer *LLVMRustThinLTOBufferCreate(LLVMModuleRef M,
bool is_thin) {
auto Ret = std::make_unique<LLVMRustThinLTOBuffer>();
{
auto OS = raw_string_ostream(Ret->data);
auto ThinLinkOS = raw_string_ostream(Ret->thin_link_data);
{
if (is_thin) {
PassBuilder PB;
@ -1587,11 +1586,7 @@ LLVMRustThinLTOBufferCreate(LLVMModuleRef M, bool is_thin, bool emit_summary) {
PB.registerLoopAnalyses(LAM);
PB.crossRegisterProxies(LAM, FAM, CGAM, MAM);
ModulePassManager MPM;
// We only pass ThinLinkOS to be filled in if we want the summary,
// because otherwise LLVM does extra work and may double-emit some
// errors or warnings.
MPM.addPass(
ThinLTOBitcodeWriterPass(OS, emit_summary ? &ThinLinkOS : nullptr));
MPM.addPass(ThinLTOBitcodeWriterPass(OS, nullptr));
MPM.run(*unwrap(M), MAM);
} else {
WriteBitcodeToFile(*unwrap(M), OS);

View file

@ -5,8 +5,8 @@ edition = "2024"
[dependencies]
# tidy-alphabetical-start
tracing = "0.1.28"
tracing-core = "=0.1.30" # FIXME(Nilstrieb) tracing has a deadlock: https://github.com/tokio-rs/tracing/issues/2635
# tracing > 0.1.37 have huge binary size / instructions regression
tracing = "=0.1.37"
tracing-subscriber = { version = "0.3.3", default-features = false, features = ["fmt", "env-filter", "smallvec", "parking_lot", "ansi"] }
tracing-tree = "0.3.1"
# tidy-alphabetical-end

View file

@ -38,7 +38,7 @@ use std::fmt::{self, Display};
use std::io::{self, IsTerminal};
use tracing::dispatcher::SetGlobalDefaultError;
use tracing_core::{Event, Subscriber};
use tracing::{Event, Subscriber};
use tracing_subscriber::filter::{Directive, EnvFilter, LevelFilter};
use tracing_subscriber::fmt::FmtContext;
use tracing_subscriber::fmt::format::{self, FormatEvent, FormatFields};

View file

@ -579,7 +579,7 @@ impl<'tcx> Display for Const<'tcx> {
}
///////////////////////////////////////////////////////////////////////////
/// Const-related utilities
// Const-related utilities
impl<'tcx> TyCtxt<'tcx> {
pub fn span_as_caller_location(self, span: Span) -> ConstValue {

View file

@ -408,14 +408,14 @@ impl<'tcx> Place<'tcx> {
self.as_ref().project_deeper(more_projections, tcx)
}
pub fn ty_from<D: ?Sized>(
pub fn ty_from<D>(
local: Local,
projection: &[PlaceElem<'tcx>],
local_decls: &D,
tcx: TyCtxt<'tcx>,
) -> PlaceTy<'tcx>
where
D: HasLocalDecls<'tcx>,
D: ?Sized + HasLocalDecls<'tcx>,
{
PlaceTy::from_ty(local_decls.local_decls()[local].ty).multi_projection_ty(tcx, projection)
}
@ -529,9 +529,9 @@ impl<'tcx> PlaceRef<'tcx> {
Place { local: self.local, projection: tcx.mk_place_elems(new_projections) }
}
pub fn ty<D: ?Sized>(&self, local_decls: &D, tcx: TyCtxt<'tcx>) -> PlaceTy<'tcx>
pub fn ty<D>(&self, local_decls: &D, tcx: TyCtxt<'tcx>) -> PlaceTy<'tcx>
where
D: HasLocalDecls<'tcx>,
D: ?Sized + HasLocalDecls<'tcx>,
{
Place::ty_from(self.local, self.projection, local_decls, tcx)
}
@ -630,9 +630,9 @@ impl<'tcx> Operand<'tcx> {
if let ty::FnDef(def_id, args) = *const_ty.kind() { Some((def_id, args)) } else { None }
}
pub fn ty<D: ?Sized>(&self, local_decls: &D, tcx: TyCtxt<'tcx>) -> Ty<'tcx>
pub fn ty<D>(&self, local_decls: &D, tcx: TyCtxt<'tcx>) -> Ty<'tcx>
where
D: HasLocalDecls<'tcx>,
D: ?Sized + HasLocalDecls<'tcx>,
{
match self {
&Operand::Copy(ref l) | &Operand::Move(ref l) => l.ty(local_decls, tcx).ty,
@ -640,9 +640,9 @@ impl<'tcx> Operand<'tcx> {
}
}
pub fn span<D: ?Sized>(&self, local_decls: &D) -> Span
pub fn span<D>(&self, local_decls: &D) -> Span
where
D: HasLocalDecls<'tcx>,
D: ?Sized + HasLocalDecls<'tcx>,
{
match self {
&Operand::Copy(ref l) | &Operand::Move(ref l) => {
@ -674,7 +674,7 @@ impl<'tcx> ConstOperand<'tcx> {
}
///////////////////////////////////////////////////////////////////////////
/// Rvalues
// Rvalues
pub enum RvalueInitializationState {
Shallow,
@ -721,9 +721,9 @@ impl<'tcx> Rvalue<'tcx> {
}
}
pub fn ty<D: ?Sized>(&self, local_decls: &D, tcx: TyCtxt<'tcx>) -> Ty<'tcx>
pub fn ty<D>(&self, local_decls: &D, tcx: TyCtxt<'tcx>) -> Ty<'tcx>
where
D: HasLocalDecls<'tcx>,
D: ?Sized + HasLocalDecls<'tcx>,
{
match *self {
Rvalue::Use(ref operand) => operand.ty(local_decls, tcx),

View file

@ -1415,6 +1415,24 @@ impl PlaceContext {
)
}
/// Returns `true` if this place context may be used to know the address of the given place.
#[inline]
pub fn may_observe_address(self) -> bool {
matches!(
self,
PlaceContext::NonMutatingUse(
NonMutatingUseContext::SharedBorrow
| NonMutatingUseContext::RawBorrow
| NonMutatingUseContext::FakeBorrow
) | PlaceContext::MutatingUse(
MutatingUseContext::Drop
| MutatingUseContext::Borrow
| MutatingUseContext::RawBorrow
| MutatingUseContext::AsmOutput
)
)
}
/// Returns `true` if this place context represents a storage live or storage dead marker.
#[inline]
pub fn is_storage_marker(self) -> bool {

View file

@ -216,10 +216,7 @@ impl<'tcx, E: TyEncoder<'tcx>> Encodable<E> for ty::ParamEnv<'tcx> {
#[inline]
fn decode_arena_allocable<'tcx, D: TyDecoder<'tcx>, T: ArenaAllocatable<'tcx> + Decodable<D>>(
decoder: &mut D,
) -> &'tcx T
where
D: TyDecoder<'tcx>,
{
) -> &'tcx T {
decoder.interner().arena.alloc(Decodable::decode(decoder))
}
@ -230,10 +227,7 @@ fn decode_arena_allocable_slice<
T: ArenaAllocatable<'tcx> + Decodable<D>,
>(
decoder: &mut D,
) -> &'tcx [T]
where
D: TyDecoder<'tcx>,
{
) -> &'tcx [T] {
decoder.interner().arena.alloc_from_iter(<Vec<T> as Decodable<D>>::decode(decoder))
}

View file

@ -92,7 +92,7 @@ impl<'tcx> Visitor<'tcx> for TransferFunction<'_> {
}
match DefUse::for_place(*place, context) {
Some(DefUse::Def) => {
DefUse::Def => {
if let PlaceContext::MutatingUse(
MutatingUseContext::Call | MutatingUseContext::AsmOutput,
) = context
@ -105,8 +105,8 @@ impl<'tcx> Visitor<'tcx> for TransferFunction<'_> {
self.0.kill(place.local);
}
}
Some(DefUse::Use) => self.0.gen_(place.local),
None => {}
DefUse::Use => self.0.gen_(place.local),
DefUse::PartialWrite | DefUse::NonUse => {}
}
self.visit_projection(place.as_ref(), context, location);
@ -131,23 +131,29 @@ impl<'tcx> Visitor<'tcx> for YieldResumeEffect<'_> {
}
#[derive(Eq, PartialEq, Clone)]
enum DefUse {
pub enum DefUse {
/// Full write to the local.
Def,
/// Read of any part of the local.
Use,
/// Partial write to the local.
PartialWrite,
/// Non-use, like debuginfo.
NonUse,
}
impl DefUse {
fn apply(state: &mut DenseBitSet<Local>, place: Place<'_>, context: PlaceContext) {
match DefUse::for_place(place, context) {
Some(DefUse::Def) => state.kill(place.local),
Some(DefUse::Use) => state.gen_(place.local),
None => {}
DefUse::Def => state.kill(place.local),
DefUse::Use => state.gen_(place.local),
DefUse::PartialWrite | DefUse::NonUse => {}
}
}
fn for_place(place: Place<'_>, context: PlaceContext) -> Option<DefUse> {
pub fn for_place(place: Place<'_>, context: PlaceContext) -> DefUse {
match context {
PlaceContext::NonUse(_) => None,
PlaceContext::NonUse(_) => DefUse::NonUse,
PlaceContext::MutatingUse(
MutatingUseContext::Call
@ -156,21 +162,20 @@ impl DefUse {
| MutatingUseContext::Store
| MutatingUseContext::Deinit,
) => {
// Treat derefs as a use of the base local. `*p = 4` is not a def of `p` but a use.
if place.is_indirect() {
// Treat derefs as a use of the base local. `*p = 4` is not a def of `p` but a
// use.
Some(DefUse::Use)
DefUse::Use
} else if place.projection.is_empty() {
Some(DefUse::Def)
DefUse::Def
} else {
None
DefUse::PartialWrite
}
}
// Setting the discriminant is not a use because it does no reading, but it is also not
// a def because it does not overwrite the whole place
PlaceContext::MutatingUse(MutatingUseContext::SetDiscriminant) => {
place.is_indirect().then_some(DefUse::Use)
if place.is_indirect() { DefUse::Use } else { DefUse::PartialWrite }
}
// All other contexts are uses...
@ -188,7 +193,7 @@ impl DefUse {
| NonMutatingUseContext::PlaceMention
| NonMutatingUseContext::FakeBorrow
| NonMutatingUseContext::SharedBorrow,
) => Some(DefUse::Use),
) => DefUse::Use,
PlaceContext::MutatingUse(MutatingUseContext::Projection)
| PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => {

View file

@ -9,7 +9,8 @@ pub use self::initialized::{
MaybeUninitializedPlaces, MaybeUninitializedPlacesDomain,
};
pub use self::liveness::{
MaybeLiveLocals, MaybeTransitiveLiveLocals, TransferFunction as LivenessTransferFunction,
DefUse, MaybeLiveLocals, MaybeTransitiveLiveLocals,
TransferFunction as LivenessTransferFunction,
};
pub use self::storage_liveness::{
MaybeRequiresStorage, MaybeStorageDead, MaybeStorageLive, always_storage_live_locals,

View file

@ -1,9 +1,5 @@
use rustc_index::bit_set::DenseBitSet;
use rustc_index::interval::SparseIntervalMatrix;
use rustc_index::{Idx, IndexVec};
use rustc_middle::mir::{self, BasicBlock, Body, Location};
use crate::framework::{Analysis, Results, ResultsVisitor, visit_results};
use rustc_middle::mir::{BasicBlock, Body, Location};
/// Maps between a `Location` and a `PointIndex` (and vice versa).
pub struct DenseLocationMap {
@ -93,65 +89,3 @@ rustc_index::newtype_index! {
#[debug_format = "PointIndex({})"]
pub struct PointIndex {}
}
/// Add points depending on the result of the given dataflow analysis.
pub fn save_as_intervals<'tcx, N, A>(
elements: &DenseLocationMap,
body: &mir::Body<'tcx>,
mut analysis: A,
results: Results<A::Domain>,
) -> SparseIntervalMatrix<N, PointIndex>
where
N: Idx,
A: Analysis<'tcx, Domain = DenseBitSet<N>>,
{
let values = SparseIntervalMatrix::new(elements.num_points());
let mut visitor = Visitor { elements, values };
visit_results(
body,
body.basic_blocks.reverse_postorder().iter().copied(),
&mut analysis,
&results,
&mut visitor,
);
visitor.values
}
struct Visitor<'a, N: Idx> {
elements: &'a DenseLocationMap,
values: SparseIntervalMatrix<N, PointIndex>,
}
impl<'tcx, A, N> ResultsVisitor<'tcx, A> for Visitor<'_, N>
where
A: Analysis<'tcx, Domain = DenseBitSet<N>>,
N: Idx,
{
fn visit_after_primary_statement_effect<'mir>(
&mut self,
_analysis: &mut A,
state: &A::Domain,
_statement: &'mir mir::Statement<'tcx>,
location: Location,
) {
let point = self.elements.point_from_location(location);
// Use internal iterator manually as it is much more efficient.
state.iter().for_each(|node| {
self.values.insert(node, point);
});
}
fn visit_after_primary_terminator_effect<'mir>(
&mut self,
_analysis: &mut A,
state: &A::Domain,
_terminator: &'mir mir::Terminator<'tcx>,
location: Location,
) {
let point = self.elements.point_from_location(location);
// Use internal iterator manually as it is much more efficient.
state.iter().for_each(|node| {
self.values.insert(node, point);
});
}
}

View file

@ -6,7 +6,7 @@ use rustc_data_structures::fx::{FxHashMap, FxIndexSet, StdEntry};
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_index::IndexVec;
use rustc_index::bit_set::DenseBitSet;
use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor};
use rustc_middle::mir::visit::{PlaceContext, Visitor};
use rustc_middle::mir::*;
use rustc_middle::ty::{self, Ty, TyCtxt};
use tracing::debug;
@ -917,12 +917,7 @@ pub fn excluded_locals(body: &Body<'_>) -> DenseBitSet<Local> {
impl<'tcx> Visitor<'tcx> for Collector {
fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) {
if (context.is_borrow()
|| context.is_address_of()
|| context.is_drop()
|| context == PlaceContext::MutatingUse(MutatingUseContext::AsmOutput))
&& !place.is_indirect()
{
if context.may_observe_address() && !place.is_indirect() {
// A pointer to a place could be used to access other places with the same local,
// hence we have to exclude the local completely.
self.result.insert(place.local);

View file

@ -82,7 +82,7 @@ impl ExpnNode {
Self {
expn_id,
expn_kind: expn_data.kind.clone(),
expn_kind: expn_data.kind,
call_site,
call_site_expn_id,

View file

@ -135,7 +135,16 @@ impl<'tcx> Visitor<'tcx> for CostChecker<'_, 'tcx> {
}
}
}
TerminatorKind::Call { unwind, .. } => {
TerminatorKind::Call { ref func, unwind, .. } => {
// We track calls because they make our function not a leaf (and in theory, the
// number of calls indicates how likely this function is to perturb other CGUs).
// But intrinsics don't have a body that gets assigned to a CGU, so they are
// ignored.
if let Some((fn_def_id, _)) = func.const_fn_def()
&& self.tcx.has_attr(fn_def_id, sym::rustc_intrinsic)
{
return;
}
self.calls += 1;
if let UnwindAction::Cleanup(_) = unwind {
self.landing_pads += 1;

View file

@ -59,6 +59,12 @@
//! The first two conditions are simple structural requirements on the `Assign` statements that can
//! be trivially checked. The third requirement however is more difficult and costly to check.
//!
//! ## Current implementation
//!
//! The current implementation relies on live range computation to check for conflicts. We only
//! allow to merge locals that have disjoint live ranges. The live range are defined with
//! half-statement granularity, so as to make all writes be live for at least a half statement.
//!
//! ## Future Improvements
//!
//! There are a number of ways in which this pass could be improved in the future:
@ -117,9 +123,8 @@
//! - Layout optimizations for coroutines have been added to improve code generation for
//! async/await, which are very similar in spirit to what this optimization does.
//!
//! Also, rustc now has a simple NRVO pass (see `nrvo.rs`), which handles a subset of the cases that
//! this destination propagation pass handles, proving that similar optimizations can be performed
//! on MIR.
//! [The next approach][attempt 4] computes a conflict matrix between locals by forbidding merging
//! locals with competing writes or with one write while the other is live.
//!
//! ## Pre/Post Optimization
//!
@ -130,20 +135,18 @@
//! [attempt 1]: https://github.com/rust-lang/rust/pull/47954
//! [attempt 2]: https://github.com/rust-lang/rust/pull/71003
//! [attempt 3]: https://github.com/rust-lang/rust/pull/72632
//! [attempt 4]: https://github.com/rust-lang/rust/pull/96451
use rustc_data_structures::fx::{FxIndexMap, IndexEntry, IndexOccupiedEntry};
use rustc_data_structures::union_find::UnionFind;
use rustc_index::bit_set::DenseBitSet;
use rustc_index::interval::SparseIntervalMatrix;
use rustc_middle::bug;
use rustc_index::{IndexVec, newtype_index};
use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor};
use rustc_middle::mir::{
Body, HasLocalDecls, InlineAsmOperand, Local, LocalKind, Location, MirDumper, Operand,
PassWhere, Place, Rvalue, Statement, StatementKind, TerminatorKind, traversal,
};
use rustc_middle::mir::*;
use rustc_middle::ty::TyCtxt;
use rustc_mir_dataflow::Analysis;
use rustc_mir_dataflow::impls::MaybeLiveLocals;
use rustc_mir_dataflow::points::{DenseLocationMap, PointIndex, save_as_intervals};
use rustc_mir_dataflow::impls::{DefUse, MaybeLiveLocals};
use rustc_mir_dataflow::points::DenseLocationMap;
use rustc_mir_dataflow::{Analysis, Results};
use tracing::{debug, trace};
pub(super) struct DestinationPropagation;
@ -161,84 +164,81 @@ impl<'tcx> crate::MirPass<'tcx> for DestinationPropagation {
sess.mir_opt_level() >= 3
}
#[tracing::instrument(level = "trace", skip(self, tcx, body))]
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let def_id = body.source.def_id();
let mut candidates = Candidates::default();
let mut write_info = WriteInfo::default();
trace!(func = ?tcx.def_path_str(def_id));
trace!(?def_id);
let borrowed = rustc_mir_dataflow::impls::borrowed_locals(body);
let live = MaybeLiveLocals.iterate_to_fixpoint(tcx, body, Some("MaybeLiveLocals-DestProp"));
let points = DenseLocationMap::new(body);
let mut live = save_as_intervals(&points, body, live.analysis, live.results);
// In order to avoid having to collect data for every single pair of locals in the body, we
// do not allow doing more than one merge for places that are derived from the same local at
// once. To avoid missed opportunities, we instead iterate to a fixed point - we'll refer to
// each of these iterations as a "round."
//
// Reaching a fixed point could in theory take up to `min(l, s)` rounds - however, we do not
// expect to see MIR like that. To verify this, a test was run against `[rust-lang/regex]` -
// the average MIR body saw 1.32 full iterations of this loop. The most that was hit were 30
// for a single function. Only 80/2801 (2.9%) of functions saw at least 5.
//
// [rust-lang/regex]:
// https://github.com/rust-lang/regex/tree/b5372864e2df6a2f5e543a556a62197f50ca3650
let mut round_count = 0;
loop {
// PERF: Can we do something smarter than recalculating the candidates and liveness
// results?
candidates.reset_and_find(body, &borrowed);
trace!(?candidates);
dest_prop_mir_dump(tcx, body, &points, &live, round_count);
FilterInformation::filter_liveness(
&mut candidates,
&points,
&live,
&mut write_info,
body,
);
// Because we only filter once per round, it is unsound to use a local for more than
// one merge operation within a single round of optimizations. We store here which ones
// we have already used.
let mut merged_locals: DenseBitSet<Local> =
DenseBitSet::new_empty(body.local_decls.len());
// This is the set of merges we will apply this round. It is a subset of the candidates.
let mut merges = FxIndexMap::default();
for (src, candidates) in candidates.c.iter() {
if merged_locals.contains(*src) {
continue;
}
let Some(dest) = candidates.iter().find(|dest| !merged_locals.contains(**dest))
else {
continue;
};
// Replace `src` by `dest` everywhere.
merges.insert(*src, *dest);
merged_locals.insert(*src);
merged_locals.insert(*dest);
// Update liveness information based on the merge we just performed.
// Every location where `src` was live, `dest` will be live.
live.union_rows(*src, *dest);
}
trace!(merging = ?merges);
if merges.is_empty() {
break;
}
round_count += 1;
apply_merges(body, tcx, merges, merged_locals);
let candidates = Candidates::find(body, &borrowed);
trace!(?candidates);
if candidates.c.is_empty() {
return;
}
trace!(round_count);
let live = MaybeLiveLocals.iterate_to_fixpoint(tcx, body, Some("MaybeLiveLocals-DestProp"));
let points = DenseLocationMap::new(body);
let mut relevant = RelevantLocals::compute(&candidates, body.local_decls.len());
let mut live = save_as_intervals(&points, body, &relevant, live.results);
dest_prop_mir_dump(tcx, body, &points, &live, &relevant);
let mut merged_locals = DenseBitSet::new_empty(body.local_decls.len());
for (src, dst) in candidates.c.into_iter() {
trace!(?src, ?dst);
let Some(mut src) = relevant.find(src) else { continue };
let Some(mut dst) = relevant.find(dst) else { continue };
if src == dst {
continue;
}
let Some(src_live_ranges) = live.row(src) else { continue };
let Some(dst_live_ranges) = live.row(dst) else { continue };
trace!(?src, ?src_live_ranges);
trace!(?dst, ?dst_live_ranges);
if src_live_ranges.disjoint(dst_live_ranges) {
// We want to replace `src` by `dst`.
let mut orig_src = relevant.original[src];
let mut orig_dst = relevant.original[dst];
// The return place and function arguments are required and cannot be renamed.
// This check cannot be made during candidate collection, as we may want to
// unify the same non-required local with several required locals.
match (is_local_required(orig_src, body), is_local_required(orig_dst, body)) {
// Renaming `src` is ok.
(false, _) => {}
// Renaming `src` is wrong, but renaming `dst` is ok.
(true, false) => {
std::mem::swap(&mut src, &mut dst);
std::mem::swap(&mut orig_src, &mut orig_dst);
}
// Neither local can be renamed, so skip this case.
(true, true) => continue,
}
trace!(?src, ?dst, "merge");
merged_locals.insert(orig_src);
merged_locals.insert(orig_dst);
// Replace `src` by `dst`.
let head = relevant.union(src, dst);
live.union_rows(/* read */ src, /* write */ head);
live.union_rows(/* read */ dst, /* write */ head);
}
}
trace!(?merged_locals);
trace!(?relevant.renames);
if merged_locals.is_empty() {
return;
}
apply_merges(body, tcx, relevant, merged_locals);
}
fn is_required(&self) -> bool {
@ -246,30 +246,6 @@ impl<'tcx> crate::MirPass<'tcx> for DestinationPropagation {
}
}
#[derive(Debug, Default)]
struct Candidates {
/// The set of candidates we are considering in this optimization.
///
/// We will always merge the key into at most one of its values.
///
/// Whether a place ends up in the key or the value does not correspond to whether it appears as
/// the lhs or rhs of any assignment. As a matter of fact, the places in here might never appear
/// in an assignment at all. This happens because if we see an assignment like this:
///
/// ```ignore (syntax-highlighting-only)
/// _1.0 = _2.0
/// ```
///
/// We will still report that we would like to merge `_1` and `_2` in an attempt to allow us to
/// remove that assignment.
c: FxIndexMap<Local, Vec<Local>>,
/// A reverse index of the `c` set; if the `c` set contains `a => Place { local: b, proj }`,
/// then this contains `b => a`.
// PERF: Possibly these should be `SmallVec`s?
reverse: FxIndexMap<Local, Vec<Local>>,
}
//////////////////////////////////////////////////////////
// Merging
//
@ -278,16 +254,16 @@ struct Candidates {
fn apply_merges<'tcx>(
body: &mut Body<'tcx>,
tcx: TyCtxt<'tcx>,
merges: FxIndexMap<Local, Local>,
relevant: RelevantLocals,
merged_locals: DenseBitSet<Local>,
) {
let mut merger = Merger { tcx, merges, merged_locals };
let mut merger = Merger { tcx, relevant, merged_locals };
merger.visit_body_preserves_cfg(body);
}
struct Merger<'tcx> {
tcx: TyCtxt<'tcx>,
merges: FxIndexMap<Local, Local>,
relevant: RelevantLocals,
merged_locals: DenseBitSet<Local>,
}
@ -297,8 +273,8 @@ impl<'tcx> MutVisitor<'tcx> for Merger<'tcx> {
}
fn visit_local(&mut self, local: &mut Local, _: PlaceContext, _location: Location) {
if let Some(dest) = self.merges.get(local) {
*local = *dest;
if let Some(relevant) = self.relevant.find(*local) {
*local = self.relevant.original[relevant];
}
}
@ -336,17 +312,75 @@ impl<'tcx> MutVisitor<'tcx> for Merger<'tcx> {
}
//////////////////////////////////////////////////////////
// Liveness filtering
// Relevant locals
//
// This section enforces bullet point 2
// Small utility to reduce size of the conflict matrix by only considering locals that appear in
// the candidates
struct FilterInformation<'a, 'tcx> {
body: &'a Body<'tcx>,
points: &'a DenseLocationMap,
live: &'a SparseIntervalMatrix<Local, PointIndex>,
candidates: &'a mut Candidates,
write_info: &'a mut WriteInfo,
at: Location,
newtype_index! {
/// Represent a subset of locals which appear in candidates.
struct RelevantLocal {}
}
#[derive(Debug)]
struct RelevantLocals {
original: IndexVec<RelevantLocal, Local>,
shrink: IndexVec<Local, Option<RelevantLocal>>,
renames: UnionFind<RelevantLocal>,
}
impl RelevantLocals {
#[tracing::instrument(level = "trace", skip(candidates, num_locals), ret)]
fn compute(candidates: &Candidates, num_locals: usize) -> RelevantLocals {
let mut original = IndexVec::with_capacity(candidates.c.len());
let mut shrink = IndexVec::from_elem_n(None, num_locals);
// Mark a local as relevant and record it into the maps.
let mut declare = |local| {
shrink.get_or_insert_with(local, || original.push(local));
};
for &(src, dest) in candidates.c.iter() {
declare(src);
declare(dest)
}
let renames = UnionFind::new(original.len());
RelevantLocals { original, shrink, renames }
}
fn find(&mut self, src: Local) -> Option<RelevantLocal> {
let src = self.shrink[src]?;
let src = self.renames.find(src);
Some(src)
}
fn union(&mut self, lhs: RelevantLocal, rhs: RelevantLocal) -> RelevantLocal {
let head = self.renames.unify(lhs, rhs);
// We need to ensure we keep the original local of the RHS, as it may be a required local.
self.original[head] = self.original[rhs];
head
}
}
/////////////////////////////////////////////////////
// Candidate accumulation
#[derive(Debug, Default)]
struct Candidates {
/// The set of candidates we are considering in this optimization.
///
/// Whether a place ends up in the key or the value does not correspond to whether it appears as
/// the lhs or rhs of any assignment. As a matter of fact, the places in here might never appear
/// in an assignment at all. This happens because if we see an assignment like this:
///
/// ```ignore (syntax-highlighting-only)
/// _1.0 = _2.0
/// ```
///
/// We will still report that we would like to merge `_1` and `_2` in an attempt to allow us to
/// remove that assignment.
c: Vec<(Local, Local)>,
}
// We first implement some utility functions which we will expose removing candidates according to
@ -356,394 +390,17 @@ impl Candidates {
/// Collects the candidates for merging.
///
/// This is responsible for enforcing the first and third bullet point.
fn reset_and_find<'tcx>(&mut self, body: &Body<'tcx>, borrowed: &DenseBitSet<Local>) {
self.c.clear();
self.reverse.clear();
let mut visitor = FindAssignments { body, candidates: &mut self.c, borrowed };
fn find(body: &Body<'_>, borrowed: &DenseBitSet<Local>) -> Candidates {
let mut visitor = FindAssignments { body, candidates: Default::default(), borrowed };
visitor.visit_body(body);
// Deduplicate candidates.
for (_, cands) in self.c.iter_mut() {
cands.sort();
cands.dedup();
}
// Generate the reverse map.
for (src, cands) in self.c.iter() {
for dest in cands.iter().copied() {
self.reverse.entry(dest).or_default().push(*src);
}
}
Candidates { c: visitor.candidates }
}
/// Just `Vec::retain`, but the condition is inverted and we add debugging output
fn vec_filter_candidates(
src: Local,
v: &mut Vec<Local>,
mut f: impl FnMut(Local) -> CandidateFilter,
at: Location,
) {
v.retain(|dest| {
let remove = f(*dest);
if remove == CandidateFilter::Remove {
trace!("eliminating {:?} => {:?} due to conflict at {:?}", src, dest, at);
}
remove == CandidateFilter::Keep
});
}
/// `vec_filter_candidates` but for an `Entry`
fn entry_filter_candidates(
mut entry: IndexOccupiedEntry<'_, Local, Vec<Local>>,
p: Local,
f: impl FnMut(Local) -> CandidateFilter,
at: Location,
) {
let candidates = entry.get_mut();
Self::vec_filter_candidates(p, candidates, f, at);
if candidates.len() == 0 {
// FIXME(#120456) - is `swap_remove` correct?
entry.swap_remove();
}
}
/// For all candidates `(p, q)` or `(q, p)` removes the candidate if `f(q)` says to do so
fn filter_candidates_by(
&mut self,
p: Local,
mut f: impl FnMut(Local) -> CandidateFilter,
at: Location,
) {
// Cover the cases where `p` appears as a `src`
if let IndexEntry::Occupied(entry) = self.c.entry(p) {
Self::entry_filter_candidates(entry, p, &mut f, at);
}
// And the cases where `p` appears as a `dest`
let Some(srcs) = self.reverse.get_mut(&p) else {
return;
};
// We use `retain` here to remove the elements from the reverse set if we've removed the
// matching candidate in the forward set.
srcs.retain(|src| {
if f(*src) == CandidateFilter::Keep {
return true;
}
let IndexEntry::Occupied(entry) = self.c.entry(*src) else {
return false;
};
Self::entry_filter_candidates(
entry,
*src,
|dest| {
if dest == p { CandidateFilter::Remove } else { CandidateFilter::Keep }
},
at,
);
false
});
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
enum CandidateFilter {
Keep,
Remove,
}
impl<'a, 'tcx> FilterInformation<'a, 'tcx> {
/// Filters the set of candidates to remove those that conflict.
///
/// The steps we take are exactly those that are outlined at the top of the file. For each
/// statement/terminator, we collect the set of locals that are written to in that
/// statement/terminator, and then we remove all pairs of candidates that contain one such local
/// and another one that is live.
///
/// We need to be careful about the ordering of operations within each statement/terminator
/// here. Many statements might write and read from more than one place, and we need to consider
/// them all. The strategy for doing this is as follows: We first gather all the places that are
/// written to within the statement/terminator via `WriteInfo`. Then, we use the liveness
/// analysis from *before* the statement/terminator (in the control flow sense) to eliminate
/// candidates - this is because we want to conservatively treat a pair of locals that is both
/// read and written in the statement/terminator to be conflicting, and the liveness analysis
/// before the statement/terminator will correctly report locals that are read in the
/// statement/terminator to be live. We are additionally conservative by treating all written to
/// locals as also being read from.
fn filter_liveness(
candidates: &mut Candidates,
points: &DenseLocationMap,
live: &SparseIntervalMatrix<Local, PointIndex>,
write_info: &mut WriteInfo,
body: &Body<'tcx>,
) {
let mut this = FilterInformation {
body,
points,
live,
candidates,
// We don't actually store anything at this scope, we just keep things here to be able
// to reuse the allocation.
write_info,
// Doesn't matter what we put here, will be overwritten before being used
at: Location::START,
};
this.internal_filter_liveness();
}
fn internal_filter_liveness(&mut self) {
for (block, data) in traversal::preorder(self.body) {
self.at = Location { block, statement_index: data.statements.len() };
self.write_info.for_terminator(&data.terminator().kind);
self.apply_conflicts();
for (i, statement) in data.statements.iter().enumerate().rev() {
self.at = Location { block, statement_index: i };
self.write_info.for_statement(&statement.kind, self.body);
self.apply_conflicts();
}
}
}
fn apply_conflicts(&mut self) {
let writes = &self.write_info.writes;
for p in writes {
let other_skip = self.write_info.skip_pair.and_then(|(a, b)| {
if a == *p {
Some(b)
} else if b == *p {
Some(a)
} else {
None
}
});
let at = self.points.point_from_location(self.at);
self.candidates.filter_candidates_by(
*p,
|q| {
if Some(q) == other_skip {
return CandidateFilter::Keep;
}
// It is possible that a local may be live for less than the
// duration of a statement This happens in the case of function
// calls or inline asm. Because of this, we also mark locals as
// conflicting when both of them are written to in the same
// statement.
if self.live.contains(q, at) || writes.contains(&q) {
CandidateFilter::Remove
} else {
CandidateFilter::Keep
}
},
self.at,
);
}
}
}
/// Describes where a statement/terminator writes to
#[derive(Default, Debug)]
struct WriteInfo {
writes: Vec<Local>,
/// If this pair of locals is a candidate pair, completely skip processing it during this
/// statement. All other candidates are unaffected.
skip_pair: Option<(Local, Local)>,
}
impl WriteInfo {
fn for_statement<'tcx>(&mut self, statement: &StatementKind<'tcx>, body: &Body<'tcx>) {
self.reset();
match statement {
StatementKind::Assign(box (lhs, rhs)) => {
self.add_place(*lhs);
match rhs {
Rvalue::Use(op) => {
self.add_operand(op);
self.consider_skipping_for_assign_use(*lhs, op, body);
}
Rvalue::Repeat(op, _) => {
self.add_operand(op);
}
Rvalue::Cast(_, op, _)
| Rvalue::UnaryOp(_, op)
| Rvalue::ShallowInitBox(op, _) => {
self.add_operand(op);
}
Rvalue::BinaryOp(_, ops) => {
for op in [&ops.0, &ops.1] {
self.add_operand(op);
}
}
Rvalue::Aggregate(_, ops) => {
for op in ops {
self.add_operand(op);
}
}
Rvalue::WrapUnsafeBinder(op, _) => {
self.add_operand(op);
}
Rvalue::ThreadLocalRef(_)
| Rvalue::NullaryOp(_, _)
| Rvalue::Ref(_, _, _)
| Rvalue::RawPtr(_, _)
| Rvalue::Len(_)
| Rvalue::Discriminant(_)
| Rvalue::CopyForDeref(_) => {}
}
}
// Retags are technically also reads, but reporting them as a write suffices
StatementKind::SetDiscriminant { place, .. }
| StatementKind::Deinit(place)
| StatementKind::Retag(_, place) => {
self.add_place(**place);
}
StatementKind::Intrinsic(_)
| StatementKind::ConstEvalCounter
| StatementKind::Nop
| StatementKind::Coverage(_)
| StatementKind::StorageLive(_)
| StatementKind::StorageDead(_)
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::PlaceMention(_) => {}
StatementKind::FakeRead(_) | StatementKind::AscribeUserType(_, _) => {
bug!("{:?} not found in this MIR phase", statement)
}
}
}
fn consider_skipping_for_assign_use<'tcx>(
&mut self,
lhs: Place<'tcx>,
rhs: &Operand<'tcx>,
body: &Body<'tcx>,
) {
let Some(rhs) = rhs.place() else { return };
if let Some(pair) = places_to_candidate_pair(lhs, rhs, body) {
self.skip_pair = Some(pair);
}
}
fn for_terminator<'tcx>(&mut self, terminator: &TerminatorKind<'tcx>) {
self.reset();
match terminator {
TerminatorKind::SwitchInt { discr: op, .. }
| TerminatorKind::Assert { cond: op, .. } => {
self.add_operand(op);
}
TerminatorKind::Call { destination, func, args, .. } => {
self.add_place(*destination);
self.add_operand(func);
for arg in args {
self.add_operand(&arg.node);
}
}
TerminatorKind::TailCall { func, args, .. } => {
self.add_operand(func);
for arg in args {
self.add_operand(&arg.node);
}
}
TerminatorKind::InlineAsm { operands, .. } => {
for asm_operand in operands {
match asm_operand {
InlineAsmOperand::In { value, .. } => {
self.add_operand(value);
}
InlineAsmOperand::Out { place, .. } => {
if let Some(place) = place {
self.add_place(*place);
}
}
// Note that the `late` field in `InOut` is about whether the registers used
// for these things overlap, and is of absolutely no interest to us.
InlineAsmOperand::InOut { in_value, out_place, .. } => {
if let Some(place) = out_place {
self.add_place(*place);
}
self.add_operand(in_value);
}
InlineAsmOperand::Const { .. }
| InlineAsmOperand::SymFn { .. }
| InlineAsmOperand::SymStatic { .. }
| InlineAsmOperand::Label { .. } => {}
}
}
}
TerminatorKind::Goto { .. }
| TerminatorKind::UnwindResume
| TerminatorKind::UnwindTerminate(_)
| TerminatorKind::Return
| TerminatorKind::Unreachable { .. } => (),
TerminatorKind::Drop { .. } => {
// `Drop`s create a `&mut` and so are not considered
}
TerminatorKind::Yield { .. }
| TerminatorKind::CoroutineDrop
| TerminatorKind::FalseEdge { .. }
| TerminatorKind::FalseUnwind { .. } => {
bug!("{:?} not found in this MIR phase", terminator)
}
}
}
fn add_place(&mut self, place: Place<'_>) {
self.writes.push(place.local);
}
fn add_operand<'tcx>(&mut self, op: &Operand<'tcx>) {
match op {
// FIXME(JakobDegen): In a previous version, the `Move` case was incorrectly treated as
// being a read only. This was unsound, however we cannot add a regression test because
// it is not possible to set this off with current MIR. Once we have that ability, a
// regression test should be added.
Operand::Move(p) => self.add_place(*p),
Operand::Copy(_) | Operand::Constant(_) => (),
}
}
fn reset(&mut self) {
self.writes.clear();
self.skip_pair = None;
}
}
/////////////////////////////////////////////////////
// Candidate accumulation
/// If the pair of places is being considered for merging, returns the candidate which would be
/// merged in order to accomplish this.
///
/// The contract here is in one direction - there is a guarantee that merging the locals that are
/// outputted by this function would result in an assignment between the inputs becoming a
/// self-assignment. However, there is no guarantee that the returned pair is actually suitable for
/// merging - candidate collection must still check this independently.
///
/// This output is unique for each unordered pair of input places.
fn places_to_candidate_pair<'tcx>(
a: Place<'tcx>,
b: Place<'tcx>,
body: &Body<'tcx>,
) -> Option<(Local, Local)> {
let (mut a, mut b) = if a.projection.len() == 0 && b.projection.len() == 0 {
(a.local, b.local)
} else {
return None;
};
// By sorting, we make sure we're input order independent
if a > b {
std::mem::swap(&mut a, &mut b);
}
// We could now return `(a, b)`, but then we miss some candidates in the case where `a` can't be
// used as a `src`.
if is_local_required(a, body) {
std::mem::swap(&mut a, &mut b);
}
// We could check `is_local_required` again here, but there's no need - after all, we make no
// promise that the candidate pair is actually valid
Some((a, b))
}
struct FindAssignments<'a, 'tcx> {
body: &'a Body<'tcx>,
candidates: &'a mut FxIndexMap<Local, Vec<Local>>,
candidates: Vec<(Local, Local)>,
borrowed: &'a DenseBitSet<Local>,
}
@ -753,11 +410,9 @@ impl<'tcx> Visitor<'tcx> for FindAssignments<'_, 'tcx> {
lhs,
Rvalue::CopyForDeref(rhs) | Rvalue::Use(Operand::Copy(rhs) | Operand::Move(rhs)),
)) = &statement.kind
&& let Some(src) = lhs.as_local()
&& let Some(dest) = rhs.as_local()
{
let Some((src, dest)) = places_to_candidate_pair(*lhs, *rhs, self.body) else {
return;
};
// As described at the top of the file, we do not go near things that have
// their address taken.
if self.borrowed.contains(src) || self.borrowed.contains(dest) {
@ -774,13 +429,8 @@ impl<'tcx> Visitor<'tcx> for FindAssignments<'_, 'tcx> {
return;
}
// Also, we need to make sure that MIR actually allows the `src` to be removed
if is_local_required(src, self.body) {
return;
}
// We may insert duplicates here, but that's fine
self.candidates.entry(src).or_default().push(dest);
self.candidates.push((src, dest));
}
}
}
@ -803,22 +453,162 @@ fn dest_prop_mir_dump<'tcx>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
points: &DenseLocationMap,
live: &SparseIntervalMatrix<Local, PointIndex>,
round: usize,
live: &SparseIntervalMatrix<RelevantLocal, TwoStepIndex>,
relevant: &RelevantLocals,
) {
let locals_live_at = |location| {
let location = points.point_from_location(location);
live.rows().filter(|&r| live.contains(r, location)).collect::<Vec<_>>()
live.rows()
.filter(|&r| live.contains(r, location))
.map(|rl| relevant.original[rl])
.collect::<Vec<_>>()
};
if let Some(dumper) = MirDumper::new(tcx, "DestinationPropagation-dataflow", body) {
let extra_data = &|pass_where, w: &mut dyn std::io::Write| {
if let PassWhere::BeforeLocation(loc) = pass_where {
writeln!(w, " // live: {:?}", locals_live_at(loc))?;
let location = TwoStepIndex::new(points, loc, Effect::Before);
let live = locals_live_at(location);
writeln!(w, " // before: {:?} => {:?}", location, live)?;
}
if let PassWhere::AfterLocation(loc) = pass_where {
let location = TwoStepIndex::new(points, loc, Effect::After);
let live = locals_live_at(location);
writeln!(w, " // after: {:?} => {:?}", location, live)?;
}
Ok(())
};
dumper.set_disambiguator(&round).set_extra_data(extra_data).dump_mir(body)
dumper.set_extra_data(extra_data).dump_mir(body)
}
}
#[derive(Copy, Clone, Debug)]
enum Effect {
Before,
After,
}
rustc_index::newtype_index! {
/// A reversed `PointIndex` but with the lower bit encoding early/late inside the statement.
/// The reversed order allows to use the more efficient `IntervalSet::append` method while we
/// iterate on the statements in reverse order.
#[orderable]
#[debug_format = "TwoStepIndex({})"]
struct TwoStepIndex {}
}
impl TwoStepIndex {
fn new(elements: &DenseLocationMap, location: Location, effect: Effect) -> TwoStepIndex {
let point = elements.point_from_location(location);
let effect = match effect {
Effect::Before => 0,
Effect::After => 1,
};
let max_index = 2 * elements.num_points() as u32 - 1;
let index = 2 * point.as_u32() + (effect as u32);
// Reverse the indexing to use more efficient `IntervalSet::append`.
TwoStepIndex::from_u32(max_index - index)
}
}
struct VisitPlacesWith<F>(F);
impl<'tcx, F> Visitor<'tcx> for VisitPlacesWith<F>
where
F: FnMut(Place<'tcx>, PlaceContext),
{
fn visit_local(&mut self, local: Local, ctxt: PlaceContext, _: Location) {
(self.0)(local.into(), ctxt);
}
fn visit_place(&mut self, place: &Place<'tcx>, ctxt: PlaceContext, location: Location) {
(self.0)(*place, ctxt);
self.visit_projection(place.as_ref(), ctxt, location);
}
}
/// Add points depending on the result of the given dataflow analysis.
fn save_as_intervals<'tcx>(
elements: &DenseLocationMap,
body: &Body<'tcx>,
relevant: &RelevantLocals,
results: Results<DenseBitSet<Local>>,
) -> SparseIntervalMatrix<RelevantLocal, TwoStepIndex> {
let mut values = SparseIntervalMatrix::new(2 * elements.num_points());
let mut state = MaybeLiveLocals.bottom_value(body);
let reachable_blocks = traversal::reachable_as_bitset(body);
let two_step_loc = |location, effect| TwoStepIndex::new(elements, location, effect);
let append_at =
|values: &mut SparseIntervalMatrix<_, _>, state: &DenseBitSet<Local>, twostep| {
for (relevant, &original) in relevant.original.iter_enumerated() {
if state.contains(original) {
values.append(relevant, twostep);
}
}
};
// Iterate blocks in decreasing order, to visit locations in decreasing order. This
// allows to use the more efficient `append` method to interval sets.
for block in body.basic_blocks.indices().rev() {
if !reachable_blocks.contains(block) {
continue;
}
state.clone_from(&results[block]);
let block_data = &body.basic_blocks[block];
let loc = Location { block, statement_index: block_data.statements.len() };
let term = block_data.terminator();
let mut twostep = two_step_loc(loc, Effect::After);
append_at(&mut values, &state, twostep);
// Ensure we have a non-zero live range even for dead stores. This is done by marking all
// the written-to locals as live in the second half of the statement.
// We also ensure that operands read by terminators conflict with writes by that terminator.
// For instance a function call may read args after having written to the destination.
VisitPlacesWith(|place, ctxt| match DefUse::for_place(place, ctxt) {
DefUse::Def | DefUse::Use | DefUse::PartialWrite => {
if let Some(relevant) = relevant.shrink[place.local] {
values.insert(relevant, twostep);
}
}
DefUse::NonUse => {}
})
.visit_terminator(term, loc);
twostep = TwoStepIndex::from_u32(twostep.as_u32() + 1);
debug_assert_eq!(twostep, two_step_loc(loc, Effect::Before));
MaybeLiveLocals.apply_early_terminator_effect(&mut state, term, loc);
MaybeLiveLocals.apply_primary_terminator_effect(&mut state, term, loc);
append_at(&mut values, &state, twostep);
for (statement_index, stmt) in block_data.statements.iter().enumerate().rev() {
let loc = Location { block, statement_index };
twostep = TwoStepIndex::from_u32(twostep.as_u32() + 1);
debug_assert_eq!(twostep, two_step_loc(loc, Effect::After));
append_at(&mut values, &state, twostep);
// Ensure we have a non-zero live range even for dead stores. This is done by marking
// all the written-to locals as live in the second half of the statement.
VisitPlacesWith(|place, ctxt| match DefUse::for_place(place, ctxt) {
DefUse::Def | DefUse::PartialWrite => {
if let Some(relevant) = relevant.shrink[place.local] {
values.insert(relevant, twostep);
}
}
DefUse::Use | DefUse::NonUse => {}
})
.visit_statement(stmt, loc);
twostep = TwoStepIndex::from_u32(twostep.as_u32() + 1);
debug_assert_eq!(twostep, two_step_loc(loc, Effect::Before));
MaybeLiveLocals.apply_early_statement_effect(&mut state, stmt, loc);
MaybeLiveLocals.apply_primary_statement_effect(&mut state, stmt, loc);
// ... but reads from operands are marked as live here so they do not conflict with
// the all the writes we manually marked as live in the second half of the statement.
append_at(&mut values, &state, twostep);
}
}
values
}

View file

@ -87,6 +87,7 @@
use std::borrow::Cow;
use either::Either;
use itertools::Itertools as _;
use rustc_abi::{self as abi, BackendRepr, FIRST_VARIANT, FieldIdx, Primitive, Size, VariantIdx};
use rustc_const_eval::const_eval::DummyMachine;
use rustc_const_eval::interpret::{
@ -895,18 +896,13 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
fn simplify_aggregate_to_copy(
&mut self,
lhs: &Place<'tcx>,
rvalue: &mut Rvalue<'tcx>,
location: Location,
fields: &[VnIndex],
ty: Ty<'tcx>,
variant_index: VariantIdx,
fields: &[VnIndex],
) -> Option<VnIndex> {
let Some(&first_field) = fields.first() else {
return None;
};
let Value::Projection(copy_from_value, _) = *self.get(first_field) else {
return None;
};
let Some(&first_field) = fields.first() else { return None };
let Value::Projection(copy_from_value, _) = *self.get(first_field) else { return None };
// All fields must correspond one-to-one and come from the same aggregate value.
if fields.iter().enumerate().any(|(index, &v)| {
if let Value::Projection(pointer, ProjectionElem::Field(from_index, _)) = *self.get(v)
@ -933,21 +929,8 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
}
}
// Allow introducing places with non-constant offsets, as those are still better than
// reconstructing an aggregate.
if self.ty(copy_from_local_value) == rvalue.ty(self.local_decls, self.tcx)
&& let Some(place) = self.try_as_place(copy_from_local_value, location, true)
{
// Avoid creating `*a = copy (*b)`, as they might be aliases resulting in overlapping assignments.
// FIXME: This also avoids any kind of projection, not just derefs. We can add allowed projections.
if lhs.as_local().is_some() {
self.reused_locals.insert(place.local);
*rvalue = Rvalue::Use(Operand::Copy(place));
}
return Some(copy_from_local_value);
}
None
// Both must be variants of the same type.
if self.ty(copy_from_local_value) == ty { Some(copy_from_local_value) } else { None }
}
fn simplify_aggregate(
@ -1023,20 +1006,27 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
}
};
if ty.is_array() && fields.len() > 4 {
let first = fields[0];
if fields.iter().all(|&v| v == first) {
let len = ty::Const::from_target_usize(self.tcx, fields.len().try_into().unwrap());
if let Some(op) = self.try_as_operand(first, location) {
*rvalue = Rvalue::Repeat(op, len);
}
return Some(self.insert(ty, Value::Repeat(first, len)));
if ty.is_array()
&& fields.len() > 4
&& let Ok(&first) = fields.iter().all_equal_value()
{
let len = ty::Const::from_target_usize(self.tcx, fields.len().try_into().unwrap());
if let Some(op) = self.try_as_operand(first, location) {
*rvalue = Rvalue::Repeat(op, len);
}
return Some(self.insert(ty, Value::Repeat(first, len)));
}
if let Some(value) =
self.simplify_aggregate_to_copy(lhs, rvalue, location, &fields, variant_index)
{
if let Some(value) = self.simplify_aggregate_to_copy(ty, variant_index, &fields) {
// Allow introducing places with non-constant offsets, as those are still better than
// reconstructing an aggregate. But avoid creating `*a = copy (*b)`, as they might be
// aliases resulting in overlapping assignments.
let allow_complex_projection =
lhs.projection[..].iter().all(PlaceElem::is_stable_offset);
if let Some(place) = self.try_as_place(value, location, allow_complex_projection) {
self.reused_locals.insert(place.local);
*rvalue = Rvalue::Use(Operand::Copy(place));
}
return Some(value);
}

View file

@ -2,6 +2,7 @@ use std::cell::RefCell;
use std::collections::hash_map;
use std::rc::Rc;
use itertools::Itertools as _;
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
use rustc_data_structures::unord::{UnordMap, UnordSet};
use rustc_errors::Subdiagnostic;
@ -339,9 +340,9 @@ pub(crate) fn run_lint<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &Body<
// Suppose that all BIDs point into the same local,
// we can remove the this local from the observed drops,
// so that we can focus our diagnosis more on the others.
if candidates.iter().all(|&(_, place)| candidates[0].1.local == place.local) {
if let Ok(local) = candidates.iter().map(|&(_, place)| place.local).all_equal_value() {
for path_idx in all_locals_dropped.iter() {
if move_data.move_paths[path_idx].place.local == candidates[0].1.local {
if move_data.move_paths[path_idx].place.local == local {
to_exclude.insert(path_idx);
}
}

View file

@ -34,6 +34,7 @@
//! The normal logic that a program with UB can be changed to do anything does not apply to
//! pre-"runtime" MIR!
use itertools::Itertools as _;
use rustc_index::{Idx, IndexSlice, IndexVec};
use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
use rustc_middle::mir::*;
@ -288,20 +289,13 @@ impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> {
return false;
};
let first_succ = {
if let Some(first_succ) = terminator.successors().next() {
if terminator.successors().all(|s| s == first_succ) {
let count = terminator.successors().count();
self.pred_count[first_succ] -= (count - 1) as u32;
first_succ
} else {
return false;
}
} else {
return false;
}
let Ok(first_succ) = terminator.successors().all_equal_value() else {
return false;
};
let count = terminator.successors().count();
self.pred_count[first_succ] -= (count - 1) as u32;
debug!("simplifying branch {:?}", terminator);
terminator.kind = TerminatorKind::Goto { target: first_succ };
true

View file

@ -225,6 +225,9 @@ impl SsaVisitor<'_, '_> {
impl<'tcx> Visitor<'tcx> for SsaVisitor<'_, 'tcx> {
fn visit_local(&mut self, local: Local, ctxt: PlaceContext, loc: Location) {
if ctxt.may_observe_address() {
self.borrowed_locals.insert(local);
}
match ctxt {
PlaceContext::MutatingUse(MutatingUseContext::Projection)
| PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => bug!(),
@ -237,7 +240,6 @@ impl<'tcx> Visitor<'tcx> for SsaVisitor<'_, 'tcx> {
PlaceContext::NonMutatingUse(
NonMutatingUseContext::SharedBorrow | NonMutatingUseContext::FakeBorrow,
) => {
self.borrowed_locals.insert(local);
self.check_dominates(local, loc);
self.direct_uses[local] += 1;
}

View file

@ -44,19 +44,44 @@ pub(crate) struct UnmatchedDelim {
pub candidate_span: Option<Span>,
}
/// Which tokens should be stripped before lexing the tokens.
pub(crate) enum StripTokens {
/// Strip both shebang and frontmatter.
ShebangAndFrontmatter,
/// Strip the shebang but not frontmatter.
///
/// That means that char sequences looking like frontmatter are simply
/// interpreted as regular Rust lexemes.
Shebang,
/// Strip nothing.
///
/// In other words, char sequences looking like a shebang or frontmatter
/// are simply interpreted as regular Rust lexemes.
Nothing,
}
pub(crate) fn lex_token_trees<'psess, 'src>(
psess: &'psess ParseSess,
mut src: &'src str,
mut start_pos: BytePos,
override_span: Option<Span>,
frontmatter_allowed: FrontmatterAllowed,
strip_tokens: StripTokens,
) -> Result<TokenStream, Vec<Diag<'psess>>> {
// Skip `#!`, if present.
if let Some(shebang_len) = rustc_lexer::strip_shebang(src) {
src = &src[shebang_len..];
start_pos = start_pos + BytePos::from_usize(shebang_len);
match strip_tokens {
StripTokens::Shebang | StripTokens::ShebangAndFrontmatter => {
if let Some(shebang_len) = rustc_lexer::strip_shebang(src) {
src = &src[shebang_len..];
start_pos = start_pos + BytePos::from_usize(shebang_len);
}
}
StripTokens::Nothing => {}
}
let frontmatter_allowed = match strip_tokens {
StripTokens::ShebangAndFrontmatter => FrontmatterAllowed::Yes,
StripTokens::Shebang | StripTokens::Nothing => FrontmatterAllowed::No,
};
let cursor = Cursor::new(src, frontmatter_allowed);
let mut lexer = Lexer {
psess,

View file

@ -21,7 +21,6 @@ use rustc_ast::tokenstream::{DelimSpan, TokenStream};
use rustc_ast::{AttrItem, Attribute, MetaItemInner, token};
use rustc_ast_pretty::pprust;
use rustc_errors::{Diag, EmissionGuarantee, FatalError, PResult, pluralize};
use rustc_lexer::FrontmatterAllowed;
use rustc_session::parse::ParseSess;
use rustc_span::source_map::SourceMap;
use rustc_span::{FileName, SourceFile, Span};
@ -34,6 +33,8 @@ pub mod parser;
use parser::Parser;
use rustc_ast::token::Delimiter;
use crate::lexer::StripTokens;
pub mod lexer;
mod errors;
@ -62,10 +63,10 @@ pub fn new_parser_from_source_str(
source: String,
) -> Result<Parser<'_>, Vec<Diag<'_>>> {
let source_file = psess.source_map().new_source_file(name, source);
new_parser_from_source_file(psess, source_file, FrontmatterAllowed::Yes)
new_parser_from_source_file(psess, source_file, StripTokens::ShebangAndFrontmatter)
}
/// Creates a new parser from a simple (no frontmatter) source string.
/// Creates a new parser from a simple (no shebang, no frontmatter) source string.
///
/// On failure, the errors must be consumed via `unwrap_or_emit_fatal`, `emit`, `cancel`,
/// etc., otherwise a panic will occur when they are dropped.
@ -75,7 +76,7 @@ pub fn new_parser_from_simple_source_str(
source: String,
) -> Result<Parser<'_>, Vec<Diag<'_>>> {
let source_file = psess.source_map().new_source_file(name, source);
new_parser_from_source_file(psess, source_file, FrontmatterAllowed::No)
new_parser_from_source_file(psess, source_file, StripTokens::Nothing)
}
/// Creates a new parser from a filename. On failure, the errors must be consumed via
@ -109,7 +110,7 @@ pub fn new_parser_from_file<'a>(
}
err.emit();
});
new_parser_from_source_file(psess, source_file, FrontmatterAllowed::Yes)
new_parser_from_source_file(psess, source_file, StripTokens::ShebangAndFrontmatter)
}
pub fn utf8_error<E: EmissionGuarantee>(
@ -160,10 +161,10 @@ pub fn utf8_error<E: EmissionGuarantee>(
fn new_parser_from_source_file(
psess: &ParseSess,
source_file: Arc<SourceFile>,
frontmatter_allowed: FrontmatterAllowed,
strip_tokens: StripTokens,
) -> Result<Parser<'_>, Vec<Diag<'_>>> {
let end_pos = source_file.end_position();
let stream = source_file_to_stream(psess, source_file, None, frontmatter_allowed)?;
let stream = source_file_to_stream(psess, source_file, None, strip_tokens)?;
let mut parser = Parser::new(psess, stream, None);
if parser.token == token::Eof {
parser.token.span = Span::new(end_pos, end_pos, parser.token.span.ctxt(), None);
@ -179,8 +180,8 @@ pub fn source_str_to_stream(
) -> Result<TokenStream, Vec<Diag<'_>>> {
let source_file = psess.source_map().new_source_file(name, source);
// used mainly for `proc_macro` and the likes, not for our parsing purposes, so don't parse
// frontmatters as frontmatters.
source_file_to_stream(psess, source_file, override_span, FrontmatterAllowed::No)
// frontmatters as frontmatters, but for compatibility reason still strip the shebang
source_file_to_stream(psess, source_file, override_span, StripTokens::Shebang)
}
/// Given a source file, produces a sequence of token trees. Returns any buffered errors from
@ -189,7 +190,7 @@ fn source_file_to_stream<'psess>(
psess: &'psess ParseSess,
source_file: Arc<SourceFile>,
override_span: Option<Span>,
frontmatter_allowed: FrontmatterAllowed,
strip_tokens: StripTokens,
) -> Result<TokenStream, Vec<Diag<'psess>>> {
let src = source_file.src.as_ref().unwrap_or_else(|| {
psess.dcx().bug(format!(
@ -198,13 +199,7 @@ fn source_file_to_stream<'psess>(
));
});
lexer::lex_token_trees(
psess,
src.as_str(),
source_file.start_pos,
override_span,
frontmatter_allowed,
)
lexer::lex_token_trees(psess, src.as_str(), source_file.start_pos, override_span, strip_tokens)
}
/// Runs the given subparser `f` on the tokens of the given `attr`'s item.

View file

@ -2742,7 +2742,7 @@ impl<'a> Parser<'a> {
/// The specified `edition` in `let_chains_policy` should be that of the whole `if` construct,
/// i.e. the same span we use to later decide whether the drop behaviour should be that of
/// edition `..=2021` or that of `2024..`.
// Public because it is used in rustfmt forks such as https://github.com/tucant/rustfmt/blob/30c83df9e1db10007bdd16dafce8a86b404329b2/src/parse/macros/html.rs#L57 for custom if expressions.
// Public to use it for custom `if` expressions in rustfmt forks like https://github.com/tucant/rustfmt
pub fn parse_expr_cond(
&mut self,
let_chains_policy: LetChainsPolicy,

View file

@ -22,6 +22,8 @@ use std::{fmt, mem, slice};
use attr_wrapper::{AttrWrapper, UsePreAttrPos};
pub use diagnostics::AttemptLocalParseRecovery;
pub(crate) use expr::ForbiddenLetReason;
// Public to use it for custom `if` expressions in rustfmt forks like https://github.com/tucant/rustfmt
pub use expr::LetChainsPolicy;
pub(crate) use item::{FnContext, FnParseMode};
pub use pat::{CommaRecoveryMode, RecoverColon, RecoverComma};
pub use path::PathStyle;

View file

@ -58,7 +58,7 @@ impl<'tcx, C: QueryCache, const ANON: bool, const DEPTH_LIMIT: bool, const FEEDA
for DynamicConfig<'tcx, C, ANON, DEPTH_LIMIT, FEEDABLE>
{
fn clone(&self) -> Self {
DynamicConfig { dynamic: self.dynamic }
*self
}
}

View file

@ -1,3 +1,4 @@
use itertools::Itertools as _;
use rustc_ast::visit::{self, Visitor};
use rustc_ast::{
self as ast, CRATE_NODE_ID, Crate, ItemKind, ModKind, NodeId, Path, join_path_idents,
@ -1986,7 +1987,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
let extern_prelude_ambiguity = || {
self.extern_prelude.get(&Macros20NormalizedIdent::new(ident)).is_some_and(|entry| {
entry.item_binding.map(|(b, _)| b) == Some(b1)
&& entry.flag_binding.as_ref().and_then(|pb| pb.get().binding()) == Some(b2)
&& entry.flag_binding.as_ref().and_then(|pb| pb.get().0.binding()) == Some(b2)
})
};
let (b1, b2, misc1, misc2, swapped) = if b2.span.is_dummy() && !b1.span.is_dummy() {
@ -3469,16 +3470,11 @@ fn show_candidates(
err.note(note.to_string());
}
} else {
let (_, descr_first, _, _, _) = &inaccessible_path_strings[0];
let descr = if inaccessible_path_strings
let descr = inaccessible_path_strings
.iter()
.skip(1)
.all(|(_, descr, _, _, _)| descr == descr_first)
{
descr_first
} else {
"item"
};
.map(|&(_, descr, _, _, _)| descr)
.all_equal_value()
.unwrap_or("item");
let plural_descr =
if descr.ends_with('s') { format!("{descr}es") } else { format!("{descr}s") };

View file

@ -3099,7 +3099,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
|err, _, span, message, suggestion, span_suggs| {
err.multipart_suggestion_verbose(
message,
std::iter::once((span, suggestion)).chain(span_suggs.clone()).collect(),
std::iter::once((span, suggestion)).chain(span_suggs).collect(),
Applicability::MaybeIncorrect,
);
true

View file

@ -1031,7 +1031,7 @@ struct ExternPreludeEntry<'ra> {
/// `flag_binding` is `None`, or when `extern crate` introducing `item_binding` used renaming.
item_binding: Option<(NameBinding<'ra>, /* introduced by item */ bool)>,
/// Binding from an `--extern` flag, lazily populated on first use.
flag_binding: Option<Cell<PendingBinding<'ra>>>,
flag_binding: Option<Cell<(PendingBinding<'ra>, /* finalized */ bool)>>,
}
impl ExternPreludeEntry<'_> {
@ -1042,7 +1042,7 @@ impl ExternPreludeEntry<'_> {
fn flag() -> Self {
ExternPreludeEntry {
item_binding: None,
flag_binding: Some(Cell::new(PendingBinding::Pending)),
flag_binding: Some(Cell::new((PendingBinding::Pending, false))),
}
}
}
@ -2245,14 +2245,16 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
fn extern_prelude_get_flag(&self, ident: Ident, finalize: bool) -> Option<NameBinding<'ra>> {
let entry = self.extern_prelude.get(&Macros20NormalizedIdent::new(ident));
entry.and_then(|entry| entry.flag_binding.as_ref()).and_then(|flag_binding| {
let binding = match flag_binding.get() {
let (pending_binding, finalized) = flag_binding.get();
let binding = match pending_binding {
PendingBinding::Ready(binding) => {
if finalize {
if finalize && !finalized {
self.cstore_mut().process_path_extern(self.tcx, ident.name, ident.span);
}
binding
}
PendingBinding::Pending => {
debug_assert!(!finalized);
let crate_id = if finalize {
self.cstore_mut().process_path_extern(self.tcx, ident.name, ident.span)
} else {
@ -2264,7 +2266,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
})
}
};
flag_binding.set(PendingBinding::Ready(binding));
flag_binding.set((PendingBinding::Ready(binding), finalize || finalized));
binding.or_else(|| finalize.then_some(self.dummy_binding))
})
}

View file

@ -81,8 +81,8 @@ cfg_select! {
// use `loadu`, which supports unaligned loading.
let chunk = unsafe { _mm_loadu_si128(chunk.as_ptr() as *const __m128i) };
// For each character in the chunk, see if its byte value is < 0,
// which indicates that it's part of a UTF-8 char.
// For character in the chunk, see if its byte value is < 0, which
// indicates that it's part of a UTF-8 char.
let multibyte_test = _mm_cmplt_epi8(chunk, _mm_set1_epi8(0));
// Create a bit mask from the comparison results.
let multibyte_mask = _mm_movemask_epi8(multibyte_test);
@ -132,111 +132,8 @@ cfg_select! {
}
}
}
target_arch = "loongarch64" => {
fn analyze_source_file_dispatch(
src: &str,
lines: &mut Vec<RelativeBytePos>,
multi_byte_chars: &mut Vec<MultiByteChar>,
) {
use std::arch::is_loongarch_feature_detected;
if is_loongarch_feature_detected!("lsx") {
unsafe {
analyze_source_file_lsx(src, lines, multi_byte_chars);
}
} else {
analyze_source_file_generic(
src,
src.len(),
RelativeBytePos::from_u32(0),
lines,
multi_byte_chars,
);
}
}
/// Checks 16 byte chunks of text at a time. If the chunk contains
/// something other than printable ASCII characters and newlines, the
/// function falls back to the generic implementation. Otherwise it uses
/// LSX intrinsics to quickly find all newlines.
#[target_feature(enable = "lsx")]
unsafe fn analyze_source_file_lsx(
src: &str,
lines: &mut Vec<RelativeBytePos>,
multi_byte_chars: &mut Vec<MultiByteChar>,
) {
use std::arch::loongarch64::*;
const CHUNK_SIZE: usize = 16;
let (chunks, tail) = src.as_bytes().as_chunks::<CHUNK_SIZE>();
// This variable keeps track of where we should start decoding a
// chunk. If a multi-byte character spans across chunk boundaries,
// we need to skip that part in the next chunk because we already
// handled it.
let mut intra_chunk_offset = 0;
for (chunk_index, chunk) in chunks.iter().enumerate() {
// All LSX memory instructions support unaligned access, so using
// vld is fine.
let chunk = unsafe { lsx_vld::<0>(chunk.as_ptr() as *const i8) };
// For each character in the chunk, see if its byte value is < 0,
// which indicates that it's part of a UTF-8 char.
let multibyte_mask = lsx_vmskltz_b(chunk);
// Create a bit mask from the comparison results.
let multibyte_mask = lsx_vpickve2gr_w::<0>(multibyte_mask);
// If the bit mask is all zero, we only have ASCII chars here:
if multibyte_mask == 0 {
assert!(intra_chunk_offset == 0);
// Check for newlines in the chunk
let newlines_test = lsx_vseqi_b::<{b'\n' as i32}>(chunk);
let newlines_mask = lsx_vmskltz_b(newlines_test);
let mut newlines_mask = lsx_vpickve2gr_w::<0>(newlines_mask);
let output_offset = RelativeBytePos::from_usize(chunk_index * CHUNK_SIZE + 1);
while newlines_mask != 0 {
let index = newlines_mask.trailing_zeros();
lines.push(RelativeBytePos(index) + output_offset);
// Clear the bit, so we can find the next one.
newlines_mask &= newlines_mask - 1;
}
} else {
// The slow path.
// There are multibyte chars in here, fallback to generic decoding.
let scan_start = chunk_index * CHUNK_SIZE + intra_chunk_offset;
intra_chunk_offset = analyze_source_file_generic(
&src[scan_start..],
CHUNK_SIZE - intra_chunk_offset,
RelativeBytePos::from_usize(scan_start),
lines,
multi_byte_chars,
);
}
}
// There might still be a tail left to analyze
let tail_start = src.len() - tail.len() + intra_chunk_offset;
if tail_start < src.len() {
analyze_source_file_generic(
&src[tail_start..],
src.len() - tail_start,
RelativeBytePos::from_usize(tail_start),
lines,
multi_byte_chars,
);
}
}
}
_ => {
// The target (or compiler version) does not support vector instructions
// our specialized implementations need (x86 SSE2, loongarch64 LSX)...
// The target (or compiler version) does not support SSE2 ...
fn analyze_source_file_dispatch(
src: &str,
lines: &mut Vec<RelativeBytePos>,

View file

@ -18,7 +18,6 @@
// tidy-alphabetical-start
#![allow(internal_features)]
#![cfg_attr(bootstrap, feature(round_char_boundary))]
#![cfg_attr(target_arch = "loongarch64", feature(stdarch_loongarch))]
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
#![doc(rust_logo)]
#![feature(array_windows)]

View file

@ -82,9 +82,13 @@ pub(super) fn mangle<'tcx>(
}
pub fn mangle_internal_symbol<'tcx>(tcx: TyCtxt<'tcx>, item_name: &str) -> String {
if item_name == "rust_eh_personality" {
match item_name {
// rust_eh_personality must not be renamed as LLVM hard-codes the name
return "rust_eh_personality".to_owned();
"rust_eh_personality" => return item_name.to_owned(),
// Apple availability symbols need to not be mangled to be usable by
// C/Objective-C code.
"__isPlatformVersionAtLeast" | "__isOSVersionAtLeast" => return item_name.to_owned(),
_ => {}
}
let prefix = "_R";

View file

@ -8,16 +8,16 @@ use crate::spec::HasTargetSpec;
#[derive(Copy, Clone)]
enum RegPassKind {
Float(Reg),
Integer(Reg),
Float { offset_from_start: Size, ty: Reg },
Integer { offset_from_start: Size, ty: Reg },
Unknown,
}
#[derive(Copy, Clone)]
enum FloatConv {
FloatPair(Reg, Reg),
FloatPair { first_ty: Reg, second_ty_offset_from_start: Size, second_ty: Reg },
Float(Reg),
MixedPair(Reg, Reg),
MixedPair { first_ty: Reg, second_ty_offset_from_start: Size, second_ty: Reg },
}
#[derive(Copy, Clone)]
@ -37,6 +37,7 @@ fn should_use_fp_conv_helper<'a, Ty, C>(
flen: u64,
field1_kind: &mut RegPassKind,
field2_kind: &mut RegPassKind,
offset_from_start: Size,
) -> Result<(), CannotUseFpConv>
where
Ty: TyAbiInterface<'a, C> + Copy,
@ -49,16 +50,16 @@ where
}
match (*field1_kind, *field2_kind) {
(RegPassKind::Unknown, _) => {
*field1_kind = RegPassKind::Integer(Reg {
kind: RegKind::Integer,
size: arg_layout.size,
});
*field1_kind = RegPassKind::Integer {
offset_from_start,
ty: Reg { kind: RegKind::Integer, size: arg_layout.size },
};
}
(RegPassKind::Float(_), RegPassKind::Unknown) => {
*field2_kind = RegPassKind::Integer(Reg {
kind: RegKind::Integer,
size: arg_layout.size,
});
(RegPassKind::Float { .. }, RegPassKind::Unknown) => {
*field2_kind = RegPassKind::Integer {
offset_from_start,
ty: Reg { kind: RegKind::Integer, size: arg_layout.size },
};
}
_ => return Err(CannotUseFpConv),
}
@ -69,12 +70,16 @@ where
}
match (*field1_kind, *field2_kind) {
(RegPassKind::Unknown, _) => {
*field1_kind =
RegPassKind::Float(Reg { kind: RegKind::Float, size: arg_layout.size });
*field1_kind = RegPassKind::Float {
offset_from_start,
ty: Reg { kind: RegKind::Float, size: arg_layout.size },
};
}
(_, RegPassKind::Unknown) => {
*field2_kind =
RegPassKind::Float(Reg { kind: RegKind::Float, size: arg_layout.size });
*field2_kind = RegPassKind::Float {
offset_from_start,
ty: Reg { kind: RegKind::Float, size: arg_layout.size },
};
}
_ => return Err(CannotUseFpConv),
}
@ -96,13 +101,14 @@ where
flen,
field1_kind,
field2_kind,
offset_from_start,
);
}
return Err(CannotUseFpConv);
}
}
FieldsShape::Array { count, .. } => {
for _ in 0..count {
for i in 0..count {
let elem_layout = arg_layout.field(cx, 0);
should_use_fp_conv_helper(
cx,
@ -111,6 +117,7 @@ where
flen,
field1_kind,
field2_kind,
offset_from_start + elem_layout.size * i,
)?;
}
}
@ -121,7 +128,15 @@ where
}
for i in arg_layout.fields.index_by_increasing_offset() {
let field = arg_layout.field(cx, i);
should_use_fp_conv_helper(cx, &field, xlen, flen, field1_kind, field2_kind)?;
should_use_fp_conv_helper(
cx,
&field,
xlen,
flen,
field1_kind,
field2_kind,
offset_from_start + arg_layout.fields.offset(i),
)?;
}
}
},
@ -140,14 +155,52 @@ where
{
let mut field1_kind = RegPassKind::Unknown;
let mut field2_kind = RegPassKind::Unknown;
if should_use_fp_conv_helper(cx, arg, xlen, flen, &mut field1_kind, &mut field2_kind).is_err() {
if should_use_fp_conv_helper(
cx,
arg,
xlen,
flen,
&mut field1_kind,
&mut field2_kind,
Size::ZERO,
)
.is_err()
{
return None;
}
match (field1_kind, field2_kind) {
(RegPassKind::Integer(l), RegPassKind::Float(r)) => Some(FloatConv::MixedPair(l, r)),
(RegPassKind::Float(l), RegPassKind::Integer(r)) => Some(FloatConv::MixedPair(l, r)),
(RegPassKind::Float(l), RegPassKind::Float(r)) => Some(FloatConv::FloatPair(l, r)),
(RegPassKind::Float(f), RegPassKind::Unknown) => Some(FloatConv::Float(f)),
(
RegPassKind::Integer { offset_from_start, .. }
| RegPassKind::Float { offset_from_start, .. },
_,
) if offset_from_start != Size::ZERO => {
panic!("type {:?} has a first field with non-zero offset {offset_from_start:?}", arg.ty)
}
(
RegPassKind::Integer { ty: first_ty, .. },
RegPassKind::Float { offset_from_start, ty: second_ty },
) => Some(FloatConv::MixedPair {
first_ty,
second_ty_offset_from_start: offset_from_start,
second_ty,
}),
(
RegPassKind::Float { ty: first_ty, .. },
RegPassKind::Integer { offset_from_start, ty: second_ty },
) => Some(FloatConv::MixedPair {
first_ty,
second_ty_offset_from_start: offset_from_start,
second_ty,
}),
(
RegPassKind::Float { ty: first_ty, .. },
RegPassKind::Float { offset_from_start, ty: second_ty },
) => Some(FloatConv::FloatPair {
first_ty,
second_ty_offset_from_start: offset_from_start,
second_ty,
}),
(RegPassKind::Float { ty, .. }, RegPassKind::Unknown) => Some(FloatConv::Float(ty)),
_ => None,
}
}
@ -165,11 +218,19 @@ where
FloatConv::Float(f) => {
arg.cast_to(f);
}
FloatConv::FloatPair(l, r) => {
arg.cast_to(CastTarget::pair(l, r));
FloatConv::FloatPair { first_ty, second_ty_offset_from_start, second_ty } => {
arg.cast_to(CastTarget::offset_pair(
first_ty,
second_ty_offset_from_start,
second_ty,
));
}
FloatConv::MixedPair(l, r) => {
arg.cast_to(CastTarget::pair(l, r));
FloatConv::MixedPair { first_ty, second_ty_offset_from_start, second_ty } => {
arg.cast_to(CastTarget::offset_pair(
first_ty,
second_ty_offset_from_start,
second_ty,
));
}
}
return false;
@ -233,15 +294,27 @@ fn classify_arg<'a, Ty, C>(
arg.cast_to(f);
return;
}
Some(FloatConv::FloatPair(l, r)) if *avail_fprs >= 2 => {
Some(FloatConv::FloatPair { first_ty, second_ty_offset_from_start, second_ty })
if *avail_fprs >= 2 =>
{
*avail_fprs -= 2;
arg.cast_to(CastTarget::pair(l, r));
arg.cast_to(CastTarget::offset_pair(
first_ty,
second_ty_offset_from_start,
second_ty,
));
return;
}
Some(FloatConv::MixedPair(l, r)) if *avail_fprs >= 1 && *avail_gprs >= 1 => {
Some(FloatConv::MixedPair { first_ty, second_ty_offset_from_start, second_ty })
if *avail_fprs >= 1 && *avail_gprs >= 1 =>
{
*avail_gprs -= 1;
*avail_fprs -= 1;
arg.cast_to(CastTarget::pair(l, r));
arg.cast_to(CastTarget::offset_pair(
first_ty,
second_ty_offset_from_start,
second_ty,
));
return;
}
_ => (),

View file

@ -318,7 +318,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
let (expected, found) = if expected_str == found_str {
(join_path_syms(&expected_abs), join_path_syms(&found_abs))
} else {
(expected_str.clone(), found_str.clone())
(expected_str, found_str)
};
// We've displayed "expected `a::b`, found `a::b`". We add context to

View file

@ -32,8 +32,8 @@ use rustc_middle::ty::print::{
};
use rustc_middle::ty::{
self, AdtKind, GenericArgs, InferTy, IsSuggestable, Ty, TyCtxt, TypeFoldable, TypeFolder,
TypeSuperFoldable, TypeVisitableExt, TypeckResults, Upcast, suggest_arbitrary_trait_bound,
suggest_constraining_type_param,
TypeSuperFoldable, TypeSuperVisitable, TypeVisitableExt, TypeVisitor, TypeckResults, Upcast,
suggest_arbitrary_trait_bound, suggest_constraining_type_param,
};
use rustc_middle::{bug, span_bug};
use rustc_span::def_id::LocalDefId;
@ -263,6 +263,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
_ => (false, None),
};
let mut finder = ParamFinder { .. };
finder.visit_binder(&trait_pred);
// FIXME: Add check for trait bound that is already present, particularly `?Sized` so we
// don't suggest `T: Sized + ?Sized`.
loop {
@ -411,6 +414,26 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
}
}
hir::Node::TraitItem(hir::TraitItem {
generics,
kind: hir::TraitItemKind::Fn(..),
..
})
| hir::Node::ImplItem(hir::ImplItem {
generics,
trait_item_def_id: None,
kind: hir::ImplItemKind::Fn(..),
..
}) if finder.can_suggest_bound(generics) => {
// Missing generic type parameter bound.
suggest_arbitrary_trait_bound(
self.tcx,
generics,
err,
trait_pred,
associated_ty,
);
}
hir::Node::Item(hir::Item {
kind:
hir::ItemKind::Struct(_, generics, _)
@ -423,7 +446,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
| hir::ItemKind::Const(_, generics, _, _)
| hir::ItemKind::TraitAlias(_, generics, _),
..
}) if !param_ty => {
}) if finder.can_suggest_bound(generics) => {
// Missing generic type parameter bound.
if suggest_arbitrary_trait_bound(
self.tcx,
@ -5068,8 +5091,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
// Suggesting `T: ?Sized` is only valid in an ADT if `T` is only used in a
// borrow. `struct S<'a, T: ?Sized>(&'a T);` is valid, `struct S<T: ?Sized>(T);`
// is not. Look for invalid "bare" parameter uses, and suggest using indirection.
let mut visitor =
FindTypeParam { param: param.name.ident().name, invalid_spans: vec![], nested: false };
let mut visitor = FindTypeParam { param: param.name.ident().name, .. };
visitor.visit_item(item);
if visitor.invalid_spans.is_empty() {
return false;
@ -5228,7 +5250,7 @@ fn hint_missing_borrow<'tcx>(
/// Used to suggest replacing associated types with an explicit type in `where` clauses.
#[derive(Debug)]
pub struct SelfVisitor<'v> {
pub paths: Vec<&'v hir::Ty<'v>>,
pub paths: Vec<&'v hir::Ty<'v>> = Vec::new(),
pub name: Option<Symbol>,
}
@ -5599,7 +5621,7 @@ fn point_at_assoc_type_restriction<G: EmissionGuarantee>(
);
// Search for the associated type `Self::{name}`, get
// its type and suggest replacing the bound with it.
let mut visitor = SelfVisitor { paths: vec![], name: Some(name) };
let mut visitor = SelfVisitor { name: Some(name), .. };
visitor.visit_trait_ref(trait_ref);
for path in visitor.paths {
err.span_suggestion_verbose(
@ -5610,7 +5632,7 @@ fn point_at_assoc_type_restriction<G: EmissionGuarantee>(
);
}
} else {
let mut visitor = SelfVisitor { paths: vec![], name: None };
let mut visitor = SelfVisitor { name: None, .. };
visitor.visit_trait_ref(trait_ref);
let span: MultiSpan =
visitor.paths.iter().map(|p| p.span).collect::<Vec<Span>>().into();
@ -5640,8 +5662,8 @@ fn get_deref_type_and_refs(mut ty: Ty<'_>) -> (Ty<'_>, Vec<hir::Mutability>) {
/// `param: ?Sized` would be a valid constraint.
struct FindTypeParam {
param: rustc_span::Symbol,
invalid_spans: Vec<Span>,
nested: bool,
invalid_spans: Vec<Span> = Vec::new(),
nested: bool = false,
}
impl<'v> Visitor<'v> for FindTypeParam {
@ -5679,3 +5701,38 @@ impl<'v> Visitor<'v> for FindTypeParam {
}
}
}
/// Look for type parameters in predicates. We use this to identify whether a bound is suitable in
/// on a given item.
struct ParamFinder {
params: Vec<Symbol> = Vec::new(),
}
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for ParamFinder {
fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result {
match t.kind() {
ty::Param(p) => self.params.push(p.name),
_ => {}
}
t.super_visit_with(self)
}
}
impl ParamFinder {
/// Whether the `hir::Generics` of the current item can suggest the evaluated bound because its
/// references to type parameters are present in the generics.
fn can_suggest_bound(&self, generics: &hir::Generics<'_>) -> bool {
if self.params.is_empty() {
// There are no references to type parameters at all, so suggesting the bound
// would be reasonable.
return true;
}
generics.params.iter().any(|p| match p.name {
hir::ParamName::Plain(p_name) => {
// All of the parameters in the bound can be referenced in the current item.
self.params.iter().any(|p| *p == p_name.name || *p == kw::SelfUpper)
}
_ => true,
})
}
}

View file

@ -19,6 +19,7 @@
#![feature(assert_matches)]
#![feature(associated_type_defaults)]
#![feature(box_patterns)]
#![feature(default_field_values)]
#![feature(if_let_guard)]
#![feature(iter_intersperse)]
#![feature(iterator_try_reduce)]

View file

@ -328,7 +328,7 @@ dependencies = [
"std_detect",
"unwind",
"wasi 0.11.1+wasi-snapshot-preview1",
"wasi 0.14.3+wasi-0.2.4",
"wasi 0.14.4+wasi-0.2.4",
"windows-targets 0.0.0",
]
@ -402,9 +402,9 @@ dependencies = [
[[package]]
name = "wasi"
version = "0.14.3+wasi-0.2.4"
version = "0.14.4+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95"
checksum = "88a5f4a424faf49c3c2c344f166f0662341d470ea185e939657aaff130f0ec4a"
dependencies = [
"rustc-std-workspace-alloc",
"rustc-std-workspace-core",

View file

@ -154,9 +154,8 @@
/// [`String`]: ../../std/string/struct.String.html
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "Borrow"]
#[const_trait]
#[rustc_const_unstable(feature = "const_convert", issue = "143773")]
pub trait Borrow<Borrowed: ?Sized> {
pub const trait Borrow<Borrowed: ?Sized> {
/// Immutably borrows from an owned value.
///
/// # Examples
@ -187,9 +186,8 @@ pub trait Borrow<Borrowed: ?Sized> {
/// for more information on borrowing as another type.
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "BorrowMut"]
#[const_trait]
#[rustc_const_unstable(feature = "const_convert", issue = "143773")]
pub trait BorrowMut<Borrowed: ?Sized>: Borrow<Borrowed> {
pub const trait BorrowMut<Borrowed: ?Sized>: Borrow<Borrowed> {
/// Mutably borrows from an owned value.
///
/// # Examples

View file

@ -191,8 +191,7 @@ mod uninit;
#[rustc_diagnostic_item = "Clone"]
#[rustc_trivial_field_reads]
#[rustc_const_unstable(feature = "const_clone", issue = "142757")]
#[const_trait]
pub trait Clone: Sized {
pub const trait Clone: Sized {
/// Returns a duplicate of the value.
///
/// Note that what "duplicate" means varies by type:

View file

@ -247,9 +247,8 @@ use crate::ops::ControlFlow;
append_const_msg
)]
#[rustc_diagnostic_item = "PartialEq"]
#[const_trait]
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
pub trait PartialEq<Rhs: PointeeSized = Self>: PointeeSized {
pub const trait PartialEq<Rhs: PointeeSized = Self>: PointeeSized {
/// Tests for `self` and `other` values to be equal, and is used by `==`.
#[must_use]
#[stable(feature = "rust1", since = "1.0.0")]

View file

@ -17,7 +17,7 @@ use crate::num::NonZero;
/// - Neither `Self` nor `Rhs` have provenance, so integer comparisons are correct.
/// - `<Self as PartialEq<Rhs>>::{eq,ne}` are equivalent to comparing the bytes.
#[rustc_specialization_trait]
#[const_trait]
#[const_trait] // FIXME(const_trait_impl): Migrate to `const unsafe trait` once #146122 is fixed.
pub(crate) unsafe trait BytewiseEq<Rhs = Self>:
[const] PartialEq<Rhs> + Sized
{

View file

@ -216,9 +216,8 @@ pub const fn identity<T>(x: T) -> T {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "AsRef"]
#[const_trait]
#[rustc_const_unstable(feature = "const_convert", issue = "143773")]
pub trait AsRef<T: PointeeSized>: PointeeSized {
pub const trait AsRef<T: PointeeSized>: PointeeSized {
/// Converts this type into a shared reference of the (usually inferred) input type.
#[stable(feature = "rust1", since = "1.0.0")]
fn as_ref(&self) -> &T;
@ -369,9 +368,8 @@ pub trait AsRef<T: PointeeSized>: PointeeSized {
/// `&mut Vec<u8>`, for example, is the better choice (callers need to pass the correct type then).
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "AsMut"]
#[const_trait]
#[rustc_const_unstable(feature = "const_convert", issue = "143773")]
pub trait AsMut<T: PointeeSized>: PointeeSized {
pub const trait AsMut<T: PointeeSized>: PointeeSized {
/// Converts this type into a mutable reference of the (usually inferred) input type.
#[stable(feature = "rust1", since = "1.0.0")]
fn as_mut(&mut self) -> &mut T;
@ -450,8 +448,7 @@ pub trait AsMut<T: PointeeSized>: PointeeSized {
#[stable(feature = "rust1", since = "1.0.0")]
#[doc(search_unbox)]
#[rustc_const_unstable(feature = "const_convert", issue = "143773")]
#[const_trait]
pub trait Into<T>: Sized {
pub const trait Into<T>: Sized {
/// Converts this type into the (usually inferred) input type.
#[must_use]
#[stable(feature = "rust1", since = "1.0.0")]
@ -587,8 +584,7 @@ pub trait Into<T>: Sized {
))]
#[doc(search_unbox)]
#[rustc_const_unstable(feature = "const_convert", issue = "143773")]
#[const_trait]
pub trait From<T>: Sized {
pub const trait From<T>: Sized {
/// Converts to this type from the input type.
#[rustc_diagnostic_item = "from_fn"]
#[must_use]
@ -616,8 +612,7 @@ pub trait From<T>: Sized {
#[rustc_diagnostic_item = "TryInto"]
#[stable(feature = "try_from", since = "1.34.0")]
#[rustc_const_unstable(feature = "const_convert", issue = "143773")]
#[const_trait]
pub trait TryInto<T>: Sized {
pub const trait TryInto<T>: Sized {
/// The type returned in the event of a conversion error.
#[stable(feature = "try_from", since = "1.34.0")]
type Error;
@ -696,8 +691,7 @@ pub trait TryInto<T>: Sized {
#[rustc_diagnostic_item = "TryFrom"]
#[stable(feature = "try_from", since = "1.34.0")]
#[rustc_const_unstable(feature = "const_convert", issue = "143773")]
#[const_trait]
pub trait TryFrom<T>: Sized {
pub const trait TryFrom<T>: Sized {
/// The type returned in the event of a conversion error.
#[stable(feature = "try_from", since = "1.34.0")]
type Error;

View file

@ -103,9 +103,8 @@ use crate::ascii::Char as AsciiChar;
/// ```
#[rustc_diagnostic_item = "Default"]
#[stable(feature = "rust1", since = "1.0.0")]
#[const_trait]
#[rustc_const_unstable(feature = "const_default", issue = "143894")]
pub trait Default: Sized {
pub const trait Default: Sized {
/// Returns the "default value" for a type.
///
/// Default values are often some kind of initial value, identity value, or anything else that

View file

@ -3,7 +3,7 @@
use crate::fmt::NumBuffer;
use crate::mem::MaybeUninit;
use crate::num::fmt as numfmt;
use crate::{fmt, ptr, slice, str};
use crate::{fmt, str};
/// Formatting of integers with a non-decimal radix.
macro_rules! radix_integer {
@ -96,8 +96,8 @@ macro_rules! impl_Debug {
};
}
// 2 digit decimal look up table
static DEC_DIGITS_LUT: &[u8; 200] = b"\
// The string of all two-digit numbers in range 00..99 is used as a lookup table.
static DECIMAL_PAIRS: &[u8; 200] = b"\
0001020304050607080910111213141516171819\
2021222324252627282930313233343536373839\
4041424344454647484950515253545556575859\
@ -123,6 +123,9 @@ macro_rules! impl_Display {
$(
const _: () = {
assert!($Signed::MIN < 0, "need signed");
assert!($Unsigned::MIN == 0, "need unsigned");
assert!($Signed::BITS == $Unsigned::BITS, "need counterparts");
assert!($Signed::BITS <= $T::BITS, "need lossless conversion");
assert!($Unsigned::BITS <= $T::BITS, "need lossless conversion");
};
@ -207,10 +210,10 @@ macro_rules! impl_Display {
remain /= scale;
let pair1 = (quad / 100) as usize;
let pair2 = (quad % 100) as usize;
buf[offset + 0].write(DEC_DIGITS_LUT[pair1 * 2 + 0]);
buf[offset + 1].write(DEC_DIGITS_LUT[pair1 * 2 + 1]);
buf[offset + 2].write(DEC_DIGITS_LUT[pair2 * 2 + 0]);
buf[offset + 3].write(DEC_DIGITS_LUT[pair2 * 2 + 1]);
buf[offset + 0].write(DECIMAL_PAIRS[pair1 * 2 + 0]);
buf[offset + 1].write(DECIMAL_PAIRS[pair1 * 2 + 1]);
buf[offset + 2].write(DECIMAL_PAIRS[pair2 * 2 + 0]);
buf[offset + 3].write(DECIMAL_PAIRS[pair2 * 2 + 1]);
}
// Format per two digits from the lookup table.
@ -225,8 +228,8 @@ macro_rules! impl_Display {
let pair = (remain % 100) as usize;
remain /= 100;
buf[offset + 0].write(DEC_DIGITS_LUT[pair * 2 + 0]);
buf[offset + 1].write(DEC_DIGITS_LUT[pair * 2 + 1]);
buf[offset + 0].write(DECIMAL_PAIRS[pair * 2 + 0]);
buf[offset + 1].write(DECIMAL_PAIRS[pair * 2 + 1]);
}
// Format the last remaining digit, if any.
@ -242,7 +245,7 @@ macro_rules! impl_Display {
// Either the compiler sees that remain < 10, or it prevents
// a boundary check up next.
let last = (remain & 15) as usize;
buf[offset].write(DEC_DIGITS_LUT[last * 2 + 1]);
buf[offset].write(DECIMAL_PAIRS[last * 2 + 1]);
// not used: remain = 0;
}
@ -335,7 +338,6 @@ macro_rules! impl_Display {
}
}
)*
#[cfg(feature = "optimize_for_size")]
@ -374,178 +376,213 @@ macro_rules! impl_Display {
macro_rules! impl_Exp {
($($Signed:ident, $Unsigned:ident),* ; as $T:ident into $fmt_fn:ident) => {
const _: () = assert!($T::MIN == 0, "need unsigned");
fn $fmt_fn(
mut n: $T,
f: &mut fmt::Formatter<'_>,
n: $T,
is_nonnegative: bool,
upper: bool,
f: &mut fmt::Formatter<'_>
letter_e: u8
) -> fmt::Result {
let (mut n, mut exponent, trailing_zeros, added_precision) = {
let mut exponent = 0;
// count and remove trailing decimal zeroes
while n % 10 == 0 && n >= 10 {
n /= 10;
exponent += 1;
}
let (added_precision, subtracted_precision) = match f.precision() {
Some(fmt_prec) => {
// number of decimal digits minus 1
let mut tmp = n;
let mut prec = 0;
while tmp >= 10 {
tmp /= 10;
prec += 1;
}
(fmt_prec.saturating_sub(prec), prec.saturating_sub(fmt_prec))
debug_assert!(letter_e.is_ascii_alphabetic(), "single-byte character");
// Print the integer as a coefficient in range (-10, 10).
let mut exp = n.checked_ilog10().unwrap_or(0) as usize;
debug_assert!(n / (10 as $T).pow(exp as u32) < 10);
// Precisison is counted as the number of digits in the fraction.
let mut coef_prec = exp;
// Keep the digits as an integer (paired with its coef_prec count).
let mut coef = n;
// A Formatter may set the precision to a fixed number of decimals.
let more_prec = match f.precision() {
None => {
// Omit any and all trailing zeroes.
while coef_prec != 0 && coef % 10 == 0 {
coef /= 10;
coef_prec -= 1;
}
None => (0, 0)
};
for _ in 1..subtracted_precision {
n /= 10;
exponent += 1;
}
if subtracted_precision != 0 {
let rem = n % 10;
n /= 10;
exponent += 1;
// round up last digit, round to even on a tie
if rem > 5 || (rem == 5 && (n % 2 != 0 || subtracted_precision > 1 )) {
n += 1;
// if the digit is rounded to the next power
// instead adjust the exponent
if n.ilog10() > (n - 1).ilog10() {
n /= 10;
exponent += 1;
}
0
},
Some(fmt_prec) if fmt_prec >= coef_prec => {
// Count the number of additional zeroes needed.
fmt_prec - coef_prec
},
Some(fmt_prec) => {
// Count the number of digits to drop.
let less_prec = coef_prec - fmt_prec;
assert!(less_prec > 0);
// Scale down the coefficient/precision pair. For example,
// coef 123456 gets coef_prec 5 (to make 1.23456). To format
// the number with 2 decimals, i.e., fmt_prec 2, coef should
// be scaled by 10⁵⁻²=1000 to get coef 123 with coef_prec 2.
// SAFETY: Any precision less than coef_prec will cause a
// power of ten below the coef value.
let scale = unsafe {
(10 as $T).checked_pow(less_prec as u32).unwrap_unchecked()
};
let floor = coef / scale;
// Round half to even conform documentation.
let over = coef % scale;
let half = scale / 2;
let round_up = if over < half {
0
} else if over > half {
1
} else {
floor & 1 // round odd up to even
};
// Adding one to a scale down of at least 10 won't overflow.
coef = floor + round_up;
coef_prec = fmt_prec;
// The round_up may have caused the coefficient to reach 10
// (which is not permitted). For example, anything in range
// [9.95, 10) becomes 10.0 when adjusted to precision 1.
if round_up != 0 && coef.checked_ilog10().unwrap_or(0) as usize > coef_prec {
debug_assert_eq!(coef, (10 as $T).pow(coef_prec as u32 + 1));
coef /= 10; // drop one trailing zero
exp += 1; // one power of ten higher
}
}
(n, exponent, exponent, added_precision)
0
},
};
// Since `curr` always decreases by the number of digits copied, this means
// that `curr >= 0`.
let mut buf = [MaybeUninit::<u8>::uninit(); 40];
let mut curr = buf.len(); //index for buf
let buf_ptr = MaybeUninit::slice_as_mut_ptr(&mut buf);
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
// Allocate a text buffer with lazy initialization.
const MAX_DEC_N: usize = $T::MAX.ilog10() as usize + 1;
const MAX_COEF_LEN: usize = MAX_DEC_N + ".".len();
const MAX_TEXT_LEN: usize = MAX_COEF_LEN + "e99".len();
let mut buf = [MaybeUninit::<u8>::uninit(); MAX_TEXT_LEN];
// decode 2 chars at a time
while n >= 100 {
let d1 = ((n % 100) as usize) << 1;
curr -= 2;
// SAFETY: `d1 <= 198`, so we can copy from `lut_ptr[d1..d1 + 2]` since
// `DEC_DIGITS_LUT` has a length of 200.
unsafe {
ptr::copy_nonoverlapping(lut_ptr.add(d1), buf_ptr.add(curr), 2);
}
n /= 100;
exponent += 2;
}
// n is <= 99, so at most 2 chars long
let mut n = n as isize; // possibly reduce 64bit math
// decode second-to-last character
if n >= 10 {
curr -= 1;
// SAFETY: Safe since `40 > curr >= 0` (see comment)
unsafe {
*buf_ptr.add(curr) = (n as u8 % 10_u8) + b'0';
}
n /= 10;
exponent += 1;
}
// add decimal point iff >1 mantissa digit will be printed
if exponent != trailing_zeros || added_precision != 0 {
curr -= 1;
// SAFETY: Safe since `40 > curr >= 0`
unsafe {
*buf_ptr.add(curr) = b'.';
}
}
// SAFETY: Safe since `40 > curr >= 0`
let buf_slice = unsafe {
// decode last character
curr -= 1;
*buf_ptr.add(curr) = (n as u8) + b'0';
let len = buf.len() - curr as usize;
slice::from_raw_parts(buf_ptr.add(curr), len)
};
// stores 'e' (or 'E') and the up to 2-digit exponent
let mut exp_buf = [MaybeUninit::<u8>::uninit(); 3];
let exp_ptr = MaybeUninit::slice_as_mut_ptr(&mut exp_buf);
// SAFETY: In either case, `exp_buf` is written within bounds and `exp_ptr[..len]`
// is contained within `exp_buf` since `len <= 3`.
let exp_slice = unsafe {
*exp_ptr.add(0) = if upper { b'E' } else { b'e' };
let len = if exponent < 10 {
*exp_ptr.add(1) = (exponent as u8) + b'0';
2
} else {
let off = exponent << 1;
ptr::copy_nonoverlapping(lut_ptr.add(off), exp_ptr.add(1), 2);
3
};
slice::from_raw_parts(exp_ptr, len)
};
let parts = &[
numfmt::Part::Copy(buf_slice),
numfmt::Part::Zero(added_precision),
numfmt::Part::Copy(exp_slice),
];
let sign = if !is_nonnegative {
"-"
} else if f.sign_plus() {
"+"
// Encode the coefficient in buf[..coef_len].
let (lead_dec, coef_len) = if coef_prec == 0 && more_prec == 0 {
(coef, 1_usize) // single digit; no fraction
} else {
""
buf[1].write(b'.');
let fraction_range = 2..(2 + coef_prec);
// Consume the least-significant decimals from a working copy.
let mut remain = coef;
#[cfg(feature = "optimize_for_size")] {
for i in fraction_range.clone().rev() {
let digit = (remain % 10) as usize;
remain /= 10;
buf[i].write(b'0' + digit as u8);
}
}
#[cfg(not(feature = "optimize_for_size"))] {
// Write digits per two at a time with a lookup table.
for i in fraction_range.clone().skip(1).rev().step_by(2) {
let pair = (remain % 100) as usize;
remain /= 100;
buf[i - 1].write(DECIMAL_PAIRS[pair * 2 + 0]);
buf[i - 0].write(DECIMAL_PAIRS[pair * 2 + 1]);
}
// An odd number of digits leave one digit remaining.
if coef_prec & 1 != 0 {
let digit = (remain % 10) as usize;
remain /= 10;
buf[fraction_range.start].write(b'0' + digit as u8);
}
}
(remain, fraction_range.end)
};
let formatted = numfmt::Formatted { sign, parts };
// SAFETY: `buf_slice` and `exp_slice` contain only ASCII characters.
unsafe { f.pad_formatted_parts(&formatted) }
debug_assert!(lead_dec < 10);
debug_assert!(lead_dec != 0 || coef == 0, "significant digits only");
buf[0].write(b'0' + lead_dec as u8);
// SAFETY: The number of decimals is limited, captured by MAX.
unsafe { core::hint::assert_unchecked(coef_len <= MAX_COEF_LEN) }
// Encode the scale factor in buf[coef_len..text_len].
buf[coef_len].write(letter_e);
let text_len: usize = match exp {
..10 => {
buf[coef_len + 1].write(b'0' + exp as u8);
coef_len + 2
},
10..100 => {
#[cfg(feature = "optimize_for_size")] {
buf[coef_len + 1].write(b'0' + (exp / 10) as u8);
buf[coef_len + 2].write(b'0' + (exp % 10) as u8);
}
#[cfg(not(feature = "optimize_for_size"))] {
buf[coef_len + 1].write(DECIMAL_PAIRS[exp * 2 + 0]);
buf[coef_len + 2].write(DECIMAL_PAIRS[exp * 2 + 1]);
}
coef_len + 3
},
_ => {
const { assert!($T::MAX.ilog10() < 100) };
// SAFETY: A `u256::MAX` would get exponent 77.
unsafe { core::hint::unreachable_unchecked() }
}
};
// SAFETY: All bytes up until text_len have been set.
let text = unsafe { buf[..text_len].assume_init_ref() };
if more_prec == 0 {
// SAFETY: Text is set with ASCII exclusively: either a decimal,
// or a LETTER_E, or a dot. ASCII implies valid UTF-8.
let as_str = unsafe { str::from_utf8_unchecked(text) };
f.pad_integral(is_nonnegative, "", as_str)
} else {
let parts = &[
numfmt::Part::Copy(&text[..coef_len]),
numfmt::Part::Zero(more_prec),
numfmt::Part::Copy(&text[coef_len..]),
];
let sign = if !is_nonnegative {
"-"
} else if f.sign_plus() {
"+"
} else {
""
};
// SAFETY: Text is set with ASCII exclusively: either a decimal,
// or a LETTER_E, or a dot. ASCII implies valid UTF-8.
unsafe { f.pad_formatted_parts(&numfmt::Formatted { sign, parts }) }
}
}
$(
#[stable(feature = "integer_exp_format", since = "1.42.0")]
impl fmt::LowerExp for $Signed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let is_nonnegative = *self >= 0;
let n = if is_nonnegative {
*self as $T
} else {
self.unsigned_abs() as $T
};
$fmt_fn(n, is_nonnegative, false, f)
}
const _: () = {
assert!($Signed::MIN < 0, "need signed");
assert!($Unsigned::MIN == 0, "need unsigned");
assert!($Signed::BITS == $Unsigned::BITS, "need counterparts");
assert!($Signed::BITS <= $T::BITS, "need lossless conversion");
assert!($Unsigned::BITS <= $T::BITS, "need lossless conversion");
};
#[stable(feature = "integer_exp_format", since = "1.42.0")]
impl fmt::LowerExp for $Signed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
$fmt_fn(f, self.unsigned_abs() as $T, *self >= 0, b'e')
}
#[stable(feature = "integer_exp_format", since = "1.42.0")]
impl fmt::LowerExp for $Unsigned {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
$fmt_fn(*self as $T, true, false, f)
}
})*
}
#[stable(feature = "integer_exp_format", since = "1.42.0")]
impl fmt::LowerExp for $Unsigned {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
$fmt_fn(f, *self as $T, true, b'e')
}
}
#[stable(feature = "integer_exp_format", since = "1.42.0")]
impl fmt::UpperExp for $Signed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
$fmt_fn(f, self.unsigned_abs() as $T, *self >= 0, b'E')
}
}
#[stable(feature = "integer_exp_format", since = "1.42.0")]
impl fmt::UpperExp for $Unsigned {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
$fmt_fn(f, *self as $T, true, b'E')
}
}
)*
$(
#[stable(feature = "integer_exp_format", since = "1.42.0")]
impl fmt::UpperExp for $Signed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let is_nonnegative = *self >= 0;
let n = if is_nonnegative {
*self as $T
} else {
self.unsigned_abs() as $T
};
$fmt_fn(n, is_nonnegative, true, f)
}
}
#[stable(feature = "integer_exp_format", since = "1.42.0")]
impl fmt::UpperExp for $Unsigned {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
$fmt_fn(*self as $T, true, true, f)
}
})*
};
}
@ -658,10 +695,10 @@ impl u128 {
remain /= 1_00_00;
let pair1 = (quad / 100) as usize;
let pair2 = (quad % 100) as usize;
buf[offset + 0].write(DEC_DIGITS_LUT[pair1 * 2 + 0]);
buf[offset + 1].write(DEC_DIGITS_LUT[pair1 * 2 + 1]);
buf[offset + 2].write(DEC_DIGITS_LUT[pair2 * 2 + 0]);
buf[offset + 3].write(DEC_DIGITS_LUT[pair2 * 2 + 1]);
buf[offset + 0].write(DECIMAL_PAIRS[pair1 * 2 + 0]);
buf[offset + 1].write(DECIMAL_PAIRS[pair1 * 2 + 1]);
buf[offset + 2].write(DECIMAL_PAIRS[pair2 * 2 + 0]);
buf[offset + 3].write(DECIMAL_PAIRS[pair2 * 2 + 1]);
}
// Format per two digits from the lookup table.
@ -676,8 +713,8 @@ impl u128 {
let pair = (remain % 100) as usize;
remain /= 100;
buf[offset + 0].write(DEC_DIGITS_LUT[pair * 2 + 0]);
buf[offset + 1].write(DEC_DIGITS_LUT[pair * 2 + 1]);
buf[offset + 0].write(DECIMAL_PAIRS[pair * 2 + 0]);
buf[offset + 1].write(DECIMAL_PAIRS[pair * 2 + 1]);
}
// Format the last remaining digit, if any.
@ -693,7 +730,7 @@ impl u128 {
// Either the compiler sees that remain < 10, or it prevents
// a boundary check up next.
let last = (remain & 15) as usize;
buf[offset].write(DEC_DIGITS_LUT[last * 2 + 1]);
buf[offset].write(DECIMAL_PAIRS[last * 2 + 1]);
// not used: remain = 0;
}
offset
@ -792,10 +829,10 @@ fn enc_16lsd<const OFFSET: usize>(buf: &mut [MaybeUninit<u8>], n: u64) {
remain /= 1_00_00;
let pair1 = (quad / 100) as usize;
let pair2 = (quad % 100) as usize;
buf[quad_index * 4 + OFFSET + 0].write(DEC_DIGITS_LUT[pair1 * 2 + 0]);
buf[quad_index * 4 + OFFSET + 1].write(DEC_DIGITS_LUT[pair1 * 2 + 1]);
buf[quad_index * 4 + OFFSET + 2].write(DEC_DIGITS_LUT[pair2 * 2 + 0]);
buf[quad_index * 4 + OFFSET + 3].write(DEC_DIGITS_LUT[pair2 * 2 + 1]);
buf[quad_index * 4 + OFFSET + 0].write(DECIMAL_PAIRS[pair1 * 2 + 0]);
buf[quad_index * 4 + OFFSET + 1].write(DECIMAL_PAIRS[pair1 * 2 + 1]);
buf[quad_index * 4 + OFFSET + 2].write(DECIMAL_PAIRS[pair2 * 2 + 0]);
buf[quad_index * 4 + OFFSET + 3].write(DECIMAL_PAIRS[pair2 * 2 + 1]);
}
}

View file

@ -7,9 +7,8 @@
)]
#![allow(missing_docs)]
#[const_trait]
#[rustc_const_unstable(feature = "core_intrinsics_fallbacks", issue = "none")]
pub trait CarryingMulAdd: Copy + 'static {
pub const trait CarryingMulAdd: Copy + 'static {
type Unsigned: Copy + 'static;
fn carrying_mul_add(
self,
@ -111,9 +110,8 @@ impl const CarryingMulAdd for i128 {
}
}
#[const_trait]
#[rustc_const_unstable(feature = "core_intrinsics_fallbacks", issue = "none")]
pub trait DisjointBitOr: Copy + 'static {
pub const trait DisjointBitOr: Copy + 'static {
/// See [`super::disjoint_bitor`]; we just need the trait indirection to handle
/// different types since calling intrinsics with generics doesn't work.
unsafe fn disjoint_bitor(self, other: Self) -> Self;
@ -149,9 +147,8 @@ impl_disjoint_bitor! {
i8, i16, i32, i64, i128, isize,
}
#[const_trait]
#[rustc_const_unstable(feature = "core_intrinsics_fallbacks", issue = "none")]
pub trait FunnelShift: Copy + 'static {
pub const trait FunnelShift: Copy + 'static {
/// See [`super::unchecked_funnel_shl`]; we just need the trait indirection to handle
/// different types since calling intrinsics with generics doesn't work.
unsafe fn unchecked_funnel_shl(self, rhs: Self, shift: u32) -> Self;

View file

@ -1057,8 +1057,7 @@ marker_impls! {
#[rustc_on_unimplemented(message = "can't drop `{Self}`", append_const_msg)]
#[rustc_deny_explicit_impl]
#[rustc_do_not_implement_via_object]
#[const_trait]
pub trait Destruct {}
pub const trait Destruct {}
/// A marker for tuple types.
///

View file

@ -832,7 +832,6 @@ impl f128 {
#[unstable(feature = "f128", issue = "116909")]
#[rustc_const_unstable(feature = "f128", issue = "116909")]
pub const fn midpoint(self, other: f128) -> f128 {
const LO: f128 = f128::MIN_POSITIVE * 2.;
const HI: f128 = f128::MAX / 2.;
let (a, b) = (self, other);
@ -842,14 +841,7 @@ impl f128 {
if abs_a <= HI && abs_b <= HI {
// Overflow is impossible
(a + b) / 2.
} else if abs_a < LO {
// Not safe to halve `a` (would underflow)
a + (b / 2.)
} else if abs_b < LO {
// Not safe to halve `b` (would underflow)
(a / 2.) + b
} else {
// Safe to halve `a` and `b`
(a / 2.) + (b / 2.)
}
}

View file

@ -820,7 +820,6 @@ impl f16 {
#[unstable(feature = "f16", issue = "116909")]
#[rustc_const_unstable(feature = "f16", issue = "116909")]
pub const fn midpoint(self, other: f16) -> f16 {
const LO: f16 = f16::MIN_POSITIVE * 2.;
const HI: f16 = f16::MAX / 2.;
let (a, b) = (self, other);
@ -830,14 +829,7 @@ impl f16 {
if abs_a <= HI && abs_b <= HI {
// Overflow is impossible
(a + b) / 2.
} else if abs_a < LO {
// Not safe to halve `a` (would underflow)
a + (b / 2.)
} else if abs_b < LO {
// Not safe to halve `b` (would underflow)
(a / 2.) + b
} else {
// Safe to halve `a` and `b`
(a / 2.) + (b / 2.)
}
}

View file

@ -1025,7 +1025,6 @@ impl f32 {
((self as f64 + other as f64) / 2.0) as f32
}
_ => {
const LO: f32 = f32::MIN_POSITIVE * 2.;
const HI: f32 = f32::MAX / 2.;
let (a, b) = (self, other);
@ -1035,14 +1034,7 @@ impl f32 {
if abs_a <= HI && abs_b <= HI {
// Overflow is impossible
(a + b) / 2.
} else if abs_a < LO {
// Not safe to halve `a` (would underflow)
a + (b / 2.)
} else if abs_b < LO {
// Not safe to halve `b` (would underflow)
(a / 2.) + b
} else {
// Safe to halve `a` and `b`
(a / 2.) + (b / 2.)
}
}
@ -1954,8 +1946,8 @@ pub mod math {
/// let abs_difference_x = (f32::math::abs_sub(x, 1.0) - 2.0).abs();
/// let abs_difference_y = (f32::math::abs_sub(y, 1.0) - 0.0).abs();
///
/// assert!(abs_difference_x <= f32::EPSILON);
/// assert!(abs_difference_y <= f32::EPSILON);
/// assert!(abs_difference_x <= 1e-6);
/// assert!(abs_difference_y <= 1e-6);
/// ```
///
/// _This standalone function is for testing only.
@ -2000,7 +1992,7 @@ pub mod math {
/// // x^(1/3) - 2 == 0
/// let abs_difference = (f32::math::cbrt(x) - 2.0).abs();
///
/// assert!(abs_difference <= f32::EPSILON);
/// assert!(abs_difference <= 1e-6);
/// ```
///
/// _This standalone function is for testing only.

View file

@ -1026,7 +1026,6 @@ impl f64 {
#[stable(feature = "num_midpoint", since = "1.85.0")]
#[rustc_const_stable(feature = "num_midpoint", since = "1.85.0")]
pub const fn midpoint(self, other: f64) -> f64 {
const LO: f64 = f64::MIN_POSITIVE * 2.;
const HI: f64 = f64::MAX / 2.;
let (a, b) = (self, other);
@ -1036,14 +1035,7 @@ impl f64 {
if abs_a <= HI && abs_b <= HI {
// Overflow is impossible
(a + b) / 2.
} else if abs_a < LO {
// Not safe to halve `a` (would underflow)
a + (b / 2.)
} else if abs_b < LO {
// Not safe to halve `b` (would underflow)
(a / 2.) + b
} else {
// Safe to halve `a` and `b`
(a / 2.) + (b / 2.)
}
}

View file

@ -1413,6 +1413,66 @@ macro_rules! int_impl {
}
}
/// Exact shift left. Computes `self << rhs` as long as it can be reversed losslessly.
///
/// Returns `None` if any bits that would be shifted out differ from the resulting sign bit
/// or if `rhs` >=
#[doc = concat!("`", stringify!($SelfT), "::BITS`.")]
/// Otherwise, returns `Some(self << rhs)`.
///
/// # Examples
///
/// ```
/// #![feature(exact_bitshifts)]
///
#[doc = concat!("assert_eq!(0x1", stringify!($SelfT), ".exact_shl(4), Some(0x10));")]
#[doc = concat!("assert_eq!(0x1", stringify!($SelfT), ".exact_shl(", stringify!($SelfT), "::BITS - 2), Some(1 << ", stringify!($SelfT), "::BITS - 2));")]
#[doc = concat!("assert_eq!(0x1", stringify!($SelfT), ".exact_shl(", stringify!($SelfT), "::BITS - 1), None);")]
#[doc = concat!("assert_eq!((-0x2", stringify!($SelfT), ").exact_shl(", stringify!($SelfT), "::BITS - 2), Some(-0x2 << ", stringify!($SelfT), "::BITS - 2));")]
#[doc = concat!("assert_eq!((-0x2", stringify!($SelfT), ").exact_shl(", stringify!($SelfT), "::BITS - 1), None);")]
/// ```
#[unstable(feature = "exact_bitshifts", issue = "144336")]
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline]
pub const fn exact_shl(self, rhs: u32) -> Option<$SelfT> {
if rhs < self.leading_zeros() || rhs < self.leading_ones() {
// SAFETY: rhs is checked above
Some(unsafe { self.unchecked_shl(rhs) })
} else {
None
}
}
/// Unchecked exact shift left. Computes `self << rhs`, assuming the operation can be
/// losslessly reversed and `rhs` cannot be larger than
#[doc = concat!("`", stringify!($SelfT), "::BITS`.")]
///
/// # Safety
///
/// This results in undefined behavior when `rhs >= self.leading_zeros() && rhs >=
/// self.leading_ones()` i.e. when
#[doc = concat!("[`", stringify!($SelfT), "::exact_shl`]")]
/// would return `None`.
#[unstable(feature = "exact_bitshifts", issue = "144336")]
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline]
pub const unsafe fn unchecked_exact_shl(self, rhs: u32) -> $SelfT {
assert_unsafe_precondition!(
check_language_ub,
concat!(stringify!($SelfT), "::unchecked_exact_shl cannot shift out non-zero bits"),
(
zeros: u32 = self.leading_zeros(),
ones: u32 = self.leading_ones(),
rhs: u32 = rhs,
) => rhs < zeros || rhs < ones,
);
// SAFETY: this is guaranteed to be safe by the caller
unsafe { self.unchecked_shl(rhs) }
}
/// Checked shift right. Computes `self >> rhs`, returning `None` if `rhs` is
/// larger than or equal to the number of bits in `self`.
///
@ -1534,6 +1594,63 @@ macro_rules! int_impl {
}
}
/// Exact shift right. Computes `self >> rhs` as long as it can be reversed losslessly.
///
/// Returns `None` if any non-zero bits would be shifted out or if `rhs` >=
#[doc = concat!("`", stringify!($SelfT), "::BITS`.")]
/// Otherwise, returns `Some(self >> rhs)`.
///
/// # Examples
///
/// ```
/// #![feature(exact_bitshifts)]
///
#[doc = concat!("assert_eq!(0x10", stringify!($SelfT), ".exact_shr(4), Some(0x1));")]
#[doc = concat!("assert_eq!(0x10", stringify!($SelfT), ".exact_shr(5), None);")]
/// ```
#[unstable(feature = "exact_bitshifts", issue = "144336")]
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline]
pub const fn exact_shr(self, rhs: u32) -> Option<$SelfT> {
if rhs <= self.trailing_zeros() && rhs < <$SelfT>::BITS {
// SAFETY: rhs is checked above
Some(unsafe { self.unchecked_shr(rhs) })
} else {
None
}
}
/// Unchecked exact shift right. Computes `self >> rhs`, assuming the operation can be
/// losslessly reversed and `rhs` cannot be larger than
#[doc = concat!("`", stringify!($SelfT), "::BITS`.")]
///
/// # Safety
///
/// This results in undefined behavior when `rhs > self.trailing_zeros() || rhs >=
#[doc = concat!(stringify!($SelfT), "::BITS`")]
/// i.e. when
#[doc = concat!("[`", stringify!($SelfT), "::exact_shr`]")]
/// would return `None`.
#[unstable(feature = "exact_bitshifts", issue = "144336")]
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline]
pub const unsafe fn unchecked_exact_shr(self, rhs: u32) -> $SelfT {
assert_unsafe_precondition!(
check_language_ub,
concat!(stringify!($SelfT), "::unchecked_exact_shr cannot shift out non-zero bits"),
(
zeros: u32 = self.trailing_zeros(),
bits: u32 = <$SelfT>::BITS,
rhs: u32 = rhs,
) => rhs <= zeros && rhs < bits,
);
// SAFETY: this is guaranteed to be safe by the caller
unsafe { self.unchecked_shr(rhs) }
}
/// Checked absolute value. Computes `self.abs()`, returning `None` if
/// `self == MIN`.
///

View file

@ -1821,6 +1821,63 @@ macro_rules! uint_impl {
}
}
/// Exact shift left. Computes `self << rhs` as long as it can be reversed losslessly.
///
/// Returns `None` if any non-zero bits would be shifted out or if `rhs` >=
#[doc = concat!("`", stringify!($SelfT), "::BITS`.")]
/// Otherwise, returns `Some(self << rhs)`.
///
/// # Examples
///
/// ```
/// #![feature(exact_bitshifts)]
///
#[doc = concat!("assert_eq!(0x1", stringify!($SelfT), ".exact_shl(4), Some(0x10));")]
#[doc = concat!("assert_eq!(0x1", stringify!($SelfT), ".exact_shl(129), None);")]
/// ```
#[unstable(feature = "exact_bitshifts", issue = "144336")]
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline]
pub const fn exact_shl(self, rhs: u32) -> Option<$SelfT> {
if rhs <= self.leading_zeros() && rhs < <$SelfT>::BITS {
// SAFETY: rhs is checked above
Some(unsafe { self.unchecked_shl(rhs) })
} else {
None
}
}
/// Unchecked exact shift left. Computes `self << rhs`, assuming the operation can be
/// losslessly reversed `rhs` cannot be larger than
#[doc = concat!("`", stringify!($SelfT), "::BITS`.")]
///
/// # Safety
///
/// This results in undefined behavior when `rhs > self.leading_zeros() || rhs >=
#[doc = concat!(stringify!($SelfT), "::BITS`")]
/// i.e. when
#[doc = concat!("[`", stringify!($SelfT), "::exact_shl`]")]
/// would return `None`.
#[unstable(feature = "exact_bitshifts", issue = "144336")]
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline]
pub const unsafe fn unchecked_exact_shl(self, rhs: u32) -> $SelfT {
assert_unsafe_precondition!(
check_language_ub,
concat!(stringify!($SelfT), "::exact_shl_unchecked cannot shift out non-zero bits"),
(
zeros: u32 = self.leading_zeros(),
bits: u32 = <$SelfT>::BITS,
rhs: u32 = rhs,
) => rhs <= zeros && rhs < bits,
);
// SAFETY: this is guaranteed to be safe by the caller
unsafe { self.unchecked_shl(rhs) }
}
/// Checked shift right. Computes `self >> rhs`, returning `None`
/// if `rhs` is larger than or equal to the number of bits in `self`.
///
@ -1936,6 +1993,63 @@ macro_rules! uint_impl {
}
}
/// Exact shift right. Computes `self >> rhs` as long as it can be reversed losslessly.
///
/// Returns `None` if any non-zero bits would be shifted out or if `rhs` >=
#[doc = concat!("`", stringify!($SelfT), "::BITS`.")]
/// Otherwise, returns `Some(self >> rhs)`.
///
/// # Examples
///
/// ```
/// #![feature(exact_bitshifts)]
///
#[doc = concat!("assert_eq!(0x10", stringify!($SelfT), ".exact_shr(4), Some(0x1));")]
#[doc = concat!("assert_eq!(0x10", stringify!($SelfT), ".exact_shr(5), None);")]
/// ```
#[unstable(feature = "exact_bitshifts", issue = "144336")]
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline]
pub const fn exact_shr(self, rhs: u32) -> Option<$SelfT> {
if rhs <= self.trailing_zeros() && rhs < <$SelfT>::BITS {
// SAFETY: rhs is checked above
Some(unsafe { self.unchecked_shr(rhs) })
} else {
None
}
}
/// Unchecked exact shift right. Computes `self >> rhs`, assuming the operation can be
/// losslessly reversed and `rhs` cannot be larger than
#[doc = concat!("`", stringify!($SelfT), "::BITS`.")]
///
/// # Safety
///
/// This results in undefined behavior when `rhs > self.trailing_zeros() || rhs >=
#[doc = concat!(stringify!($SelfT), "::BITS`")]
/// i.e. when
#[doc = concat!("[`", stringify!($SelfT), "::exact_shr`]")]
/// would return `None`.
#[unstable(feature = "exact_bitshifts", issue = "144336")]
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline]
pub const unsafe fn unchecked_exact_shr(self, rhs: u32) -> $SelfT {
assert_unsafe_precondition!(
check_language_ub,
concat!(stringify!($SelfT), "::exact_shr_unchecked cannot shift out non-zero bits"),
(
zeros: u32 = self.trailing_zeros(),
bits: u32 = <$SelfT>::BITS,
rhs: u32 = rhs,
) => rhs <= zeros && rhs < bits,
);
// SAFETY: this is guaranteed to be safe by the caller
unsafe { self.unchecked_shr(rhs) }
}
/// Checked exponentiation. Computes `self.pow(exp)`, returning `None` if
/// overflow occurred.
///

View file

@ -74,8 +74,7 @@
append_const_msg
)]
#[doc(alias = "+")]
#[const_trait]
pub trait Add<Rhs = Self> {
pub const trait Add<Rhs = Self> {
/// The resulting type after applying the `+` operator.
#[stable(feature = "rust1", since = "1.0.0")]
type Output;
@ -188,8 +187,7 @@ add_impl! { usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f16 f32 f64 f128
append_const_msg
)]
#[doc(alias = "-")]
#[const_trait]
pub trait Sub<Rhs = Self> {
pub const trait Sub<Rhs = Self> {
/// The resulting type after applying the `-` operator.
#[stable(feature = "rust1", since = "1.0.0")]
type Output;
@ -323,8 +321,7 @@ sub_impl! { usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f16 f32 f64 f128
label = "no implementation for `{Self} * {Rhs}`"
)]
#[doc(alias = "*")]
#[const_trait]
pub trait Mul<Rhs = Self> {
pub const trait Mul<Rhs = Self> {
/// The resulting type after applying the `*` operator.
#[stable(feature = "rust1", since = "1.0.0")]
type Output;
@ -462,8 +459,7 @@ mul_impl! { usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f16 f32 f64 f128
label = "no implementation for `{Self} / {Rhs}`"
)]
#[doc(alias = "/")]
#[const_trait]
pub trait Div<Rhs = Self> {
pub const trait Div<Rhs = Self> {
/// The resulting type after applying the `/` operator.
#[stable(feature = "rust1", since = "1.0.0")]
type Output;
@ -572,8 +568,7 @@ div_impl_float! { f16 f32 f64 f128 }
label = "no implementation for `{Self} % {Rhs}`"
)]
#[doc(alias = "%")]
#[const_trait]
pub trait Rem<Rhs = Self> {
pub const trait Rem<Rhs = Self> {
/// The resulting type after applying the `%` operator.
#[stable(feature = "rust1", since = "1.0.0")]
type Output;
@ -694,8 +689,7 @@ rem_impl_float! { f16 f32 f64 f128 }
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_ops", issue = "143802")]
#[doc(alias = "-")]
#[const_trait]
pub trait Neg {
pub const trait Neg {
/// The resulting type after applying the `-` operator.
#[stable(feature = "rust1", since = "1.0.0")]
type Output;
@ -772,8 +766,7 @@ neg_impl! { isize i8 i16 i32 i64 i128 f16 f32 f64 f128 }
)]
#[doc(alias = "+")]
#[doc(alias = "+=")]
#[const_trait]
pub trait AddAssign<Rhs = Self> {
pub const trait AddAssign<Rhs = Self> {
/// Performs the `+=` operation.
///
/// # Example
@ -844,8 +837,7 @@ add_assign_impl! { usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f16 f32 f
)]
#[doc(alias = "-")]
#[doc(alias = "-=")]
#[const_trait]
pub trait SubAssign<Rhs = Self> {
pub const trait SubAssign<Rhs = Self> {
/// Performs the `-=` operation.
///
/// # Example
@ -907,8 +899,7 @@ sub_assign_impl! { usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f16 f32 f
)]
#[doc(alias = "*")]
#[doc(alias = "*=")]
#[const_trait]
pub trait MulAssign<Rhs = Self> {
pub const trait MulAssign<Rhs = Self> {
/// Performs the `*=` operation.
///
/// # Example
@ -970,8 +961,7 @@ mul_assign_impl! { usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f16 f32 f
)]
#[doc(alias = "/")]
#[doc(alias = "/=")]
#[const_trait]
pub trait DivAssign<Rhs = Self> {
pub const trait DivAssign<Rhs = Self> {
/// Performs the `/=` operation.
///
/// # Example
@ -1036,8 +1026,7 @@ div_assign_impl! { usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f16 f32 f
)]
#[doc(alias = "%")]
#[doc(alias = "%=")]
#[const_trait]
pub trait RemAssign<Rhs = Self> {
pub const trait RemAssign<Rhs = Self> {
/// Performs the `%=` operation.
///
/// # Example

View file

@ -32,8 +32,7 @@
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_ops", issue = "143802")]
#[doc(alias = "!")]
#[const_trait]
pub trait Not {
pub const trait Not {
/// The resulting type after applying the `!` operator.
#[stable(feature = "rust1", since = "1.0.0")]
type Output;
@ -148,8 +147,7 @@ impl const Not for ! {
message = "no implementation for `{Self} & {Rhs}`",
label = "no implementation for `{Self} & {Rhs}`"
)]
#[const_trait]
pub trait BitAnd<Rhs = Self> {
pub const trait BitAnd<Rhs = Self> {
/// The resulting type after applying the `&` operator.
#[stable(feature = "rust1", since = "1.0.0")]
type Output;
@ -253,8 +251,7 @@ bitand_impl! { bool usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 }
message = "no implementation for `{Self} | {Rhs}`",
label = "no implementation for `{Self} | {Rhs}`"
)]
#[const_trait]
pub trait BitOr<Rhs = Self> {
pub const trait BitOr<Rhs = Self> {
/// The resulting type after applying the `|` operator.
#[stable(feature = "rust1", since = "1.0.0")]
type Output;
@ -358,8 +355,7 @@ bitor_impl! { bool usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 }
message = "no implementation for `{Self} ^ {Rhs}`",
label = "no implementation for `{Self} ^ {Rhs}`"
)]
#[const_trait]
pub trait BitXor<Rhs = Self> {
pub const trait BitXor<Rhs = Self> {
/// The resulting type after applying the `^` operator.
#[stable(feature = "rust1", since = "1.0.0")]
type Output;
@ -462,8 +458,7 @@ bitxor_impl! { bool usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 }
message = "no implementation for `{Self} << {Rhs}`",
label = "no implementation for `{Self} << {Rhs}`"
)]
#[const_trait]
pub trait Shl<Rhs = Self> {
pub const trait Shl<Rhs = Self> {
/// The resulting type after applying the `<<` operator.
#[stable(feature = "rust1", since = "1.0.0")]
type Output;
@ -585,8 +580,7 @@ shl_impl_all! { u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize }
message = "no implementation for `{Self} >> {Rhs}`",
label = "no implementation for `{Self} >> {Rhs}`"
)]
#[const_trait]
pub trait Shr<Rhs = Self> {
pub const trait Shr<Rhs = Self> {
/// The resulting type after applying the `>>` operator.
#[stable(feature = "rust1", since = "1.0.0")]
type Output;
@ -717,8 +711,7 @@ shr_impl_all! { u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize }
message = "no implementation for `{Self} &= {Rhs}`",
label = "no implementation for `{Self} &= {Rhs}`"
)]
#[const_trait]
pub trait BitAndAssign<Rhs = Self> {
pub const trait BitAndAssign<Rhs = Self> {
/// Performs the `&=` operation.
///
/// # Examples
@ -793,8 +786,7 @@ bitand_assign_impl! { bool usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 }
message = "no implementation for `{Self} |= {Rhs}`",
label = "no implementation for `{Self} |= {Rhs}`"
)]
#[const_trait]
pub trait BitOrAssign<Rhs = Self> {
pub const trait BitOrAssign<Rhs = Self> {
/// Performs the `|=` operation.
///
/// # Examples
@ -869,8 +861,7 @@ bitor_assign_impl! { bool usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 }
message = "no implementation for `{Self} ^= {Rhs}`",
label = "no implementation for `{Self} ^= {Rhs}`"
)]
#[const_trait]
pub trait BitXorAssign<Rhs = Self> {
pub const trait BitXorAssign<Rhs = Self> {
/// Performs the `^=` operation.
///
/// # Examples
@ -943,8 +934,7 @@ bitxor_assign_impl! { bool usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 }
message = "no implementation for `{Self} <<= {Rhs}`",
label = "no implementation for `{Self} <<= {Rhs}`"
)]
#[const_trait]
pub trait ShlAssign<Rhs = Self> {
pub const trait ShlAssign<Rhs = Self> {
/// Performs the `<<=` operation.
///
/// # Examples
@ -1030,8 +1020,7 @@ shl_assign_impl_all! { u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize }
message = "no implementation for `{Self} >>= {Rhs}`",
label = "no implementation for `{Self} >>= {Rhs}`"
)]
#[const_trait]
pub trait ShrAssign<Rhs = Self> {
pub const trait ShrAssign<Rhs = Self> {
/// Performs the `>>=` operation.
///
/// # Examples

View file

@ -135,9 +135,8 @@ use crate::marker::PointeeSized;
#[doc(alias = "&*")]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "Deref"]
#[const_trait]
#[rustc_const_unstable(feature = "const_convert", issue = "143773")]
pub trait Deref: PointeeSized {
pub const trait Deref: PointeeSized {
/// The resulting type after dereferencing.
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "deref_target"]
@ -267,9 +266,8 @@ impl<T: ?Sized> const Deref for &mut T {
#[lang = "deref_mut"]
#[doc(alias = "*")]
#[stable(feature = "rust1", since = "1.0.0")]
#[const_trait]
#[rustc_const_unstable(feature = "const_convert", issue = "143773")]
pub trait DerefMut: [const] Deref + PointeeSized {
pub const trait DerefMut: [const] Deref + PointeeSized {
/// Mutably dereferences the value.
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "deref_mut_method"]

View file

@ -203,9 +203,8 @@
/// [nomicon]: ../../nomicon/phantom-data.html#an-exception-the-special-case-of-the-standard-library-and-its-unstable-may_dangle
#[lang = "drop"]
#[stable(feature = "rust1", since = "1.0.0")]
#[const_trait]
#[rustc_const_unstable(feature = "const_destruct", issue = "133214")]
pub trait Drop {
pub const trait Drop {
/// Executes the destructor for this type.
///
/// This method is called implicitly when the value goes out of scope,

View file

@ -72,9 +72,8 @@ use crate::marker::Tuple;
)]
#[fundamental] // so that regex can rely that `&str: !FnMut`
#[must_use = "closures are lazy and do nothing unless called"]
#[const_trait]
#[rustc_const_unstable(feature = "const_trait_impl", issue = "143874")]
pub trait Fn<Args: Tuple>: FnMut<Args> {
pub const trait Fn<Args: Tuple>: FnMut<Args> {
/// Performs the call operation.
#[unstable(feature = "fn_traits", issue = "29625")]
extern "rust-call" fn call(&self, args: Args) -> Self::Output;
@ -160,9 +159,8 @@ pub trait Fn<Args: Tuple>: FnMut<Args> {
)]
#[fundamental] // so that regex can rely that `&str: !FnMut`
#[must_use = "closures are lazy and do nothing unless called"]
#[const_trait]
#[rustc_const_unstable(feature = "const_trait_impl", issue = "143874")]
pub trait FnMut<Args: Tuple>: FnOnce<Args> {
pub const trait FnMut<Args: Tuple>: FnOnce<Args> {
/// Performs the call operation.
#[unstable(feature = "fn_traits", issue = "29625")]
extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
@ -240,9 +238,8 @@ pub trait FnMut<Args: Tuple>: FnOnce<Args> {
)]
#[fundamental] // so that regex can rely that `&str: !FnMut`
#[must_use = "closures are lazy and do nothing unless called"]
#[const_trait]
#[rustc_const_unstable(feature = "const_trait_impl", issue = "143874")]
pub trait FnOnce<Args: Tuple> {
pub const trait FnOnce<Args: Tuple> {
/// The returned type after the call operator is used.
#[lang = "fn_once_output"]
#[stable(feature = "fn_once_output", since = "1.12.0")]

View file

@ -55,9 +55,8 @@
#[doc(alias = "]")]
#[doc(alias = "[")]
#[doc(alias = "[]")]
#[const_trait]
#[rustc_const_unstable(feature = "const_index", issue = "143775")]
pub trait Index<Idx: ?Sized> {
pub const trait Index<Idx: ?Sized> {
/// The returned type after indexing.
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "IndexOutput"]
@ -168,8 +167,7 @@ see chapter in The Book <https://doc.rust-lang.org/book/ch08-02-strings.html#ind
#[doc(alias = "]")]
#[doc(alias = "[]")]
#[rustc_const_unstable(feature = "const_index", issue = "143775")]
#[const_trait]
pub trait IndexMut<Idx: ?Sized>: [const] Index<Idx> {
pub const trait IndexMut<Idx: ?Sized>: [const] Index<Idx> {
/// Performs the mutable indexing (`container[index]`) operation.
///
/// # Panics

View file

@ -128,9 +128,8 @@ use crate::ops::ControlFlow;
)]
#[doc(alias = "?")]
#[lang = "Try"]
#[const_trait]
#[rustc_const_unstable(feature = "const_try", issue = "74935")]
pub trait Try: [const] FromResidual {
pub const trait Try: [const] FromResidual {
/// The type of the value produced by `?` when *not* short-circuiting.
#[unstable(feature = "try_trait_v2", issue = "84277", old_name = "try_trait")]
type Output;
@ -306,9 +305,8 @@ pub trait Try: [const] FromResidual {
)]
#[rustc_diagnostic_item = "FromResidual"]
#[unstable(feature = "try_trait_v2", issue = "84277", old_name = "try_trait")]
#[const_trait]
#[rustc_const_unstable(feature = "const_try", issue = "74935")]
pub trait FromResidual<R = <Self as Try>::Residual> {
pub const trait FromResidual<R = <Self as Try>::Residual> {
/// Constructs the type from a compatible `Residual` type.
///
/// This should be implemented consistently with the `branch` method such
@ -361,9 +359,8 @@ where
/// and in the other direction,
/// `<Result<Infallible, E> as Residual<T>>::TryType = Result<T, E>`.
#[unstable(feature = "try_trait_v2_residual", issue = "91285")]
#[const_trait]
#[rustc_const_unstable(feature = "const_try", issue = "74935")]
pub trait Residual<O> {
pub const trait Residual<O> {
/// The "return" type of this meta-function.
#[unstable(feature = "try_trait_v2_residual", issue = "91285")]
type TryType: Try<Output = O, Residual = Self>;

View file

@ -1640,7 +1640,7 @@ impl<T> Option<T> {
pub const fn or_else<F>(self, f: F) -> Option<T>
where
F: [const] FnOnce() -> Option<T> + [const] Destruct,
//FIXME(const_hack): this `T: ~const Destruct` is unnecessary, but even precise live drops can't tell
//FIXME(const_hack): this `T: [const] Destruct` is unnecessary, but even precise live drops can't tell
// no value of type `T` gets dropped here
T: [const] Destruct,
{
@ -2185,7 +2185,7 @@ const fn expect_failed(msg: &str) -> ! {
#[rustc_const_unstable(feature = "const_clone", issue = "142757")]
impl<T> const Clone for Option<T>
where
// FIXME(const_hack): the T: ~const Destruct should be inferred from the Self: ~const Destruct in clone_from.
// FIXME(const_hack): the T: [const] Destruct should be inferred from the Self: [const] Destruct in clone_from.
// See https://github.com/rust-lang/rust/issues/144207
T: [const] Clone + [const] Destruct,
{

View file

@ -18,12 +18,11 @@ macro_rules! pattern_type {
/// used right now to simplify ast lowering of pattern type ranges.
#[unstable(feature = "pattern_type_range_trait", issue = "123646")]
#[rustc_const_unstable(feature = "pattern_type_range_trait", issue = "123646")]
#[const_trait]
#[diagnostic::on_unimplemented(
message = "`{Self}` is not a valid base type for range patterns",
label = "only integer types and `char` are supported"
)]
pub trait RangePattern {
pub const trait RangePattern {
/// Trait version of the inherent `MIN` assoc const.
#[lang = "RangeMin"]
const MIN: Self;

View file

@ -95,9 +95,8 @@ impl<T: PartialOrd> PartialOrd for [T] {
#[doc(hidden)]
// intermediate trait for specialization of slice's PartialEq
#[const_trait]
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
trait SlicePartialEq<B> {
const trait SlicePartialEq<B> {
fn equal(&self, other: &[B]) -> bool;
fn not_equal(&self, other: &[B]) -> bool {

View file

@ -151,7 +151,7 @@ mod private_slice_index {
message = "the type `{T}` cannot be indexed by `{Self}`",
label = "slice indices are of type `usize` or ranges of `usize`"
)]
#[const_trait]
#[const_trait] // FIXME(const_trait_impl): Migrate to `const unsafe trait` once #146122 is fixed.
#[rustc_const_unstable(feature = "const_index", issue = "143775")]
pub unsafe trait SliceIndex<T: ?Sized>: private_slice_index::Sealed {
/// The output type returned by methods.

View file

@ -825,9 +825,8 @@ unsafe impl const SliceIndex<str> for ops::RangeToInclusive<usize> {
/// assert!(Point::from_str("(1 2)").is_err());
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[const_trait]
#[rustc_const_unstable(feature = "const_convert", issue = "143773")]
pub trait FromStr: Sized {
pub const trait FromStr: Sized {
/// The associated error which can be returned from parsing.
#[stable(feature = "rust1", since = "1.0.0")]
type Err;

View file

@ -1,4 +1,4 @@
///! This file is generated by `./x run src/tools/unicode-table-generator`; do not edit manually!
//! This file is generated by `./x run src/tools/unicode-table-generator`; do not edit manually!
// Alphabetic : 1727 bytes, 142759 codepoints in 757 ranges (U+000041 - U+0323B0) using skiplist
// Case_Ignorable : 1053 bytes, 2749 codepoints in 452 ranges (U+000027 - U+0E01F0) using skiplist
// Cased : 407 bytes, 4578 codepoints in 159 ranges (U+000041 - U+01F18A) using skiplist

View file

@ -342,3 +342,27 @@ fn write_i128_hex(bh: &mut Bencher) {
black_box(&mut buf).clear();
});
}
#[bench]
fn write_i64_exp(bh: &mut Bencher) {
let mut buf = String::with_capacity(1024);
bh.iter(|| {
write!(black_box(&mut buf), "{:e}", black_box(0_i64)).unwrap();
write!(black_box(&mut buf), "{:e}", black_box(100_i64)).unwrap();
write!(black_box(&mut buf), "{:e}", black_box(-100_i64)).unwrap();
write!(black_box(&mut buf), "{:e}", black_box(1_i64 << 32)).unwrap();
black_box(&mut buf).clear();
});
}
#[bench]
fn write_i128_exp(bh: &mut Bencher) {
let mut buf = String::with_capacity(1024);
bh.iter(|| {
write!(black_box(&mut buf), "{:e}", black_box(0_i128)).unwrap();
write!(black_box(&mut buf), "{:e}", black_box(100_i128)).unwrap();
write!(black_box(&mut buf), "{:e}", black_box(-100_i128)).unwrap();
write!(black_box(&mut buf), "{:e}", black_box(1_i128 << 64)).unwrap();
black_box(&mut buf).clear();
});
}

View file

@ -1,7 +1,9 @@
// FIXME(f16_f128): only tested on platforms that have symbols and aren't buggy
#![cfg(target_has_reliable_f128)]
use super::{assert_approx_eq, assert_biteq};
#[cfg(any(miri, target_has_reliable_f128_math))]
use super::assert_approx_eq;
use super::assert_biteq;
// Note these tolerances make sense around zero, but not for more extreme exponents.
@ -74,25 +76,6 @@ fn test_float_bits_conv() {
assert_eq!(f128::from_bits(masked_nan2).to_bits(), masked_nan2);
}
#[test]
fn test_algebraic() {
let a: f128 = 123.0;
let b: f128 = 456.0;
// Check that individual operations match their primitive counterparts.
//
// This is a check of current implementations and does NOT imply any form of
// guarantee about future behavior. The compiler reserves the right to make
// these operations inexact matches in the future.
let eps = if cfg!(miri) { 1e-6 } else { 0.0 };
assert_approx_eq!(a.algebraic_add(b), a + b, eps);
assert_approx_eq!(a.algebraic_sub(b), a - b, eps);
assert_approx_eq!(a.algebraic_mul(b), a * b, eps);
assert_approx_eq!(a.algebraic_div(b), a / b, eps);
assert_approx_eq!(a.algebraic_rem(b), a % b, eps);
}
#[test]
fn test_from() {
assert_biteq!(f128::from(false), 0.0);

View file

@ -73,27 +73,6 @@ fn test_float_bits_conv() {
assert_eq!(f16::from_bits(masked_nan2).to_bits(), masked_nan2);
}
#[test]
fn test_algebraic() {
let a: f16 = 123.0;
let b: f16 = 456.0;
// Check that individual operations match their primitive counterparts.
//
// This is a check of current implementations and does NOT imply any form of
// guarantee about future behavior. The compiler reserves the right to make
// these operations inexact matches in the future.
let eps_add = if cfg!(miri) { 1e1 } else { 0.0 };
let eps_mul = if cfg!(miri) { 1e3 } else { 0.0 };
let eps_div = if cfg!(miri) { 1e0 } else { 0.0 };
assert_approx_eq!(a.algebraic_add(b), a + b, eps_add);
assert_approx_eq!(a.algebraic_sub(b), a - b, eps_add);
assert_approx_eq!(a.algebraic_mul(b), a * b, eps_mul);
assert_approx_eq!(a.algebraic_div(b), a / b, eps_div);
assert_approx_eq!(a.algebraic_rem(b), a % b, eps_div);
}
#[test]
fn test_from() {
assert_biteq!(f16::from(false), 0.0);

Some files were not shown because too many files have changed in this diff Show more