commit
3430751d7e
320 changed files with 6104 additions and 4125 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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(..) {..}
|
||||
|
|
|
|||
|
|
@ -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()))
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 }],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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") };
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
_ => (),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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.)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue