From 08a997eea28c75e23e5b765de1578c8537ac16bf Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Wed, 5 Jul 2023 14:03:37 -0700 Subject: [PATCH 001/111] style-guide: Add section on bugs, and resolving bugs --- src/doc/style-guide/src/README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/doc/style-guide/src/README.md b/src/doc/style-guide/src/README.md index b8aa64ba14f6..9955f95e3f0c 100644 --- a/src/doc/style-guide/src/README.md +++ b/src/doc/style-guide/src/README.md @@ -32,6 +32,19 @@ This should not be interpreted as forbidding developers from following a non-default style, or forbidding tools from adding any particular configuration options. +## Bugs + +If the style guide differs from rustfmt, that may represent a bug in rustfmt, +or a bug in the style guide; either way, please report it to the style team or +the rustfmt team or both, for investigation and fix. + +If implementing a new formatting tool based on the style guide and default Rust +style, please test it on the corpus of existing Rust code, and avoid causing +widespread breakage. The implementation and testing of such a tool may surface +bugs in either the style guide or rustfmt, as well as bugs in the tool itself. + +We typically resolve bugs in a fashion that avoids widespread breakage. + ## Formatting conventions ### Indentation and line width From 42fa7df38cea93d1a1991b7aa085a7a57b2800fa Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 31 Jul 2023 16:07:09 -0700 Subject: [PATCH 002/111] style-guide: Expand documentation on `as` casts (mostly like a binary operator) `as` casts currently get formatted like a binary operator, except that the second line can stack several `as` casts rather than breaking them each onto their own line. Merge `as` into a subsection of the binary operators section, and then go into detail on the one difference between `as` formatting and binary operator formatting. --- src/doc/style-guide/src/expressions.md | 28 ++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/doc/style-guide/src/expressions.md b/src/doc/style-guide/src/expressions.md index 32c604f9f3e1..91dd3d2224ab 100644 --- a/src/doc/style-guide/src/expressions.md +++ b/src/doc/style-guide/src/expressions.md @@ -328,6 +328,26 @@ foo_bar Prefer line-breaking at an assignment operator (either `=` or `+=`, etc.) rather than at other binary operators. +### Casts (`as`) + +Format `as` casts like a binary operator. In particular, always include spaces +around `as`, and if line-breaking, break before the `as` (never after) and +block-indent the subsequent line. Format the type on the right-hand side using +the rules for types. + +However, unlike with other binary operators, if chaining a series of `as` casts +that require line-breaking, and line-breaking before the first `as` suffices to +make the remainder fit on the next line, don't break before any subsequent +`as`; instead, leave the series of types all on the same line: + +```rust +let cstr = very_long_expression() + as *const str as *const [u8] as *const std::os::raw::c_char; +``` + +If the subsequent line still requires line-breaking, break and block-indent +before each `as` as with other binary operators. + ## Control flow Do not include extraneous parentheses for `if` and `while` expressions. @@ -421,14 +441,6 @@ assert_eq!( ); ``` -## Casts (`as`) - -Put spaces before and after `as`: - -```rust -let cstr = "Hi\0" as *const str as *const [u8] as *const std::os::raw::c_char; -``` - ## Chains of fields and method calls A chain is a sequence of field accesses, method calls, and/or uses of the try From 61cfb1734aefee0f7dfa2ce55964372b02327285 Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Fri, 12 Aug 2022 16:28:59 +0800 Subject: [PATCH 003/111] libstd: add xous to libstd Add the `xous` target to libstd. Currently this defers everything to the `unsupported` target. Signed-off-by: Sean Cross --- library/std/build.rs | 1 + library/std/src/sys/mod.rs | 3 +++ library/std/src/sys/xous/mod.rs | 44 +++++++++++++++++++++++++++++++ library/std/src/sys_common/mod.rs | 1 + 4 files changed, 49 insertions(+) create mode 100644 library/std/src/sys/xous/mod.rs diff --git a/library/std/build.rs b/library/std/build.rs index ddf6e84d8d03..a81c45609ea5 100644 --- a/library/std/build.rs +++ b/library/std/build.rs @@ -37,6 +37,7 @@ fn main() { || target.contains("nintendo-3ds") || target.contains("vita") || target.contains("nto") + || target.contains("xous") // See src/bootstrap/synthetic_targets.rs || env::var("RUSTC_BOOTSTRAP_SYNTHETIC_TARGET").is_ok() { diff --git a/library/std/src/sys/mod.rs b/library/std/src/sys/mod.rs index beea3f23c2df..d30dbebe7972 100644 --- a/library/std/src/sys/mod.rs +++ b/library/std/src/sys/mod.rs @@ -44,6 +44,9 @@ cfg_if::cfg_if! { } else if #[cfg(target_family = "wasm")] { mod wasm; pub use self::wasm::*; + } else if #[cfg(target_os = "xous")] { + mod xous; + pub use self::xous::*; } else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] { mod sgx; pub use self::sgx::*; diff --git a/library/std/src/sys/xous/mod.rs b/library/std/src/sys/xous/mod.rs new file mode 100644 index 000000000000..836647c81ba7 --- /dev/null +++ b/library/std/src/sys/xous/mod.rs @@ -0,0 +1,44 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +#[path = "../unsupported/alloc.rs"] +pub mod alloc; +#[path = "../unsupported/args.rs"] +pub mod args; +#[path = "../unix/cmath.rs"] +pub mod cmath; +#[path = "../unsupported/env.rs"] +pub mod env; +#[path = "../unsupported/fs.rs"] +pub mod fs; +#[path = "../unsupported/io.rs"] +pub mod io; +#[path = "../unsupported/locks/mod.rs"] +pub mod locks; +#[path = "../unsupported/net.rs"] +pub mod net; +#[path = "../unsupported/once.rs"] +pub mod once; +#[path = "../unsupported/os.rs"] +pub mod os; +#[path = "../unix/os_str.rs"] +pub mod os_str; +#[path = "../unix/path.rs"] +pub mod path; +#[path = "../unsupported/pipe.rs"] +pub mod pipe; +#[path = "../unsupported/process.rs"] +pub mod process; +#[path = "../unsupported/stdio.rs"] +pub mod stdio; +#[path = "../unsupported/thread.rs"] +pub mod thread; +#[path = "../unsupported/thread_local_key.rs"] +pub mod thread_local_key; +#[path = "../unsupported/thread_parking.rs"] +pub mod thread_parking; +#[path = "../unsupported/time.rs"] +pub mod time; + +#[path = "../unsupported/common.rs"] +mod common; +pub use common::*; diff --git a/library/std/src/sys_common/mod.rs b/library/std/src/sys_common/mod.rs index e9c727cbbd12..f7d821750635 100644 --- a/library/std/src/sys_common/mod.rs +++ b/library/std/src/sys_common/mod.rs @@ -46,6 +46,7 @@ cfg_if::cfg_if! { if #[cfg(any(target_os = "l4re", feature = "restricted-std", all(target_family = "wasm", not(target_os = "emscripten")), + target_os = "xous", all(target_vendor = "fortanix", target_env = "sgx")))] { pub use crate::sys::net; } else { From 7892cfb60e4fcf1f7024e09f13d81d665d4f5183 Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Fri, 4 Nov 2022 14:02:43 +0800 Subject: [PATCH 004/111] std: xous: add os-specific ffi calls Xous has no C FFI. Instead, all FFI is done via syscalls that are specified in Rust. Add these FFI calls to libstd, as well as some of the currently-supported syscalls. This enables Rust programs to interact with the Xous operating system while avoiding adding an extra dependency to libstd. Signed-off-by: Sean Cross --- library/std/src/os/mod.rs | 2 + library/std/src/os/xous/ffi.rs | 647 ++++++++++++++++++ library/std/src/os/xous/ffi/definitions.rs | 283 ++++++++ .../os/xous/ffi/definitions/memoryflags.rs | 176 +++++ library/std/src/os/xous/mod.rs | 14 + 5 files changed, 1122 insertions(+) create mode 100644 library/std/src/os/xous/ffi.rs create mode 100644 library/std/src/os/xous/ffi/definitions.rs create mode 100644 library/std/src/os/xous/ffi/definitions/memoryflags.rs create mode 100644 library/std/src/os/xous/mod.rs diff --git a/library/std/src/os/mod.rs b/library/std/src/os/mod.rs index 634c3cc4a15c..de6d784c65b1 100644 --- a/library/std/src/os/mod.rs +++ b/library/std/src/os/mod.rs @@ -146,6 +146,8 @@ pub mod vita; pub mod vxworks; #[cfg(target_os = "watchos")] pub(crate) mod watchos; +#[cfg(target_os = "xous")] +pub mod xous; #[cfg(any(unix, target_os = "wasi", doc))] pub mod fd; diff --git a/library/std/src/os/xous/ffi.rs b/library/std/src/os/xous/ffi.rs new file mode 100644 index 000000000000..8be7fbb102f3 --- /dev/null +++ b/library/std/src/os/xous/ffi.rs @@ -0,0 +1,647 @@ +#![allow(dead_code)] +#![allow(unused_variables)] +#![stable(feature = "rust1", since = "1.0.0")] + +#[path = "../unix/ffi/os_str.rs"] +mod os_str; + +#[stable(feature = "rust1", since = "1.0.0")] +pub use self::os_str::{OsStrExt, OsStringExt}; + +mod definitions; +#[stable(feature = "rust1", since = "1.0.0")] +pub use definitions::*; + +fn lend_mut_impl( + connection: Connection, + opcode: usize, + data: &mut [u8], + arg1: usize, + arg2: usize, + blocking: bool, +) -> Result<(usize, usize), Error> { + let mut a0 = if blocking { Syscall::SendMessage } else { Syscall::TrySendMessage } as usize; + let mut a1: usize = connection.try_into().unwrap(); + let mut a2 = InvokeType::LendMut as usize; + let a3 = opcode; + let a4 = data.as_mut_ptr() as usize; + let a5 = data.len(); + let a6 = arg1; + let a7 = arg2; + + unsafe { + core::arch::asm!( + "ecall", + inlateout("a0") a0, + inlateout("a1") a1, + inlateout("a2") a2, + inlateout("a3") a3 => _, + inlateout("a4") a4 => _, + inlateout("a5") a5 => _, + inlateout("a6") a6 => _, + inlateout("a7") a7 => _, + ) + }; + + let result = a0; + + if result == SyscallResult::MemoryReturned as usize { + Ok((a1, a2)) + } else if result == SyscallResult::Error as usize { + Err(a1.into()) + } else { + Err(Error::InternalError) + } +} + +pub(crate) fn lend_mut( + connection: Connection, + opcode: usize, + data: &mut [u8], + arg1: usize, + arg2: usize, +) -> Result<(usize, usize), Error> { + lend_mut_impl(connection, opcode, data, arg1, arg2, true) +} + +pub(crate) fn try_lend_mut( + connection: Connection, + opcode: usize, + data: &mut [u8], + arg1: usize, + arg2: usize, +) -> Result<(usize, usize), Error> { + lend_mut_impl(connection, opcode, data, arg1, arg2, false) +} + +fn lend_impl( + connection: Connection, + opcode: usize, + data: &[u8], + arg1: usize, + arg2: usize, + blocking: bool, +) -> Result<(usize, usize), Error> { + let mut a0 = if blocking { Syscall::SendMessage } else { Syscall::TrySendMessage } as usize; + let a1: usize = connection.try_into().unwrap(); + let a2 = InvokeType::Lend as usize; + let a3 = opcode; + let a4 = data.as_ptr() as usize; + let a5 = data.len(); + let mut a6 = arg1; + let mut a7 = arg2; + + unsafe { + core::arch::asm!( + "ecall", + inlateout("a0") a0, + inlateout("a1") a1 => _, + inlateout("a2") a2 => _, + inlateout("a3") a3 => _, + inlateout("a4") a4 => _, + inlateout("a5") a5 => _, + inlateout("a6") a6, + inlateout("a7") a7, + ) + }; + + let result = a0; + + if result == SyscallResult::MemoryReturned as usize { + Ok((a6, a7)) + } else if result == SyscallResult::Error as usize { + Err(a1.into()) + } else { + Err(Error::InternalError) + } +} + +pub(crate) fn lend( + connection: Connection, + opcode: usize, + data: &[u8], + arg1: usize, + arg2: usize, +) -> Result<(usize, usize), Error> { + lend_impl(connection, opcode, data, arg1, arg2, true) +} + +pub(crate) fn try_lend( + connection: Connection, + opcode: usize, + data: &[u8], + arg1: usize, + arg2: usize, +) -> Result<(usize, usize), Error> { + lend_impl(connection, opcode, data, arg1, arg2, false) +} + +fn scalar_impl(connection: Connection, args: [usize; 5], blocking: bool) -> Result<(), Error> { + let mut a0 = if blocking { Syscall::SendMessage } else { Syscall::TrySendMessage } as usize; + let mut a1: usize = connection.try_into().unwrap(); + let a2 = InvokeType::Scalar as usize; + let a3 = args[0]; + let a4 = args[1]; + let a5 = args[2]; + let a6 = args[3]; + let a7 = args[4]; + + unsafe { + core::arch::asm!( + "ecall", + inlateout("a0") a0, + inlateout("a1") a1, + inlateout("a2") a2 => _, + inlateout("a3") a3 => _, + inlateout("a4") a4 => _, + inlateout("a5") a5 => _, + inlateout("a6") a6 => _, + inlateout("a7") a7 => _, + ) + }; + + let result = a0; + + if result == SyscallResult::Ok as usize { + Ok(()) + } else if result == SyscallResult::Error as usize { + Err(a1.into()) + } else { + Err(Error::InternalError) + } +} + +pub(crate) fn scalar(connection: Connection, args: [usize; 5]) -> Result<(), Error> { + scalar_impl(connection, args, true) +} + +pub(crate) fn try_scalar(connection: Connection, args: [usize; 5]) -> Result<(), Error> { + scalar_impl(connection, args, false) +} + +fn blocking_scalar_impl( + connection: Connection, + args: [usize; 5], + blocking: bool, +) -> Result<[usize; 5], Error> { + let mut a0 = if blocking { Syscall::SendMessage } else { Syscall::TrySendMessage } as usize; + let mut a1: usize = connection.try_into().unwrap(); + let mut a2 = InvokeType::BlockingScalar as usize; + let mut a3 = args[0]; + let mut a4 = args[1]; + let mut a5 = args[2]; + let a6 = args[3]; + let a7 = args[4]; + + unsafe { + core::arch::asm!( + "ecall", + inlateout("a0") a0, + inlateout("a1") a1, + inlateout("a2") a2, + inlateout("a3") a3, + inlateout("a4") a4, + inlateout("a5") a5, + inlateout("a6") a6 => _, + inlateout("a7") a7 => _, + ) + }; + + let result = a0; + + if result == SyscallResult::Scalar1 as usize { + Ok([a1, 0, 0, 0, 0]) + } else if result == SyscallResult::Scalar2 as usize { + Ok([a1, a2, 0, 0, 0]) + } else if result == SyscallResult::Scalar5 as usize { + Ok([a1, a2, a3, a4, a5]) + } else if result == SyscallResult::Error as usize { + Err(a1.into()) + } else { + Err(Error::InternalError) + } +} + +pub(crate) fn blocking_scalar( + connection: Connection, + args: [usize; 5], +) -> Result<[usize; 5], Error> { + blocking_scalar_impl(connection, args, true) +} + +pub(crate) fn try_blocking_scalar( + connection: Connection, + args: [usize; 5], +) -> Result<[usize; 5], Error> { + blocking_scalar_impl(connection, args, false) +} + +fn connect_impl(address: ServerAddress, blocking: bool) -> Result { + let a0 = if blocking { Syscall::Connect } else { Syscall::TryConnect } as usize; + let address: [u32; 4] = address.into(); + let a1: usize = address[0].try_into().unwrap(); + let a2: usize = address[1].try_into().unwrap(); + let a3: usize = address[2].try_into().unwrap(); + let a4: usize = address[3].try_into().unwrap(); + let a5 = 0; + let a6 = 0; + let a7 = 0; + + let mut result: usize; + let mut value: usize; + + unsafe { + core::arch::asm!( + "ecall", + inlateout("a0") a0 => result, + inlateout("a1") a1 => value, + inlateout("a2") a2 => _, + inlateout("a3") a3 => _, + inlateout("a4") a4 => _, + inlateout("a5") a5 => _, + inlateout("a6") a6 => _, + inlateout("a7") a7 => _, + ) + }; + if result == SyscallResult::ConnectionId as usize { + Ok(value.try_into().unwrap()) + } else if result == SyscallResult::Error as usize { + Err(value.into()) + } else { + Err(Error::InternalError) + } +} + +/// Connect to a Xous server represented by the specified `address`. +/// +/// The current thread will block until the server is available. Returns +/// an error if the server cannot accept any more connections. +pub(crate) fn connect(address: ServerAddress) -> Result { + connect_impl(address, true) +} + +/// Attempt to connect to a Xous server represented by the specified `address`. +/// +/// If the server does not exist then None is returned. +pub(crate) fn try_connect(address: ServerAddress) -> Result, Error> { + match connect_impl(address, false) { + Ok(conn) => Ok(Some(conn)), + Err(Error::ServerNotFound) => Ok(None), + Err(e) => Err(e), + } +} + +/// Terminate the current process and return the specified code to the parent process. +pub(crate) fn exit(return_code: u32) -> ! { + let a0 = Syscall::TerminateProcess as usize; + let a1 = return_code as usize; + let a2 = 0; + let a3 = 0; + let a4 = 0; + let a5 = 0; + let a6 = 0; + let a7 = 0; + + unsafe { + core::arch::asm!( + "ecall", + in("a0") a0, + in("a1") a1, + in("a2") a2, + in("a3") a3, + in("a4") a4, + in("a5") a5, + in("a6") a6, + in("a7") a7, + ) + }; + unreachable!(); +} + +/// Suspend the current thread and allow another thread to run. This thread may +/// continue executing again immediately if there are no other threads available +/// to run on the system. +pub(crate) fn do_yield() { + let a0 = Syscall::Yield as usize; + let a1 = 0; + let a2 = 0; + let a3 = 0; + let a4 = 0; + let a5 = 0; + let a6 = 0; + let a7 = 0; + + unsafe { + core::arch::asm!( + "ecall", + inlateout("a0") a0 => _, + inlateout("a1") a1 => _, + inlateout("a2") a2 => _, + inlateout("a3") a3 => _, + inlateout("a4") a4 => _, + inlateout("a5") a5 => _, + inlateout("a6") a6 => _, + inlateout("a7") a7 => _, + ) + }; +} + +/// Allocate memory from the system. An optional physical and/or virtual address +/// may be specified in order to ensure memory is allocated at specific offsets, +/// otherwise the kernel will select an address. +/// +/// # Safety +/// +/// This function is safe unless a virtual address is specified. In that case, +/// the kernel will return an alias to the existing range. This violates Rust's +/// pointer uniqueness guarantee. +pub(crate) unsafe fn map_memory( + phys: Option>, + virt: Option>, + count: usize, + flags: MemoryFlags, +) -> Result<&'static mut [T], Error> { + let mut a0 = Syscall::MapMemory as usize; + let mut a1 = phys.map(|p| p.as_ptr() as usize).unwrap_or_default(); + let mut a2 = virt.map(|p| p.as_ptr() as usize).unwrap_or_default(); + let a3 = count * core::mem::size_of::(); + let a4 = flags.bits(); + let a5 = 0; + let a6 = 0; + let a7 = 0; + + unsafe { + core::arch::asm!( + "ecall", + inlateout("a0") a0, + inlateout("a1") a1, + inlateout("a2") a2, + inlateout("a3") a3 => _, + inlateout("a4") a4 => _, + inlateout("a5") a5 => _, + inlateout("a6") a6 => _, + inlateout("a7") a7 => _, + ) + }; + + let result = a0; + + if result == SyscallResult::MemoryRange as usize { + let start = core::ptr::from_exposed_addr_mut::(a1); + let len = a2 / core::mem::size_of::(); + let end = unsafe { start.add(len) }; + Ok(unsafe { core::slice::from_raw_parts_mut(start, len) }) + } else if result == SyscallResult::Error as usize { + Err(a1.into()) + } else { + Err(Error::InternalError) + } +} + +/// Destroy the given memory, returning it to the compiler. +/// +/// Safety: The memory pointed to by `range` should not be used after this +/// function returns, even if this function returns Err(). +pub(crate) unsafe fn unmap_memory(range: *mut [T]) -> Result<(), Error> { + let mut a0 = Syscall::UnmapMemory as usize; + let mut a1 = range.as_mut_ptr() as usize; + let a2 = range.len(); + let a3 = 0; + let a4 = 0; + let a5 = 0; + let a6 = 0; + let a7 = 0; + + unsafe { + core::arch::asm!( + "ecall", + inlateout("a0") a0, + inlateout("a1") a1, + inlateout("a2") a2 => _, + inlateout("a3") a3 => _, + inlateout("a4") a4 => _, + inlateout("a5") a5 => _, + inlateout("a6") a6 => _, + inlateout("a7") a7 => _, + ) + }; + + let result = a0; + + if result == SyscallResult::Ok as usize { + Ok(()) + } else if result == SyscallResult::Error as usize { + Err(a1.into()) + } else { + Err(Error::InternalError) + } +} + +/// Adjust the memory flags for the given range. This can be used to remove flags +/// from a given region in order to harden memory access. Note that flags may +/// only be removed and may never be added. +/// +/// Safety: The memory pointed to by `range` may become inaccessible or have its +/// mutability removed. It is up to the caller to ensure that the flags specified +/// by `new_flags` are upheld, otherwise the program will crash. +pub(crate) unsafe fn update_memory_flags( + range: *mut [T], + new_flags: MemoryFlags, +) -> Result<(), Error> { + let mut a0 = Syscall::UpdateMemoryFlags as usize; + let mut a1 = range.as_mut_ptr() as usize; + let a2 = range.len(); + let a3 = new_flags.bits(); + let a4 = 0; // Process ID is currently None + let a5 = 0; + let a6 = 0; + let a7 = 0; + + unsafe { + core::arch::asm!( + "ecall", + inlateout("a0") a0, + inlateout("a1") a1, + inlateout("a2") a2 => _, + inlateout("a3") a3 => _, + inlateout("a4") a4 => _, + inlateout("a5") a5 => _, + inlateout("a6") a6 => _, + inlateout("a7") a7 => _, + ) + }; + + let result = a0; + + if result == SyscallResult::Ok as usize { + Ok(()) + } else if result == SyscallResult::Error as usize { + Err(a1.into()) + } else { + Err(Error::InternalError) + } +} + +/// Create a thread with a given stack and up to four arguments +pub(crate) fn create_thread( + start: *mut usize, + stack: *mut [u8], + arg0: usize, + arg1: usize, + arg2: usize, + arg3: usize, +) -> Result { + let mut a0 = Syscall::CreateThread as usize; + let mut a1 = start as usize; + let a2 = stack.as_mut_ptr() as usize; + let a3 = stack.len(); + let a4 = arg0; + let a5 = arg1; + let a6 = arg2; + let a7 = arg3; + + unsafe { + core::arch::asm!( + "ecall", + inlateout("a0") a0, + inlateout("a1") a1, + inlateout("a2") a2 => _, + inlateout("a3") a3 => _, + inlateout("a4") a4 => _, + inlateout("a5") a5 => _, + inlateout("a6") a6 => _, + inlateout("a7") a7 => _, + ) + }; + + let result = a0; + + if result == SyscallResult::ThreadId as usize { + Ok(a1.into()) + } else if result == SyscallResult::Error as usize { + Err(a1.into()) + } else { + Err(Error::InternalError) + } +} + +/// Wait for the given thread to terminate and return the exit code from that thread. +pub(crate) fn join_thread(thread_id: ThreadId) -> Result { + let mut a0 = Syscall::JoinThread as usize; + let mut a1 = thread_id.into(); + let a2 = 0; + let a3 = 0; + let a4 = 0; + let a5 = 0; + let a6 = 0; + let a7 = 0; + + unsafe { + core::arch::asm!( + "ecall", + inlateout("a0") a0, + inlateout("a1") a1, + inlateout("a2") a2 => _, + inlateout("a3") a3 => _, + inlateout("a4") a4 => _, + inlateout("a5") a5 => _, + inlateout("a6") a6 => _, + inlateout("a7") a7 => _, + ) + }; + + let result = a0; + + if result == SyscallResult::Scalar1 as usize { + Ok(a1) + } else if result == SyscallResult::Scalar2 as usize { + Ok(a1) + } else if result == SyscallResult::Scalar5 as usize { + Ok(a1) + } else if result == SyscallResult::Error as usize { + Err(a1.into()) + } else { + Err(Error::InternalError) + } +} + +/// Get the current thread's ID +pub(crate) fn thread_id() -> Result { + let mut a0 = Syscall::GetThreadId as usize; + let mut a1 = 0; + let a2 = 0; + let a3 = 0; + let a4 = 0; + let a5 = 0; + let a6 = 0; + let a7 = 0; + + unsafe { + core::arch::asm!( + "ecall", + inlateout("a0") a0, + inlateout("a1") a1, + inlateout("a2") a2 => _, + inlateout("a3") a3 => _, + inlateout("a4") a4 => _, + inlateout("a5") a5 => _, + inlateout("a6") a6 => _, + inlateout("a7") a7 => _, + ) + }; + + let result = a0; + + if result == SyscallResult::ThreadId as usize { + Ok(a1.into()) + } else if result == SyscallResult::Error as usize { + Err(a1.into()) + } else { + Err(Error::InternalError) + } +} + +/// Adjust the given `knob` limit to match the new value `new`. The current value must +/// match the `current` in order for this to take effect. +/// +/// The new value is returned as a result of this call. If the call fails, then the old +/// value is returned. In either case, this function returns successfully. +/// +/// An error is generated if the `knob` is not a valid limit, or if the call +/// would not succeed. +pub(crate) fn adjust_limit(knob: Limits, current: usize, new: usize) -> Result { + let mut a0 = Syscall::JoinThread as usize; + let mut a1 = knob as usize; + let a2 = current; + let a3 = new; + let a4 = 0; + let a5 = 0; + let a6 = 0; + let a7 = 0; + + unsafe { + core::arch::asm!( + "ecall", + inlateout("a0") a0, + inlateout("a1") a1, + inlateout("a2") a2 => _, + inlateout("a3") a3 => _, + inlateout("a4") a4 => _, + inlateout("a5") a5 => _, + inlateout("a6") a6 => _, + inlateout("a7") a7 => _, + ) + }; + + let result = a0; + + if result == SyscallResult::Scalar2 as usize && a1 == knob as usize { + Ok(a2) + } else if result == SyscallResult::Scalar5 as usize && a1 == knob as usize { + Ok(a1) + } else if result == SyscallResult::Error as usize { + Err(a1.into()) + } else { + Err(Error::InternalError) + } +} diff --git a/library/std/src/os/xous/ffi/definitions.rs b/library/std/src/os/xous/ffi/definitions.rs new file mode 100644 index 000000000000..345005bcc78d --- /dev/null +++ b/library/std/src/os/xous/ffi/definitions.rs @@ -0,0 +1,283 @@ +mod memoryflags; +pub(crate) use memoryflags::*; + +#[stable(feature = "rust1", since = "1.0.0")] +/// Indicates a particular syscall number as used by the Xous kernel. +#[derive(Copy, Clone)] +#[repr(usize)] +pub enum Syscall { + MapMemory = 2, + Yield = 3, + UpdateMemoryFlags = 12, + ReceiveMessage = 15, + SendMessage = 16, + Connect = 17, + CreateThread = 18, + UnmapMemory = 19, + ReturnMemory = 20, + TerminateProcess = 22, + TrySendMessage = 24, + TryConnect = 25, + GetThreadId = 32, + JoinThread = 36, + AdjustProcessLimit = 38, + ReturnScalar = 40, +} + +#[stable(feature = "rust1", since = "1.0.0")] +/// Copies of these invocation types here for when we're running +/// in environments without libxous. +#[derive(Copy, Clone)] +#[repr(usize)] +pub enum SyscallResult { + Ok = 0, + Error = 1, + MemoryRange = 3, + ConnectionId = 7, + Message = 9, + ThreadId = 10, + Scalar1 = 14, + Scalar2 = 15, + MemoryReturned = 18, + Scalar5 = 20, +} + +#[stable(feature = "rust1", since = "1.0.0")] +#[derive(Copy, Clone)] +/// A list of all known errors that may be returned by the Xous kernel. +#[repr(usize)] +pub enum Error { + NoError = 0, + BadAlignment = 1, + BadAddress = 2, + OutOfMemory = 3, + MemoryInUse = 4, + InterruptNotFound = 5, + InterruptInUse = 6, + InvalidString = 7, + ServerExists = 8, + ServerNotFound = 9, + ProcessNotFound = 10, + ProcessNotChild = 11, + ProcessTerminated = 12, + Timeout = 13, + InternalError = 14, + ServerQueueFull = 15, + ThreadNotAvailable = 16, + UnhandledSyscall = 17, + InvalidSyscall = 18, + ShareViolation = 19, + InvalidThread = 20, + InvalidPid = 21, + UnknownError = 22, + AccessDenied = 23, + UseBeforeInit = 24, + DoubleFree = 25, + DebugInProgress = 26, + InvalidLimit = 27, +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl From for Error { + fn from(src: usize) -> Self { + match src { + 0 => Self::NoError, + 1 => Self::BadAlignment, + 2 => Self::BadAddress, + 3 => Self::OutOfMemory, + 4 => Self::MemoryInUse, + 5 => Self::InterruptNotFound, + 6 => Self::InterruptInUse, + 7 => Self::InvalidString, + 8 => Self::ServerExists, + 9 => Self::ServerNotFound, + 10 => Self::ProcessNotFound, + 11 => Self::ProcessNotChild, + 12 => Self::ProcessTerminated, + 13 => Self::Timeout, + 14 => Self::InternalError, + 15 => Self::ServerQueueFull, + 16 => Self::ThreadNotAvailable, + 17 => Self::UnhandledSyscall, + 18 => Self::InvalidSyscall, + 19 => Self::ShareViolation, + 20 => Self::InvalidThread, + 21 => Self::InvalidPid, + 23 => Self::AccessDenied, + 24 => Self::UseBeforeInit, + 25 => Self::DoubleFree, + 26 => Self::DebugInProgress, + 27 => Self::InvalidLimit, + 22 | _ => Self::UnknownError, + } + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl From for Error { + fn from(src: i32) -> Self { + let Ok(src) = core::convert::TryInto::::try_into(src) else { + return Self::UnknownError; + }; + src.into() + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "{}", + match self { + Error::NoError => "no error occurred", + Error::BadAlignment => "memory was not properly aligned", + Error::BadAddress => "an invalid address was supplied", + Error::OutOfMemory => "the process or service has run out of memory", + Error::MemoryInUse => "the requested address is in use", + Error::InterruptNotFound => + "the requested interrupt does not exist on this platform", + Error::InterruptInUse => "the requested interrupt is currently in use", + Error::InvalidString => "the specified string was not formatted correctly", + Error::ServerExists => "a server with that address already exists", + Error::ServerNotFound => "the requetsed server could not be found", + Error::ProcessNotFound => "the target process does not exist", + Error::ProcessNotChild => + "the requested operation can only be done on child processes", + Error::ProcessTerminated => "the target process has crashed", + Error::Timeout => "the requested operation timed out", + Error::InternalError => "an internal error occurred", + Error::ServerQueueFull => "the server has too many pending messages", + Error::ThreadNotAvailable => "the specified thread does not exist", + Error::UnhandledSyscall => "the kernel did not recognize that syscall", + Error::InvalidSyscall => "the syscall had incorrect parameters", + Error::ShareViolation => "an attempt was made to share memory twice", + Error::InvalidThread => "tried to resume a thread that was not ready", + Error::InvalidPid => "kernel attempted to use a pid that was not valid", + Error::AccessDenied => "no permission to perform the requested operation", + Error::UseBeforeInit => "attempt to use a service before initialization finished", + Error::DoubleFree => "the requested resource was freed twice", + Error::DebugInProgress => "kernel attempted to activate a thread being debugged", + Error::InvalidLimit => "process attempted to adjust an invalid limit", + Error::UnknownError => "an unknown error occurred", + } + ) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl core::fmt::Debug for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl crate::error::Error for Error {} + +/// Indicates the type of Message that is sent when making a `SendMessage` syscall. +#[derive(Copy, Clone)] +#[repr(usize)] +pub(crate) enum InvokeType { + LendMut = 1, + Lend = 2, + Move = 3, + Scalar = 4, + BlockingScalar = 5, +} + +#[stable(feature = "rust1", since = "1.0.0")] +#[derive(Debug, Copy, Clone)] +/// A representation of a connection to a Xous service. +pub struct Connection(u32); + +#[stable(feature = "rust1", since = "1.0.0")] +impl From for Connection { + fn from(src: u32) -> Connection { + Connection(src) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl TryFrom for Connection { + type Error = core::num::TryFromIntError; + fn try_from(src: usize) -> Result { + Ok(Connection(src.try_into()?)) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl Into for Connection { + fn into(self) -> u32 { + self.0 + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl TryInto for Connection { + type Error = core::num::TryFromIntError; + fn try_into(self) -> Result { + self.0.try_into() + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +#[derive(Debug)] +pub enum ServerAddressError { + InvalidLength, +} + +#[stable(feature = "rust1", since = "1.0.0")] +pub struct ServerAddress([u32; 4]); + +#[stable(feature = "rust1", since = "1.0.0")] +impl TryFrom<&str> for ServerAddress { + type Error = ServerAddressError; + fn try_from(value: &str) -> Result { + let b = value.as_bytes(); + if b.len() == 0 || b.len() > 16 { + return Err(Self::Error::InvalidLength); + } + + let mut this_temp = [0u8; 16]; + for (dest, src) in this_temp.iter_mut().zip(b.iter()) { + *dest = *src; + } + + let mut this = [0u32; 4]; + for (dest, src) in this.iter_mut().zip(this_temp.chunks_exact(4)) { + *dest = u32::from_le_bytes(src.try_into().unwrap()); + } + Ok(ServerAddress(this)) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl Into<[u32; 4]> for ServerAddress { + fn into(self) -> [u32; 4] { + self.0 + } +} + +#[derive(Debug, Copy, Clone)] +pub(crate) struct ThreadId(usize); + +impl From for ThreadId { + fn from(src: usize) -> ThreadId { + ThreadId(src) + } +} + +impl Into for ThreadId { + fn into(self) -> usize { + self.0 + } +} + +#[derive(Copy, Clone)] +#[repr(usize)] +/// Limits that can be passed to `AdjustLimit` +pub(crate) enum Limits { + HeapMaximum = 1, + HeapSize = 2, +} diff --git a/library/std/src/os/xous/ffi/definitions/memoryflags.rs b/library/std/src/os/xous/ffi/definitions/memoryflags.rs new file mode 100644 index 000000000000..af9de3cbff29 --- /dev/null +++ b/library/std/src/os/xous/ffi/definitions/memoryflags.rs @@ -0,0 +1,176 @@ +/// Flags to be passed to the MapMemory struct. +/// Note that it is an error to have memory be +/// writable and not readable. +#[derive(Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash, Debug)] +#[stable(feature = "rust1", since = "1.0.0")] +pub struct MemoryFlags { + bits: usize, +} + +impl MemoryFlags { + /// Free this memory + #[stable(feature = "rust1", since = "1.0.0")] + pub const FREE: Self = Self { bits: 0b0000_0000 }; + + /// Immediately allocate this memory. Otherwise it will + /// be demand-paged. This is implicitly set when `phys` + /// is not 0. + #[stable(feature = "rust1", since = "1.0.0")] + pub const RESERVE: Self = Self { bits: 0b0000_0001 }; + + /// Allow the CPU to read from this page. + #[stable(feature = "rust1", since = "1.0.0")] + pub const R: Self = Self { bits: 0b0000_0010 }; + + /// Allow the CPU to write to this page. + #[stable(feature = "rust1", since = "1.0.0")] + pub const W: Self = Self { bits: 0b0000_0100 }; + + /// Allow the CPU to execute from this page. + #[stable(feature = "rust1", since = "1.0.0")] + pub const X: Self = Self { bits: 0b0000_1000 }; + + #[stable(feature = "rust1", since = "1.0.0")] + pub fn bits(&self) -> usize { + self.bits + } + + #[stable(feature = "rust1", since = "1.0.0")] + pub fn from_bits(raw: usize) -> Option { + if raw > 16 { None } else { Some(MemoryFlags { bits: raw }) } + } + + #[stable(feature = "rust1", since = "1.0.0")] + pub fn is_empty(&self) -> bool { + self.bits == 0 + } + + #[stable(feature = "rust1", since = "1.0.0")] + pub fn empty() -> MemoryFlags { + MemoryFlags { bits: 0 } + } + + #[stable(feature = "rust1", since = "1.0.0")] + pub fn all() -> MemoryFlags { + MemoryFlags { bits: 15 } + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl core::fmt::Binary for MemoryFlags { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Binary::fmt(&self.bits, f) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl core::fmt::Octal for MemoryFlags { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Octal::fmt(&self.bits, f) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl core::fmt::LowerHex for MemoryFlags { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::LowerHex::fmt(&self.bits, f) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl core::fmt::UpperHex for MemoryFlags { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::UpperHex::fmt(&self.bits, f) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl core::ops::BitOr for MemoryFlags { + type Output = Self; + + /// Returns the union of the two sets of flags. + #[inline] + fn bitor(self, other: MemoryFlags) -> Self { + Self { bits: self.bits | other.bits } + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl core::ops::BitOrAssign for MemoryFlags { + /// Adds the set of flags. + #[inline] + fn bitor_assign(&mut self, other: Self) { + self.bits |= other.bits; + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl core::ops::BitXor for MemoryFlags { + type Output = Self; + + /// Returns the left flags, but with all the right flags toggled. + #[inline] + fn bitxor(self, other: Self) -> Self { + Self { bits: self.bits ^ other.bits } + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl core::ops::BitXorAssign for MemoryFlags { + /// Toggles the set of flags. + #[inline] + fn bitxor_assign(&mut self, other: Self) { + self.bits ^= other.bits; + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl core::ops::BitAnd for MemoryFlags { + type Output = Self; + + /// Returns the intersection between the two sets of flags. + #[inline] + fn bitand(self, other: Self) -> Self { + Self { bits: self.bits & other.bits } + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl core::ops::BitAndAssign for MemoryFlags { + /// Disables all flags disabled in the set. + #[inline] + fn bitand_assign(&mut self, other: Self) { + self.bits &= other.bits; + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl core::ops::Sub for MemoryFlags { + type Output = Self; + + /// Returns the set difference of the two sets of flags. + #[inline] + fn sub(self, other: Self) -> Self { + Self { bits: self.bits & !other.bits } + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl core::ops::SubAssign for MemoryFlags { + /// Disables all flags enabled in the set. + #[inline] + fn sub_assign(&mut self, other: Self) { + self.bits &= !other.bits; + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl core::ops::Not for MemoryFlags { + type Output = Self; + + /// Returns the complement of this set of flags. + #[inline] + fn not(self) -> Self { + Self { bits: !self.bits } & MemoryFlags { bits: 15 } + } +} diff --git a/library/std/src/os/xous/mod.rs b/library/std/src/os/xous/mod.rs new file mode 100644 index 000000000000..3d69bc2e04e5 --- /dev/null +++ b/library/std/src/os/xous/mod.rs @@ -0,0 +1,14 @@ +#![stable(feature = "rust1", since = "1.0.0")] +#![doc(cfg(target_os = "xous"))] + +pub mod ffi; + +/// A prelude for conveniently writing platform-specific code. +/// +/// Includes all extension traits, and some important type definitions. +#[stable(feature = "rust1", since = "1.0.0")] +pub mod prelude { + #[doc(no_inline)] + #[stable(feature = "rust1", since = "1.0.0")] + pub use super::ffi::{OsStrExt, OsStringExt}; +} From 9afc1e958b36de2d0b9c2a5dddca47f8cde9d112 Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Fri, 4 Nov 2022 14:18:02 +0800 Subject: [PATCH 005/111] std: xous: the basics of `os` Add the basics to get the operating system running, including how to exit the operating system. Since Xous has no libc, there is no default entrypoint. Add a `_start` entrypoint to the system-specific os module. Signed-off-by: Sean Cross --- library/std/src/sys/xous/mod.rs | 1 - library/std/src/sys/xous/os.rs | 147 ++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 library/std/src/sys/xous/os.rs diff --git a/library/std/src/sys/xous/mod.rs b/library/std/src/sys/xous/mod.rs index 836647c81ba7..92089e8384c4 100644 --- a/library/std/src/sys/xous/mod.rs +++ b/library/std/src/sys/xous/mod.rs @@ -18,7 +18,6 @@ pub mod locks; pub mod net; #[path = "../unsupported/once.rs"] pub mod once; -#[path = "../unsupported/os.rs"] pub mod os; #[path = "../unix/os_str.rs"] pub mod os_str; diff --git a/library/std/src/sys/xous/os.rs b/library/std/src/sys/xous/os.rs new file mode 100644 index 000000000000..3d19fa4b31af --- /dev/null +++ b/library/std/src/sys/xous/os.rs @@ -0,0 +1,147 @@ +use super::unsupported; +use crate::error::Error as StdError; +use crate::ffi::{OsStr, OsString}; +use crate::fmt; +use crate::io; +use crate::marker::PhantomData; +use crate::os::xous::ffi::Error as XousError; +use crate::path::{self, PathBuf}; + +#[cfg(not(test))] +mod c_compat { + use crate::os::xous::ffi::exit; + extern "C" { + fn main() -> u32; + } + + #[no_mangle] + pub extern "C" fn abort() { + exit(1); + } + + #[no_mangle] + pub extern "C" fn _start() { + exit(unsafe { main() }); + } + + // This function is needed by the panic runtime. The symbol is named in + // pre-link args for the target specification, so keep that in sync. + #[no_mangle] + // NB. used by both libunwind and libpanic_abort + pub extern "C" fn __rust_abort() -> ! { + exit(101); + } +} + +pub fn errno() -> i32 { + 0 +} + +pub fn error_string(errno: i32) -> String { + Into::::into(errno).to_string() +} + +pub fn getcwd() -> io::Result { + unsupported() +} + +pub fn chdir(_: &path::Path) -> io::Result<()> { + unsupported() +} + +pub struct SplitPaths<'a>(!, PhantomData<&'a ()>); + +pub fn split_paths(_unparsed: &OsStr) -> SplitPaths<'_> { + panic!("unsupported") +} + +impl<'a> Iterator for SplitPaths<'a> { + type Item = PathBuf; + fn next(&mut self) -> Option { + self.0 + } +} + +#[derive(Debug)] +pub struct JoinPathsError; + +pub fn join_paths(_paths: I) -> Result +where + I: Iterator, + T: AsRef, +{ + Err(JoinPathsError) +} + +impl fmt::Display for JoinPathsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + "not supported on this platform yet".fmt(f) + } +} + +impl StdError for JoinPathsError { + #[allow(deprecated)] + fn description(&self) -> &str { + "not supported on this platform yet" + } +} + +pub fn current_exe() -> io::Result { + unsupported() +} + +pub struct Env(!); + +impl Env { + // FIXME(https://github.com/rust-lang/rust/issues/114583): Remove this when ::fmt matches ::fmt. + pub fn str_debug(&self) -> impl fmt::Debug + '_ { + let Self(inner) = self; + match *inner {} + } +} + +impl fmt::Debug for Env { + fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self(inner) = self; + match *inner {} + } +} + +impl Iterator for Env { + type Item = (OsString, OsString); + fn next(&mut self) -> Option<(OsString, OsString)> { + self.0 + } +} + +pub fn env() -> Env { + panic!("not supported on this platform") +} + +pub fn getenv(_: &OsStr) -> Option { + None +} + +pub fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> { + Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot set env vars on this platform")) +} + +pub fn unsetenv(_: &OsStr) -> io::Result<()> { + Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot unset env vars on this platform")) +} + +pub fn temp_dir() -> PathBuf { + panic!("no filesystem on this platform") +} + +pub fn home_dir() -> Option { + None +} + +pub fn exit(code: i32) -> ! { + crate::os::xous::ffi::exit(code as u32); +} + +pub fn getpid() -> u32 { + panic!("no pids on this platform") +} From 778e8038a25c06801339cc7d57dfb7385ab89399 Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Sun, 22 Jan 2023 17:56:38 +0800 Subject: [PATCH 006/111] std: net: skip tests on xous Network functionality is not yet ready for merging. Signed-off-by: Sean Cross --- library/std/src/net/tcp.rs | 2 +- library/std/src/net/udp.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/std/src/net/tcp.rs b/library/std/src/net/tcp.rs index 32fd54c8e751..9667d5f920e4 100644 --- a/library/std/src/net/tcp.rs +++ b/library/std/src/net/tcp.rs @@ -1,6 +1,6 @@ #![deny(unsafe_op_in_unsafe_fn)] -#[cfg(all(test, not(target_os = "emscripten")))] +#[cfg(all(test, not(any(target_os = "emscripten", target_os = "xous"))))] mod tests; use crate::io::prelude::*; diff --git a/library/std/src/net/udp.rs b/library/std/src/net/udp.rs index 5ca4ed832f34..227e418b7099 100644 --- a/library/std/src/net/udp.rs +++ b/library/std/src/net/udp.rs @@ -1,4 +1,4 @@ -#[cfg(all(test, not(any(target_os = "emscripten", target_env = "sgx"))))] +#[cfg(all(test, not(any(target_os = "emscripten", target_env = "sgx", target_os = "xous"))))] mod tests; use crate::fmt; From 0b800577d141759b7b4b65ad3c1c776364f35a32 Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Sun, 22 Jan 2023 17:58:22 +0800 Subject: [PATCH 007/111] std: fs: skip fs tests on xous The xous filesystem support is not yet ready for merging. Signed-off-by: Sean Cross --- library/std/src/fs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index a7e65305386e..370cec7734a6 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -8,7 +8,7 @@ #![stable(feature = "rust1", since = "1.0.0")] #![deny(unsafe_op_in_unsafe_fn)] -#[cfg(all(test, not(any(target_os = "emscripten", target_env = "sgx"))))] +#[cfg(all(test, not(any(target_os = "emscripten", target_env = "sgx", target_os = "xous"))))] mod tests; use crate::ffi::OsString; From 112d101f880bafa2060a5bfa2b65563b8585624b Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Sun, 22 Jan 2023 17:58:55 +0800 Subject: [PATCH 008/111] std: process: skip tests on xous Xous does not yet support spawning processes. Signed-off-by: Sean Cross --- library/std/src/process.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/process.rs b/library/std/src/process.rs index f54d59341750..e28a42a27fc5 100644 --- a/library/std/src/process.rs +++ b/library/std/src/process.rs @@ -101,7 +101,7 @@ #![stable(feature = "process", since = "1.0.0")] #![deny(unsafe_op_in_unsafe_fn)] -#[cfg(all(test, not(any(target_os = "emscripten", target_env = "sgx"))))] +#[cfg(all(test, not(any(target_os = "emscripten", target_env = "sgx", target_os = "xous"))))] mod tests; use crate::io::prelude::*; From dffc86472e2dc5139850c8fd2ae18282857016a1 Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Mon, 31 Jul 2023 16:23:47 +0800 Subject: [PATCH 009/111] bootstrap: builder: don't add origin paths on xous Don't add the origin rpath when calling the linker. This is unnecessary on Xous since there is no dynamic linker, and Xous can use an ordinary bare-metal linker with no rpath options. As part of this patch, the logic was inverted so that the "else" clause contains everything except "windows" and "xous". Signed-off-by: Sean Cross --- src/bootstrap/builder.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs index b36661928533..9a37ed42cf20 100644 --- a/src/bootstrap/builder.rs +++ b/src/bootstrap/builder.rs @@ -1635,7 +1635,10 @@ impl<'a> Builder<'a> { // flesh out rpath support more fully in the future. rustflags.arg("-Zosx-rpath-install-name"); Some(format!("-Wl,-rpath,@loader_path/../{libdir}")) - } else if !target.contains("windows") && !target.contains("aix") { + } else if !target.contains("windows") + && !target.contains("aix") + && !target.contains("xous") + { rustflags.arg("-Clink-args=-Wl,-z,origin"); Some(format!("-Wl,-rpath,$ORIGIN/../{libdir}")) } else { From 823cba9cc8015772594344ce2aab6b70000934c9 Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Mon, 31 Jul 2023 16:22:27 +0800 Subject: [PATCH 010/111] std: xous: add alloc support Basic alloc support on Xous is supported by the `dlmalloc` crate. This necessitates bumping the dlmalloc version to 0.2.4. Signed-off-by: Sean Cross --- library/std/Cargo.toml | 4 +- library/std/src/sys/xous/alloc.rs | 62 +++++++++++++++++++++++++++++++ library/std/src/sys/xous/mod.rs | 1 - 3 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 library/std/src/sys/xous/alloc.rs diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index 33c9c6e63c15..e2b97f983d22 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -36,8 +36,8 @@ object = { version = "0.32.0", default-features = false, optional = true, featur rand = { version = "0.8.5", default-features = false, features = ["alloc"] } rand_xorshift = "0.3.0" -[target.'cfg(any(all(target_family = "wasm", target_os = "unknown"), all(target_vendor = "fortanix", target_env = "sgx")))'.dependencies] -dlmalloc = { version = "0.2.3", features = ['rustc-dep-of-std'] } +[target.'cfg(any(all(target_family = "wasm", target_os = "unknown"), target_os = "xous", all(target_vendor = "fortanix", target_env = "sgx")))'.dependencies] +dlmalloc = { version = "0.2.4", features = ['rustc-dep-of-std'] } [target.x86_64-fortanix-unknown-sgx.dependencies] fortanix-sgx-abi = { version = "0.5.0", features = ['rustc-dep-of-std'], public = true } diff --git a/library/std/src/sys/xous/alloc.rs b/library/std/src/sys/xous/alloc.rs new file mode 100644 index 000000000000..b3a3e691e0d0 --- /dev/null +++ b/library/std/src/sys/xous/alloc.rs @@ -0,0 +1,62 @@ +use crate::alloc::{GlobalAlloc, Layout, System}; + +static mut DLMALLOC: dlmalloc::Dlmalloc = dlmalloc::Dlmalloc::new(); + +#[stable(feature = "alloc_system_type", since = "1.28.0")] +unsafe impl GlobalAlloc for System { + #[inline] + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + // SAFETY: DLMALLOC access is guaranteed to be safe because the lock gives us unique and non-reentrant access. + // Calling malloc() is safe because preconditions on this function match the trait method preconditions. + let _lock = lock::lock(); + unsafe { DLMALLOC.malloc(layout.size(), layout.align()) } + } + + #[inline] + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + // SAFETY: DLMALLOC access is guaranteed to be safe because the lock gives us unique and non-reentrant access. + // Calling calloc() is safe because preconditions on this function match the trait method preconditions. + let _lock = lock::lock(); + unsafe { DLMALLOC.calloc(layout.size(), layout.align()) } + } + + #[inline] + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + // SAFETY: DLMALLOC access is guaranteed to be safe because the lock gives us unique and non-reentrant access. + // Calling free() is safe because preconditions on this function match the trait method preconditions. + let _lock = lock::lock(); + unsafe { DLMALLOC.free(ptr, layout.size(), layout.align()) } + } + + #[inline] + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + // SAFETY: DLMALLOC access is guaranteed to be safe because the lock gives us unique and non-reentrant access. + // Calling realloc() is safe because preconditions on this function match the trait method preconditions. + let _lock = lock::lock(); + unsafe { DLMALLOC.realloc(ptr, layout.size(), layout.align(), new_size) } + } +} + +mod lock { + use crate::sync::atomic::{AtomicI32, Ordering::SeqCst}; + + static LOCKED: AtomicI32 = AtomicI32::new(0); + + pub struct DropLock; + + pub fn lock() -> DropLock { + loop { + if LOCKED.swap(1, SeqCst) == 0 { + return DropLock; + } + crate::os::xous::ffi::do_yield(); + } + } + + impl Drop for DropLock { + fn drop(&mut self) { + let r = LOCKED.swap(0, SeqCst); + debug_assert_eq!(r, 1); + } + } +} diff --git a/library/std/src/sys/xous/mod.rs b/library/std/src/sys/xous/mod.rs index 92089e8384c4..3c9eed101276 100644 --- a/library/std/src/sys/xous/mod.rs +++ b/library/std/src/sys/xous/mod.rs @@ -1,6 +1,5 @@ #![deny(unsafe_op_in_unsafe_fn)] -#[path = "../unsupported/alloc.rs"] pub mod alloc; #[path = "../unsupported/args.rs"] pub mod args; From dfff5bf62f466fbf6f17f20dc847e8f98b943535 Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Mon, 7 Nov 2022 19:04:47 +0800 Subject: [PATCH 011/111] panic_abort: call __rust_abort on xous Xous does not properly handle invalid instructions. Instead, call `__rust_abort` and exit normally. Signed-off-by: Sean Cross --- library/panic_abort/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/panic_abort/src/lib.rs b/library/panic_abort/src/lib.rs index 76b359196585..2f8c2f770937 100644 --- a/library/panic_abort/src/lib.rs +++ b/library/panic_abort/src/lib.rs @@ -43,7 +43,8 @@ pub unsafe fn __rust_start_panic(_payload: &mut dyn BoxMeUp) -> u32 { libc::abort(); } } else if #[cfg(any(target_os = "hermit", - all(target_vendor = "fortanix", target_env = "sgx") + all(target_vendor = "fortanix", target_env = "sgx"), + target_os = "xous" ))] { unsafe fn abort() -> ! { // call std::sys::abort_internal From 10dad67f89a320c315c6c758dffe0bee610cbbfa Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Mon, 7 Nov 2022 11:06:38 +0800 Subject: [PATCH 012/111] std: xous: add services support Xous has a concept of `services` that provide various features. Processes may connect to these services by name or by address. Most services require a name server in order to connect. Add a file with the most common services, and provide a way to connect to a service by querying the name server. Signed-off-by: Sean Cross --- library/std/src/os/xous/mod.rs | 3 + library/std/src/os/xous/services.rs | 132 ++++++++++++++++++ library/std/src/os/xous/services/log.rs | 63 +++++++++ library/std/src/os/xous/services/systime.rs | 28 ++++ library/std/src/os/xous/services/ticktimer.rs | 38 +++++ 5 files changed, 264 insertions(+) create mode 100644 library/std/src/os/xous/services.rs create mode 100644 library/std/src/os/xous/services/log.rs create mode 100644 library/std/src/os/xous/services/systime.rs create mode 100644 library/std/src/os/xous/services/ticktimer.rs diff --git a/library/std/src/os/xous/mod.rs b/library/std/src/os/xous/mod.rs index 3d69bc2e04e5..153694a89a78 100644 --- a/library/std/src/os/xous/mod.rs +++ b/library/std/src/os/xous/mod.rs @@ -3,6 +3,9 @@ pub mod ffi; +#[stable(feature = "rust1", since = "1.0.0")] +pub mod services; + /// A prelude for conveniently writing platform-specific code. /// /// Includes all extension traits, and some important type definitions. diff --git a/library/std/src/os/xous/services.rs b/library/std/src/os/xous/services.rs new file mode 100644 index 000000000000..5c219f1fbb95 --- /dev/null +++ b/library/std/src/os/xous/services.rs @@ -0,0 +1,132 @@ +use crate::os::xous::ffi::Connection; +use core::sync::atomic::{AtomicU32, Ordering}; + +mod log; +pub(crate) use log::*; + +mod systime; +pub(crate) use systime::*; + +mod ticktimer; +pub(crate) use ticktimer::*; + +mod ns { + const NAME_MAX_LENGTH: usize = 64; + use crate::os::xous::ffi::{lend_mut, Connection}; + // By making this repr(C), the layout of this struct becomes well-defined + // and no longer shifts around. + // By marking it as `align(4096)` we define that it will be page-aligned, + // meaning it can be sent between processes. We make sure to pad out the + // entire struct so that memory isn't leaked to the name server. + #[repr(C, align(4096))] + struct ConnectRequest { + data: [u8; 4096], + } + + impl ConnectRequest { + pub fn new(name: &str) -> Self { + let mut cr = ConnectRequest { data: [0u8; 4096] }; + let name_bytes = name.as_bytes(); + + // Copy the string into our backing store. + for (&src_byte, dest_byte) in name_bytes.iter().zip(&mut cr.data[0..NAME_MAX_LENGTH]) { + *dest_byte = src_byte; + } + + // Set the string length to the length of the passed-in String, + // or the maximum possible length. Which ever is smaller. + for (&src_byte, dest_byte) in (name.len().min(NAME_MAX_LENGTH) as u32) + .to_le_bytes() + .iter() + .zip(&mut cr.data[NAME_MAX_LENGTH..]) + { + *dest_byte = src_byte; + } + cr + } + } + + pub fn connect_with_name_impl(name: &str, blocking: bool) -> Option { + let mut request = ConnectRequest::new(name); + let opcode = if blocking { + 6 /* BlockingConnect */ + } else { + 7 /* TryConnect */ + }; + let cid = if blocking { super::name_server() } else { super::try_name_server()? }; + + lend_mut(cid, opcode, &mut request.data, 0, name.len().min(NAME_MAX_LENGTH)) + .expect("unable to perform lookup"); + + // Read the result code back from the nameserver + let result = u32::from_le_bytes(request.data[0..4].try_into().unwrap()); + if result == 0 { + // If the result was successful, then the CID is stored in the next 4 bytes + Some(u32::from_le_bytes(request.data[4..8].try_into().unwrap()).into()) + } else { + None + } + } + + pub fn connect_with_name(name: &str) -> Option { + connect_with_name_impl(name, true) + } + + pub fn try_connect_with_name(name: &str) -> Option { + connect_with_name_impl(name, false) + } +} + +/// Attempt to connect to a server by name. If the server does not exist, this will +/// block until the server is created. +/// +/// Note that this is different from connecting to a server by address. Server +/// addresses are always 16 bytes long, whereas server names are arbitrary-length +/// strings up to 64 bytes in length. +#[stable(feature = "rust1", since = "1.0.0")] +pub fn connect(name: &str) -> Option { + ns::connect_with_name(name) +} + +/// Attempt to connect to a server by name. If the server does not exist, this will +/// immediately return `None`. +/// +/// Note that this is different from connecting to a server by address. Server +/// addresses are always 16 bytes long, whereas server names are arbitrary-length +/// strings. +#[stable(feature = "rust1", since = "1.0.0")] +pub fn try_connect(name: &str) -> Option { + ns::try_connect_with_name(name) +} + +static NAME_SERVER_CONNECTION: AtomicU32 = AtomicU32::new(0); + +/// Return a `Connection` to the name server. If the name server has not been started, +/// then this call will block until the name server has been started. The `Connection` +/// will be shared among all connections in a process, so it is safe to call this +/// multiple times. +pub(crate) fn name_server() -> Connection { + let cid = NAME_SERVER_CONNECTION.load(Ordering::Relaxed); + if cid != 0 { + return cid.into(); + } + + let cid = crate::os::xous::ffi::connect("xous-name-server".try_into().unwrap()).unwrap(); + NAME_SERVER_CONNECTION.store(cid.into(), Ordering::Relaxed); + cid +} + +fn try_name_server() -> Option { + let cid = NAME_SERVER_CONNECTION.load(Ordering::Relaxed); + if cid != 0 { + return Some(cid.into()); + } + + if let Ok(Some(cid)) = crate::os::xous::ffi::try_connect("xous-name-server".try_into().unwrap()) + { + NAME_SERVER_CONNECTION.store(cid.into(), Ordering::Relaxed); + Some(cid) + } else { + None + } +} diff --git a/library/std/src/os/xous/services/log.rs b/library/std/src/os/xous/services/log.rs new file mode 100644 index 000000000000..e6bae929eac0 --- /dev/null +++ b/library/std/src/os/xous/services/log.rs @@ -0,0 +1,63 @@ +use crate::os::xous::ffi::Connection; +use core::sync::atomic::{AtomicU32, Ordering}; + +/// Group `usize` bytes into a `usize` and return it, beginning +/// from `offset` * sizeof(usize) bytes from the start. For example, +/// `group_or_null([1,2,3,4,5,6,7,8], 1)` on a 32-bit system will +/// return a usize with 5678 packed into it. +fn group_or_null(data: &[u8], offset: usize) -> usize { + let start = offset * core::mem::size_of::(); + let mut out_array = [0u8; core::mem::size_of::()]; + if start < data.len() { + for (dest, src) in out_array.iter_mut().zip(&data[start..]) { + *dest = *src; + } + } + usize::from_le_bytes(out_array) +} + +pub(crate) enum LogScalar<'a> { + /// A panic occurred, and a panic log is forthcoming + BeginPanic, + + /// Some number of bytes will be appended to the log message + AppendPanicMessage(&'a [u8]), +} + +impl<'a> Into<[usize; 5]> for LogScalar<'a> { + fn into(self) -> [usize; 5] { + match self { + LogScalar::BeginPanic => [1000, 0, 0, 0, 0], + LogScalar::AppendPanicMessage(c) => + // Text is grouped into 4x `usize` words. The id is 1100 plus + // the number of characters in this message. + // Ignore errors since we're already panicking. + { + [ + 1100 + c.len(), + group_or_null(&c, 0), + group_or_null(&c, 1), + group_or_null(&c, 2), + group_or_null(&c, 3), + ] + } + } + } +} + +/// Return a `Connection` to the log server, which is used for printing messages to +/// the console and reporting panics. If the log server has not yet started, this +/// will block until the server is running. It is safe to call this multiple times, +/// because the address is shared among all threads in a process. +pub(crate) fn log_server() -> Connection { + static LOG_SERVER_CONNECTION: AtomicU32 = AtomicU32::new(0); + + let cid = LOG_SERVER_CONNECTION.load(Ordering::Relaxed); + if cid != 0 { + return cid.into(); + } + + let cid = crate::os::xous::ffi::connect("xous-log-server ".try_into().unwrap()).unwrap(); + LOG_SERVER_CONNECTION.store(cid.into(), Ordering::Relaxed); + cid +} diff --git a/library/std/src/os/xous/services/systime.rs b/library/std/src/os/xous/services/systime.rs new file mode 100644 index 000000000000..bbb875c69426 --- /dev/null +++ b/library/std/src/os/xous/services/systime.rs @@ -0,0 +1,28 @@ +use crate::os::xous::ffi::{connect, Connection}; +use core::sync::atomic::{AtomicU32, Ordering}; + +pub(crate) enum SystimeScalar { + GetUtcTimeMs, +} + +impl Into<[usize; 5]> for SystimeScalar { + fn into(self) -> [usize; 5] { + match self { + SystimeScalar::GetUtcTimeMs => [3, 0, 0, 0, 0], + } + } +} + +/// Return a `Connection` to the systime server. This server is used for reporting the +/// realtime clock. +pub(crate) fn systime_server() -> Connection { + static SYSTIME_SERVER_CONNECTION: AtomicU32 = AtomicU32::new(0); + let cid = SYSTIME_SERVER_CONNECTION.load(Ordering::Relaxed); + if cid != 0 { + return cid.into(); + } + + let cid = connect("timeserverpublic".try_into().unwrap()).unwrap(); + SYSTIME_SERVER_CONNECTION.store(cid.into(), Ordering::Relaxed); + cid +} diff --git a/library/std/src/os/xous/services/ticktimer.rs b/library/std/src/os/xous/services/ticktimer.rs new file mode 100644 index 000000000000..447f8f9304a1 --- /dev/null +++ b/library/std/src/os/xous/services/ticktimer.rs @@ -0,0 +1,38 @@ +use crate::os::xous::ffi::Connection; +use core::sync::atomic::{AtomicU32, Ordering}; + +pub(crate) enum TicktimerScalar { + ElapsedMs, + SleepMs(usize), + LockMutex(usize /* cookie */), + UnlockMutex(usize /* cookie */), + WaitForCondition(usize /* cookie */, usize /* timeout (ms) */), + NotifyCondition(usize /* cookie */, usize /* count */), +} + +impl Into<[usize; 5]> for TicktimerScalar { + fn into(self) -> [usize; 5] { + match self { + TicktimerScalar::ElapsedMs => [0, 0, 0, 0, 0], + TicktimerScalar::SleepMs(msecs) => [1, msecs, 0, 0, 0], + TicktimerScalar::LockMutex(cookie) => [6, cookie, 0, 0, 0], + TicktimerScalar::UnlockMutex(cookie) => [7, cookie, 0, 0, 0], + TicktimerScalar::WaitForCondition(cookie, timeout_ms) => [8, cookie, timeout_ms, 0, 0], + TicktimerScalar::NotifyCondition(cookie, count) => [9, cookie, count, 0, 0], + } + } +} + +/// Return a `Connection` to the ticktimer server. This server is used for synchronization +/// primitives such as sleep, Mutex, and Condvar. +pub(crate) fn ticktimer_server() -> Connection { + static TICKTIMER_SERVER_CONNECTION: AtomicU32 = AtomicU32::new(0); + let cid = TICKTIMER_SERVER_CONNECTION.load(Ordering::Relaxed); + if cid != 0 { + return cid.into(); + } + + let cid = crate::os::xous::ffi::connect("ticktimer-server".try_into().unwrap()).unwrap(); + TICKTIMER_SERVER_CONNECTION.store(cid.into(), Ordering::Relaxed); + cid +} From 4af7d2cf8d452d72d836ef316c01ceaebd6387bf Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Mon, 7 Nov 2022 11:10:42 +0800 Subject: [PATCH 013/111] std: xous: add output support for stdio Add support for stdout. This enables basic console printing via `println!()`. Output is written to the log server. Signed-off-by: Sean Cross --- library/std/src/sys/xous/mod.rs | 1 - library/std/src/sys/xous/stdio.rs | 131 ++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 library/std/src/sys/xous/stdio.rs diff --git a/library/std/src/sys/xous/mod.rs b/library/std/src/sys/xous/mod.rs index 3c9eed101276..6a5b9fdd08d6 100644 --- a/library/std/src/sys/xous/mod.rs +++ b/library/std/src/sys/xous/mod.rs @@ -26,7 +26,6 @@ pub mod path; pub mod pipe; #[path = "../unsupported/process.rs"] pub mod process; -#[path = "../unsupported/stdio.rs"] pub mod stdio; #[path = "../unsupported/thread.rs"] pub mod thread; diff --git a/library/std/src/sys/xous/stdio.rs b/library/std/src/sys/xous/stdio.rs new file mode 100644 index 000000000000..2ac694641ba9 --- /dev/null +++ b/library/std/src/sys/xous/stdio.rs @@ -0,0 +1,131 @@ +use crate::io; + +pub struct Stdin; +pub struct Stdout {} +pub struct Stderr; + +use crate::os::xous::ffi::{lend, try_lend, try_scalar, Connection}; +use crate::os::xous::services::{log_server, try_connect, LogScalar}; + +impl Stdin { + pub const fn new() -> Stdin { + Stdin + } +} + +impl io::Read for Stdin { + fn read(&mut self, _buf: &mut [u8]) -> io::Result { + Ok(0) + } +} + +impl Stdout { + pub const fn new() -> Stdout { + Stdout {} + } +} + +impl io::Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + #[repr(align(4096))] + struct LendBuffer([u8; 4096]); + let mut lend_buffer = LendBuffer([0u8; 4096]); + let connection = log_server(); + for chunk in buf.chunks(lend_buffer.0.len()) { + for (dest, src) in lend_buffer.0.iter_mut().zip(chunk) { + *dest = *src; + } + lend(connection, 1, &lend_buffer.0, 0, chunk.len()).unwrap(); + } + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Stderr { + pub const fn new() -> Stderr { + Stderr + } +} + +impl io::Write for Stderr { + fn write(&mut self, buf: &[u8]) -> io::Result { + #[repr(align(4096))] + struct LendBuffer([u8; 4096]); + let mut lend_buffer = LendBuffer([0u8; 4096]); + let connection = log_server(); + for chunk in buf.chunks(lend_buffer.0.len()) { + for (dest, src) in lend_buffer.0.iter_mut().zip(chunk) { + *dest = *src; + } + lend(connection, 1, &lend_buffer.0, 0, chunk.len()).unwrap(); + } + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub const STDIN_BUF_SIZE: usize = 0; + +pub fn is_ebadf(_err: &io::Error) -> bool { + true +} + +#[derive(Copy, Clone)] +pub struct PanicWriter { + log: Connection, + gfx: Option, +} + +impl io::Write for PanicWriter { + fn write(&mut self, s: &[u8]) -> core::result::Result { + for c in s.chunks(core::mem::size_of::() * 4) { + // Text is grouped into 4x `usize` words. The id is 1100 plus + // the number of characters in this message. + // Ignore errors since we're already panicking. + try_scalar(self.log, LogScalar::AppendPanicMessage(&c).into()).ok(); + } + + // Serialize the text to the graphics panic handler, only if we were able + // to acquire a connection to it. Text length is encoded in the `valid` field, + // the data itself in the buffer. Typically several messages are require to + // fully transmit the entire panic message. + if let Some(gfx) = self.gfx { + #[repr(C, align(4096))] + struct Request([u8; 4096]); + let mut request = Request([0u8; 4096]); + for (&s, d) in s.iter().zip(request.0.iter_mut()) { + *d = s; + } + try_lend(gfx, 0 /* AppendPanicText */, &request.0, 0, s.len()).ok(); + } + Ok(s.len()) + } + + // Tests show that this does not seem to be reliably called at the end of a panic + // print, so, we can't rely on this to e.g. trigger a graphics update. + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub fn panic_output() -> Option { + // Generally this won't fail because every server has already connected, so + // this is likely to succeed. + let log = log_server(); + + // Send the "We're panicking" message (1000). + try_scalar(log, LogScalar::BeginPanic.into()).ok(); + + // This is will fail in the case that the connection table is full, or if the + // graphics server is not running. Most servers do not already have this connection. + let gfx = try_connect("panic-to-screen!"); + + Some(PanicWriter { log, gfx }) +} From efa470d0ae6707facf608500c2f7417312dd0f29 Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Mon, 31 Jul 2023 17:12:31 +0800 Subject: [PATCH 014/111] std: xous: add support for time Add support for determining the current time. This connects to the ticktimer server in order to get the system uptime. Signed-off-by: Sean Cross --- library/std/src/sys/xous/mod.rs | 1 - library/std/src/sys/xous/time.rs | 57 ++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 library/std/src/sys/xous/time.rs diff --git a/library/std/src/sys/xous/mod.rs b/library/std/src/sys/xous/mod.rs index 6a5b9fdd08d6..1b59cc632c64 100644 --- a/library/std/src/sys/xous/mod.rs +++ b/library/std/src/sys/xous/mod.rs @@ -33,7 +33,6 @@ pub mod thread; pub mod thread_local_key; #[path = "../unsupported/thread_parking.rs"] pub mod thread_parking; -#[path = "../unsupported/time.rs"] pub mod time; #[path = "../unsupported/common.rs"] diff --git a/library/std/src/sys/xous/time.rs b/library/std/src/sys/xous/time.rs new file mode 100644 index 000000000000..4e4ae67efffa --- /dev/null +++ b/library/std/src/sys/xous/time.rs @@ -0,0 +1,57 @@ +use crate::os::xous::ffi::blocking_scalar; +use crate::os::xous::services::{ + systime_server, ticktimer_server, SystimeScalar::GetUtcTimeMs, TicktimerScalar::ElapsedMs, +}; +use crate::time::Duration; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct Instant(Duration); + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct SystemTime(Duration); + +pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::from_secs(0)); + +impl Instant { + pub fn now() -> Instant { + let result = blocking_scalar(ticktimer_server(), ElapsedMs.into()) + .expect("failed to request elapsed_ms"); + let lower = result[0]; + let upper = result[1]; + Instant { 0: Duration::from_millis(lower as u64 | (upper as u64) << 32) } + } + + pub fn checked_sub_instant(&self, other: &Instant) -> Option { + self.0.checked_sub(other.0) + } + + pub fn checked_add_duration(&self, other: &Duration) -> Option { + self.0.checked_add(*other).map(Instant) + } + + pub fn checked_sub_duration(&self, other: &Duration) -> Option { + self.0.checked_sub(*other).map(Instant) + } +} + +impl SystemTime { + pub fn now() -> SystemTime { + let result = blocking_scalar(systime_server(), GetUtcTimeMs.into()) + .expect("failed to request utc time in ms"); + let lower = result[0]; + let upper = result[1]; + SystemTime { 0: Duration::from_millis((upper as u64) << 32 | lower as u64) } + } + + pub fn sub_time(&self, other: &SystemTime) -> Result { + self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0) + } + + pub fn checked_add_duration(&self, other: &Duration) -> Option { + Some(SystemTime(self.0.checked_add(*other)?)) + } + + pub fn checked_sub_duration(&self, other: &Duration) -> Option { + Some(SystemTime(self.0.checked_sub(*other)?)) + } +} From d36e516478cf34b360cb5d0f6225a28e6076ac37 Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Mon, 7 Nov 2022 13:16:04 +0800 Subject: [PATCH 015/111] std: xous: add thread support Add initial support for threads on Xous. This includes thread creation and joining. Signed-off-by: Sean Cross --- library/std/src/sys/xous/mod.rs | 1 - library/std/src/sys/xous/thread.rs | 144 +++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 library/std/src/sys/xous/thread.rs diff --git a/library/std/src/sys/xous/mod.rs b/library/std/src/sys/xous/mod.rs index 1b59cc632c64..e3f527912db2 100644 --- a/library/std/src/sys/xous/mod.rs +++ b/library/std/src/sys/xous/mod.rs @@ -27,7 +27,6 @@ pub mod pipe; #[path = "../unsupported/process.rs"] pub mod process; pub mod stdio; -#[path = "../unsupported/thread.rs"] pub mod thread; #[path = "../unsupported/thread_local_key.rs"] pub mod thread_local_key; diff --git a/library/std/src/sys/xous/thread.rs b/library/std/src/sys/xous/thread.rs new file mode 100644 index 000000000000..78c68de7bf38 --- /dev/null +++ b/library/std/src/sys/xous/thread.rs @@ -0,0 +1,144 @@ +use crate::ffi::CStr; +use crate::io; +use crate::num::NonZeroUsize; +use crate::os::xous::ffi::{ + blocking_scalar, create_thread, do_yield, join_thread, map_memory, update_memory_flags, + MemoryFlags, Syscall, ThreadId, +}; +use crate::os::xous::services::{ticktimer_server, TicktimerScalar}; +use crate::time::Duration; +use core::arch::asm; + +pub struct Thread { + tid: ThreadId, +} + +pub const DEFAULT_MIN_STACK_SIZE: usize = 131072; +const MIN_STACK_SIZE: usize = 4096; +pub const GUARD_PAGE_SIZE: usize = 4096; + +impl Thread { + // unsafe: see thread::Builder::spawn_unchecked for safety requirements + pub unsafe fn new(stack: usize, p: Box) -> io::Result { + let p = Box::into_raw(Box::new(p)); + let mut stack_size = crate::cmp::max(stack, MIN_STACK_SIZE); + + if (stack_size & 4095) != 0 { + stack_size = (stack_size + 4095) & !4095; + } + + // Allocate the whole thing, then divide it up after the fact. This ensures that + // even if there's a context switch during this function, the whole stack plus + // guard pages will remain contiguous. + let stack_plus_guard_pages: &mut [u8] = unsafe { + map_memory( + None, + None, + GUARD_PAGE_SIZE + stack_size + GUARD_PAGE_SIZE, + MemoryFlags::R | MemoryFlags::W | MemoryFlags::X, + ) + } + .map_err(|code| io::Error::from_raw_os_error(code as i32))?; + + // No access to this page. Note: Write-only pages are illegal, and will + // cause an access violation. + unsafe { + update_memory_flags(&mut stack_plus_guard_pages[0..GUARD_PAGE_SIZE], MemoryFlags::W) + .map_err(|code| io::Error::from_raw_os_error(code as i32))? + }; + + // No access to this page. Note: Write-only pages are illegal, and will + // cause an access violation. + unsafe { + update_memory_flags( + &mut stack_plus_guard_pages[(GUARD_PAGE_SIZE + stack_size)..], + MemoryFlags::W, + ) + .map_err(|code| io::Error::from_raw_os_error(code as i32))? + }; + + let guard_page_pre = stack_plus_guard_pages.as_ptr() as usize; + let tid = create_thread( + thread_start as *mut usize, + &mut stack_plus_guard_pages[GUARD_PAGE_SIZE..(stack_size + GUARD_PAGE_SIZE)], + p as usize, + guard_page_pre, + stack_size, + 0, + ) + .map_err(|code| io::Error::from_raw_os_error(code as i32))?; + + extern "C" fn thread_start(main: *mut usize, guard_page_pre: usize, stack_size: usize) { + unsafe { + // Finally, let's run some code. + Box::from_raw(main as *mut Box)(); + } + + // Destroy TLS, which will free the TLS page and call the destructor for + // any thread local storage. + unsafe { + crate::sys::thread_local_key::destroy_tls(); + } + + // Deallocate the stack memory, along with the guard pages. Afterwards, + // exit the thread by returning to the magic address 0xff80_3000usize, + // which tells the kernel to deallocate this thread. + let mapped_memory_base = guard_page_pre; + let mapped_memory_length = GUARD_PAGE_SIZE + stack_size + GUARD_PAGE_SIZE; + unsafe { + asm!( + "ecall", + "ret", + in("a0") Syscall::UnmapMemory as usize, + in("a1") mapped_memory_base, + in("a2") mapped_memory_length, + in("ra") 0xff80_3000usize, + options(nomem, nostack, noreturn) + ); + } + } + + Ok(Thread { tid }) + } + + pub fn yield_now() { + do_yield(); + } + + pub fn set_name(_name: &CStr) { + // nope + } + + pub fn sleep(dur: Duration) { + // Because the sleep server works on units of `usized milliseconds`, split + // the messages up into these chunks. This means we may run into issues + // if you try to sleep a thread for more than 49 days on a 32-bit system. + let mut millis = dur.as_millis(); + while millis > 0 { + let sleep_duration = + if millis > (usize::MAX as _) { usize::MAX } else { millis as usize }; + blocking_scalar(ticktimer_server(), TicktimerScalar::SleepMs(sleep_duration).into()) + .expect("failed to send message to ticktimer server"); + millis -= sleep_duration as u128; + } + } + + pub fn join(self) { + join_thread(self.tid).unwrap(); + } +} + +pub fn available_parallelism() -> io::Result { + // We're unicore right now. + Ok(unsafe { NonZeroUsize::new_unchecked(1) }) +} + +pub mod guard { + pub type Guard = !; + pub unsafe fn current() -> Option { + None + } + pub unsafe fn init() -> Option { + None + } +} From 46a4ec3a71a3e941b2d254c80f5780a2ed9eeff3 Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Mon, 7 Nov 2022 16:06:01 +0800 Subject: [PATCH 016/111] std: xous: add thread_local_key Add an implementation of thread local storage. This uses a container that is pointed to by the otherwise-unsed `$tp` register. This container is allocated on-demand, so threads that use no TLS will not allocate this extra memory. Signed-off-by: Sean Cross --- library/std/src/sys/xous/mod.rs | 1 - library/std/src/sys/xous/thread_local_key.rs | 190 +++++++++++++++++++ 2 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 library/std/src/sys/xous/thread_local_key.rs diff --git a/library/std/src/sys/xous/mod.rs b/library/std/src/sys/xous/mod.rs index e3f527912db2..79d9c00f64a9 100644 --- a/library/std/src/sys/xous/mod.rs +++ b/library/std/src/sys/xous/mod.rs @@ -28,7 +28,6 @@ pub mod pipe; pub mod process; pub mod stdio; pub mod thread; -#[path = "../unsupported/thread_local_key.rs"] pub mod thread_local_key; #[path = "../unsupported/thread_parking.rs"] pub mod thread_parking; diff --git a/library/std/src/sys/xous/thread_local_key.rs b/library/std/src/sys/xous/thread_local_key.rs new file mode 100644 index 000000000000..3771ea657008 --- /dev/null +++ b/library/std/src/sys/xous/thread_local_key.rs @@ -0,0 +1,190 @@ +use crate::mem::ManuallyDrop; +use crate::ptr; +use crate::sync::atomic::AtomicPtr; +use crate::sync::atomic::AtomicUsize; +use crate::sync::atomic::Ordering::SeqCst; +use core::arch::asm; + +use crate::os::xous::ffi::{map_memory, unmap_memory, MemoryFlags}; + +/// Thread Local Storage +/// +/// Currently, we are limited to 1023 TLS entries. The entries +/// live in a page of memory that's unique per-process, and is +/// stored in the `$tp` register. If this register is 0, then +/// TLS has not been initialized and thread cleanup can be skipped. +/// +/// The index into this register is the `key`. This key is identical +/// between all threads, but indexes a different offset within this +/// pointer. +pub type Key = usize; + +pub type Dtor = unsafe extern "C" fn(*mut u8); + +const TLS_MEMORY_SIZE: usize = 4096; + +/// TLS keys start at `1` to mimic POSIX. +static TLS_KEY_INDEX: AtomicUsize = AtomicUsize::new(1); + +fn tls_ptr_addr() -> *mut usize { + let mut tp: usize; + unsafe { + asm!( + "mv {}, tp", + out(reg) tp, + ); + } + core::ptr::from_exposed_addr_mut::(tp) +} + +/// Create an area of memory that's unique per thread. This area will +/// contain all thread local pointers. +fn tls_ptr() -> *mut usize { + let mut tp = tls_ptr_addr(); + + // If the TP register is `0`, then this thread hasn't initialized + // its TLS yet. Allocate a new page to store this memory. + if tp.is_null() { + tp = unsafe { + map_memory( + None, + None, + TLS_MEMORY_SIZE / core::mem::size_of::(), + MemoryFlags::R | MemoryFlags::W, + ) + } + .expect("Unable to allocate memory for thread local storage") + .as_mut_ptr(); + + unsafe { + // Key #0 is currently unused. + (tp).write_volatile(0); + + // Set the thread's `$tp` register + asm!( + "mv tp, {}", + in(reg) tp as usize, + ); + } + } + tp +} + +/// Allocate a new TLS key. These keys are shared among all threads. +fn tls_alloc() -> usize { + TLS_KEY_INDEX.fetch_add(1, SeqCst) +} + +#[inline] +pub unsafe fn create(dtor: Option) -> Key { + let key = tls_alloc(); + if let Some(f) = dtor { + unsafe { register_dtor(key, f) }; + } + key +} + +#[inline] +pub unsafe fn set(key: Key, value: *mut u8) { + assert!((key < 1022) && (key >= 1)); + unsafe { tls_ptr().add(key).write_volatile(value as usize) }; +} + +#[inline] +pub unsafe fn get(key: Key) -> *mut u8 { + assert!((key < 1022) && (key >= 1)); + core::ptr::from_exposed_addr_mut::(unsafe { tls_ptr().add(key).read_volatile() }) +} + +#[inline] +pub unsafe fn destroy(_key: Key) { + panic!("can't destroy keys on Xous"); +} + +// ------------------------------------------------------------------------- +// Dtor registration (stolen from Windows) +// +// Xous has no native support for running destructors so we manage our own +// list of destructors to keep track of how to destroy keys. We then install a +// callback later to get invoked whenever a thread exits, running all +// appropriate destructors. +// +// Currently unregistration from this list is not supported. A destructor can be +// registered but cannot be unregistered. There's various simplifying reasons +// for doing this, the big ones being: +// +// 1. Currently we don't even support deallocating TLS keys, so normal operation +// doesn't need to deallocate a destructor. +// 2. There is no point in time where we know we can unregister a destructor +// because it could always be getting run by some remote thread. +// +// Typically processes have a statically known set of TLS keys which is pretty +// small, and we'd want to keep this memory alive for the whole process anyway +// really. +// +// Perhaps one day we can fold the `Box` here into a static allocation, +// expanding the `StaticKey` structure to contain not only a slot for the TLS +// key but also a slot for the destructor queue on windows. An optimization for +// another day! + +static DTORS: AtomicPtr = AtomicPtr::new(ptr::null_mut()); + +struct Node { + dtor: Dtor, + key: Key, + next: *mut Node, +} + +unsafe fn register_dtor(key: Key, dtor: Dtor) { + let mut node = ManuallyDrop::new(Box::new(Node { key, dtor, next: ptr::null_mut() })); + + let mut head = DTORS.load(SeqCst); + loop { + node.next = head; + match DTORS.compare_exchange(head, &mut **node, SeqCst, SeqCst) { + Ok(_) => return, // nothing to drop, we successfully added the node to the list + Err(cur) => head = cur, + } + } +} + +pub unsafe fn destroy_tls() { + let tp = tls_ptr_addr(); + + // If the pointer address is 0, then this thread has no TLS. + if tp.is_null() { + return; + } + unsafe { run_dtors() }; + + // Finally, free the TLS array + unsafe { + unmap_memory(core::slice::from_raw_parts_mut( + tp, + TLS_MEMORY_SIZE / core::mem::size_of::(), + )) + .unwrap() + }; +} + +unsafe fn run_dtors() { + let mut any_run = true; + for _ in 0..5 { + if !any_run { + break; + } + any_run = false; + let mut cur = DTORS.load(SeqCst); + while !cur.is_null() { + let ptr = unsafe { get((*cur).key) }; + + if !ptr.is_null() { + unsafe { set((*cur).key, ptr::null_mut()) }; + unsafe { ((*cur).dtor)(ptr as *mut _) }; + any_run = true; + } + + unsafe { cur = (*cur).next }; + } + } +} From 6413844a22e81b4c589bcd286668d90707e66800 Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Mon, 7 Nov 2022 13:33:37 +0800 Subject: [PATCH 017/111] std: xous: add support for locks Add support for Condvar, Mutex, and RWLock. These are all backed by the ticktimer server. Signed-off-by: Sean Cross --- library/std/src/os/xous/services/ticktimer.rs | 4 + library/std/src/sys/xous/locks/condvar.rs | 111 +++++++++++++++++ library/std/src/sys/xous/locks/mod.rs | 7 ++ library/std/src/sys/xous/locks/mutex.rs | 116 ++++++++++++++++++ library/std/src/sys/xous/locks/rwlock.rs | 72 +++++++++++ library/std/src/sys/xous/mod.rs | 1 - 6 files changed, 310 insertions(+), 1 deletion(-) create mode 100644 library/std/src/sys/xous/locks/condvar.rs create mode 100644 library/std/src/sys/xous/locks/mod.rs create mode 100644 library/std/src/sys/xous/locks/mutex.rs create mode 100644 library/std/src/sys/xous/locks/rwlock.rs diff --git a/library/std/src/os/xous/services/ticktimer.rs b/library/std/src/os/xous/services/ticktimer.rs index 447f8f9304a1..7759303fdbe2 100644 --- a/library/std/src/os/xous/services/ticktimer.rs +++ b/library/std/src/os/xous/services/ticktimer.rs @@ -8,6 +8,8 @@ pub(crate) enum TicktimerScalar { UnlockMutex(usize /* cookie */), WaitForCondition(usize /* cookie */, usize /* timeout (ms) */), NotifyCondition(usize /* cookie */, usize /* count */), + FreeMutex(usize /* cookie */), + FreeCondition(usize /* cookie */), } impl Into<[usize; 5]> for TicktimerScalar { @@ -19,6 +21,8 @@ impl Into<[usize; 5]> for TicktimerScalar { TicktimerScalar::UnlockMutex(cookie) => [7, cookie, 0, 0, 0], TicktimerScalar::WaitForCondition(cookie, timeout_ms) => [8, cookie, timeout_ms, 0, 0], TicktimerScalar::NotifyCondition(cookie, count) => [9, cookie, count, 0, 0], + TicktimerScalar::FreeMutex(cookie) => [10, cookie, 0, 0, 0], + TicktimerScalar::FreeCondition(cookie) => [11, cookie, 0, 0, 0], } } } diff --git a/library/std/src/sys/xous/locks/condvar.rs b/library/std/src/sys/xous/locks/condvar.rs new file mode 100644 index 000000000000..1bb38dfa3415 --- /dev/null +++ b/library/std/src/sys/xous/locks/condvar.rs @@ -0,0 +1,111 @@ +use super::mutex::Mutex; +use crate::os::xous::ffi::{blocking_scalar, scalar}; +use crate::os::xous::services::ticktimer_server; +use crate::sync::Mutex as StdMutex; +use crate::time::Duration; + +// The implementation is inspired by Andrew D. Birrell's paper +// "Implementing Condition Variables with Semaphores" + +pub struct Condvar { + counter: StdMutex, +} + +unsafe impl Send for Condvar {} +unsafe impl Sync for Condvar {} + +impl Condvar { + #[inline] + #[rustc_const_stable(feature = "const_locks", since = "1.63.0")] + pub const fn new() -> Condvar { + Condvar { counter: StdMutex::new(0) } + } + + pub fn notify_one(&self) { + let mut counter = self.counter.lock().unwrap(); + if *counter <= 0 { + return; + } else { + *counter -= 1; + } + let result = blocking_scalar( + ticktimer_server(), + crate::os::xous::services::TicktimerScalar::NotifyCondition(self.index(), 1).into(), + ); + drop(counter); + result.expect("failure to send NotifyCondition command"); + } + + pub fn notify_all(&self) { + let mut counter = self.counter.lock().unwrap(); + if *counter <= 0 { + return; + } + let result = blocking_scalar( + ticktimer_server(), + crate::os::xous::services::TicktimerScalar::NotifyCondition(self.index(), *counter) + .into(), + ); + *counter = 0; + drop(counter); + + result.expect("failure to send NotifyCondition command"); + } + + fn index(&self) -> usize { + self as *const Condvar as usize + } + + pub unsafe fn wait(&self, mutex: &Mutex) { + let mut counter = self.counter.lock().unwrap(); + *counter += 1; + unsafe { mutex.unlock() }; + drop(counter); + + let result = blocking_scalar( + ticktimer_server(), + crate::os::xous::services::TicktimerScalar::WaitForCondition(self.index(), 0).into(), + ); + unsafe { mutex.lock() }; + + result.expect("Ticktimer: failure to send WaitForCondition command"); + } + + pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool { + let mut counter = self.counter.lock().unwrap(); + *counter += 1; + unsafe { mutex.unlock() }; + drop(counter); + + let mut millis = dur.as_millis() as usize; + if millis == 0 { + millis = 1; + } + + let result = blocking_scalar( + ticktimer_server(), + crate::os::xous::services::TicktimerScalar::WaitForCondition(self.index(), millis) + .into(), + ); + unsafe { mutex.lock() }; + + let result = result.expect("Ticktimer: failure to send WaitForCondition command")[0] == 0; + + // If we awoke due to a timeout, decrement the wake count, as that would not have + // been done in the `notify()` call. + if !result { + *self.counter.lock().unwrap() -= 1; + } + result + } +} + +impl Drop for Condvar { + fn drop(&mut self) { + scalar( + ticktimer_server(), + crate::os::xous::services::TicktimerScalar::FreeCondition(self.index()).into(), + ) + .ok(); + } +} diff --git a/library/std/src/sys/xous/locks/mod.rs b/library/std/src/sys/xous/locks/mod.rs new file mode 100644 index 000000000000..f3c5c5d9fb0c --- /dev/null +++ b/library/std/src/sys/xous/locks/mod.rs @@ -0,0 +1,7 @@ +mod condvar; +mod mutex; +mod rwlock; + +pub use condvar::*; +pub use mutex::*; +pub use rwlock::*; diff --git a/library/std/src/sys/xous/locks/mutex.rs b/library/std/src/sys/xous/locks/mutex.rs new file mode 100644 index 000000000000..ea51776d54ec --- /dev/null +++ b/library/std/src/sys/xous/locks/mutex.rs @@ -0,0 +1,116 @@ +use crate::os::xous::ffi::{blocking_scalar, do_yield, scalar}; +use crate::os::xous::services::ticktimer_server; +use crate::sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed, Ordering::SeqCst}; + +pub struct Mutex { + /// The "locked" value indicates how many threads are waiting on this + /// Mutex. Possible values are: + /// 0: The lock is unlocked + /// 1: The lock is locked and uncontended + /// >=2: The lock is locked and contended + /// + /// A lock is "contended" when there is more than one thread waiting + /// for a lock, or it is locked for long periods of time. Rather than + /// spinning, these locks send a Message to the ticktimer server + /// requesting that they be woken up when a lock is unlocked. + locked: AtomicUsize, + + /// Whether this Mutex ever was contended, and therefore made a trip + /// to the ticktimer server. If this was never set, then we were never + /// on the slow path and can skip deregistering the mutex. + contended: AtomicBool, +} + +impl Mutex { + #[inline] + #[rustc_const_stable(feature = "const_locks", since = "1.63.0")] + pub const fn new() -> Mutex { + Mutex { locked: AtomicUsize::new(0), contended: AtomicBool::new(false) } + } + + fn index(&self) -> usize { + self as *const Mutex as usize + } + + #[inline] + pub unsafe fn lock(&self) { + // Try multiple times to acquire the lock without resorting to the ticktimer + // server. For locks that are held for a short amount of time, this will + // result in the ticktimer server never getting invoked. The `locked` value + // will be either 0 or 1. + for _attempts in 0..3 { + if unsafe { self.try_lock() } { + return; + } + do_yield(); + } + + // Try one more time to lock. If the lock is released between the previous code and + // here, then the inner `locked` value will be 1 at the end of this. If it was not + // locked, then the value will be more than 1, for example if there are multiple other + // threads waiting on this lock. + if unsafe { self.try_lock_or_poison() } { + return; + } + + // When this mutex is dropped, we will need to deregister it with the server. + self.contended.store(true, Relaxed); + + // The lock is now "contended". When the lock is released, a Message will get sent to the + // ticktimer server to wake it up. Note that this may already have happened, so the actual + // value of `lock` may be anything (0, 1, 2, ...). + blocking_scalar( + ticktimer_server(), + crate::os::xous::services::TicktimerScalar::LockMutex(self.index()).into(), + ) + .expect("failure to send LockMutex command"); + } + + #[inline] + pub unsafe fn unlock(&self) { + let prev = self.locked.fetch_sub(1, SeqCst); + + // If the previous value was 1, then this was a "fast path" unlock, so no + // need to involve the Ticktimer server + if prev == 1 { + return; + } + + // If it was 0, then something has gone seriously wrong and the counter + // has just wrapped around. + if prev == 0 { + panic!("mutex lock count underflowed"); + } + + // Unblock one thread that is waiting on this message. + scalar( + ticktimer_server(), + crate::os::xous::services::TicktimerScalar::UnlockMutex(self.index()).into(), + ) + .expect("failure to send UnlockMutex command"); + } + + #[inline] + pub unsafe fn try_lock(&self) -> bool { + self.locked.compare_exchange(0, 1, SeqCst, SeqCst).is_ok() + } + + #[inline] + pub unsafe fn try_lock_or_poison(&self) -> bool { + self.locked.fetch_add(1, SeqCst) == 0 + } +} + +impl Drop for Mutex { + fn drop(&mut self) { + // If there was Mutex contention, then we involved the ticktimer. Free + // the resources associated with this Mutex as it is deallocated. + if self.contended.load(Relaxed) { + scalar( + ticktimer_server(), + crate::os::xous::services::TicktimerScalar::FreeMutex(self.index()).into(), + ) + .ok(); + } + } +} diff --git a/library/std/src/sys/xous/locks/rwlock.rs b/library/std/src/sys/xous/locks/rwlock.rs new file mode 100644 index 000000000000..618da758adfa --- /dev/null +++ b/library/std/src/sys/xous/locks/rwlock.rs @@ -0,0 +1,72 @@ +use crate::os::xous::ffi::do_yield; +use crate::sync::atomic::{AtomicIsize, Ordering::SeqCst}; + +pub struct RwLock { + /// The "mode" value indicates how many threads are waiting on this + /// Mutex. Possible values are: + /// -1: The lock is locked for writing + /// 0: The lock is unlocked + /// >=1: The lock is locked for reading + /// + /// This currently spins waiting for the lock to be freed. An + /// optimization would be to involve the ticktimer server to + /// coordinate unlocks. + mode: AtomicIsize, +} + +unsafe impl Send for RwLock {} +unsafe impl Sync for RwLock {} + +impl RwLock { + #[inline] + #[rustc_const_stable(feature = "const_locks", since = "1.63.0")] + pub const fn new() -> RwLock { + RwLock { mode: AtomicIsize::new(0) } + } + + #[inline] + pub unsafe fn read(&self) { + while !unsafe { self.try_read() } { + do_yield(); + } + } + + #[inline] + pub unsafe fn try_read(&self) -> bool { + // Non-atomically determine the current value. + let current = self.mode.load(SeqCst); + + // If it's currently locked for writing, then we cannot read. + if current < 0 { + return false; + } + + // Attempt to lock. If the `current` value has changed, then this + // operation will fail and we will not obtain the lock even if we + // could potentially keep it. + let new = current + 1; + self.mode.compare_exchange(current, new, SeqCst, SeqCst).is_ok() + } + + #[inline] + pub unsafe fn write(&self) { + while !unsafe { self.try_write() } { + do_yield(); + } + } + + #[inline] + pub unsafe fn try_write(&self) -> bool { + self.mode.compare_exchange(0, -1, SeqCst, SeqCst).is_ok() + } + + #[inline] + pub unsafe fn read_unlock(&self) { + self.mode.fetch_sub(1, SeqCst); + } + + #[inline] + pub unsafe fn write_unlock(&self) { + assert_eq!(self.mode.compare_exchange(-1, 0, SeqCst, SeqCst), Ok(-1)); + } +} diff --git a/library/std/src/sys/xous/mod.rs b/library/std/src/sys/xous/mod.rs index 79d9c00f64a9..6d5c218d1957 100644 --- a/library/std/src/sys/xous/mod.rs +++ b/library/std/src/sys/xous/mod.rs @@ -11,7 +11,6 @@ pub mod env; pub mod fs; #[path = "../unsupported/io.rs"] pub mod io; -#[path = "../unsupported/locks/mod.rs"] pub mod locks; #[path = "../unsupported/net.rs"] pub mod net; From 00280c8972150d6d4ee27926dfbe5b39bbf11fc3 Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Sat, 12 Aug 2023 10:53:18 +0200 Subject: [PATCH 018/111] Cargo.lock: bump compiler_builtins to 0.1.101 The Xous operating system has no libc, and relies on `compiler_builtins` to supply basic functions such as `memcpy`. Bump the version to 0.1.101 to pull in a version of this crate with the flag enabled for Xous. Signed-off-by: Sean Cross --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 266f34e69edb..915c3d26da25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -639,9 +639,9 @@ dependencies = [ [[package]] name = "compiler_builtins" -version = "0.1.100" +version = "0.1.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c0f24437059853f0fa64afc51f338f93647a3de4cf3358ba1bb4171a199775" +checksum = "01a6d58e9c3408138099a396a98fd0d0e6cfb25d723594d2ae48b5004513fd5b" dependencies = [ "cc", "rustc-std-workspace-core", From 6c32a64962f591e6fd4cab72f63555b157684323 Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Wed, 16 Aug 2023 09:58:51 +0200 Subject: [PATCH 019/111] std: enable feature(slice_ptr_len) on xous Xous passes slice pointers around in order to manipulate memory. This is feature-gated behind `slice_ptr_len`. Xous is currently the only target to use this feature, so gate it behind an OS flag. Signed-off-by: Sean Cross --- library/std/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 58684ffe500a..4d4aeff2a973 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -236,6 +236,7 @@ feature(slice_index_methods, coerce_unsized, sgx_platform) )] #![cfg_attr(windows, feature(round_char_boundary))] +#![cfg_attr(target_os = "xous", feature(slice_ptr_len))] // // Language features: // tidy-alphabetical-start From fbd62b831e61ee008d8d304cc4925e3b8665d0ad Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 26 Aug 2023 21:01:01 +0000 Subject: [PATCH 020/111] Comments --- .../src/fn_ctxt/adjust_fulfillment_errors.rs | 55 +++++++++---------- compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs | 11 ++-- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs index 2acb43c51da8..43d4496dd48c 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs @@ -33,27 +33,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; let generics = self.tcx.generics_of(def_id); - let predicate_args = match unsubstituted_pred.kind().skip_binder() { - ty::ClauseKind::Trait(pred) => pred.trait_ref.args.to_vec(), - ty::ClauseKind::Projection(pred) => pred.projection_ty.args.to_vec(), - ty::ClauseKind::ConstArgHasType(arg, ty) => { - vec![ty.into(), arg.into()] - } - ty::ClauseKind::ConstEvaluatable(e) => vec![e.into()], - _ => return false, - }; + let (predicate_args, predicate_self_type_to_point_at) = + match unsubstituted_pred.kind().skip_binder() { + ty::ClauseKind::Trait(pred) => { + (pred.trait_ref.args.to_vec(), Some(pred.self_ty().into())) + } + ty::ClauseKind::Projection(pred) => (pred.projection_ty.args.to_vec(), None), + ty::ClauseKind::ConstArgHasType(arg, ty) => (vec![ty.into(), arg.into()], None), + ty::ClauseKind::ConstEvaluatable(e) => (vec![e.into()], None), + _ => return false, + }; - let direct_param = if let ty::ClauseKind::Trait(pred) = unsubstituted_pred.kind().skip_binder() - && let ty = pred.trait_ref.self_ty() - && let ty::Param(_param) = ty.kind() - && let Some(arg) = predicate_args.get(0) - && let ty::GenericArgKind::Type(arg_ty) = arg.unpack() - && arg_ty == ty - { - Some(*arg) - } else { - None - }; let find_param_matching = |matches: &dyn Fn(ty::ParamTerm) -> bool| { predicate_args.iter().find_map(|arg| { arg.walk().find_map(|arg| { @@ -112,18 +102,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let qpath = if let hir::ExprKind::Path(qpath) = expr.kind { Some(qpath) } else { None }; - (Some(*expr), qpath) + (Some(&expr.kind), qpath) } hir::Node::Ty(hir::Ty { kind: hir::TyKind::Path(qpath), .. }) => (None, Some(*qpath)), _ => return false, }; if let Some(qpath) = qpath { - if let Some(param) = direct_param { - if self.point_at_path_if_possible(error, def_id, param, &qpath) { - return true; - } + // Prefer pointing at the turbofished arg that corresponds to the + // self type of the failing predicate over anything else. + if let Some(param) = predicate_self_type_to_point_at + && self.point_at_path_if_possible(error, def_id, param, &qpath) + { + return true; } + if let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Call(callee, args), hir_id: call_hir_id, @@ -166,11 +159,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - match expr.map(|e| e.kind) { + match expr { Some(hir::ExprKind::MethodCall(segment, receiver, args, ..)) => { - if let Some(param) = direct_param + if let Some(param) = predicate_self_type_to_point_at && self.point_at_generic_if_possible(error, def_id, param, segment) { + // HACK: This is not correct, since `predicate_self_type_to_point_at` might + // not actually correspond to the receiver of the method call. But we + // re-adjust the cause code here in order to prefer pointing at one of + // the method's turbofish segments but still use `FunctionArgumentObligation` + // elsewhere. Hopefully this doesn't break something. error.obligation.cause.map_code(|parent_code| { ObligationCauseCode::FunctionArgumentObligation { arg_hir_id: receiver.hir_id, @@ -180,6 +178,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }); return true; } + for param in [param_to_point_at, fallback_param_to_point_at, self_param_to_point_at] .into_iter() .flatten() @@ -237,7 +236,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } for param in [ - direct_param, + predicate_self_type_to_point_at, param_to_point_at, fallback_param_to_point_at, self_param_to_point_at, diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs index 7707cc6de546..5785f9b5ae1b 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs @@ -318,13 +318,16 @@ impl<'a, 'tcx> AstConv<'tcx> for FnCtxt<'a, 'tcx> { fn record_ty(&self, hir_id: hir::HirId, ty: Ty<'tcx>, span: Span) { // FIXME: normalization and escaping regions let ty = if !ty.has_escaping_bound_vars() { - if let ty::Alias( - ty::AliasKind::Projection | ty::AliasKind::Weak, - ty::AliasTy { args, def_id, .. }, - ) = ty.kind() + // NOTE: These obligations are 100% redundant and are implied by + // WF obligations that are registered elsewhere, but they have a + // better cause code assigned to them in `add_required_obligations_for_hir`. + // This means that they should shadow obligations with worse spans. + if let ty::Alias(ty::Projection | ty::Weak, ty::AliasTy { args, def_id, .. }) = + ty.kind() { self.add_required_obligations_for_hir(span, *def_id, args, hir_id); } + self.normalize(span, ty) } else { ty From e8f9d1a80f1e21dd3b4198c35b9596fb62e8786d Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 3 Aug 2023 19:13:08 -0400 Subject: [PATCH 021/111] Refactor the const `strlen` implementation to `const_strlen` Currently, `CStr::from_ptr` contains its own implementation of `strlen` that uses `const_eval_select` to either call libc's `strlen` or use a naive Rust implementation. Refactor that into its own function so we can use it elsewhere in the module. --- library/core/src/ffi/c_str.rs | 78 ++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/library/core/src/ffi/c_str.rs b/library/core/src/ffi/c_str.rs index 163a65c909e4..801447e96cd8 100644 --- a/library/core/src/ffi/c_str.rs +++ b/library/core/src/ffi/c_str.rs @@ -214,6 +214,8 @@ impl CStr { /// * The memory referenced by the returned `CStr` must not be mutated for /// the duration of lifetime `'a`. /// + /// * The nul terminator must be within `isize::MAX` from `ptr` + /// /// > **Note**: This operation is intended to be a 0-cost cast but it is /// > currently implemented with an up-front calculation of the length of /// > the string. This is not guaranteed to always be the case. @@ -259,42 +261,16 @@ impl CStr { #[rustc_const_unstable(feature = "const_cstr_from_ptr", issue = "113219")] pub const unsafe fn from_ptr<'a>(ptr: *const c_char) -> &'a CStr { // SAFETY: The caller has provided a pointer that points to a valid C - // string with a NUL terminator of size less than `isize::MAX`, whose - // content remain valid and doesn't change for the lifetime of the - // returned `CStr`. - // - // Thus computing the length is fine (a NUL byte exists), the call to - // from_raw_parts is safe because we know the length is at most `isize::MAX`, meaning - // the call to `from_bytes_with_nul_unchecked` is correct. + // string with a NUL terminator less than `isize::MAX` from `ptr`. + let len = unsafe { const_strlen(ptr) }; + + // SAFETY: The caller has provided a valid pointer with length less than + // `isize::MAX`, so `from_raw_parts` is safe. The content remains valid + // and doesn't change for the lifetime of the returned `CStr`. This + // means the call to `from_bytes_with_nul_unchecked` is correct. // // The cast from c_char to u8 is ok because a c_char is always one byte. - unsafe { - const fn strlen_ct(s: *const c_char) -> usize { - let mut len = 0; - - // SAFETY: Outer caller has provided a pointer to a valid C string. - while unsafe { *s.add(len) } != 0 { - len += 1; - } - - len - } - - // `inline` is necessary for codegen to see strlen. - #[inline] - fn strlen_rt(s: *const c_char) -> usize { - extern "C" { - /// Provided by libc or compiler_builtins. - fn strlen(s: *const c_char) -> usize; - } - - // SAFETY: Outer caller has provided a pointer to a valid C string. - unsafe { strlen(s) } - } - - let len = intrinsics::const_eval_select((ptr,), strlen_ct, strlen_rt); - Self::from_bytes_with_nul_unchecked(slice::from_raw_parts(ptr.cast(), len + 1)) - } + unsafe { Self::from_bytes_with_nul_unchecked(slice::from_raw_parts(ptr.cast(), len + 1)) } } /// Creates a C string wrapper from a byte slice with any number of nuls. @@ -681,3 +657,37 @@ impl AsRef for CStr { self } } + +/// Calculate the length of a nul-terminated string. Defers to C's `strlen` when possible. +/// +/// # Safety +/// +/// The pointer must point to a valid buffer that contains a NUL terminator. The NUL must be +/// located within `isize::MAX` from `ptr`. +#[inline] +const unsafe fn const_strlen(ptr: *const c_char) -> usize { + const fn strlen_ct(s: *const c_char) -> usize { + let mut len = 0; + + // SAFETY: Outer caller has provided a pointer to a valid C string. + while unsafe { *s.add(len) } != 0 { + len += 1; + } + + len + } + + #[inline] + fn strlen_rt(s: *const c_char) -> usize { + extern "C" { + /// Provided by libc or compiler_builtins. + fn strlen(s: *const c_char) -> usize; + } + + // SAFETY: Outer caller has provided a pointer to a valid C string. + unsafe { strlen(s) } + } + + // SAFETY: the two functions always provide equivalent functionality + unsafe { intrinsics::const_eval_select((ptr,), strlen_ct, strlen_rt) } +} From fe0eb8b49bd31cb956d15ac61ff6bee5b17f59ed Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 3 Aug 2023 19:38:43 -0400 Subject: [PATCH 022/111] Implement `CStr::count_bytes` This is feature gated under `cstr_count_bytes` and provides a more straightforward way to access the length of a `CStr` Link: https://github.com/rust-lang/rust/issues/113219 --- library/core/src/ffi/c_str.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/library/core/src/ffi/c_str.rs b/library/core/src/ffi/c_str.rs index 801447e96cd8..5592fe8e3242 100644 --- a/library/core/src/ffi/c_str.rs +++ b/library/core/src/ffi/c_str.rs @@ -491,6 +491,34 @@ impl CStr { self.inner.as_ptr() } + /// Returns the length of `self`. Like C's `strlen`, this does not include the nul terminator. + /// + /// > **Note**: This method is currently implemented as a constant-time + /// > cast, but it is planned to alter its definition in the future to + /// > perform the length calculation whenever this method is called. + /// + /// # Examples + /// + /// ``` + /// #![feature(cstr_count_bytes)] + /// + /// use std::ffi::CStr; + /// + /// let cstr = CStr::from_bytes_with_nul(b"foo\0").unwrap(); + /// assert_eq!(cstr.count_bytes(), 3); + /// + /// let cstr = CStr::from_bytes_with_nul(b"\0").unwrap(); + /// assert_eq!(cstr.count_bytes(), 0); + /// ``` + #[inline] + #[must_use] + #[doc(alias("len", "strlen"))] + #[unstable(feature = "cstr_count_bytes", issue = "114441")] + #[rustc_const_unstable(feature = "const_cstr_from_ptr", issue = "113219")] + pub const fn count_bytes(&self) -> usize { + self.inner.len() - 1 + } + /// Returns `true` if `self.to_bytes()` has a length of 0. /// /// # Examples From 3907072af4dcb6a11a498817fa5fd1e7e3b4070a Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Fri, 8 Apr 2022 23:29:07 +0200 Subject: [PATCH 023/111] Pretty-print impl trait to name it. --- compiler/rustc_ast_lowering/src/lib.rs | 12 ++++++------ compiler/rustc_hir/src/definitions.rs | 3 ++- compiler/rustc_middle/src/ty/mod.rs | 4 ---- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index a4ba1a5c9bf4..965acae62a98 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -1421,12 +1421,6 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { ), ImplTraitContext::Universal => { let span = t.span; - self.create_def( - self.current_hir_id_owner.def_id, - *def_node_id, - DefPathData::ImplTrait, - span, - ); // HACK: pprust breaks strings with newlines when the type // gets too long. We don't want these to show up in compiler @@ -1437,6 +1431,12 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { span, ); + self.create_def( + self.current_hir_id_owner.def_id, + *def_node_id, + DefPathData::TypeNs(ident.name), + span, + ); let (param, bounds, path) = self.lower_universal_param_and_bounds( *def_node_id, span, diff --git a/compiler/rustc_hir/src/definitions.rs b/compiler/rustc_hir/src/definitions.rs index 66b153d8931b..168b336e374f 100644 --- a/compiler/rustc_hir/src/definitions.rs +++ b/compiler/rustc_hir/src/definitions.rs @@ -278,7 +278,8 @@ pub enum DefPathData { Ctor, /// A constant expression (see `{ast,hir}::AnonConst`). AnonConst, - /// An `impl Trait` type node. + /// An existential `impl Trait` type node. + /// Argument position `impl Trait` have a `TypeNs` with their pretty-printed name. ImplTrait, /// `impl Trait` generated associated type node. ImplTraitAssocTy, diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 8fedf4dca957..70f75ebe0f14 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -2216,10 +2216,6 @@ impl<'tcx> TyCtxt<'tcx> { // The name of a constructor is that of its parent. rustc_hir::definitions::DefPathData::Ctor => self .opt_item_name(DefId { krate: def_id.krate, index: def_key.parent.unwrap() }), - // The name of opaque types only exists in HIR. - rustc_hir::definitions::DefPathData::ImplTrait - if let Some(def_id) = def_id.as_local() => - self.hir().opt_name(self.hir().local_def_id_to_hir_id(def_id)), _ => def_key.get_opt_name(), } } From 832a2a18e722f13141898a7c3918c5cb45a8c043 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Sat, 22 Jul 2023 12:32:03 +0000 Subject: [PATCH 024/111] Do not ICE in rustdoc. --- src/librustdoc/clean/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index 80a7a33d2bda..7e2aad830037 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -151,7 +151,7 @@ pub(super) fn external_path<'tcx>( args: ty::Binder<'tcx, GenericArgsRef<'tcx>>, ) -> Path { let def_kind = cx.tcx.def_kind(did); - let name = cx.tcx.item_name(did); + let name = cx.tcx.opt_item_name(did).unwrap_or(kw::Empty); Path { res: Res::Def(def_kind, did), segments: thin_vec![PathSegment { From 217fe24e52f87eba69162801e52622bfbe61e4c8 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Thu, 15 Jun 2023 13:55:45 -0700 Subject: [PATCH 025/111] rustdoc-search: `null`, not `-1`, for missing id This allows us to use negative numbers for others purposes. --- src/librustdoc/html/static/js/externs.js | 2 +- src/librustdoc/html/static/js/search.js | 26 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/librustdoc/html/static/js/externs.js b/src/librustdoc/html/static/js/externs.js index f697abd07765..354f140213a7 100644 --- a/src/librustdoc/html/static/js/externs.js +++ b/src/librustdoc/html/static/js/externs.js @@ -9,7 +9,7 @@ function initSearch(searchIndex){} /** * @typedef {{ * name: string, - * id: integer, + * id: integer|null, * fullPath: Array, * pathWithoutLast: Array, * pathLast: string, diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 0e270bbcc402..6ae827dc7782 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -252,7 +252,7 @@ function initSearch(rawSearchIndex) { /** * Add an item to the type Name->ID map, or, if one already exists, use it. - * Returns the number. If name is "" or null, return -1 (pure generic). + * Returns the number. If name is "" or null, return null (pure generic). * * This is effectively string interning, so that function matching can be * done more quickly. Two types with the same name but different item kinds @@ -264,7 +264,7 @@ function initSearch(rawSearchIndex) { */ function buildTypeMapIndex(name) { if (name === "" || name === null) { - return -1; + return null; } if (typeNameIdMap.has(name)) { @@ -489,7 +489,7 @@ function initSearch(rawSearchIndex) { } return { name: "never", - id: -1, + id: null, fullPath: ["never"], pathWithoutLast: [], pathLast: "never", @@ -531,7 +531,7 @@ function initSearch(rawSearchIndex) { } return { name: name.trim(), - id: -1, + id: null, fullPath: pathSegments, pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1), pathLast: pathSegments[pathSegments.length - 1], @@ -660,7 +660,7 @@ function initSearch(rawSearchIndex) { } elems.push({ name: "[]", - id: -1, + id: null, fullPath: ["[]"], pathWithoutLast: [], pathLast: "[]", @@ -1172,7 +1172,7 @@ function initSearch(rawSearchIndex) { const out = []; for (const result of results) { - if (result.id > -1) { + if (result.id !== -1) { const obj = searchIndex[result.id]; obj.dist = result.dist; const res = buildHrefAndPath(obj); @@ -1403,7 +1403,7 @@ function initSearch(rawSearchIndex) { // [unboxing]: // http://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf const queryContainsArrayOrSliceElem = queryElemSet.has(typeNameIdOfArrayOrSlice); - if (fnType.id === -1 || !( + if (fnType.id === null || !( queryElemSet.has(fnType.id) || (fnType.id === typeNameIdOfSlice && queryContainsArrayOrSliceElem) || (fnType.id === typeNameIdOfArray && queryContainsArrayOrSliceElem) @@ -1564,7 +1564,7 @@ function initSearch(rawSearchIndex) { * @return {boolean} - Returns true if the type matches, false otherwise. */ function checkType(row, elem) { - if (row.id === -1) { + if (row.id === null) { // This is a pure "generic" search, no need to run other checks. return row.generics.length > 0 ? checkIfInList(row.generics, elem) : false; } @@ -1891,7 +1891,7 @@ function initSearch(rawSearchIndex) { if (typeNameIdMap.has(elem.pathLast)) { elem.id = typeNameIdMap.get(elem.pathLast); } else if (!parsedQuery.literalSearch) { - let match = -1; + let match = null; let matchDist = maxEditDistance + 1; let matchName = ""; for (const [name, id] of typeNameIdMap) { @@ -1905,7 +1905,7 @@ function initSearch(rawSearchIndex) { matchName = name; } } - if (match !== -1) { + if (match !== null) { parsedQuery.correction = matchName; } elem.id = match; @@ -2413,7 +2413,7 @@ ${item.displayPath}${name}\ // `0` is used as a sentinel because it's fewer bytes than `null` if (pathIndex === 0) { return { - id: -1, + id: null, ty: null, path: null, generics: generics, @@ -2457,7 +2457,7 @@ ${item.displayPath}${name}\ const pathIndex = functionSearchType[INPUTS_DATA]; if (pathIndex === 0) { inputs = [{ - id: -1, + id: null, ty: null, path: null, generics: [], @@ -2482,7 +2482,7 @@ ${item.displayPath}${name}\ const pathIndex = functionSearchType[OUTPUT_DATA]; if (pathIndex === 0) { output = [{ - id: -1, + id: null, ty: null, path: null, generics: [], From 0b3c617ec0c92081d0eba66914ae11054bb80e52 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 16 Jun 2023 14:43:28 -0700 Subject: [PATCH 026/111] rustdoc-search: add support for type parameters When writing a type-driven search query in rustdoc, specifically one with more than one query element, non-existent types become generic parameters instead of auto-correcting (which is currently only done for single-element queries) or giving no result. You can also force a generic type parameter by writing `generic:T` (and can force it to not use a generic type parameter with something like `struct:T` or whatever, though if this happens it means the thing you're looking for doesn't exist and will give you no results). There is no syntax provided for specifying type constraints for generic type parameters. When you have a generic type parameter in a search query, it will only match up with generic type parameters in the actual function, not concrete types that match, not concrete types that implement a trait. It also strictly matches based on when they're the same or different, so `option, option -> option` matches `Option::and`, but not `Option::or`. Similarly, `option, option -> option`` matches `Option::or`, but not `Option::and`. --- src/librustdoc/clean/types.rs | 4 - src/librustdoc/html/render/mod.rs | 19 +- src/librustdoc/html/render/search_index.rs | 234 ++++----- src/librustdoc/html/static/js/externs.js | 51 +- src/librustdoc/html/static/js/search.js | 456 +++++++++++------- tests/rustdoc-gui/search-corrections.goml | 42 ++ .../rustdoc-js-std/option-type-signatures.js | 53 ++ tests/rustdoc-js/type-parameters.js | 87 ++++ tests/rustdoc-js/type-parameters.rs | 15 + 9 files changed, 670 insertions(+), 291 deletions(-) create mode 100644 tests/rustdoc-js/type-parameters.js create mode 100644 tests/rustdoc-js/type-parameters.rs diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 9134d5268dab..98702a9ebc73 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1637,10 +1637,6 @@ impl Type { matches!(self, Type::Generic(_)) } - pub(crate) fn is_impl_trait(&self) -> bool { - matches!(self, Type::ImplTrait(_)) - } - pub(crate) fn is_unit(&self) -> bool { matches!(self, Type::Tuple(v) if v.is_empty()) } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index aef8f1a74fb5..26f33d8cf8dc 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -101,7 +101,7 @@ pub(crate) struct IndexItem { pub(crate) path: String, pub(crate) desc: String, pub(crate) parent: Option, - pub(crate) parent_idx: Option, + pub(crate) parent_idx: Option, pub(crate) search_type: Option, pub(crate) aliases: Box<[Symbol]>, pub(crate) deprecation: Option, @@ -122,7 +122,10 @@ impl Serialize for RenderType { let id = match &self.id { // 0 is a sentinel, everything else is one-indexed None => 0, - Some(RenderTypeId::Index(idx)) => idx + 1, + // concrete type + Some(RenderTypeId::Index(idx)) if *idx >= 0 => idx + 1, + // generic type parameter + Some(RenderTypeId::Index(idx)) => *idx, _ => panic!("must convert render types to indexes before serializing"), }; if let Some(generics) = &self.generics { @@ -140,7 +143,7 @@ impl Serialize for RenderType { pub(crate) enum RenderTypeId { DefId(DefId), Primitive(clean::PrimitiveType), - Index(usize), + Index(isize), } /// Full type of functions/methods in the search index. @@ -148,6 +151,7 @@ pub(crate) enum RenderTypeId { pub(crate) struct IndexItemFunctionType { inputs: Vec, output: Vec, + where_clause: Vec>, } impl Serialize for IndexItemFunctionType { @@ -170,10 +174,17 @@ impl Serialize for IndexItemFunctionType { _ => seq.serialize_element(&self.inputs)?, } match &self.output[..] { - [] => {} + [] if self.where_clause.is_empty() => {} [one] if one.generics.is_none() => seq.serialize_element(one)?, _ => seq.serialize_element(&self.output)?, } + for constraint in &self.where_clause { + if let [one] = &constraint[..] && one.generics.is_none() { + seq.serialize_element(one)?; + } else { + seq.serialize_element(constraint)?; + } + } seq.end() } } diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index 145c7d18dd01..78c443b2257d 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -68,16 +68,16 @@ pub(crate) fn build_index<'tcx>( // Reduce `DefId` in paths into smaller sequential numbers, // and prune the paths that do not appear in the index. let mut lastpath = ""; - let mut lastpathid = 0usize; + let mut lastpathid = 0isize; // First, on function signatures let mut search_index = std::mem::replace(&mut cache.search_index, Vec::new()); for item in search_index.iter_mut() { fn insert_into_map( ty: &mut RenderType, - map: &mut FxHashMap, + map: &mut FxHashMap, itemid: F, - lastpathid: &mut usize, + lastpathid: &mut isize, crate_paths: &mut Vec<(ItemType, Vec)>, item_type: ItemType, path: &[Symbol], @@ -97,9 +97,9 @@ pub(crate) fn build_index<'tcx>( fn convert_render_type( ty: &mut RenderType, cache: &mut Cache, - itemid_to_pathid: &mut FxHashMap, - primitives: &mut FxHashMap, - lastpathid: &mut usize, + itemid_to_pathid: &mut FxHashMap, + primitives: &mut FxHashMap, + lastpathid: &mut isize, crate_paths: &mut Vec<(ItemType, Vec)>, ) { if let Some(generics) = &mut ty.generics { @@ -173,6 +173,18 @@ pub(crate) fn build_index<'tcx>( &mut crate_paths, ); } + for constraint in &mut search_type.where_clause { + for trait_ in &mut constraint[..] { + convert_render_type( + trait_, + cache, + &mut itemid_to_pathid, + &mut primitives, + &mut lastpathid, + &mut crate_paths, + ); + } + } } } @@ -402,7 +414,7 @@ pub(crate) fn get_function_type_for_search<'tcx>( impl_generics: Option<&(clean::Type, clean::Generics)>, cache: &Cache, ) -> Option { - let (mut inputs, mut output) = match *item.kind { + let (mut inputs, mut output, where_clause) = match *item.kind { clean::FunctionItem(ref f) => get_fn_inputs_and_outputs(f, tcx, impl_generics, cache), clean::MethodItem(ref m, _) => get_fn_inputs_and_outputs(m, tcx, impl_generics, cache), clean::TyMethodItem(ref m) => get_fn_inputs_and_outputs(m, tcx, impl_generics, cache), @@ -412,7 +424,7 @@ pub(crate) fn get_function_type_for_search<'tcx>( inputs.retain(|a| a.id.is_some() || a.generics.is_some()); output.retain(|a| a.id.is_some() || a.generics.is_some()); - Some(IndexItemFunctionType { inputs, output }) + Some(IndexItemFunctionType { inputs, output, where_clause }) } fn get_index_type(clean_type: &clean::Type, generics: Vec) -> RenderType { @@ -432,96 +444,48 @@ fn get_index_type_id(clean_type: &clean::Type) -> Option { clean::BorrowedRef { ref type_, .. } | clean::RawPointer(_, ref type_) => { get_index_type_id(type_) } - // The type parameters are converted to generics in `add_generics_and_bounds_as_types` + // The type parameters are converted to generics in `simplify_fn_type` clean::Slice(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Slice)), clean::Array(_, _) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Array)), + clean::Tuple(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Tuple)), // Not supported yet clean::BareFunction(_) | clean::Generic(_) | clean::ImplTrait(_) - | clean::Tuple(_) | clean::QPath { .. } | clean::Infer => None, } } -/// The point of this function is to replace bounds with types. +#[derive(Clone, Copy, Eq, Hash, PartialEq)] +enum SimplifiedParam { + // other kinds of type parameters are identified by their name + Symbol(Symbol), + // every argument-position impl trait is its own type parameter + Anonymous(isize), +} + +/// The point of this function is to lower generics and types into the simplified form that the +/// frontend search engine can use. /// -/// i.e. `[T, U]` when you have the following bounds: `T: Display, U: Option` will return -/// `[Display, Option]`. If a type parameter has no trait bound, it is discarded. +/// For example, `[T, U, i32]]` where you have the bounds: `T: Display, U: Option` will return +/// `[-1, -2, i32] where -1: Display, -2: Option<-1>`. If a type parameter has no traid bound, it +/// will still get a number. If a constraint is present but not used in the actual types, it will +/// not be added to the map. /// -/// Important note: It goes through generics recursively. So if you have -/// `T: Option>`, it'll go into `Option` and then into `Result`. -#[instrument(level = "trace", skip(tcx, res, cache))] -fn add_generics_and_bounds_as_types<'tcx, 'a>( +/// This function also works recursively. +#[instrument(level = "trace", skip(tcx, res, rgen, cache))] +fn simplify_fn_type<'tcx, 'a>( self_: Option<&'a Type>, generics: &Generics, arg: &'a Type, tcx: TyCtxt<'tcx>, recurse: usize, res: &mut Vec, + rgen: &mut FxHashMap)>, + is_return: bool, cache: &Cache, ) { - fn insert_ty(res: &mut Vec, ty: Type, mut generics: Vec) { - // generics and impl trait are both identified by their generics, - // rather than a type name itself - let anonymous = ty.is_full_generic() || ty.is_impl_trait(); - let generics_empty = generics.is_empty(); - - if anonymous { - if generics_empty { - // This is a type parameter with no trait bounds (for example: `T` in - // `fn f(p: T)`, so not useful for the rustdoc search because we would end up - // with an empty type with an empty name. Let's just discard it. - return; - } else if generics.len() == 1 { - // In this case, no need to go through an intermediate state if the type parameter - // contains only one trait bound. - // - // For example: - // - // `fn foo(r: Option) {}` - // - // In this case, it would contain: - // - // ``` - // [{ - // name: "option", - // generics: [{ - // name: "", - // generics: [ - // name: "Display", - // generics: [] - // }] - // }] - // }] - // ``` - // - // After removing the intermediate (unnecessary) type parameter, it'll become: - // - // ``` - // [{ - // name: "option", - // generics: [{ - // name: "Display", - // generics: [] - // }] - // }] - // ``` - // - // To be noted that it can work if there is ONLY ONE trait bound, otherwise we still - // need to keep it as is! - res.push(generics.pop().unwrap()); - return; - } - } - let index_ty = get_index_type(&ty, generics); - if index_ty.id.is_none() && generics_empty { - return; - } - res.push(index_ty); - } - if recurse >= 10 { // FIXME: remove this whole recurse thing when the recursion bug is fixed // See #59502 for the original issue. @@ -548,88 +512,126 @@ fn add_generics_and_bounds_as_types<'tcx, 'a>( // for its bounds. if let Type::Generic(arg_s) = *arg { // First we check if the bounds are in a `where` predicate... + let mut type_bounds = Vec::new(); for where_pred in generics.where_predicates.iter().filter(|g| match g { WherePredicate::BoundPredicate { ty: Type::Generic(ty_s), .. } => *ty_s == arg_s, _ => false, }) { - let mut ty_generics = Vec::new(); let bounds = where_pred.get_bounds().unwrap_or_else(|| &[]); for bound in bounds.iter() { if let Some(path) = bound.get_trait_path() { let ty = Type::Path { path }; - add_generics_and_bounds_as_types( + simplify_fn_type( self_, generics, &ty, tcx, recurse + 1, - &mut ty_generics, + &mut type_bounds, + rgen, + is_return, cache, ); } } - insert_ty(res, arg.clone(), ty_generics); } // Otherwise we check if the trait bounds are "inlined" like `T: Option`... if let Some(bound) = generics.params.iter().find(|g| g.is_type() && g.name == arg_s) { - let mut ty_generics = Vec::new(); for bound in bound.get_bounds().unwrap_or(&[]) { if let Some(path) = bound.get_trait_path() { let ty = Type::Path { path }; - add_generics_and_bounds_as_types( + simplify_fn_type( self_, generics, &ty, tcx, recurse + 1, - &mut ty_generics, + &mut type_bounds, + rgen, + is_return, cache, ); } } - insert_ty(res, arg.clone(), ty_generics); + } + if let Some((idx, _)) = rgen.get(&SimplifiedParam::Symbol(arg_s)) { + res.push(RenderType { id: Some(RenderTypeId::Index(*idx)), generics: None }); + } else { + let idx = -isize::try_from(rgen.len() + 1).unwrap(); + rgen.insert(SimplifiedParam::Symbol(arg_s), (idx, type_bounds)); + res.push(RenderType { id: Some(RenderTypeId::Index(idx)), generics: None }); } } else if let Type::ImplTrait(ref bounds) = *arg { - let mut ty_generics = Vec::new(); + let mut type_bounds = Vec::new(); for bound in bounds { if let Some(path) = bound.get_trait_path() { let ty = Type::Path { path }; - add_generics_and_bounds_as_types( + simplify_fn_type( self_, generics, &ty, tcx, recurse + 1, - &mut ty_generics, + &mut type_bounds, + rgen, + is_return, cache, ); } } - insert_ty(res, arg.clone(), ty_generics); + if is_return && !type_bounds.is_empty() { + // In parameter position, `impl Trait` is a unique thing. + res.push(RenderType { id: None, generics: Some(type_bounds) }); + } else { + // In parameter position, `impl Trait` is the same as an unnamed generic parameter. + let idx = -isize::try_from(rgen.len() + 1).unwrap(); + rgen.insert(SimplifiedParam::Anonymous(idx), (idx, type_bounds)); + res.push(RenderType { id: Some(RenderTypeId::Index(idx)), generics: None }); + } } else if let Type::Slice(ref ty) = *arg { let mut ty_generics = Vec::new(); - add_generics_and_bounds_as_types( + simplify_fn_type( self_, generics, &ty, tcx, recurse + 1, &mut ty_generics, + rgen, + is_return, cache, ); - insert_ty(res, arg.clone(), ty_generics); + res.push(get_index_type(arg, ty_generics)); } else if let Type::Array(ref ty, _) = *arg { let mut ty_generics = Vec::new(); - add_generics_and_bounds_as_types( + simplify_fn_type( self_, generics, &ty, tcx, recurse + 1, &mut ty_generics, + rgen, + is_return, cache, ); - insert_ty(res, arg.clone(), ty_generics); + res.push(get_index_type(arg, ty_generics)); + } else if let Type::Tuple(ref tys) = *arg { + let mut ty_generics = Vec::new(); + for ty in tys { + simplify_fn_type( + self_, + generics, + &ty, + tcx, + recurse + 1, + &mut ty_generics, + rgen, + is_return, + cache, + ); + } + res.push(get_index_type(arg, ty_generics)); } else { // This is not a type parameter. So for example if we have `T, U: Option`, and we're // looking at `Option`, we enter this "else" condition, otherwise if it's `T`, we don't. @@ -639,18 +641,26 @@ fn add_generics_and_bounds_as_types<'tcx, 'a>( let mut ty_generics = Vec::new(); if let Some(arg_generics) = arg.generics() { for gen in arg_generics.iter() { - add_generics_and_bounds_as_types( + simplify_fn_type( self_, generics, gen, tcx, recurse + 1, &mut ty_generics, + rgen, + is_return, cache, ); } } - insert_ty(res, arg.clone(), ty_generics); + let id = get_index_type_id(&arg); + if id.is_some() || !ty_generics.is_empty() { + res.push(RenderType { + id, + generics: if ty_generics.is_empty() { None } else { Some(ty_generics) }, + }); + } } } @@ -663,7 +673,7 @@ fn get_fn_inputs_and_outputs<'tcx>( tcx: TyCtxt<'tcx>, impl_generics: Option<&(clean::Type, clean::Generics)>, cache: &Cache, -) -> (Vec, Vec) { +) -> (Vec, Vec, Vec>) { let decl = &func.decl; let combined_generics; @@ -689,21 +699,27 @@ fn get_fn_inputs_and_outputs<'tcx>( (None, &func.generics) }; - let mut all_types = Vec::new(); + let mut rgen: FxHashMap)> = Default::default(); + + let mut arg_types = Vec::new(); for arg in decl.inputs.values.iter() { - let mut args = Vec::new(); - add_generics_and_bounds_as_types(self_, generics, &arg.type_, tcx, 0, &mut args, cache); - if !args.is_empty() { - all_types.extend(args); - } else { - all_types.push(get_index_type(&arg.type_, vec![])); - } + simplify_fn_type( + self_, + generics, + &arg.type_, + tcx, + 0, + &mut arg_types, + &mut rgen, + false, + cache, + ); } let mut ret_types = Vec::new(); - add_generics_and_bounds_as_types(self_, generics, &decl.output, tcx, 0, &mut ret_types, cache); - if ret_types.is_empty() { - ret_types.push(get_index_type(&decl.output, vec![])); - } - (all_types, ret_types) + simplify_fn_type(self_, generics, &decl.output, tcx, 0, &mut ret_types, &mut rgen, true, cache); + + let mut simplified_params = rgen.into_values().collect::>(); + simplified_params.sort_by_key(|(idx, _)| -idx); + (arg_types, ret_types, simplified_params.into_iter().map(|(_idx, traits)| traits).collect()) } diff --git a/src/librustdoc/html/static/js/externs.js b/src/librustdoc/html/static/js/externs.js index 354f140213a7..9595aae4020b 100644 --- a/src/librustdoc/html/static/js/externs.js +++ b/src/librustdoc/html/static/js/externs.js @@ -103,7 +103,7 @@ let ResultObject; * * fn something() -> Result * - * If output was allowed to be any RawFunctionType, it would look like this + * If output was allowed to be any RawFunctionType, it would look like thi * * [[], [50, [3, 3]]] * @@ -113,10 +113,56 @@ let ResultObject; * in favor of the pair of types interpretation. This is why the `(number|Array)` * is used instead of `(RawFunctionType|Array)`. * + * The output can be skipped if it's actually unit and there's no type constraints. If thi + * function accepts constrained generics, then the output will be unconditionally emitted, and + * after it will come a list of trait constraints. The position of the item in the list will + * determine which type parameter it is. For example: + * + * [1, 2, 3, 4, 5] + * ^ ^ ^ ^ ^ + * | | | | - generic parameter (-3) of trait 5 + * | | | - generic parameter (-2) of trait 4 + * | | - generic parameter (-1) of trait 3 + * | - this function returns a single value (type 2) + * - this function takes a single input parameter (type 1) + * + * Or, for a less contrived version: + * + * [[[4, -1], 3], [[5, -1]], 11] + * -^^^^^^^---- ^^^^^^^ ^^ + * | | | - generic parameter, roughly `where -1: 11` + * | | | since -1 is the type parameter and 11 the trait + * | | - function output 5<-1> + * | - the overall function signature is something like + * | `fn(4<-1>, 3) -> 5<-1> where -1: 11` + * - function input, corresponds roughly to 4<-1> + * 4 is an index into the `p` array for a type + * -1 is the generic parameter, given by 11 + * + * If a generic parameter has multiple trait constraints, it gets wrapped in an array, just like + * function inputs and outputs: + * + * [-1, -1, [4, 3]] + * ^^^^^^ where -1: 4 + 3 + * + * If a generic parameter's trait constraint has generic parameters, it gets wrapped in the array + * even if only one exists. In other words, the ambiguity of `4<3>` and `4 + 3` is resolved in + * favor of `4 + 3`: + * + * [-1, -1, [[4, 3]]] + * ^^^^^^^^ where -1: 4 + 3 + * + * [-1, -1, [5, [4, 3]]] + * ^^^^^^^^^^^ where -1: 5, -2: 4 + 3 + * + * If a generic parameter has no trait constraints (like in Rust, the `Sized` constraint i + * implied and a fake `?Sized` constraint used to note its absence), it will be filled in with 0. + * * @typedef {( * 0 | * [(number|Array)] | - * [(number|Array), (number|Array)] + * [(number|Array), (number|Array)] | + * Array<(number|Array)> * )} */ let RawFunctionSearchType; @@ -136,6 +182,7 @@ let RawFunctionType; * @typedef {{ * inputs: Array, * output: Array, + * where_clause: Array>, * }} */ let FunctionSearchType; diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 6ae827dc7782..6301dd728af5 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -33,6 +33,7 @@ const itemTypes = [ "attr", "derive", "traitalias", + "generic", ]; const longItemTypes = [ @@ -67,6 +68,7 @@ const longItemTypes = [ // used for special search precedence const TY_PRIMITIVE = itemTypes.indexOf("primitive"); const TY_KEYWORD = itemTypes.indexOf("keyword"); +const TY_GENERIC = itemTypes.indexOf("generic"); const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../"; function hasOwnPropertyRustdoc(obj, property) { @@ -974,6 +976,8 @@ function initSearch(rawSearchIndex) { literalSearch: false, error: null, correction: null, + proposeCorrectionFrom: null, + proposeCorrectionTo: null, }; } @@ -1014,64 +1018,10 @@ function initSearch(rawSearchIndex) { /** * Parses the query. * - * The supported syntax by this parser is as follow: + * The supported syntax by this parser is given in the rustdoc book chapter + * /src/doc/rustdoc/src/read-documentation/search.md * - * ident = *(ALPHA / DIGIT / "_") - * path = ident *(DOUBLE-COLON/{WS} ident) [!] - * slice = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET - * arg = [type-filter *WS COLON *WS] (path [generics] / slice) - * type-sep = *WS COMMA *(COMMA) - * nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep) - * generics = OPEN-ANGLE-BRACKET [ nonempty-arg-list ] *(type-sep) - * CLOSE-ANGLE-BRACKET - * return-args = RETURN-ARROW *(type-sep) nonempty-arg-list - * - * exact-search = [type-filter *WS COLON] [ RETURN-ARROW ] *WS QUOTE ident QUOTE [ generics ] - * type-search = [ nonempty-arg-list ] [ return-args ] - * - * query = *WS (exact-search / type-search) *WS - * - * type-filter = ( - * "mod" / - * "externcrate" / - * "import" / - * "struct" / - * "enum" / - * "fn" / - * "type" / - * "static" / - * "trait" / - * "impl" / - * "tymethod" / - * "method" / - * "structfield" / - * "variant" / - * "macro" / - * "primitive" / - * "associatedtype" / - * "constant" / - * "associatedconstant" / - * "union" / - * "foreigntype" / - * "keyword" / - * "existential" / - * "attr" / - * "derive" / - * "traitalias") - * - * OPEN-ANGLE-BRACKET = "<" - * CLOSE-ANGLE-BRACKET = ">" - * OPEN-SQUARE-BRACKET = "[" - * CLOSE-SQUARE-BRACKET = "]" - * COLON = ":" - * DOUBLE-COLON = "::" - * QUOTE = %x22 - * COMMA = "," - * RETURN-ARROW = "->" - * - * ALPHA = %x41-5A / %x61-7A ; A-Z / a-z - * DIGIT = %x30-39 - * WS = %x09 / " " + * When adding new things to the parser, add them there, too! * * @param {string} val - The user query * @@ -1348,24 +1298,28 @@ function initSearch(rawSearchIndex) { * This function checks generics in search query `queryElem` can all be found in the * search index (`fnType`), * - * @param {FunctionType} fnType - The object to check. - * @param {QueryElement} queryElem - The element from the parsed query. + * @param {FunctionType} fnType - The object to check. + * @param {QueryElement} queryElem - The element from the parsed query. + * @param {[FunctionType]} whereClause - Trait bounds for generic items. + * @param {Map|null} mgensIn - Map functions generics to query generics. * * @return {boolean} - Returns true if a match, false otherwise. */ - function checkGenerics(fnType, queryElem) { - return unifyFunctionTypes(fnType.generics, queryElem.generics); + function checkGenerics(fnType, queryElem, whereClause, mgensIn) { + return unifyFunctionTypes(fnType.generics, queryElem.generics, whereClause, mgensIn); } /** * This function checks if a list of search query `queryElems` can all be found in the * search index (`fnTypes`). * - * @param {Array} fnTypes - The objects to check. - * @param {Array} queryElems - The elements from the parsed query. + * @param {Array} fnTypes - The objects to check. + * @param {Array} queryElems - The elements from the parsed query. + * @param {[FunctionType]} whereClause - Trait bounds for generic items. + * @param {Map|null} mgensIn - Map function generics to query generics. * * @return {boolean} - Returns true if a match, false otherwise. */ - function unifyFunctionTypes(fnTypes, queryElems) { + function unifyFunctionTypes(fnTypes, queryElems, whereClause, mgensIn) { // This search engine implements order-agnostic unification. There // should be no missing duplicates (generics have "bag semantics"), // and the row is allowed to have extras. @@ -1379,13 +1333,15 @@ function initSearch(rawSearchIndex) { * @type Map */ const queryElemSet = new Map(); + const mgens = new Map(mgensIn); const addQueryElemToQueryElemSet = queryElem => { let currentQueryElemList; - if (queryElemSet.has(queryElem.id)) { - currentQueryElemList = queryElemSet.get(queryElem.id); + const qid = queryElem.id; + if (queryElemSet.has(qid)) { + currentQueryElemList = queryElemSet.get(qid); } else { currentQueryElemList = []; - queryElemSet.set(queryElem.id, currentQueryElemList); + queryElemSet.set(qid, currentQueryElemList); } currentQueryElemList.push(queryElem); }; @@ -1396,44 +1352,98 @@ function initSearch(rawSearchIndex) { * @type Map */ const fnTypeSet = new Map(); - const addFnTypeToFnTypeSet = fnType => { - // Pure generic, or an item that's not matched by any query elems. - // Try [unboxing] it. - // - // [unboxing]: - // http://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf - const queryContainsArrayOrSliceElem = queryElemSet.has(typeNameIdOfArrayOrSlice); - if (fnType.id === null || !( - queryElemSet.has(fnType.id) || - (fnType.id === typeNameIdOfSlice && queryContainsArrayOrSliceElem) || - (fnType.id === typeNameIdOfArray && queryContainsArrayOrSliceElem) - )) { + /** + * If `fnType` is a concrete type with generics, replace it with its generics. + * + * If `fnType` is a generic type parameter, replace it with any traits that it's + * constrained by. This converts `T where T: Trait` into the same representation + * that's directly used by return-position `impl Trait` and by `dyn Trait` in + * all positions, so you can directly write `-> Trait` and get all three. + * + * This seems to correspond to two transforms described in + * http://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf + * One of them is actual unboxing (turning `Maybe a` into `a`), while the other is + * equivalent to converting a type parameter into an existential, which Hoogle doesn't + * seem to actually do? I notice that these two searches produce the same result: + * https://hoogle.haskell.org/?hoogle=Eq+-%3E+Set+Eq+-%3E+Set+Eq + * https://hoogle.haskell.org/?hoogle=Ord+-%3E+Set+Ord+-%3E+Set+Ord + * + * Haskell is a bit of a foreign language to me. I just want to be able to look up + * Option combinators without having to actually remember their (mostly arbitrary) + * names, and Haskell claims to already have that problem solved... + * + * @type Map + */ + const unbox = function unbox(fnType) { + if (fnType.id < 0) { + const genid = (-fnType.id) - 1; + if (whereClause && whereClause[genid]) { + for (const trait of whereClause[genid]) { + addFnTypeToFnTypeSet(trait); + } + } + mgens.set(fnType.id, 0); + } else { for (const innerFnType of fnType.generics) { addFnTypeToFnTypeSet(innerFnType); } + } + }; + /** + * @param {FunctionType} fnType + */ + const addFnTypeToFnTypeSet = function addFnTypeToFnTypeSet(fnType) { + const queryContainsArrayOrSliceElem = queryElemSet.has(typeNameIdOfArrayOrSlice); + // qsid is an index into the `queryElemSet`, while `fnType.id` is an index into + // the `fnTypeSet`. These are the same, except for generics, where they get mapped. + // If qsid = 0, it means the generic type parameter has undergone instance + // substitution, and + let qsid = fnType.id; + if (fnType.id < 0) { + if (mgens.has(fnType.id)) { + qsid = mgens.get(fnType.id); + } else { + qsid = null; + searching: for (const qid of queryElemSet.keys()) { + if (qid < 0) { + for (const qidMapped of mgens.values()) { + if (qid === qidMapped) { + continue searching; + } + } + mgens.set(fnType.id, qid); + qsid = qid; + break; + } + } + } + } + if (qsid === null || !( + queryElemSet.has(qsid) || + (qsid === typeNameIdOfSlice && queryContainsArrayOrSliceElem) || + (qsid === typeNameIdOfArray && queryContainsArrayOrSliceElem) + )) { + unbox(fnType); return; } - let currentQueryElemList = queryElemSet.get(fnType.id) || []; + let currentQueryElemList = queryElemSet.get(qsid) || []; let matchIdx = currentQueryElemList.findIndex(queryElem => { return typePassesFilter(queryElem.typeFilter, fnType.ty) && - checkGenerics(fnType, queryElem); + checkGenerics(fnType, queryElem, whereClause, mgens); }); if (matchIdx === -1 && - (fnType.id === typeNameIdOfSlice || fnType.id === typeNameIdOfArray) && + (qsid === typeNameIdOfSlice || qsid === typeNameIdOfArray) && queryContainsArrayOrSliceElem ) { currentQueryElemList = queryElemSet.get(typeNameIdOfArrayOrSlice) || []; matchIdx = currentQueryElemList.findIndex(queryElem => { return typePassesFilter(queryElem.typeFilter, fnType.ty) && - checkGenerics(fnType, queryElem); + checkGenerics(fnType, queryElem, whereClause, mgens); }); } // None of the query elems match the function type. - // Try [unboxing] it. if (matchIdx === -1) { - for (const innerFnType of fnType.generics) { - addFnTypeToFnTypeSet(innerFnType); - } + unbox(fnType); return; } let currentFnTypeList; @@ -1488,7 +1498,9 @@ function initSearch(rawSearchIndex) { continue; } } - if (queryElem.generics.length === 0 || checkGenerics(fnType, queryElem)) { + if (queryElem.generics.length === 0 + || checkGenerics(fnType, queryElem, whereClause, mgens) + ) { currentFnTypeList.splice(i, 1); const result = doHandleQueryElemList(currentFnTypeList, queryElemList); if (result) { @@ -1499,15 +1511,32 @@ function initSearch(rawSearchIndex) { } return false; }; + /** + * @param {number} id + * @param {[QueryElement]} queryElemList + */ const handleQueryElemList = (id, queryElemList) => { - if (!fnTypeSet.has(id)) { - if (id === typeNameIdOfArrayOrSlice) { + let fsid = id; + if (fsid < 0) { + fsid = null; + if (!mgens) { + return false; + } + for (const [fid, qsid] of mgens) { + if (id === qsid) { + fsid = fid; + break; + } + } + } + if (fsid === null || !fnTypeSet.has(fsid)) { + if (fsid === typeNameIdOfArrayOrSlice) { return handleQueryElemList(typeNameIdOfSlice, queryElemList) || handleQueryElemList(typeNameIdOfArray, queryElemList); } return false; } - const currentFnTypeList = fnTypeSet.get(id); + const currentFnTypeList = fnTypeSet.get(fsid); if (currentFnTypeList.length < queryElemList.length) { // It's not possible for all the query elems to find a match. return false; @@ -1518,14 +1547,14 @@ function initSearch(rawSearchIndex) { // Any items that weren't used for it can be unboxed, and might form // part of the solution for another item. for (const innerFnType of currentFnTypeList) { - addFnTypeToFnTypeSet(innerFnType); + unbox(innerFnType); } - fnTypeSet.delete(id); + fnTypeSet.delete(fsid); } return result; }; - let queryElemSetSize = -1; - while (queryElemSetSize !== queryElemSet.size) { + let queryElemSetSize = Number.MAX_VALUE; + while (queryElemSetSize > queryElemSet.size) { queryElemSetSize = queryElemSet.size; for (const [id, queryElemList] of queryElemSet) { if (handleQueryElemList(id, queryElemList)) { @@ -1533,7 +1562,14 @@ function initSearch(rawSearchIndex) { } } } - return queryElemSetSize === 0; + if (queryElemSetSize === 0) { + for (const [fid, qid] of mgens) { + mgensIn.set(fid, qid); + } + return true; + } else { + return false; + } } /** @@ -1541,13 +1577,14 @@ function initSearch(rawSearchIndex) { * generics (if any). * * @param {Array} list - * @param {QueryElement} elem - The element from the parsed query. + * @param {QueryElement} elem - The element from the parsed query. + * @param {[FunctionType]} whereClause - Trait bounds for generic items. * * @return {boolean} - Returns true if found, false otherwise. */ - function checkIfInList(list, elem) { + function checkIfInList(list, elem, whereClause) { for (const entry of list) { - if (checkType(entry, elem)) { + if (checkType(entry, elem, whereClause)) { return true; } } @@ -1559,14 +1596,26 @@ function initSearch(rawSearchIndex) { * generics (if any). * * @param {Row} row - * @param {QueryElement} elem - The element from the parsed query. + * @param {QueryElement} elem - The element from the parsed query. + * @param {[FunctionType]} whereClause - Trait bounds for generic items. * * @return {boolean} - Returns true if the type matches, false otherwise. */ - function checkType(row, elem) { + function checkType(row, elem, whereClause) { if (row.id === null) { // This is a pure "generic" search, no need to run other checks. - return row.generics.length > 0 ? checkIfInList(row.generics, elem) : false; + return row.generics.length > 0 + ? checkIfInList(row.generics, elem, whereClause) + : false; + } + + if (row.id < 0 && elem.id >= 0) { + const gid = (-row.id) - 1; + return checkIfInList(whereClause[gid], elem, whereClause); + } + + if (row.id < 0 && elem.id < 0) { + return true; } const matchesExact = row.id === elem.id; @@ -1576,7 +1625,7 @@ function initSearch(rawSearchIndex) { if ((matchesExact || matchesArrayOrSlice) && typePassesFilter(elem.typeFilter, row.ty)) { if (elem.generics.length > 0) { - return checkGenerics(row, elem); + return checkGenerics(row, elem, whereClause, new Map()); } return true; } @@ -1584,7 +1633,7 @@ function initSearch(rawSearchIndex) { // If the current item does not match, try [unboxing] the generic. // [unboxing]: // https://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf - return checkIfInList(row.generics, elem); + return checkIfInList(row.generics, elem, whereClause); } function checkPath(contains, ty, maxEditDistance) { @@ -1785,13 +1834,15 @@ function initSearch(rawSearchIndex) { const fullId = row.id; const searchWord = searchWords[pos]; - const in_args = row.type && row.type.inputs && checkIfInList(row.type.inputs, elem); + const in_args = row.type && row.type.inputs + && checkIfInList(row.type.inputs, elem, row.type.where_clause); if (in_args) { // path_dist is 0 because no parent path information is currently stored // in the search index addIntoResults(results_in_args, fullId, pos, -1, 0, 0, maxEditDistance); } - const returned = row.type && row.type.output && checkIfInList(row.type.output, elem); + const returned = row.type && row.type.output + && checkIfInList(row.type.output, elem, row.type.where_clause); if (returned) { addIntoResults(results_returned, fullId, pos, -1, 0, 0, maxEditDistance); } @@ -1853,10 +1904,24 @@ function initSearch(rawSearchIndex) { } // If the result is too "bad", we return false and it ends this search. - if (!unifyFunctionTypes(row.type.inputs, parsedQuery.elems)) { + let mgens; + if (row.type.where_clause && row.type.where_clause.length > 0) { + mgens = new Map(); + } + if (!unifyFunctionTypes( + row.type.inputs, + parsedQuery.elems, + row.type.where_clause, + mgens + )) { return; } - if (!unifyFunctionTypes(row.type.output, parsedQuery.returned)) { + if (!unifyFunctionTypes( + row.type.output, + parsedQuery.returned, + row.type.where_clause, + mgens + )) { return; } @@ -1875,6 +1940,11 @@ function initSearch(rawSearchIndex) { } const maxEditDistance = Math.floor(queryLen / 3); + /** + * @type {Map} + */ + const genericSymbols = new Map(); + /** * Convert names to ids in parsed query elements. * This is not used for the "In Names" tab, but is used for the @@ -1910,6 +1980,46 @@ function initSearch(rawSearchIndex) { } elem.id = match; } + if ((elem.id === null && parsedQuery.foundElems > 1 && elem.typeFilter === -1) + || elem.typeFilter === TY_GENERIC) { + if (genericSymbols.has(elem.name)) { + elem.id = genericSymbols.get(elem.name); + } else { + elem.id = -(genericSymbols.size + 1); + genericSymbols.set(elem.name, elem.id); + } + if (elem.typeFilter === -1 && elem.name.length >= 3) { + // Silly heuristic to catch if the user probably meant + // to not write a generic parameter. We don't use it, + // just bring it up. + const maxPartDistance = Math.floor(elem.name.length / 3); + let matchDist = maxPartDistance + 1; + let matchName = ""; + for (const name of typeNameIdMap.keys()) { + const dist = editDistance(name, elem.name, maxPartDistance); + if (dist <= matchDist && dist <= maxPartDistance) { + if (dist === matchDist && matchName > name) { + continue; + } + matchDist = dist; + matchName = name; + } + } + if (matchName !== "") { + parsedQuery.proposeCorrectionFrom = elem.name; + parsedQuery.proposeCorrectionTo = matchName; + } + } + elem.typeFilter = TY_GENERIC; + } + if (elem.generics.length > 0 && elem.typeFilter === TY_GENERIC) { + // Rust does not have HKT + parsedQuery.error = [ + "Generic type parameter ", + elem.name, + " does not accept generic parameters", + ]; + } for (const elem2 of elem.generics) { convertNameToId(elem2); } @@ -1943,8 +2053,11 @@ function initSearch(rawSearchIndex) { elem = parsedQuery.returned[0]; for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) { row = searchIndex[i]; - in_returned = row.type && - unifyFunctionTypes(row.type.output, parsedQuery.returned); + in_returned = row.type && unifyFunctionTypes( + row.type.output, + parsedQuery.returned, + row.type.where_clause, new Map() + ); if (in_returned) { addIntoResults( results_others, @@ -2295,6 +2408,13 @@ ${item.displayPath}${name}\ "Showing results for closest type name " + `"${results.query.correction}" instead.`; } + if (results.query.proposeCorrectionFrom !== null) { + const orig = results.query.proposeCorrectionFrom; + const targ = results.query.proposeCorrectionTo; + output += "

" + + `Type "${orig}" not found and used as generic parameter. ` + + `Consider searching for "${targ}" instead.

`; + } const resultsElem = document.createElement("div"); resultsElem.id = "results"; @@ -2396,37 +2516,54 @@ ${item.displayPath}${name}\ * @return {Array} */ function buildItemSearchTypeAll(types, lowercasePaths) { + return types.map(type => buildItemSearchType(type, lowercasePaths)); + } + + /** + * Converts a single type. + * + * @param {RawFunctionType} type + */ + function buildItemSearchType(type, lowercasePaths) { const PATH_INDEX_DATA = 0; const GENERICS_DATA = 1; - return types.map(type => { - let pathIndex, generics; - if (typeof type === "number") { - pathIndex = type; - generics = []; - } else { - pathIndex = type[PATH_INDEX_DATA]; - generics = buildItemSearchTypeAll( - type[GENERICS_DATA], - lowercasePaths - ); - } - // `0` is used as a sentinel because it's fewer bytes than `null` - if (pathIndex === 0) { - return { - id: null, - ty: null, - path: null, - generics: generics, - }; - } - const item = lowercasePaths[pathIndex - 1]; + let pathIndex, generics; + if (typeof type === "number") { + pathIndex = type; + generics = []; + } else { + pathIndex = type[PATH_INDEX_DATA]; + generics = buildItemSearchTypeAll( + type[GENERICS_DATA], + lowercasePaths + ); + } + if (pathIndex < 0) { + // types less than 0 are generic parameters + // the actual names of generic parameters aren't stored, since they aren't API return { - id: buildTypeMapIndex(item.name), - ty: item.ty, - path: item.path, - generics: generics, + id: pathIndex, + ty: TY_GENERIC, + path: null, + generics, }; - }); + } + if (pathIndex === 0) { + // `0` is used as a sentinel because it's fewer bytes than `null` + return { + id: null, + ty: null, + path: null, + generics, + }; + } + const item = lowercasePaths[pathIndex - 1]; + return { + id: buildTypeMapIndex(item.name), + ty: item.ty, + path: item.path, + generics, + }; } /** @@ -2454,23 +2591,7 @@ ${item.displayPath}${name}\ } let inputs, output; if (typeof functionSearchType[INPUTS_DATA] === "number") { - const pathIndex = functionSearchType[INPUTS_DATA]; - if (pathIndex === 0) { - inputs = [{ - id: null, - ty: null, - path: null, - generics: [], - }]; - } else { - const item = lowercasePaths[pathIndex - 1]; - inputs = [{ - id: buildTypeMapIndex(item.name), - ty: item.ty, - path: item.path, - generics: [], - }]; - } + inputs = [buildItemSearchType(functionSearchType[INPUTS_DATA], lowercasePaths)]; } else { inputs = buildItemSearchTypeAll( functionSearchType[INPUTS_DATA], @@ -2479,23 +2600,7 @@ ${item.displayPath}${name}\ } if (functionSearchType.length > 1) { if (typeof functionSearchType[OUTPUT_DATA] === "number") { - const pathIndex = functionSearchType[OUTPUT_DATA]; - if (pathIndex === 0) { - output = [{ - id: null, - ty: null, - path: null, - generics: [], - }]; - } else { - const item = lowercasePaths[pathIndex - 1]; - output = [{ - id: buildTypeMapIndex(item.name), - ty: item.ty, - path: item.path, - generics: [], - }]; - } + output = [buildItemSearchType(functionSearchType[OUTPUT_DATA], lowercasePaths)]; } else { output = buildItemSearchTypeAll( functionSearchType[OUTPUT_DATA], @@ -2505,8 +2610,15 @@ ${item.displayPath}${name}\ } else { output = []; } + const where_clause = []; + const l = functionSearchType.length; + for (let i = 2; i < l; ++i) { + where_clause.push(typeof functionSearchType[i] === "number" + ? [buildItemSearchType(functionSearchType[i], lowercasePaths)] + : buildItemSearchTypeAll(functionSearchType[i], lowercasePaths)); + } return { - inputs, output, + inputs, output, where_clause, }; } diff --git a/tests/rustdoc-gui/search-corrections.goml b/tests/rustdoc-gui/search-corrections.goml index 5d1b83b35c5e..46a14f967497 100644 --- a/tests/rustdoc-gui/search-corrections.goml +++ b/tests/rustdoc-gui/search-corrections.goml @@ -54,3 +54,45 @@ assert-text: ( ".search-corrections", "Type \"notablestructwithlongnamr\" not found. Showing results for closest type name \"notablestructwithlongname\" instead." ) + +// Now, generic correction +go-to: "file://" + |DOC_PATH| + "/test_docs/index.html" +// Intentionally wrong spelling of "NotableStructWithLongName" +write: (".search-input", "NotableStructWithLongNamr, NotableStructWithLongNamr") +// To be SURE that the search will be run. +press-key: 'Enter' +// Waiting for the search results to appear... +wait-for: "#search-tabs" + +assert-css: (".search-corrections", { + "display": "block" +}) +assert-text: ( + ".search-corrections", + "Type \"notablestructwithlongnamr\" not found and used as generic parameter. Consider searching for \"notablestructwithlongname\" instead." +) + +// Now, generic correction plus error +go-to: "file://" + |DOC_PATH| + "/test_docs/index.html" +// Intentionally wrong spelling of "NotableStructWithLongName" +write: (".search-input", "NotableStructWithLongNamr,y") +// To be SURE that the search will be run. +press-key: 'Enter' +// Waiting for the search results to appear... +wait-for: "#search-tabs" + +assert-css: (".search-corrections", { + "display": "block" +}) +assert-text: ( + ".search-corrections", + "Type \"notablestructwithlongnamr\" not found and used as generic parameter. Consider searching for \"notablestructwithlongname\" instead." +) + +assert-css: (".error", { + "display": "block" +}) +assert-text: ( + ".error", + "Query parser error: \"Generic type parameter notablestructwithlongnamr does not accept generic parameters\"." +) diff --git a/tests/rustdoc-js-std/option-type-signatures.js b/tests/rustdoc-js-std/option-type-signatures.js index 259978506614..c5cd6b1d2ef8 100644 --- a/tests/rustdoc-js-std/option-type-signatures.js +++ b/tests/rustdoc-js-std/option-type-signatures.js @@ -1,3 +1,5 @@ +const FILTER_CRATE = "std"; + const EXPECTED = [ { 'query': 'option, fnonce -> option', @@ -19,4 +21,55 @@ const EXPECTED = [ { 'path': 'std::option::Option', 'name': 'as_mut_slice' }, ], }, + { + 'query': 'option, option -> option', + 'others': [ + { 'path': 'std::option::Option', 'name': 'or' }, + { 'path': 'std::option::Option', 'name': 'xor' }, + ], + }, + { + 'query': 'option, option -> option', + 'others': [ + { 'path': 'std::option::Option', 'name': 'and' }, + { 'path': 'std::option::Option', 'name': 'zip' }, + ], + }, + { + 'query': 'option, option -> option', + 'others': [ + { 'path': 'std::option::Option', 'name': 'zip' }, + ], + }, + { + 'query': 'option, option -> option', + 'others': [ + { 'path': 'std::option::Option', 'name': 'zip' }, + ], + }, + { + 'query': 'option, e -> result', + 'others': [ + { 'path': 'std::option::Option', 'name': 'ok_or' }, + { 'path': 'std::result::Result', 'name': 'transpose' }, + ], + }, + { + 'query': 'result, e> -> option>', + 'others': [ + { 'path': 'std::result::Result', 'name': 'transpose' }, + ], + }, + { + 'query': 'option, option -> bool', + 'others': [ + { 'path': 'std::option::Option', 'name': 'eq' }, + ], + }, + { + 'query': 'option> -> option', + 'others': [ + { 'path': 'std::option::Option', 'name': 'flatten' }, + ], + }, ]; diff --git a/tests/rustdoc-js/type-parameters.js b/tests/rustdoc-js/type-parameters.js new file mode 100644 index 000000000000..e695f189bb67 --- /dev/null +++ b/tests/rustdoc-js/type-parameters.js @@ -0,0 +1,87 @@ +// exact-check +// ignore-order + +const EXPECTED = [ + { + query: '-> trait:Some', + others: [ + { path: 'foo', name: 'alef' }, + { path: 'foo', name: 'alpha' }, + ], + }, + { + query: '-> generic:T', + others: [ + { path: 'foo', name: 'bet' }, + { path: 'foo', name: 'alef' }, + { path: 'foo', name: 'beta' }, + ], + }, + { + query: 'A -> B', + others: [ + { path: 'foo', name: 'bet' }, + ], + }, + { + query: 'A -> A', + others: [ + { path: 'foo', name: 'beta' }, + ], + }, + { + query: 'A, A', + others: [ + { path: 'foo', name: 'alternate' }, + ], + }, + { + query: 'A, B', + others: [ + { path: 'foo', name: 'other' }, + ], + }, + { + query: 'Other, Other', + others: [ + { path: 'foo', name: 'other' }, + { path: 'foo', name: 'alternate' }, + ], + }, + { + query: 'generic:T', + in_args: [ + { path: 'foo', name: 'bet' }, + { path: 'foo', name: 'beta' }, + { path: 'foo', name: 'other' }, + { path: 'foo', name: 'alternate' }, + ], + }, + { + query: 'generic:Other', + in_args: [ + { path: 'foo', name: 'bet' }, + { path: 'foo', name: 'beta' }, + { path: 'foo', name: 'other' }, + { path: 'foo', name: 'alternate' }, + ], + }, + { + query: 'trait:Other', + in_args: [ + { path: 'foo', name: 'other' }, + { path: 'foo', name: 'alternate' }, + ], + }, + { + query: 'Other', + in_args: [ + { path: 'foo', name: 'other' }, + { path: 'foo', name: 'alternate' }, + ], + }, + { + query: 'trait:T', + in_args: [], + }, +]; diff --git a/tests/rustdoc-js/type-parameters.rs b/tests/rustdoc-js/type-parameters.rs new file mode 100644 index 000000000000..cda5e26171fc --- /dev/null +++ b/tests/rustdoc-js/type-parameters.rs @@ -0,0 +1,15 @@ +#![crate_name="foo"] + +pub trait Some {} +impl Some for () {} +pub trait Other {} +impl Other for () {} + +pub fn alef() -> T { loop {} } +pub fn alpha() -> impl Some { } + +pub fn bet(t: T) -> U { loop {} } +pub fn beta(t: T) -> T {} + +pub fn other(t: T, u: U) { loop {} } +pub fn alternate(t: T, u: T) { loop {} } From b6bb06ca5d3a5edac2c72549101fd8f932f7bc04 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 16 Jun 2023 17:35:02 -0700 Subject: [PATCH 027/111] rustdoc: write detailed chapter on search engine --- src/doc/rustdoc/src/SUMMARY.md | 1 + src/doc/rustdoc/src/how-to-read-rustdoc.md | 53 +--- .../rustdoc/src/read-documentation/search.md | 237 ++++++++++++++++++ 3 files changed, 242 insertions(+), 49 deletions(-) create mode 100644 src/doc/rustdoc/src/read-documentation/search.md diff --git a/src/doc/rustdoc/src/SUMMARY.md b/src/doc/rustdoc/src/SUMMARY.md index 12a8b2b8db4b..3b6883c0d557 100644 --- a/src/doc/rustdoc/src/SUMMARY.md +++ b/src/doc/rustdoc/src/SUMMARY.md @@ -4,6 +4,7 @@ - [Command-line arguments](command-line-arguments.md) - [How to read rustdoc output](how-to-read-rustdoc.md) - [In-doc settings](read-documentation/in-doc-settings.md) + - [Search](read-documentation/search.md) - [How to write documentation](how-to-write-documentation.md) - [What to include (and exclude)](write-documentation/what-to-include.md) - [The `#[doc]` attribute](write-documentation/the-doc-attribute.md) diff --git a/src/doc/rustdoc/src/how-to-read-rustdoc.md b/src/doc/rustdoc/src/how-to-read-rustdoc.md index 9deb7009cfe0..1f594e6b80ea 100644 --- a/src/doc/rustdoc/src/how-to-read-rustdoc.md +++ b/src/doc/rustdoc/src/how-to-read-rustdoc.md @@ -59,56 +59,11 @@ or the current item whose documentation is being displayed. ## The Theme Picker and Search Interface When viewing `rustdoc`'s output in a browser with JavaScript enabled, -a dynamic interface appears at the top of the page composed of the search -interface, help screen, and options. +a dynamic interface appears at the top of the page composed of the [search] +interface, help screen, and [options]. -### The Search Interface - -Typing in the search bar instantly searches the available documentation for -the string entered with a fuzzy matching algorithm that is tolerant of minor -typos. - -By default, the search results given are "In Names", -meaning that the fuzzy match is made against the names of items. -Matching names are shown on the left, and the first few words of their -descriptions are given on the right. -By clicking an item, you will navigate to its particular documentation. - -There are two other sets of results, shown as tabs in the search results pane. -"In Parameters" shows matches for the string in the types of parameters to -functions, and "In Return Types" shows matches in the return types of functions. -Both are very useful when looking for a function whose name you can't quite -bring to mind when you know the type you have or want. - -Names in the search interface can be prefixed with an item type followed by a -colon (such as `mod:`) to restrict the results to just that kind of item. Also, -searching for `println!` will search for a macro named `println`, just like -searching for `macro:println` does. - -Function signature searches can query generics, wrapped in angle brackets, and -traits are normalized like types in the search engine. For example, a function -with the signature `fn my_function>(input: I) -> usize` -can be matched with the following queries: - -* `Iterator -> usize` -* `trait:Iterator -> primitive:usize` -* `Iterator -> usize` - -Generics and function parameters are order-agnostic, but sensitive to nesting -and number of matches. For example, a function with the signature -`fn read_all(&mut self: impl Read) -> Result, Error>` -will match these queries: - -* `Read -> Result, Error>` -* `Read -> Result` -* `Read -> Result>` - -But it *does not* match `Result` or `Result>`. - -Function signature searches also support arrays and slices. The explicit name -`primitive:slice` and `primitive:array` can be used to match a slice -or array of bytes, while square brackets `[u8]` will match either one. Empty -square brackets, `[]`, will match any slice regardless of what it contains. +[options]: read-documentation/in-doc-settings.html +[search]: read-documentation/search.md Paths are supported as well, you can look for `Vec::new` or `Option::Some` or even `module::module_child::another_child::struct::field`. Whitespace characters diff --git a/src/doc/rustdoc/src/read-documentation/search.md b/src/doc/rustdoc/src/read-documentation/search.md new file mode 100644 index 000000000000..5f5c52dd09e0 --- /dev/null +++ b/src/doc/rustdoc/src/read-documentation/search.md @@ -0,0 +1,237 @@ +# Rustdoc search + +Typing in the search bar instantly searches the available documentation, +matching either the name and path of an item, or a function's approximate +type signature. + +## Search By Name + +To search by the name of an item (items include modules, types, traits, +functions, and macros), write its name or path. As a special case, the parts +of a path that normally get divided by `::` double colons can instead be +separated by spaces. For example: + + * [`vec new`] and [`vec::new`] both show the function `std::vec::Vec::new` + as a result. + * [`vec`], [`vec vec`], [`std::vec`], and [`std::vec::Vec`] all include the struct + `std::vec::Vec` itself in the results (and all but the last one also + include the module in the results). + +[`vec new`]: ../../std/vec/struct.Vec.html?search=vec%20new&filter-crate=std +[`vec::new`]: ../../std/vec/struct.Vec.html?search=vec::new&filter-crate=std +[`vec`]: ../../std/vec/struct.Vec.html?search=vec&filter-crate=std +[`vec vec`]: ../../std/vec/struct.Vec.html?search=vec%20vec&filter-crate=std +[`std::vec`]: ../../std/vec/struct.Vec.html?search=std::vec&filter-crate=std +[`std::vec::Vec`]: ../../std/vec/struct.Vec.html?search=std::vec::Vec&filter-crate=std +[`std::vec::Vec`]: ../../std/vec/struct.Vec.html?search=std::vec::Vec&filter-crate=std + +As a quick way to trim down the list of results, there's a drop-down selector +below the search input, labeled "Results in \[std\]". Clicking it can change +which crate is being searched. + +Rustdoc uses a fuzzy matching function that can tolerate typos for this, +though it's based on the length of the name that's typed in, so a good example +of how this works would be [`HahsMap`]. To avoid this, wrap the item in quotes, +searching for `"HahsMap"` (in this example, no results will be returned). + +[`HahsMap`]: ../../std/collections/struct.HashMap.html?search=HahsMap&filter-crate=std + +### Tabs in the Search By Name interface + +In fact, using [`HahsMap`] again as the example, it tells you that you're +using "In Names" by default, but also lists two other tabs below the crate +drop-down: "In Parameters" and "In Return Types". + +These two tabs are lists of functions, defined on the closest matching type +to the search (for `HahsMap`, it loudly auto-corrects to `hashmap`). This +auto-correct only kicks in if nothing is found that matches the literal. + +These tabs are not just methods. For example, searching the alloc crate for +[`Layout`] also lists functions that accept layouts even though they're +methods on the allocator or free functions. + +[`Layout`]: ../../alloc/index.html?search=Layout&filter-crate=alloc + +## Searching By Type Signature for functions + +If you know more specifically what the function you want to look at does, +Rustdoc can search by more than one type at once in the parameters and return +value. Multiple parameters are separated by `,` commas, and the return value +is written with after a `->` arrow. + +Before describing the syntax in more detail, here's a few sample searches of +the standard library and functions that are included in the results list: + +| Query | Results | +|-------|--------| +| [`usize -> vec`][] | `slice::repeat` and `Vec::with_capacity` | +| [`vec, vec -> bool`][] | `Vec::eq` | +| [`option, fnonce -> option`][] | `Option::map` and `Option::and_then` | +| [`option, fnonce -> option`][] | `Option::filter` and `Option::inspect` | +| [`option -> default`][] | `Option::unwrap_or_default` | +| [`stdout, [u8]`][stdoutu8] | `Stdout::write` | +| [`any -> !`][] | `panic::panic_any` | + +[`usize -> vec`]: ../../std/vec/struct.Vec.html?search=usize%20-%3E%20vec&filter-crate=std +[`vec, vec -> bool`]: ../../std/vec/struct.Vec.html?search=vec,%20vec%20-%3E%20bool&filter-crate=std +[`option, fnonce -> option`]: ../../std/vec/struct.Vec.html?search=option%2C%20fnonce%20->%20option&filter-crate=std +[`option, fnonce -> option`]: ../../std/vec/struct.Vec.html?search=option%2C%20fnonce%20->%20option&filter-crate=std +[`option -> default`]: ../../std/vec/struct.Vec.html?search=option%20-%3E%20default&filter-crate=std +[`any -> !`]: ../../std/vec/struct.Vec.html?search=any%20-%3E%20!&filter-crate=std +[stdoutu8]: ../../std/vec/struct.Vec.html?search=stdout%2C%20[u8]&filter-crate=std + +### How type-based search works + +In a complex type-based search, Rustdoc always treats every item as literal. +If a name is used and nothing in the docs matches the individual item, such as +a typo-ed [`uize -> vec`][] search, the item `uize` is treated as a generic +type parameter (resulting in `vec::from` and other generic vec constructors). + +[`uize -> vec`]: ../../std/vec/struct.Vec.html?search=uize%20-%3E%20vec&filter-crate=std + +After deciding which items are type parameters and which are actual types, it +then searches by matching up the function parameters (written before the `->`) +and the return types (written after the `->`). Type matching is order-agnostic, +and allows items to be left out of the query, but items that are present in the +query must be present in the function for it to match. + +Function signature searches can query generics, wrapped in angle brackets, and +traits will be normalized like types in the search engine if no type parameters +match them. For example, a function with the signature +`fn my_function>(input: I) -> usize` +can be matched with the following queries: + +* `Iterator -> usize` +* `Iterator -> usize` + +Generics and function parameters are order-agnostic, but sensitive to nesting +and number of matches. For example, a function with the signature +`fn read_all(&mut self: impl Read) -> Result, Error>` +will match these queries: + +* `Read -> Result, Error>` +* `Read -> Result` +* `Read -> Result>` + +But it *does not* match `Result` or `Result>`. + +Function signature searches also support arrays and slices. The explicit name +`primitive:slice` and `primitive:array` can be used to match a slice +or array of bytes, while square brackets `[u8]` will match either one. Empty +square brackets, `[]`, will match any slice or array regardless of what +it contains, while a slice with a type parameter, like `[T]`, will only match +functions that actually operate on generic slices. + +### Limitations and quirks of type-based search + +Type-based search is still a buggy, experimental, work-in-progress feature. +Most of these limitations should be addressed in future version of Rustdoc. + + * There's no way to write trait constraints on generic parameters. + You can name traits directly, and if there's a type parameter + with that bound, it'll match, but `option -> T where T: Default` + cannot be precisely searched for (use `option -> Default`). + + * Type parameters match type parameters, such that `Option` matches + `Option`, but never match concrete types in function signatures. + A trait named as if it were a type, such as `Option`, will match + a type parameter constrained by that trait, such as + `Option where T: Read`, as well as matching `dyn Trait` and + `impl Trait`. + + * `impl Trait` in argument position is treated exactly like a type + parameter, but in return position it will not match type parameters. + + * Any type named in a complex type-based search will be assumed to be a + type parameter if nothing matching the name exactly is found. If you + want to force a type parameter, write `generic:T` and it will be used + as a type parameter even if a matching name is found. If you know + that you don't want a type parameter, you can force it to match + something else by giving it a different prefix like `struct:T`. + + * It's impossible to search for references, pointers, or tuples. The + wrapped types can be searched for, so a function that takes `&File` can + be found with `File`, but you'll get a parse error when typing an `&` + into the search field. Similarly, `Option<(T, U)>` can be matched with + `Option`, but `(` will give a parse error. + + * Path searches, like `hash_map::Entry`, don't work in type-based search. + + * Searching for lifetimes is not supported. + + * It's impossible to search for closures based on their parameters or + return values. + + * It's impossible to search based on the length of an array. + +## Item filtering + +Names in the search interface can be prefixed with an item type followed by a +colon (such as `mod:`) to restrict the results to just that kind of item. Also, +searching for `println!` will search for a macro named `println`, just like +searching for `macro:println` does. The complete list of available filters is +given under the ? Help area, and in the detailed syntax below. + +Item filters can be used in both name-based and type signature-based searches. + +## Search query syntax + +```text +ident = *(ALPHA / DIGIT / "_") +path = ident *(DOUBLE-COLON ident) [!] +slice = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET +arg = [type-filter *WS COLON *WS] (path [generics] / slice / [!]) +type-sep = COMMA/WS *(COMMA/WS) +nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep) +generics = OPEN-ANGLE-BRACKET [ nonempty-arg-list ] *(type-sep) + CLOSE-ANGLE-BRACKET +return-args = RETURN-ARROW *(type-sep) nonempty-arg-list + +exact-search = [type-filter *WS COLON] [ RETURN-ARROW ] *WS QUOTE ident QUOTE [ generics ] +type-search = [ nonempty-arg-list ] [ return-args ] + +query = *WS (exact-search / type-search) *WS + +type-filter = ( + "mod" / + "externcrate" / + "import" / + "struct" / + "enum" / + "fn" / + "type" / + "static" / + "trait" / + "impl" / + "tymethod" / + "method" / + "structfield" / + "variant" / + "macro" / + "primitive" / + "associatedtype" / + "constant" / + "associatedconstant" / + "union" / + "foreigntype" / + "keyword" / + "existential" / + "attr" / + "derive" / + "traitalias" / + "generic") + +OPEN-ANGLE-BRACKET = "<" +CLOSE-ANGLE-BRACKET = ">" +OPEN-SQUARE-BRACKET = "[" +CLOSE-SQUARE-BRACKET = "]" +COLON = ":" +DOUBLE-COLON = "::" +QUOTE = %x22 +COMMA = "," +RETURN-ARROW = "->" + +ALPHA = %x41-5A / %x61-7A ; A-Z / a-z +DIGIT = %x30-39 +WS = %x09 / " " +``` From 89a4c7f552558c293b29437b4b79223786f1923d Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sat, 5 Aug 2023 11:22:21 -0700 Subject: [PATCH 028/111] rustdoc: bug fix for `-> option` --- src/librustdoc/html/static/js/externs.js | 1 + src/librustdoc/html/static/js/search.js | 5 ++++- tests/rustdoc-js-std/option-type-signatures.js | 6 ++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/librustdoc/html/static/js/externs.js b/src/librustdoc/html/static/js/externs.js index 9595aae4020b..c7811b43d164 100644 --- a/src/librustdoc/html/static/js/externs.js +++ b/src/librustdoc/html/static/js/externs.js @@ -37,6 +37,7 @@ let ParserState; * args: Array, * returned: Array, * foundElems: number, + * totalElems: number, * literalSearch: boolean, * corrections: Array<{from: string, to: integer}>, * }} diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 6301dd728af5..fb478cbff3c2 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -973,6 +973,8 @@ function initSearch(rawSearchIndex) { returned: [], // Total number of "top" elements (does not include generics). foundElems: 0, + // Total number of elements (includes generics). + totalElems: 0, literalSearch: false, error: null, correction: null, @@ -1074,6 +1076,7 @@ function initSearch(rawSearchIndex) { query.literalSearch = parserState.totalElems > 1; } query.foundElems = query.elems.length + query.returned.length; + query.totalElems = parserState.totalElems; return query; } @@ -1980,7 +1983,7 @@ function initSearch(rawSearchIndex) { } elem.id = match; } - if ((elem.id === null && parsedQuery.foundElems > 1 && elem.typeFilter === -1) + if ((elem.id === null && parsedQuery.totalElems > 1 && elem.typeFilter === -1) || elem.typeFilter === TY_GENERIC) { if (genericSymbols.has(elem.name)) { elem.id = genericSymbols.get(elem.name); diff --git a/tests/rustdoc-js-std/option-type-signatures.js b/tests/rustdoc-js-std/option-type-signatures.js index c5cd6b1d2ef8..37bb3b14ab38 100644 --- a/tests/rustdoc-js-std/option-type-signatures.js +++ b/tests/rustdoc-js-std/option-type-signatures.js @@ -72,4 +72,10 @@ const EXPECTED = [ { 'path': 'std::option::Option', 'name': 'flatten' }, ], }, + { + 'query': 'option', + 'returned': [ + { 'path': 'std::result::Result', 'name': 'ok' }, + ], + }, ]; From 60688500089f8e182e35f7ae540e9941734053a4 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sat, 5 Aug 2023 12:27:58 -0700 Subject: [PATCH 029/111] rustdoc: fix test case for generics that look like names --- src/librustdoc/html/static/js/search.js | 3 ++- src/tools/rustdoc-js/tester.js | 4 +++- tests/rustdoc-js/generics-trait.js | 8 ++++++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index fb478cbff3c2..b867311aca10 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -1983,7 +1983,8 @@ function initSearch(rawSearchIndex) { } elem.id = match; } - if ((elem.id === null && parsedQuery.totalElems > 1 && elem.typeFilter === -1) + if ((elem.id === null && parsedQuery.totalElems > 1 && elem.typeFilter === -1 + && elem.generics.length === 0) || elem.typeFilter === TY_GENERIC) { if (genericSymbols.has(elem.name)) { elem.id = genericSymbols.get(elem.name); diff --git a/src/tools/rustdoc-js/tester.js b/src/tools/rustdoc-js/tester.js index 416517d15f5d..c7e6dd3615e9 100644 --- a/src/tools/rustdoc-js/tester.js +++ b/src/tools/rustdoc-js/tester.js @@ -23,7 +23,9 @@ function contentToDiffLine(key, value) { } function shouldIgnoreField(fieldName) { - return fieldName === "query" || fieldName === "correction"; + return fieldName === "query" || fieldName === "correction" || + fieldName === "proposeCorrectionFrom" || + fieldName === "proposeCorrectionTo"; } // This function is only called when no matching result was found and therefore will only display diff --git a/tests/rustdoc-js/generics-trait.js b/tests/rustdoc-js/generics-trait.js index 4ccfb8f4e4d0..a71393b5e050 100644 --- a/tests/rustdoc-js/generics-trait.js +++ b/tests/rustdoc-js/generics-trait.js @@ -12,11 +12,15 @@ const EXPECTED = [ ], }, { - 'query': 'Result', - 'correction': null, + 'query': 'Resulx', 'in_args': [], 'returned': [], }, + { + 'query': 'Result', + 'proposeCorrectionFrom': 'SomeTraiz', + 'proposeCorrectionTo': 'SomeTrait', + }, { 'query': 'OtherThingxxxxxxxx', 'correction': null, From f42f357a73fb43c4225ca9c6f24da1f9cec5c9ae Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sat, 5 Aug 2023 14:06:24 -0700 Subject: [PATCH 030/111] rustdoc: update tests for generic parsing and correction --- tests/rustdoc-gui/search-corrections.goml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/rustdoc-gui/search-corrections.goml b/tests/rustdoc-gui/search-corrections.goml index 46a14f967497..aeb3c9b31a3f 100644 --- a/tests/rustdoc-gui/search-corrections.goml +++ b/tests/rustdoc-gui/search-corrections.goml @@ -75,7 +75,7 @@ assert-text: ( // Now, generic correction plus error go-to: "file://" + |DOC_PATH| + "/test_docs/index.html" // Intentionally wrong spelling of "NotableStructWithLongName" -write: (".search-input", "NotableStructWithLongNamr,y") +write: (".search-input", "Foo,y") // To be SURE that the search will be run. press-key: 'Enter' // Waiting for the search results to appear... @@ -89,6 +89,14 @@ assert-text: ( "Type \"notablestructwithlongnamr\" not found and used as generic parameter. Consider searching for \"notablestructwithlongname\" instead." ) +go-to: "file://" + |DOC_PATH| + "/test_docs/index.html" +// Intentionally wrong spelling of "NotableStructWithLongName" +write: (".search-input", "generic:NotableStructWithLongNamr,y") +// To be SURE that the search will be run. +press-key: 'Enter' +// Waiting for the search results to appear... +wait-for: "#search-tabs" + assert-css: (".error", { "display": "block" }) From 9ccb217422628107b9b6388a30879ec0866ebc9c Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sun, 3 Sep 2023 14:42:54 -0700 Subject: [PATCH 031/111] Update docs since path-based type search works now --- src/doc/rustdoc/src/read-documentation/search.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/doc/rustdoc/src/read-documentation/search.md b/src/doc/rustdoc/src/read-documentation/search.md index 5f5c52dd09e0..d794dbbf250c 100644 --- a/src/doc/rustdoc/src/read-documentation/search.md +++ b/src/doc/rustdoc/src/read-documentation/search.md @@ -71,6 +71,7 @@ the standard library and functions that are included in the results list: | [`option -> default`][] | `Option::unwrap_or_default` | | [`stdout, [u8]`][stdoutu8] | `Stdout::write` | | [`any -> !`][] | `panic::panic_any` | +| [`vec::intoiter -> [T]`][iterasslice] | `IntoIter::as_slice` | [`usize -> vec`]: ../../std/vec/struct.Vec.html?search=usize%20-%3E%20vec&filter-crate=std [`vec, vec -> bool`]: ../../std/vec/struct.Vec.html?search=vec,%20vec%20-%3E%20bool&filter-crate=std @@ -79,10 +80,11 @@ the standard library and functions that are included in the results list: [`option -> default`]: ../../std/vec/struct.Vec.html?search=option%20-%3E%20default&filter-crate=std [`any -> !`]: ../../std/vec/struct.Vec.html?search=any%20-%3E%20!&filter-crate=std [stdoutu8]: ../../std/vec/struct.Vec.html?search=stdout%2C%20[u8]&filter-crate=std +[iterasslice]: ../../std/vec/struct.Vec.html?search=vec%3A%3Aintoiter%20->%20[T]&filter-crate=std ### How type-based search works -In a complex type-based search, Rustdoc always treats every item as literal. +In a complex type-based search, Rustdoc always treats every item's name as literal. If a name is used and nothing in the docs matches the individual item, such as a typo-ed [`uize -> vec`][] search, the item `uize` is treated as a generic type parameter (resulting in `vec::from` and other generic vec constructors). @@ -155,8 +157,6 @@ Most of these limitations should be addressed in future version of Rustdoc. into the search field. Similarly, `Option<(T, U)>` can be matched with `Option`, but `(` will give a parse error. - * Path searches, like `hash_map::Entry`, don't work in type-based search. - * Searching for lifetimes is not supported. * It's impossible to search for closures based on their parameters or From 52aff538123137fb395a52c4ddb6fba2ef6fed07 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 2 Sep 2023 21:29:27 +0000 Subject: [PATCH 032/111] Correctly deny late-bound lifetimes from parent in anon consts and TAITs --- compiler/rustc_hir_analysis/messages.ftl | 12 ++-- .../rustc_hir_analysis/src/astconv/mod.rs | 4 +- .../src/collect/resolve_bound_vars.rs | 71 +++++++++++++------ compiler/rustc_hir_analysis/src/errors.rs | 16 ++++- .../late-bound-vars/in_closure.rs | 28 +++----- .../late-bound-vars/in_closure.stderr | 30 +++++--- .../const-generics/late-bound-vars/simple.rs | 27 +++---- .../late-bound-vars/simple.stderr | 29 +++++--- tests/ui/consts/escaping-bound-var.rs | 13 ++++ tests/ui/consts/escaping-bound-var.stderr | 20 ++++++ .../capture-late-ct-in-anon.rs | 2 +- .../capture-late-ct-in-anon.stderr | 2 +- .../late-bound-in-anon-ct.rs | 2 +- .../late-bound-in-anon-ct.stderr | 2 +- .../escaping-bound-var.rs | 22 ++++++ .../escaping-bound-var.stderr | 8 +++ tests/ui/type-alias-impl-trait/variance.rs | 30 ++++---- .../ui/type-alias-impl-trait/variance.stderr | 62 ++++++---------- 18 files changed, 230 insertions(+), 150 deletions(-) create mode 100644 tests/ui/consts/escaping-bound-var.rs create mode 100644 tests/ui/consts/escaping-bound-var.stderr create mode 100644 tests/ui/type-alias-impl-trait/escaping-bound-var.rs create mode 100644 tests/ui/type-alias-impl-trait/escaping-bound-var.stderr diff --git a/compiler/rustc_hir_analysis/messages.ftl b/compiler/rustc_hir_analysis/messages.ftl index 597cae6ff33c..d2c09168c7f5 100644 --- a/compiler/rustc_hir_analysis/messages.ftl +++ b/compiler/rustc_hir_analysis/messages.ftl @@ -21,12 +21,16 @@ hir_analysis_auto_deref_reached_recursion_limit = reached the recursion limit wh .label = deref recursion limit reached .help = consider increasing the recursion limit by adding a `#![recursion_limit = "{$suggested_limit}"]` attribute to your crate (`{$crate_name}`) -hir_analysis_cannot_capture_late_bound_const_in_anon_const = - cannot capture late-bound const parameter in a constant +hir_analysis_cannot_capture_late_bound_const = + cannot capture late-bound const parameter in {$what} .label = parameter defined here -hir_analysis_cannot_capture_late_bound_ty_in_anon_const = - cannot capture late-bound type parameter in a constant +hir_analysis_cannot_capture_late_bound_lifetime = + cannot capture late-bound lifetime in {$what} + .label = lifetime defined here + +hir_analysis_cannot_capture_late_bound_ty = + cannot capture late-bound type parameter in {$what} .label = parameter defined here hir_analysis_cast_thin_pointer_to_fat_pointer = cannot cast thin pointer `{$expr_ty}` to fat pointer `{$cast_ty}` diff --git a/compiler/rustc_hir_analysis/src/astconv/mod.rs b/compiler/rustc_hir_analysis/src/astconv/mod.rs index 90f64e18632a..065284e558ed 100644 --- a/compiler/rustc_hir_analysis/src/astconv/mod.rs +++ b/compiler/rustc_hir_analysis/src/astconv/mod.rs @@ -268,9 +268,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { // (*) -- not late-bound, won't change } - Some(rbv::ResolvedArg::Error(_)) => { - bug!("only ty/ct should resolve as ResolvedArg::Error") - } + Some(rbv::ResolvedArg::Error(guar)) => ty::Region::new_error(tcx, guar), None => { self.re_infer(def, lifetime.ident.span).unwrap_or_else(|| { diff --git a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs index 2bee27277253..6869c8696038 100644 --- a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs +++ b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs @@ -158,13 +158,14 @@ enum Scope<'a> { s: ScopeRef<'a>, }, - /// Disallows capturing non-lifetime binders from parent scopes. + /// Disallows capturing late-bound vars from parent scopes. /// /// This is necessary for something like `for [(); { /* references T */ }]:`, /// since we don't do something more correct like replacing any captured /// late-bound vars with early-bound params in the const's own generics. - AnonConstBoundary { + LateBoundary { s: ScopeRef<'a>, + what: &'static str, }, Root { @@ -216,7 +217,9 @@ impl<'a> fmt::Debug for TruncatedScopeDebug<'a> { .field("s", &"..") .finish(), Scope::TraitRefBoundary { s: _ } => f.debug_struct("TraitRefBoundary").finish(), - Scope::AnonConstBoundary { s: _ } => f.debug_struct("AnonConstBoundary").finish(), + Scope::LateBoundary { s: _, what } => { + f.debug_struct("LateBoundary").field("what", what).finish() + } Scope::Root { opt_parent_item } => { f.debug_struct("Root").field("opt_parent_item", &opt_parent_item).finish() } @@ -318,7 +321,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { break (vec![], BinderScopeType::Normal); } - Scope::ObjectLifetimeDefault { s, .. } | Scope::AnonConstBoundary { s } => { + Scope::ObjectLifetimeDefault { s, .. } | Scope::LateBoundary { s, .. } => { scope = s; } @@ -697,9 +700,12 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { }) => { intravisit::walk_ty(self, ty); - // Elided lifetimes are not allowed in non-return - // position impl Trait - let scope = Scope::TraitRefBoundary { s: self.scope }; + // Elided lifetimes and late-bound lifetimes (from the parent) + // are not allowed in non-return position impl Trait + let scope = Scope::LateBoundary { + s: &Scope::TraitRefBoundary { s: self.scope }, + what: "type alias impl trait", + }; self.with(scope, |this| intravisit::walk_item(this, opaque_ty)); return; @@ -979,7 +985,7 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { } fn visit_anon_const(&mut self, c: &'tcx hir::AnonConst) { - self.with(Scope::AnonConstBoundary { s: self.scope }, |this| { + self.with(Scope::LateBoundary { s: self.scope, what: "constant" }, |this| { intravisit::walk_anon_const(this, c); }); } @@ -1174,6 +1180,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { let mut late_depth = 0; let mut scope = self.scope; let mut outermost_body = None; + let mut crossed_late_boundary = None; let result = loop { match *scope { Scope::Body { id, s } => { @@ -1258,8 +1265,12 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { Scope::ObjectLifetimeDefault { s, .. } | Scope::Supertrait { s, .. } - | Scope::TraitRefBoundary { s, .. } - | Scope::AnonConstBoundary { s } => { + | Scope::TraitRefBoundary { s, .. } => { + scope = s; + } + + Scope::LateBoundary { s, what } => { + crossed_late_boundary = Some(what); scope = s; } } @@ -1268,6 +1279,22 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { if let Some(mut def) = result { if let ResolvedArg::EarlyBound(..) = def { // Do not free early-bound regions, only late-bound ones. + } else if let ResolvedArg::LateBound(_, _, param_def_id) = def + && let Some(what) = crossed_late_boundary + { + let use_span = lifetime_ref.ident.span; + let def_span = self.tcx.def_span(param_def_id); + let guar = match self.tcx.def_kind(param_def_id) { + DefKind::LifetimeParam => { + self.tcx.sess.emit_err(errors::CannotCaptureLateBound::Lifetime { + use_span, + def_span, + what, + }) + } + _ => unreachable!(), + }; + def = ResolvedArg::Error(guar); } else if let Some(body_id) = outermost_body { let fn_id = self.tcx.hir().body_owner(body_id); match self.tcx.hir().get(fn_id) { @@ -1322,7 +1349,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { | Scope::ObjectLifetimeDefault { s, .. } | Scope::Supertrait { s, .. } | Scope::TraitRefBoundary { s, .. } - | Scope::AnonConstBoundary { s } => { + | Scope::LateBoundary { s, .. } => { scope = s; } } @@ -1341,7 +1368,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { // search. let mut late_depth = 0; let mut scope = self.scope; - let mut crossed_anon_const = false; + let mut crossed_late_boundary = None; let result = loop { match *scope { @@ -1376,28 +1403,32 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { scope = s; } - Scope::AnonConstBoundary { s } => { - crossed_anon_const = true; + Scope::LateBoundary { s, what } => { + crossed_late_boundary = Some(what); scope = s; } } }; if let Some(def) = result { - if let ResolvedArg::LateBound(..) = def && crossed_anon_const { + if let ResolvedArg::LateBound(..) = def + && let Some(what) = crossed_late_boundary + { let use_span = self.tcx.hir().span(hir_id); let def_span = self.tcx.def_span(param_def_id); let guar = match self.tcx.def_kind(param_def_id) { DefKind::ConstParam => { - self.tcx.sess.emit_err(errors::CannotCaptureLateBoundInAnonConst::Const { + self.tcx.sess.emit_err(errors::CannotCaptureLateBound::Const { use_span, def_span, + what, }) } DefKind::TyParam => { - self.tcx.sess.emit_err(errors::CannotCaptureLateBoundInAnonConst::Type { + self.tcx.sess.emit_err(errors::CannotCaptureLateBound::Type { use_span, def_span, + what, }) } _ => unreachable!(), @@ -1446,7 +1477,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { | Scope::ObjectLifetimeDefault { s, .. } | Scope::Supertrait { s, .. } | Scope::TraitRefBoundary { s, .. } - | Scope::AnonConstBoundary { s } => { + | Scope::LateBoundary { s, .. } => { scope = s; } } @@ -1526,7 +1557,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { | Scope::ObjectLifetimeDefault { s, .. } | Scope::Supertrait { s, .. } | Scope::TraitRefBoundary { s, .. } - | Scope::AnonConstBoundary { s } => { + | Scope::LateBoundary { s, .. } => { scope = s; } } @@ -1831,7 +1862,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { Scope::Supertrait { s, .. } | Scope::TraitRefBoundary { s, .. } - | Scope::AnonConstBoundary { s } => { + | Scope::LateBoundary { s, .. } => { scope = s; } } diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs index 9471ad9ca906..55ab7133a7a7 100644 --- a/compiler/rustc_hir_analysis/src/errors.rs +++ b/compiler/rustc_hir_analysis/src/errors.rs @@ -430,20 +430,30 @@ pub(crate) struct VariadicFunctionCompatibleConvention<'a> { } #[derive(Diagnostic)] -pub(crate) enum CannotCaptureLateBoundInAnonConst { - #[diag(hir_analysis_cannot_capture_late_bound_ty_in_anon_const)] +pub(crate) enum CannotCaptureLateBound { + #[diag(hir_analysis_cannot_capture_late_bound_ty)] Type { #[primary_span] use_span: Span, #[label] def_span: Span, + what: &'static str, }, - #[diag(hir_analysis_cannot_capture_late_bound_const_in_anon_const)] + #[diag(hir_analysis_cannot_capture_late_bound_const)] Const { #[primary_span] use_span: Span, #[label] def_span: Span, + what: &'static str, + }, + #[diag(hir_analysis_cannot_capture_late_bound_lifetime)] + Lifetime { + #[primary_span] + use_span: Span, + #[label] + def_span: Span, + what: &'static str, }, } diff --git a/tests/ui/const-generics/late-bound-vars/in_closure.rs b/tests/ui/const-generics/late-bound-vars/in_closure.rs index 6549cad629b0..349e82fa45a9 100644 --- a/tests/ui/const-generics/late-bound-vars/in_closure.rs +++ b/tests/ui/const-generics/late-bound-vars/in_closure.rs @@ -1,23 +1,13 @@ -// failure-status: 1 // known-bug: unknown -// error-pattern:internal compiler error -// normalize-stderr-test "internal compiler error.*" -> "" -// normalize-stderr-test "DefId\([^)]*\)" -> "..." -// normalize-stderr-test "\nerror: internal compiler error.*\n\n" -> "" -// normalize-stderr-test "note:.*unexpectedly panicked.*\n\n" -> "" -// normalize-stderr-test "note: we would appreciate a bug report.*\n\n" -> "" -// normalize-stderr-test "note: compiler flags.*\n\n" -> "" -// normalize-stderr-test "note: rustc.*running on.*\n\n" -> "" -// normalize-stderr-test "thread.*panicked.*:\n.*\n" -> "" -// normalize-stderr-test "stack backtrace:\n" -> "" -// normalize-stderr-test "\s\d{1,}: .*\n" -> "" -// normalize-stderr-test "\s at .*\n" -> "" -// normalize-stderr-test ".*note: Some details.*\n" -> "" -// normalize-stderr-test "\n[ ]*\n" -> "" -// normalize-stderr-test "compiler/.*: projection" -> "projection" -// normalize-stderr-test ".*omitted \d{1,} frame.*\n" -> "" -// normalize-stderr-test "error: [\s\n]*query stack during panic:\n" -> "" -// this should run-pass + +// If we want this to compile, then we'd need to do something like RPITs do, +// where nested associated constants have early-bound versions of their captured +// late-bound vars inserted into their generics. This gives us substitutable +// lifetimes to actually use when borrow-checking the associated const, which is +// lowered as a totally separate body from its parent. Since this doesn't exist, +// so we should just error rather than resolving this late-bound var with no +// binder to actually attach it to, or worse, as a free region that can't even be +// substituted correctly, and ICEing. - @compiler-errors #![feature(generic_const_exprs)] #![allow(incomplete_features)] diff --git a/tests/ui/const-generics/late-bound-vars/in_closure.stderr b/tests/ui/const-generics/late-bound-vars/in_closure.stderr index ac406bf2bb13..275dd742ebd8 100644 --- a/tests/ui/const-generics/late-bound-vars/in_closure.stderr +++ b/tests/ui/const-generics/late-bound-vars/in_closure.stderr @@ -1,10 +1,20 @@ -#0 [mir_borrowck] borrow-checking `test::{closure#0}::{constant#1}` -#1 [mir_drops_elaborated_and_const_checked] elaborating drops for `test::{closure#0}::{constant#1}` -#2 [mir_for_ctfe] caching mir of `test::{closure#0}::{constant#1}` for CTFE -#3 [eval_to_allocation_raw] const-evaluating + checking `test::{closure#0}::{constant#1}` -#4 [eval_to_allocation_raw] const-evaluating + checking `test::{closure#0}::{constant#1}` -#5 [eval_to_valtree] evaluating type-level constant -#6 [typeck] type-checking `test` -#7 [analysis] running analysis passes on this crate -end of query stack -error: aborting due to previous error \ No newline at end of file +error: cannot capture late-bound lifetime in constant + --> $DIR/in_closure.rs:24:29 + | +LL | fn test<'a>() { + | -- lifetime defined here +LL | let _ = || { +LL | let _: [u8; inner::<'a>()]; + | ^^ + +error: cannot capture late-bound lifetime in constant + --> $DIR/in_closure.rs:25:29 + | +LL | fn test<'a>() { + | -- lifetime defined here +... +LL | let _ = [0; inner::<'a>()]; + | ^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/const-generics/late-bound-vars/simple.rs b/tests/ui/const-generics/late-bound-vars/simple.rs index 544073b5a563..c459419115a1 100644 --- a/tests/ui/const-generics/late-bound-vars/simple.rs +++ b/tests/ui/const-generics/late-bound-vars/simple.rs @@ -1,22 +1,13 @@ -// failure-status: 101 // known-bug: unknown -// error-pattern:internal compiler error -// normalize-stderr-test "internal compiler error.*" -> "" -// normalize-stderr-test "DefId\([^)]*\)" -> "..." -// normalize-stderr-test "\nerror: internal compiler error.*\n\n" -> "" -// normalize-stderr-test "note:.*unexpectedly panicked.*\n\n" -> "" -// normalize-stderr-test "note: we would appreciate a bug report.*\n\n" -> "" -// normalize-stderr-test "note: compiler flags.*\n\n" -> "" -// normalize-stderr-test "note: rustc.*running on.*\n\n" -> "" -// normalize-stderr-test "thread.*panicked.*:\n.*\n" -> "" -// normalize-stderr-test "stack backtrace:\n" -> "" -// normalize-stderr-test "\s\d{1,}: .*\n" -> "" -// normalize-stderr-test "\s at .*\n" -> "" -// normalize-stderr-test ".*note: Some details.*\n" -> "" -// normalize-stderr-test "\n\n[ ]*\n" -> "" -// normalize-stderr-test "compiler/.*: projection" -> "projection" -// normalize-stderr-test ".*omitted \d{1,} frame.*\n" -> "" -// normalize-stderr-test "error: [\s\n]*query stack" -> "error: query stack" + +// If we want this to compile, then we'd need to do something like RPITs do, +// where nested associated constants have early-bound versions of their captured +// late-bound vars inserted into their generics. This gives us substitutable +// lifetimes to actually use when borrow-checking the associated const, which is +// lowered as a totally separate body from its parent. Since this doesn't exist, +// so we should just error rather than resolving this late-bound var with no +// binder to actually attach it to, or worse, as a free region that can't even be +// substituted correctly, and ICEing. - @compiler-errors #![feature(generic_const_exprs)] #![allow(incomplete_features)] diff --git a/tests/ui/const-generics/late-bound-vars/simple.stderr b/tests/ui/const-generics/late-bound-vars/simple.stderr index c9f2164b6351..e72e373630da 100644 --- a/tests/ui/const-generics/late-bound-vars/simple.stderr +++ b/tests/ui/const-generics/late-bound-vars/simple.stderr @@ -1,12 +1,19 @@ -error: query stack during panic: -#0 [mir_borrowck] borrow-checking `test::{constant#1}` -#1 [mir_drops_elaborated_and_const_checked] elaborating drops for `test::{constant#1}` -#2 [mir_for_ctfe] caching mir of `test::{constant#1}` for CTFE -#3 [eval_to_allocation_raw] const-evaluating + checking `test::{constant#1}` -#4 [eval_to_allocation_raw] const-evaluating + checking `test::{constant#1}` -#5 [eval_to_valtree] evaluating type-level constant -#6 [typeck] type-checking `test` -#7 [analysis] running analysis passes on this crate -end of query stack -error: aborting due to previous error +error: cannot capture late-bound lifetime in constant + --> $DIR/simple.rs:20:25 + | +LL | fn test<'a>() { + | -- lifetime defined here +LL | let _: [u8; inner::<'a>()]; + | ^^ + +error: cannot capture late-bound lifetime in constant + --> $DIR/simple.rs:21:25 + | +LL | fn test<'a>() { + | -- lifetime defined here +LL | let _: [u8; inner::<'a>()]; +LL | let _ = [0; inner::<'a>()]; + | ^^ + +error: aborting due to 2 previous errors diff --git a/tests/ui/consts/escaping-bound-var.rs b/tests/ui/consts/escaping-bound-var.rs new file mode 100644 index 000000000000..7c1fbd24f555 --- /dev/null +++ b/tests/ui/consts/escaping-bound-var.rs @@ -0,0 +1,13 @@ +#![feature(generic_const_exprs)] +//~^ WARN the feature `generic_const_exprs` is incomplete + +fn test<'a>( + _: &'a (), +) -> [(); { + let x: &'a (); + //~^ ERROR cannot capture late-bound lifetime in constant + 1 +}] { +} + +fn main() {} diff --git a/tests/ui/consts/escaping-bound-var.stderr b/tests/ui/consts/escaping-bound-var.stderr new file mode 100644 index 000000000000..d26ae2cee1c9 --- /dev/null +++ b/tests/ui/consts/escaping-bound-var.stderr @@ -0,0 +1,20 @@ +warning: the feature `generic_const_exprs` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/escaping-bound-var.rs:1:12 + | +LL | #![feature(generic_const_exprs)] + | ^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #76560 for more information + = note: `#[warn(incomplete_features)]` on by default + +error: cannot capture late-bound lifetime in constant + --> $DIR/escaping-bound-var.rs:7:13 + | +LL | fn test<'a>( + | -- lifetime defined here +... +LL | let x: &'a (); + | ^^ + +error: aborting due to previous error; 1 warning emitted + diff --git a/tests/ui/traits/non_lifetime_binders/capture-late-ct-in-anon.rs b/tests/ui/traits/non_lifetime_binders/capture-late-ct-in-anon.rs index 91c6dfb8e0a9..bbae67f0bad4 100644 --- a/tests/ui/traits/non_lifetime_binders/capture-late-ct-in-anon.rs +++ b/tests/ui/traits/non_lifetime_binders/capture-late-ct-in-anon.rs @@ -4,7 +4,7 @@ fn b() where for [(); C]: Copy, - //~^ ERROR cannot capture late-bound const parameter in a constant + //~^ ERROR cannot capture late-bound const parameter in constant { } diff --git a/tests/ui/traits/non_lifetime_binders/capture-late-ct-in-anon.stderr b/tests/ui/traits/non_lifetime_binders/capture-late-ct-in-anon.stderr index 69bb605bf41c..d65892ec6dff 100644 --- a/tests/ui/traits/non_lifetime_binders/capture-late-ct-in-anon.stderr +++ b/tests/ui/traits/non_lifetime_binders/capture-late-ct-in-anon.stderr @@ -7,7 +7,7 @@ LL | #![feature(non_lifetime_binders)] = note: see issue #108185 for more information = note: `#[warn(incomplete_features)]` on by default -error: cannot capture late-bound const parameter in a constant +error: cannot capture late-bound const parameter in constant --> $DIR/capture-late-ct-in-anon.rs:6:30 | LL | for [(); C]: Copy, diff --git a/tests/ui/traits/non_lifetime_binders/late-bound-in-anon-ct.rs b/tests/ui/traits/non_lifetime_binders/late-bound-in-anon-ct.rs index 3903bfe9bcf5..64f09f823fc2 100644 --- a/tests/ui/traits/non_lifetime_binders/late-bound-in-anon-ct.rs +++ b/tests/ui/traits/non_lifetime_binders/late-bound-in-anon-ct.rs @@ -5,7 +5,7 @@ fn foo() -> usize where for [i32; { let _: T = todo!(); 0 }]:, - //~^ ERROR cannot capture late-bound type parameter in a constant + //~^ ERROR cannot capture late-bound type parameter in constant {} fn main() {} diff --git a/tests/ui/traits/non_lifetime_binders/late-bound-in-anon-ct.stderr b/tests/ui/traits/non_lifetime_binders/late-bound-in-anon-ct.stderr index fafff02dea6e..dc54e1acc392 100644 --- a/tests/ui/traits/non_lifetime_binders/late-bound-in-anon-ct.stderr +++ b/tests/ui/traits/non_lifetime_binders/late-bound-in-anon-ct.stderr @@ -15,7 +15,7 @@ LL | #![feature(non_lifetime_binders, generic_const_exprs)] | = note: see issue #76560 for more information -error: cannot capture late-bound type parameter in a constant +error: cannot capture late-bound type parameter in constant --> $DIR/late-bound-in-anon-ct.rs:7:27 | LL | for [i32; { let _: T = todo!(); 0 }]:, diff --git a/tests/ui/type-alias-impl-trait/escaping-bound-var.rs b/tests/ui/type-alias-impl-trait/escaping-bound-var.rs new file mode 100644 index 000000000000..bf27e76db2b4 --- /dev/null +++ b/tests/ui/type-alias-impl-trait/escaping-bound-var.rs @@ -0,0 +1,22 @@ +#![feature(type_alias_impl_trait)] + +pub trait Trait<'a> { + type Assoc; +} + +trait Test<'a> {} + +pub type Foo = impl for<'a> Trait<'a, Assoc = impl Test<'a>>; +//~^ ERROR cannot capture late-bound lifetime in type alias impl trait + +impl Trait<'_> for () { + type Assoc = (); +} + +impl Test<'_> for () {} + +fn constrain() -> Foo { + () +} + +fn main() {} diff --git a/tests/ui/type-alias-impl-trait/escaping-bound-var.stderr b/tests/ui/type-alias-impl-trait/escaping-bound-var.stderr new file mode 100644 index 000000000000..8205a60ccdd7 --- /dev/null +++ b/tests/ui/type-alias-impl-trait/escaping-bound-var.stderr @@ -0,0 +1,8 @@ +error: cannot capture late-bound lifetime in type alias impl trait + --> $DIR/escaping-bound-var.rs:9:57 + | +LL | pub type Foo = impl for<'a> Trait<'a, Assoc = impl Test<'a>>; + | -- lifetime defined here ^^ + +error: aborting due to previous error + diff --git a/tests/ui/type-alias-impl-trait/variance.rs b/tests/ui/type-alias-impl-trait/variance.rs index eba4816003ba..e92cf2513e73 100644 --- a/tests/ui/type-alias-impl-trait/variance.rs +++ b/tests/ui/type-alias-impl-trait/variance.rs @@ -9,42 +9,36 @@ type NotCapturedEarly<'a> = impl Sized; //~ [o] type CapturedEarly<'a> = impl Sized + Captures<'a>; //~ [o] +// TAIT does *not* capture `'b` type NotCapturedLate<'a> = dyn for<'b> Iterator; //~ [o] -type CapturedLate<'a> = dyn for<'b> Iterator>; //~ [o] - -type Captured<'a> = dyn for<'b> Iterator + Captures<'b>>; //~ [o] +// TAIT does *not* capture `'b` +type Captured<'a> = dyn for<'b> Iterator>; //~ [o] type Bar<'a, 'b: 'b, T> = impl Sized; //~ ERROR [o, o, o] trait Foo<'i> { - type ImplicitCapturedEarly<'a>; + type ImplicitCapture<'a>; - type ExplicitCaptureEarly<'a>; + type ExplicitCaptureFromHeader<'a>; - type ImplicitCaptureLate<'a>; - - type ExplicitCaptureLate<'a>; + type ExplicitCaptureFromGat<'a>; } impl<'i> Foo<'i> for &'i () { - type ImplicitCapturedEarly<'a> = impl Sized; //~ [o, o] + type ImplicitCapture<'a> = impl Sized; //~ [o, o] - type ExplicitCaptureEarly<'a> = impl Sized + Captures<'i>; //~ [o, o] + type ExplicitCaptureFromHeader<'a> = impl Sized + Captures<'i>; //~ [o, o] - type ImplicitCaptureLate<'a> = impl Sized; //~ [o, o] - - type ExplicitCaptureLate<'a> = impl Sized + Captures<'a>; //~ [o, o] + type ExplicitCaptureFromGat<'a> = impl Sized + Captures<'a>; //~ [o, o] } impl<'i> Foo<'i> for () { - type ImplicitCapturedEarly<'a> = impl Sized; //~ [o, o] + type ImplicitCapture<'a> = impl Sized; //~ [o, o] - type ExplicitCaptureEarly<'a> = impl Sized + Captures<'i>; //~ [o, o] + type ExplicitCaptureFromHeader<'a> = impl Sized + Captures<'i>; //~ [o, o] - type ImplicitCaptureLate<'a> = impl Sized; //~ [o, o] - - type ExplicitCaptureLate<'a> = impl Sized + Captures<'a>; //~ [o, o] + type ExplicitCaptureFromGat<'a> = impl Sized + Captures<'a>; //~ [o, o] } fn main() {} diff --git a/tests/ui/type-alias-impl-trait/variance.stderr b/tests/ui/type-alias-impl-trait/variance.stderr index f73cf617a4c9..1794447c89a2 100644 --- a/tests/ui/type-alias-impl-trait/variance.stderr +++ b/tests/ui/type-alias-impl-trait/variance.stderr @@ -11,22 +11,16 @@ LL | type CapturedEarly<'a> = impl Sized + Captures<'a>; | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: [o] - --> $DIR/variance.rs:12:56 + --> $DIR/variance.rs:13:56 | LL | type NotCapturedLate<'a> = dyn for<'b> Iterator; | ^^^^^^^^^^ -error: [o] - --> $DIR/variance.rs:14:53 - | -LL | type CapturedLate<'a> = dyn for<'b> Iterator>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - error: [o] --> $DIR/variance.rs:16:49 | -LL | type Captured<'a> = dyn for<'b> Iterator + Captures<'b>>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | type Captured<'a> = dyn for<'b> Iterator>; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: [o, o, o] --> $DIR/variance.rs:18:27 @@ -35,52 +29,40 @@ LL | type Bar<'a, 'b: 'b, T> = impl Sized; | ^^^^^^^^^^ error: [o, o] - --> $DIR/variance.rs:31:38 + --> $DIR/variance.rs:29:32 | -LL | type ImplicitCapturedEarly<'a> = impl Sized; - | ^^^^^^^^^^ +LL | type ImplicitCapture<'a> = impl Sized; + | ^^^^^^^^^^ error: [o, o] - --> $DIR/variance.rs:33:37 + --> $DIR/variance.rs:31:42 | -LL | type ExplicitCaptureEarly<'a> = impl Sized + Captures<'i>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | type ExplicitCaptureFromHeader<'a> = impl Sized + Captures<'i>; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: [o, o] - --> $DIR/variance.rs:35:36 + --> $DIR/variance.rs:33:39 | -LL | type ImplicitCaptureLate<'a> = impl Sized; - | ^^^^^^^^^^ +LL | type ExplicitCaptureFromGat<'a> = impl Sized + Captures<'a>; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: [o, o] - --> $DIR/variance.rs:37:36 + --> $DIR/variance.rs:37:32 | -LL | type ExplicitCaptureLate<'a> = impl Sized + Captures<'a>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | type ImplicitCapture<'a> = impl Sized; + | ^^^^^^^^^^ error: [o, o] - --> $DIR/variance.rs:41:38 + --> $DIR/variance.rs:39:42 | -LL | type ImplicitCapturedEarly<'a> = impl Sized; - | ^^^^^^^^^^ +LL | type ExplicitCaptureFromHeader<'a> = impl Sized + Captures<'i>; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: [o, o] - --> $DIR/variance.rs:43:37 + --> $DIR/variance.rs:41:39 | -LL | type ExplicitCaptureEarly<'a> = impl Sized + Captures<'i>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | type ExplicitCaptureFromGat<'a> = impl Sized + Captures<'a>; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error: [o, o] - --> $DIR/variance.rs:45:36 - | -LL | type ImplicitCaptureLate<'a> = impl Sized; - | ^^^^^^^^^^ - -error: [o, o] - --> $DIR/variance.rs:47:36 - | -LL | type ExplicitCaptureLate<'a> = impl Sized + Captures<'a>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: aborting due to 14 previous errors +error: aborting due to 11 previous errors From f479a7aa8d17dd3abab4f84fe9fe3e248e362eaf Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 2 Sep 2023 22:53:59 +0000 Subject: [PATCH 033/111] Restore some removed tests --- .../const-generics/late-bound-vars/in_closure.rs | 10 +--------- .../late-bound-vars/in_closure.stderr | 4 ++-- .../late-bound-in-return-issue-77357.rs | 13 +++++++++++++ .../late-bound-in-return-issue-77357.stderr | 8 ++++++++ .../late-bound-in-where-issue-83993.rs | 15 +++++++++++++++ .../late-bound-in-where-issue-83993.stderr | 10 ++++++++++ tests/ui/const-generics/late-bound-vars/simple.rs | 2 +- 7 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 tests/ui/const-generics/late-bound-vars/late-bound-in-return-issue-77357.rs create mode 100644 tests/ui/const-generics/late-bound-vars/late-bound-in-return-issue-77357.stderr create mode 100644 tests/ui/const-generics/late-bound-vars/late-bound-in-where-issue-83993.rs create mode 100644 tests/ui/const-generics/late-bound-vars/late-bound-in-where-issue-83993.stderr diff --git a/tests/ui/const-generics/late-bound-vars/in_closure.rs b/tests/ui/const-generics/late-bound-vars/in_closure.rs index 349e82fa45a9..443c755c601c 100644 --- a/tests/ui/const-generics/late-bound-vars/in_closure.rs +++ b/tests/ui/const-generics/late-bound-vars/in_closure.rs @@ -1,13 +1,5 @@ // known-bug: unknown - -// If we want this to compile, then we'd need to do something like RPITs do, -// where nested associated constants have early-bound versions of their captured -// late-bound vars inserted into their generics. This gives us substitutable -// lifetimes to actually use when borrow-checking the associated const, which is -// lowered as a totally separate body from its parent. Since this doesn't exist, -// so we should just error rather than resolving this late-bound var with no -// binder to actually attach it to, or worse, as a free region that can't even be -// substituted correctly, and ICEing. - @compiler-errors +// see comment on `tests/ui/const-generics/late-bound-vars/simple.rs` #![feature(generic_const_exprs)] #![allow(incomplete_features)] diff --git a/tests/ui/const-generics/late-bound-vars/in_closure.stderr b/tests/ui/const-generics/late-bound-vars/in_closure.stderr index 275dd742ebd8..e15496454a02 100644 --- a/tests/ui/const-generics/late-bound-vars/in_closure.stderr +++ b/tests/ui/const-generics/late-bound-vars/in_closure.stderr @@ -1,5 +1,5 @@ error: cannot capture late-bound lifetime in constant - --> $DIR/in_closure.rs:24:29 + --> $DIR/in_closure.rs:16:29 | LL | fn test<'a>() { | -- lifetime defined here @@ -8,7 +8,7 @@ LL | let _: [u8; inner::<'a>()]; | ^^ error: cannot capture late-bound lifetime in constant - --> $DIR/in_closure.rs:25:29 + --> $DIR/in_closure.rs:17:29 | LL | fn test<'a>() { | -- lifetime defined here diff --git a/tests/ui/const-generics/late-bound-vars/late-bound-in-return-issue-77357.rs b/tests/ui/const-generics/late-bound-vars/late-bound-in-return-issue-77357.rs new file mode 100644 index 000000000000..b81aa50d9a90 --- /dev/null +++ b/tests/ui/const-generics/late-bound-vars/late-bound-in-return-issue-77357.rs @@ -0,0 +1,13 @@ +// known-bug: unknown +// see comment on `tests/ui/const-generics/late-bound-vars/simple.rs` + +#![feature(generic_const_exprs)] +#![allow(incomplete_features)] + +trait MyTrait {} + +fn bug<'a, T>() -> &'static dyn MyTrait<[(); { |x: &'a u32| { x }; 4 }]> { + todo!() +} + +fn main() {} diff --git a/tests/ui/const-generics/late-bound-vars/late-bound-in-return-issue-77357.stderr b/tests/ui/const-generics/late-bound-vars/late-bound-in-return-issue-77357.stderr new file mode 100644 index 000000000000..21c8fe6865cc --- /dev/null +++ b/tests/ui/const-generics/late-bound-vars/late-bound-in-return-issue-77357.stderr @@ -0,0 +1,8 @@ +error: cannot capture late-bound lifetime in constant + --> $DIR/late-bound-in-return-issue-77357.rs:9:53 + | +LL | fn bug<'a, T>() -> &'static dyn MyTrait<[(); { |x: &'a u32| { x }; 4 }]> { + | -- lifetime defined here ^^ + +error: aborting due to previous error + diff --git a/tests/ui/const-generics/late-bound-vars/late-bound-in-where-issue-83993.rs b/tests/ui/const-generics/late-bound-vars/late-bound-in-where-issue-83993.rs new file mode 100644 index 000000000000..89f01748fc90 --- /dev/null +++ b/tests/ui/const-generics/late-bound-vars/late-bound-in-where-issue-83993.rs @@ -0,0 +1,15 @@ +// known-bug: unknown +// see comment on `tests/ui/const-generics/late-bound-vars/simple.rs` + +#![feature(generic_const_exprs)] +#![allow(incomplete_features)] + +fn bug<'a>() +where + for<'b> [(); { + let x: &'b (); + 0 + }]: +{} + +fn main() {} diff --git a/tests/ui/const-generics/late-bound-vars/late-bound-in-where-issue-83993.stderr b/tests/ui/const-generics/late-bound-vars/late-bound-in-where-issue-83993.stderr new file mode 100644 index 000000000000..a66dc8db9144 --- /dev/null +++ b/tests/ui/const-generics/late-bound-vars/late-bound-in-where-issue-83993.stderr @@ -0,0 +1,10 @@ +error: cannot capture late-bound lifetime in constant + --> $DIR/late-bound-in-where-issue-83993.rs:10:17 + | +LL | for<'b> [(); { + | -- lifetime defined here +LL | let x: &'b (); + | ^^ + +error: aborting due to previous error + diff --git a/tests/ui/const-generics/late-bound-vars/simple.rs b/tests/ui/const-generics/late-bound-vars/simple.rs index c459419115a1..a562bd8cb41c 100644 --- a/tests/ui/const-generics/late-bound-vars/simple.rs +++ b/tests/ui/const-generics/late-bound-vars/simple.rs @@ -5,7 +5,7 @@ // late-bound vars inserted into their generics. This gives us substitutable // lifetimes to actually use when borrow-checking the associated const, which is // lowered as a totally separate body from its parent. Since this doesn't exist, -// so we should just error rather than resolving this late-bound var with no +// we should just error rather than resolving this late-bound var with no // binder to actually attach it to, or worse, as a free region that can't even be // substituted correctly, and ICEing. - @compiler-errors From 8ad23794077b4380da05fc5dabb573f38bdb2718 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 7 Sep 2023 04:16:06 +0000 Subject: [PATCH 034/111] Don't modify libstd to dump rustc ICEs --- compiler/rustc_driver_impl/src/lib.rs | 63 ++++++++++++++---------- library/std/src/lib.rs | 3 -- library/std/src/panicking.rs | 36 +++----------- tests/run-make/dump-ice-to-disk/check.sh | 4 +- 4 files changed, 46 insertions(+), 60 deletions(-) diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 1a16759d7f9a..35f4ff7d964e 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -7,7 +7,7 @@ #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] #![feature(lazy_cell)] #![feature(decl_macro)] -#![feature(ice_to_disk)] +#![feature(panic_update_hook)] #![feature(let_chains)] #![recursion_limit = "256"] #![allow(rustc::potential_query_instability)] @@ -50,9 +50,9 @@ use std::collections::BTreeMap; use std::env; use std::ffi::OsString; use std::fmt::Write as _; -use std::fs; +use std::fs::{self, File}; use std::io::{self, IsTerminal, Read, Write}; -use std::panic::{self, catch_unwind}; +use std::panic::{self, catch_unwind, PanicInfo}; use std::path::PathBuf; use std::process::{self, Command, Stdio}; use std::str; @@ -1323,31 +1323,42 @@ pub fn install_ice_hook(bug_report_url: &'static str, extra_info: fn(&Handler)) std::env::set_var("RUST_BACKTRACE", "full"); } - panic::set_hook(Box::new(move |info| { - // If the error was caused by a broken pipe then this is not a bug. - // Write the error and return immediately. See #98700. - #[cfg(windows)] - if let Some(msg) = info.payload().downcast_ref::() { - if msg.starts_with("failed printing to stdout: ") && msg.ends_with("(os error 232)") { - // the error code is already going to be reported when the panic unwinds up the stack - let handler = EarlyErrorHandler::new(ErrorOutputType::default()); - let _ = handler.early_error_no_abort(msg.clone()); - return; + panic::update_hook(Box::new( + move |default_hook: &(dyn Fn(&PanicInfo<'_>) + Send + Sync + 'static), + info: &PanicInfo<'_>| { + // If the error was caused by a broken pipe then this is not a bug. + // Write the error and return immediately. See #98700. + #[cfg(windows)] + if let Some(msg) = info.payload().downcast_ref::() { + if msg.starts_with("failed printing to stdout: ") && msg.ends_with("(os error 232)") + { + // the error code is already going to be reported when the panic unwinds up the stack + let handler = EarlyErrorHandler::new(ErrorOutputType::default()); + let _ = handler.early_error_no_abort(msg.clone()); + return; + } + }; + + // Invoke the default handler, which prints the actual panic message and optionally a backtrace + // Don't do this for delayed bugs, which already emit their own more useful backtrace. + if !info.payload().is::() { + default_hook(info); + // Separate the output with an empty line + eprintln!(); + + if let Some(ice_path) = ice_path() + && let Ok(mut out) = + File::options().create(true).append(true).open(&ice_path) + { + let _ = + write!(&mut out, "{info}{:#}", std::backtrace::Backtrace::force_capture()); + } } - }; - // Invoke the default handler, which prints the actual panic message and optionally a backtrace - // Don't do this for delayed bugs, which already emit their own more useful backtrace. - if !info.payload().is::() { - std::panic_hook_with_disk_dump(info, ice_path().as_deref()); - - // Separate the output with an empty line - eprintln!(); - } - - // Print the ICE message - report_ice(info, bug_report_url, extra_info); - })); + // Print the ICE message + report_ice(info, bug_report_url, extra_info); + }, + )); } /// Prints the ICE message, including query stack, but without backtrace. diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 1955ef815ff8..0e41b35ec1e8 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -613,9 +613,6 @@ pub mod alloc; // Private support modules mod panicking; -#[unstable(feature = "ice_to_disk", issue = "none")] -pub use panicking::panic_hook_with_disk_dump; - #[path = "../../backtrace/src/lib.rs"] #[allow(dead_code, unused_attributes, fuzzy_provenance_casts)] mod backtrace_rs; diff --git a/library/std/src/panicking.rs b/library/std/src/panicking.rs index b20e5464e6e0..9d066c82a89e 100644 --- a/library/std/src/panicking.rs +++ b/library/std/src/panicking.rs @@ -236,14 +236,6 @@ where /// The default panic handler. fn default_hook(info: &PanicInfo<'_>) { - panic_hook_with_disk_dump(info, None) -} - -#[unstable(feature = "ice_to_disk", issue = "none")] -/// The implementation of the default panic handler. -/// -/// It can also write the backtrace to a given `path`. This functionality is used only by `rustc`. -pub fn panic_hook_with_disk_dump(info: &PanicInfo<'_>, path: Option<&crate::path::Path>) { // If this is a double panic, make sure that we print a backtrace // for this panic. Otherwise only print it if logging is enabled. let backtrace = if info.force_no_backtrace() { @@ -267,7 +259,7 @@ pub fn panic_hook_with_disk_dump(info: &PanicInfo<'_>, path: Option<&crate::path let thread = thread_info::current_thread(); let name = thread.as_ref().and_then(|t| t.name()).unwrap_or(""); - let write = |err: &mut dyn crate::io::Write, backtrace: Option| { + let write = |err: &mut dyn crate::io::Write| { let _ = writeln!(err, "thread '{name}' panicked at {location}:\n{msg}"); static FIRST_PANIC: AtomicBool = AtomicBool::new(true); @@ -281,19 +273,11 @@ pub fn panic_hook_with_disk_dump(info: &PanicInfo<'_>, path: Option<&crate::path } Some(BacktraceStyle::Off) => { if FIRST_PANIC.swap(false, Ordering::SeqCst) { - if let Some(path) = path { - let _ = writeln!( - err, - "note: a backtrace for this error was stored at `{}`", - path.display(), - ); - } else { - let _ = writeln!( - err, - "note: run with `RUST_BACKTRACE=1` environment variable to display a \ + let _ = writeln!( + err, + "note: run with `RUST_BACKTRACE=1` environment variable to display a \ backtrace" - ); - } + ); } } // If backtraces aren't supported or are forced-off, do nothing. @@ -301,17 +285,11 @@ pub fn panic_hook_with_disk_dump(info: &PanicInfo<'_>, path: Option<&crate::path } }; - if let Some(path) = path - && let Ok(mut out) = crate::fs::File::options().create(true).append(true).open(&path) - { - write(&mut out, BacktraceStyle::full()); - } - if let Some(local) = set_output_capture(None) { - write(&mut *local.lock().unwrap_or_else(|e| e.into_inner()), backtrace); + write(&mut *local.lock().unwrap_or_else(|e| e.into_inner())); set_output_capture(Some(local)); } else if let Some(mut out) = panic_output() { - write(&mut out, backtrace); + write(&mut out); } } diff --git a/tests/run-make/dump-ice-to-disk/check.sh b/tests/run-make/dump-ice-to-disk/check.sh index 91109596a451..ab6f9ab60188 100644 --- a/tests/run-make/dump-ice-to-disk/check.sh +++ b/tests/run-make/dump-ice-to-disk/check.sh @@ -22,8 +22,8 @@ rm $TMPDIR/rustc-ice-*.txt # Explicitly disabling ICE dump export RUSTC_ICE=0 $RUSTC src/lib.rs -Z treat-err-as-bug=1 1>$TMPDIR/rust-test-disabled.log 2>&1 -should_be_empty_tmp=$(ls -l $TMPDIR/rustc-ice-*.txt | wc -l) -should_be_empty_dot=$(ls -l ./rustc-ice-*.txt | wc -l) +should_be_empty_tmp=$(ls -l $TMPDIR/rustc-ice-*.txt 2>/dev/null | wc -l) +should_be_empty_dot=$(ls -l ./rustc-ice-*.txt 2>/dev/null | wc -l) echo "#### ICE Dump content:" echo $content From b59480784dce3e02c3ce804d3fd383e2ebede465 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 7 Sep 2023 05:33:36 +0000 Subject: [PATCH 035/111] Make ICE backtrace actually match the panic handler --- compiler/rustc_driver_impl/src/lib.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 35f4ff7d964e..9cfb599aff4c 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -1350,8 +1350,25 @@ pub fn install_ice_hook(bug_report_url: &'static str, extra_info: fn(&Handler)) && let Ok(mut out) = File::options().create(true).append(true).open(&ice_path) { - let _ = - write!(&mut out, "{info}{:#}", std::backtrace::Backtrace::force_capture()); + // The current implementation always returns `Some`. + let location = info.location().unwrap(); + let msg = match info.payload().downcast_ref::<&'static str>() { + Some(s) => *s, + None => match info.payload().downcast_ref::() { + Some(s) => &s[..], + None => "Box", + }, + }; + let thread = std::thread::current(); + let name = thread.name().unwrap_or(""); + let _ = write!( + &mut out, + "thread '{name}' panicked at {location}:\n\ + {msg}\n\ + stack backtrace:\n\ + {:#}", + std::backtrace::Backtrace::force_capture() + ); } } From 269cb579479ab950e85a2e4078810501c29d7465 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sat, 9 Sep 2023 16:49:29 -0700 Subject: [PATCH 036/111] rustdoc-search: fix bugs when unboxing and reordering combine --- src/librustdoc/html/static/js/search.js | 544 ++++++++++-------- .../rustdoc-js-std/option-type-signatures.js | 5 +- tests/rustdoc-js-std/vec-type-signatures.js | 22 + tests/rustdoc-js/generics-match-ambiguity.js | 1 + tests/rustdoc-js/generics-unbox.js | 38 ++ tests/rustdoc-js/generics-unbox.rs | 36 ++ 6 files changed, 393 insertions(+), 253 deletions(-) create mode 100644 tests/rustdoc-js-std/vec-type-signatures.js create mode 100644 tests/rustdoc-js/generics-unbox.js create mode 100644 tests/rustdoc-js/generics-unbox.rs diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index b867311aca10..407bf5f2c5fe 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -3,6 +3,17 @@ "use strict"; +// polyfill +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toSpliced +if (!Array.prototype.toSpliced) { + // Can't use arrow functions, because we want `this` + Array.prototype.toSpliced = function() { + const me = this.slice(); + Array.prototype.splice.apply(me, arguments); + return me; + }; +} + (function() { // This mapping table should match the discriminants of // `rustdoc::formats::item_type::ItemType` type in Rust. @@ -1301,278 +1312,311 @@ function initSearch(rawSearchIndex) { * This function checks generics in search query `queryElem` can all be found in the * search index (`fnType`), * - * @param {FunctionType} fnType - The object to check. - * @param {QueryElement} queryElem - The element from the parsed query. - * @param {[FunctionType]} whereClause - Trait bounds for generic items. - * @param {Map|null} mgensIn - Map functions generics to query generics. + * This function returns `true` if it matches, and also writes the results to mgensInout. + * It returns `false` if no match is found, and leaves mgensInout untouched. + * + * @param {FunctionType} fnType - The object to check. + * @param {QueryElement} queryElem - The element from the parsed query. + * @param {[FunctionType]} whereClause - Trait bounds for generic items. + * @param {Map|null} mgensInout - Map functions generics to query generics. * * @return {boolean} - Returns true if a match, false otherwise. */ - function checkGenerics(fnType, queryElem, whereClause, mgensIn) { - return unifyFunctionTypes(fnType.generics, queryElem.generics, whereClause, mgensIn); + function checkGenerics(fnType, queryElem, whereClause, mgensInout) { + return unifyFunctionTypes( + fnType.generics, + queryElem.generics, + whereClause, + mgensInout, + mgens => { + if (mgensInout) { + for (const [fid, qid] of mgens.entries()) { + mgensInout.set(fid, qid); + } + } + return true; + } + ); } /** * This function checks if a list of search query `queryElems` can all be found in the * search index (`fnTypes`). * - * @param {Array} fnTypes - The objects to check. - * @param {Array} queryElems - The elements from the parsed query. - * @param {[FunctionType]} whereClause - Trait bounds for generic items. - * @param {Map|null} mgensIn - Map function generics to query generics. + * This function returns `true` on a match, or `false` if none. If `solutionCb` is + * supplied, it will call that function with mgens, and that callback can accept or + * reject the result bu returning `true` or `false`. If the callback returns false, + * then this function will try with a different solution, or bail with false if it + * runs out of candidates. + * + * @param {Array} fnTypes - The objects to check. + * @param {Array} queryElems - The elements from the parsed query. + * @param {[FunctionType]} whereClause - Trait bounds for generic items. + * @param {Map|null} mgensIn + * - Map functions generics to query generics (never modified). + * @param {null|Map -> bool} solutionCb - Called for each `mgens` solution. * * @return {boolean} - Returns true if a match, false otherwise. */ - function unifyFunctionTypes(fnTypes, queryElems, whereClause, mgensIn) { - // This search engine implements order-agnostic unification. There - // should be no missing duplicates (generics have "bag semantics"), - // and the row is allowed to have extras. + function unifyFunctionTypes(fnTypesIn, queryElems, whereClause, mgensIn, solutionCb) { + /** + * @type Map + */ + let mgens = new Map(mgensIn); if (queryElems.length === 0) { - return true; + return !solutionCb || solutionCb(mgens); } - if (!fnTypes || fnTypes.length === 0) { + if (!fnTypesIn || fnTypesIn.length === 0) { return false; } + const ql = queryElems.length; + let fl = fnTypesIn.length; /** - * @type Map + * @type Array */ - const queryElemSet = new Map(); - const mgens = new Map(mgensIn); - const addQueryElemToQueryElemSet = queryElem => { - let currentQueryElemList; - const qid = queryElem.id; - if (queryElemSet.has(qid)) { - currentQueryElemList = queryElemSet.get(qid); - } else { - currentQueryElemList = []; - queryElemSet.set(qid, currentQueryElemList); - } - currentQueryElemList.push(queryElem); - }; - for (const queryElem of queryElems) { - addQueryElemToQueryElemSet(queryElem); - } + let fnTypes = fnTypesIn.slice(); /** - * @type Map + * loop works by building up a solution set in the working arrays + * fnTypes gets mutated in place to make this work, while queryElems + * is left alone + * + * vvvvvvv `i` points here + * queryElems = [ good, good, good, unknown, unknown ], + * fnTypes = [ good, good, good, unknown, unknown ], + * ---------------- ^^^^^^^^^^^^^^^^ `j` iterates after `i`, + * | looking for candidates + * everything before `i` is the + * current working solution + * + * Everything in the current working solution is known to be a good + * match, but it might not be the match we wind up going with, because + * there might be more than one candidate match, and we need to try them all + * before giving up. So, to handle this, it backtracks on failure. + * + * @type Array<{ + * "fnTypesScratch": Array, + * "queryElemsOffset": integer, + * "fnTypesOffset": integer + * }> */ - const fnTypeSet = new Map(); - /** - * If `fnType` is a concrete type with generics, replace it with its generics. - * - * If `fnType` is a generic type parameter, replace it with any traits that it's - * constrained by. This converts `T where T: Trait` into the same representation - * that's directly used by return-position `impl Trait` and by `dyn Trait` in - * all positions, so you can directly write `-> Trait` and get all three. - * - * This seems to correspond to two transforms described in - * http://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf - * One of them is actual unboxing (turning `Maybe a` into `a`), while the other is - * equivalent to converting a type parameter into an existential, which Hoogle doesn't - * seem to actually do? I notice that these two searches produce the same result: - * https://hoogle.haskell.org/?hoogle=Eq+-%3E+Set+Eq+-%3E+Set+Eq - * https://hoogle.haskell.org/?hoogle=Ord+-%3E+Set+Ord+-%3E+Set+Ord - * - * Haskell is a bit of a foreign language to me. I just want to be able to look up - * Option combinators without having to actually remember their (mostly arbitrary) - * names, and Haskell claims to already have that problem solved... - * - * @type Map - */ - const unbox = function unbox(fnType) { - if (fnType.id < 0) { - const genid = (-fnType.id) - 1; - if (whereClause && whereClause[genid]) { - for (const trait of whereClause[genid]) { - addFnTypeToFnTypeSet(trait); + const backtracking = []; + let i = 0; + let j = 0; + const backtrack = () => { + while (backtracking.length !== 0) { + // this session failed, but there are other possible solutions + // to backtrack, reset to (a copy of) the old array, do the swap or unboxing + const { + fnTypesScratch, + mgensScratch, + queryElemsOffset, + fnTypesOffset, + unbox, + } = backtracking.pop(); + mgens = new Map(mgensScratch); + const fnType = fnTypesScratch[fnTypesOffset]; + const queryElem = queryElems[queryElemsOffset]; + if (unbox) { + if (fnType.id < 0) { + if (mgens.has(fnType.id) && mgens.get(fnType.id) !== 0) { + continue; + } + mgens.set(fnType.id, 0); } + const generics = fnType.id < 0 ? + whereClause[(-fnType.id) - 1] : + fnType.generics; + fnTypes = fnTypesScratch.toSpliced(fnTypesOffset, 1, ...generics); + fl = fnTypes.length; + // re-run the matching algorithm on this item + i = queryElemsOffset - 1; + } else { + if (fnType.id < 0) { + if (mgens.has(fnType.id) && mgens.get(fnType.id) !== queryElem.id) { + continue; + } + mgens.set(fnType.id, queryElem.id); + } + fnTypes = fnTypesScratch.slice(); + fl = fnTypes.length; + const tmp = fnTypes[queryElemsOffset]; + fnTypes[queryElemsOffset] = fnTypes[fnTypesOffset]; + fnTypes[fnTypesOffset] = tmp; + // this is known as a good match; go to the next one + i = queryElemsOffset; } - mgens.set(fnType.id, 0); - } else { - for (const innerFnType of fnType.generics) { - addFnTypeToFnTypeSet(innerFnType); + return true; + } + return false; + }; + for (i = 0; i !== ql; ++i) { + const queryElem = queryElems[i]; + /** + * list of potential function types that go with the current query element. + * @type Array + */ + const matchCandidates = []; + let fnTypesScratch = null; + let mgensScratch = null; + // don't try anything before `i`, because they've already been + // paired off with the other query elements + for (j = i; j !== fl; ++j) { + const fnType = fnTypes[j]; + if (unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens)) { + if (!fnTypesScratch) { + fnTypesScratch = fnTypes.slice(); + } + unifyFunctionTypes( + fnType.generics, + queryElem.generics, + whereClause, + mgens, + mgensScratch => { + matchCandidates.push({ + fnTypesScratch, + mgensScratch, + queryElemsOffset: i, + fnTypesOffset: j, + unbox: false, + }); + return false; // "reject" all candidates to gather all of them + } + ); + } + if (unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens)) { + if (!fnTypesScratch) { + fnTypesScratch = fnTypes.slice(); + } + if (!mgensScratch) { + mgensScratch = new Map(mgens); + } + backtracking.push({ + fnTypesScratch, + mgensScratch, + queryElemsOffset: i, + fnTypesOffset: j, + unbox: true, + }); } } - }; - /** - * @param {FunctionType} fnType - */ - const addFnTypeToFnTypeSet = function addFnTypeToFnTypeSet(fnType) { - const queryContainsArrayOrSliceElem = queryElemSet.has(typeNameIdOfArrayOrSlice); - // qsid is an index into the `queryElemSet`, while `fnType.id` is an index into - // the `fnTypeSet`. These are the same, except for generics, where they get mapped. - // If qsid = 0, it means the generic type parameter has undergone instance - // substitution, and - let qsid = fnType.id; - if (fnType.id < 0) { - if (mgens.has(fnType.id)) { - qsid = mgens.get(fnType.id); + if (matchCandidates.length === 0) { + if (backtrack()) { + continue; } else { - qsid = null; - searching: for (const qid of queryElemSet.keys()) { - if (qid < 0) { - for (const qidMapped of mgens.values()) { - if (qid === qidMapped) { - continue searching; - } - } - mgens.set(fnType.id, qid); - qsid = qid; + return false; + } + } + // use the current candidate + const {fnTypesOffset: candidate, mgensScratch: mgensNew} = matchCandidates.pop(); + if (fnTypes[candidate].id < 0 && queryElems[i].id < 0) { + mgens.set(fnTypes[candidate].id, queryElems[i].id); + } + for (const [fid, qid] of mgensNew) { + mgens.set(fid, qid); + } + // `i` and `j` are paired off + // `queryElems[i]` is left in place + // `fnTypes[j]` is swapped with `fnTypes[i]` to pair them off + const tmp = fnTypes[candidate]; + fnTypes[candidate] = fnTypes[i]; + fnTypes[i] = tmp; + // write other candidates to backtracking queue + for (const otherCandidate of matchCandidates) { + backtracking.push(otherCandidate); + } + // If we're on the last item, check the solution with the callback + // backtrack if the callback says its unsuitable + while (i === (ql - 1) && solutionCb && !solutionCb(mgens)) { + if (!backtrack()) { + return false; + } + } + } + return true; + } + function unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens) { + // type filters look like `trait:Read` or `enum:Result` + if (!typePassesFilter(queryElem.typeFilter, fnType.ty)) { + return false; + } + // fnType.id < 0 means generic + // queryElem.id < 0 does too + // mgens[fnType.id] = queryElem.id + // or, if mgens[fnType.id] = 0, then we've matched this generic with a bare trait + // and should make that same decision everywhere it appears + if (fnType.id < 0 && queryElem.id < 0) { + if (mgens.has(fnType.id) && mgens.get(fnType.id) !== queryElem.id) { + return false; + } + for (const [fid, qid] of mgens.entries()) { + if (fnType.id !== fid && queryElem.id === qid) { + return false; + } + if (fnType.id === fid && queryElem.id !== qid) { + return false; + } + } + } else if (fnType.id !== null) { + if (queryElem.id === typeNameIdOfArrayOrSlice && + (fnType.id === typeNameIdOfSlice || fnType.id === typeNameIdOfArray) + ) { + // [] matches primitive:array or primitive:slice + // if it matches, then we're fine, and this is an appropriate match candidate + } else if (fnType.id !== queryElem.id) { + return false; + } + // If the query elem has generics, and the function doesn't, + // it can't match. + if (fnType.generics.length === 0 && queryElem.generics.length !== 0) { + return false; + } + // If the query element is a path (it contains `::`), we need to check if this + // path is compatible with the target type. + const queryElemPathLength = queryElem.pathWithoutLast.length; + if (queryElemPathLength > 0) { + const fnTypePath = fnType.path !== undefined && fnType.path !== null ? + fnType.path.split("::") : []; + // If the path provided in the query element is longer than this type, + // no need to check it since it won't match in any case. + if (queryElemPathLength > fnTypePath.length) { + return false; + } + let i = 0; + for (const path of fnTypePath) { + if (path === queryElem.pathWithoutLast[i]) { + i += 1; + if (i >= queryElemPathLength) { break; } } } - } - if (qsid === null || !( - queryElemSet.has(qsid) || - (qsid === typeNameIdOfSlice && queryContainsArrayOrSliceElem) || - (qsid === typeNameIdOfArray && queryContainsArrayOrSliceElem) - )) { - unbox(fnType); - return; - } - let currentQueryElemList = queryElemSet.get(qsid) || []; - let matchIdx = currentQueryElemList.findIndex(queryElem => { - return typePassesFilter(queryElem.typeFilter, fnType.ty) && - checkGenerics(fnType, queryElem, whereClause, mgens); - }); - if (matchIdx === -1 && - (qsid === typeNameIdOfSlice || qsid === typeNameIdOfArray) && - queryContainsArrayOrSliceElem - ) { - currentQueryElemList = queryElemSet.get(typeNameIdOfArrayOrSlice) || []; - matchIdx = currentQueryElemList.findIndex(queryElem => { - return typePassesFilter(queryElem.typeFilter, fnType.ty) && - checkGenerics(fnType, queryElem, whereClause, mgens); - }); - } - // None of the query elems match the function type. - if (matchIdx === -1) { - unbox(fnType); - return; - } - let currentFnTypeList; - if (fnTypeSet.has(fnType.id)) { - currentFnTypeList = fnTypeSet.get(fnType.id); - } else { - currentFnTypeList = []; - fnTypeSet.set(fnType.id, currentFnTypeList); - } - currentFnTypeList.push(fnType); - }; - for (const fnType of fnTypes) { - addFnTypeToFnTypeSet(fnType); - } - const doHandleQueryElemList = (currentFnTypeList, queryElemList) => { - if (queryElemList.length === 0) { - return true; - } - // Multiple items in one list might match multiple items in another. - // Since an item with fewer generics can match an item with more, we - // need to check all combinations for a potential match. - const queryElem = queryElemList.pop(); - const l = currentFnTypeList.length; - for (let i = 0; i < l; i += 1) { - const fnType = currentFnTypeList[i]; - if (!typePassesFilter(queryElem.typeFilter, fnType.ty)) { - continue; - } - const queryElemPathLength = queryElem.pathWithoutLast.length; - // If the query element is a path (it contains `::`), we need to check if this - // path is compatible with the target type. - if (queryElemPathLength > 0) { - const fnTypePath = fnType.path !== undefined && fnType.path !== null ? - fnType.path.split("::") : []; - // If the path provided in the query element is longer than this type, - // no need to check it since it won't match in any case. - if (queryElemPathLength > fnTypePath.length) { - continue; - } - let i = 0; - for (const path of fnTypePath) { - if (path === queryElem.pathWithoutLast[i]) { - i += 1; - if (i >= queryElemPathLength) { - break; - } - } - } - if (i < queryElemPathLength) { - // If we didn't find all parts of the path of the query element inside - // the fn type, then it's not the right one. - continue; - } - } - if (queryElem.generics.length === 0 - || checkGenerics(fnType, queryElem, whereClause, mgens) - ) { - currentFnTypeList.splice(i, 1); - const result = doHandleQueryElemList(currentFnTypeList, queryElemList); - if (result) { - return true; - } - currentFnTypeList.splice(i, 0, fnType); - } - } - return false; - }; - /** - * @param {number} id - * @param {[QueryElement]} queryElemList - */ - const handleQueryElemList = (id, queryElemList) => { - let fsid = id; - if (fsid < 0) { - fsid = null; - if (!mgens) { + if (i < queryElemPathLength) { + // If we didn't find all parts of the path of the query element inside + // the fn type, then it's not the right one. return false; } - for (const [fid, qsid] of mgens) { - if (id === qsid) { - fsid = fid; - break; - } - } - } - if (fsid === null || !fnTypeSet.has(fsid)) { - if (fsid === typeNameIdOfArrayOrSlice) { - return handleQueryElemList(typeNameIdOfSlice, queryElemList) || - handleQueryElemList(typeNameIdOfArray, queryElemList); - } - return false; - } - const currentFnTypeList = fnTypeSet.get(fsid); - if (currentFnTypeList.length < queryElemList.length) { - // It's not possible for all the query elems to find a match. - return false; - } - const result = doHandleQueryElemList(currentFnTypeList, queryElemList); - if (result) { - // Found a solution. - // Any items that weren't used for it can be unboxed, and might form - // part of the solution for another item. - for (const innerFnType of currentFnTypeList) { - unbox(innerFnType); - } - fnTypeSet.delete(fsid); - } - return result; - }; - let queryElemSetSize = Number.MAX_VALUE; - while (queryElemSetSize > queryElemSet.size) { - queryElemSetSize = queryElemSet.size; - for (const [id, queryElemList] of queryElemSet) { - if (handleQueryElemList(id, queryElemList)) { - queryElemSet.delete(id); - } } } - if (queryElemSetSize === 0) { - for (const [fid, qid] of mgens) { - mgensIn.set(fid, qid); + return true; + } + function unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens) { + if (fnType.id < 0 && queryElem.id >= 0) { + if (!whereClause) { + return false; } - return true; - } else { - return false; + // mgens[fnType.id] === 0 indicates that we committed to unboxing this generic + // mgens[fnType.id] === null indicates that we haven't decided yet + if (mgens.has(fnType.id) && mgens.get(fnType.id) !== 0) { + return false; + } + // This is only a potential unbox if the search query appears in the where clause + // for example, searching `Read -> usize` should find + // `fn read_all(R) -> Result` + // generic `R` is considered "unboxed" + return checkIfInList(whereClause[(-fnType.id) - 1], queryElem, whereClause); + } else if (fnType.generics && fnType.generics.length > 0) { + return checkIfInList(fnType.generics, queryElem, whereClause); } + return false; } /** @@ -1907,23 +1951,19 @@ function initSearch(rawSearchIndex) { } // If the result is too "bad", we return false and it ends this search. - let mgens; - if (row.type.where_clause && row.type.where_clause.length > 0) { - mgens = new Map(); - } if (!unifyFunctionTypes( row.type.inputs, parsedQuery.elems, row.type.where_clause, - mgens - )) { - return; - } - if (!unifyFunctionTypes( - row.type.output, - parsedQuery.returned, - row.type.where_clause, - mgens + null, + mgens => { + return unifyFunctionTypes( + row.type.output, + parsedQuery.returned, + row.type.where_clause, + mgens + ); + } )) { return; } @@ -2060,7 +2100,7 @@ function initSearch(rawSearchIndex) { in_returned = row.type && unifyFunctionTypes( row.type.output, parsedQuery.returned, - row.type.where_clause, new Map() + row.type.where_clause ); if (in_returned) { addIntoResults( diff --git a/tests/rustdoc-js-std/option-type-signatures.js b/tests/rustdoc-js-std/option-type-signatures.js index 37bb3b14ab38..e154fa707ab3 100644 --- a/tests/rustdoc-js-std/option-type-signatures.js +++ b/tests/rustdoc-js-std/option-type-signatures.js @@ -1,3 +1,5 @@ +// ignore-order + const FILTER_CRATE = "std"; const EXPECTED = [ @@ -36,8 +38,9 @@ const EXPECTED = [ ], }, { - 'query': 'option, option -> option', + 'query': 'option, option -> option', 'others': [ + { 'path': 'std::option::Option', 'name': 'and' }, { 'path': 'std::option::Option', 'name': 'zip' }, ], }, diff --git a/tests/rustdoc-js-std/vec-type-signatures.js b/tests/rustdoc-js-std/vec-type-signatures.js new file mode 100644 index 000000000000..18cf9d6efd0f --- /dev/null +++ b/tests/rustdoc-js-std/vec-type-signatures.js @@ -0,0 +1,22 @@ +// ignore-order + +const FILTER_CRATE = "std"; + +const EXPECTED = [ + { + 'query': 'vec::intoiter -> [T]', + 'others': [ + { 'path': 'std::vec::IntoIter', 'name': 'as_slice' }, + { 'path': 'std::vec::IntoIter', 'name': 'as_mut_slice' }, + { 'path': 'std::vec::IntoIter', 'name': 'next_chunk' }, + ], + }, + { + 'query': 'vec::intoiter -> []', + 'others': [ + { 'path': 'std::vec::IntoIter', 'name': 'as_slice' }, + { 'path': 'std::vec::IntoIter', 'name': 'as_mut_slice' }, + { 'path': 'std::vec::IntoIter', 'name': 'next_chunk' }, + ], + }, +]; diff --git a/tests/rustdoc-js/generics-match-ambiguity.js b/tests/rustdoc-js/generics-match-ambiguity.js index a9932a16ca38..edce4268c5ac 100644 --- a/tests/rustdoc-js/generics-match-ambiguity.js +++ b/tests/rustdoc-js/generics-match-ambiguity.js @@ -79,6 +79,7 @@ const EXPECTED = [ { 'path': 'generics_match_ambiguity', 'name': 'baac' }, { 'path': 'generics_match_ambiguity', 'name': 'baaf' }, { 'path': 'generics_match_ambiguity', 'name': 'baag' }, + { 'path': 'generics_match_ambiguity', 'name': 'baah' }, ], }, { diff --git a/tests/rustdoc-js/generics-unbox.js b/tests/rustdoc-js/generics-unbox.js new file mode 100644 index 000000000000..9cdfc7ac8b6e --- /dev/null +++ b/tests/rustdoc-js/generics-unbox.js @@ -0,0 +1,38 @@ +// exact-check + +const EXPECTED = [ + { + 'query': 'Inside -> Out1', + 'others': [ + { 'path': 'generics_unbox', 'name': 'alpha' }, + ], + }, + { + 'query': 'Inside -> Out3', + 'others': [ + { 'path': 'generics_unbox', 'name': 'beta' }, + { 'path': 'generics_unbox', 'name': 'gamma' }, + ], + }, + { + 'query': 'Inside -> Out4', + 'others': [ + { 'path': 'generics_unbox', 'name': 'beta' }, + { 'path': 'generics_unbox', 'name': 'gamma' }, + ], + }, + { + 'query': 'Inside -> Out3', + 'others': [ + { 'path': 'generics_unbox', 'name': 'beta' }, + { 'path': 'generics_unbox', 'name': 'gamma' }, + ], + }, + { + 'query': 'Inside -> Out4', + 'others': [ + { 'path': 'generics_unbox', 'name': 'beta' }, + { 'path': 'generics_unbox', 'name': 'gamma' }, + ], + }, +]; diff --git a/tests/rustdoc-js/generics-unbox.rs b/tests/rustdoc-js/generics-unbox.rs new file mode 100644 index 000000000000..bef34f891e95 --- /dev/null +++ b/tests/rustdoc-js/generics-unbox.rs @@ -0,0 +1,36 @@ +pub struct Out { + a: A, + b: B, +} + +pub struct Out1 { + a: [A; N], +} + +pub struct Out2 { + a: [A; N], +} + +pub struct Out3 { + a: A, + b: B, +} + +pub struct Out4 { + a: A, + b: B, +} + +pub struct Inside(T); + +pub fn alpha(_: Inside) -> Out, Out2> { + loop {} +} + +pub fn beta(_: Inside) -> Out, Out4> { + loop {} +} + +pub fn gamma(_: Inside) -> Out, Out4> { + loop {} +} From 4cf06e84abeb8bfc14fd7e9055db1c8ccf82266d Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sat, 9 Sep 2023 16:59:34 -0700 Subject: [PATCH 037/111] rustdoc-doc: add `next_chunk` to list of `vec::intoiter -> [T]` This didn't show up before, because of some unification bugs that were fixed in 269cb579479ab950e85a2e4078810501c29d7465 --- src/doc/rustdoc/src/read-documentation/search.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustdoc/src/read-documentation/search.md b/src/doc/rustdoc/src/read-documentation/search.md index d794dbbf250c..56a5016d0cee 100644 --- a/src/doc/rustdoc/src/read-documentation/search.md +++ b/src/doc/rustdoc/src/read-documentation/search.md @@ -71,7 +71,7 @@ the standard library and functions that are included in the results list: | [`option -> default`][] | `Option::unwrap_or_default` | | [`stdout, [u8]`][stdoutu8] | `Stdout::write` | | [`any -> !`][] | `panic::panic_any` | -| [`vec::intoiter -> [T]`][iterasslice] | `IntoIter::as_slice` | +| [`vec::intoiter -> [T]`][iterasslice] | `IntoIter::as_slice` and `IntoIter::next_chunk` | [`usize -> vec`]: ../../std/vec/struct.Vec.html?search=usize%20-%3E%20vec&filter-crate=std [`vec, vec -> bool`]: ../../std/vec/struct.Vec.html?search=vec,%20vec%20-%3E%20bool&filter-crate=std From 01e97981488ddb0b8194b6f4e27c3592bcd2c8d1 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Mon, 4 Sep 2023 12:40:08 -0400 Subject: [PATCH 038/111] Reimplement FileEncoder with a small-write optimization --- compiler/rustc_metadata/src/rmeta/table.rs | 6 +- .../src/dep_graph/serialized.rs | 11 +- compiler/rustc_serialize/src/leb128.rs | 20 +- compiler/rustc_serialize/src/lib.rs | 3 + compiler/rustc_serialize/src/opaque.rs | 270 +++++------------- compiler/rustc_serialize/tests/leb128.rs | 14 +- 6 files changed, 102 insertions(+), 222 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/table.rs b/compiler/rustc_metadata/src/rmeta/table.rs index e906c9063472..35987072ed6b 100644 --- a/compiler/rustc_metadata/src/rmeta/table.rs +++ b/compiler/rustc_metadata/src/rmeta/table.rs @@ -5,7 +5,6 @@ use rustc_hir::def::{CtorKind, CtorOf}; use rustc_index::Idx; use rustc_middle::ty::{ParameterizedOverTcx, UnusedGenericParams}; use rustc_serialize::opaque::FileEncoder; -use rustc_serialize::Encoder as _; use rustc_span::hygiene::MacroKind; use std::marker::PhantomData; use std::num::NonZeroUsize; @@ -468,7 +467,10 @@ impl> TableBui let width = self.width; for block in &self.blocks { - buf.emit_raw_bytes(&block[..width]); + buf.write_with(|dest| { + *dest = *block; + width + }); } LazyTable::from_position_and_encoded_size( diff --git a/compiler/rustc_query_system/src/dep_graph/serialized.rs b/compiler/rustc_query_system/src/dep_graph/serialized.rs index 4ba0cb31d0b6..3fd83f79a489 100644 --- a/compiler/rustc_query_system/src/dep_graph/serialized.rs +++ b/compiler/rustc_query_system/src/dep_graph/serialized.rs @@ -394,7 +394,10 @@ struct NodeInfo { impl Encodable for NodeInfo { fn encode(&self, e: &mut FileEncoder) { let header = SerializedNodeHeader::new(self); - e.emit_raw_bytes(&header.bytes); + e.write_with(|dest| { + *dest = header.bytes; + header.bytes.len() + }); if header.len().is_none() { e.emit_usize(self.edges.len()); @@ -402,8 +405,10 @@ impl Encodable for NodeInfo { let bytes_per_index = header.bytes_per_index(); for node_index in self.edges.iter() { - let bytes = node_index.as_u32().to_le_bytes(); - e.emit_raw_bytes(&bytes[..bytes_per_index]); + e.write_with(|dest| { + *dest = node_index.as_u32().to_le_bytes(); + bytes_per_index + }); } } } diff --git a/compiler/rustc_serialize/src/leb128.rs b/compiler/rustc_serialize/src/leb128.rs index e568b9e6786f..ca661bac78c7 100644 --- a/compiler/rustc_serialize/src/leb128.rs +++ b/compiler/rustc_serialize/src/leb128.rs @@ -15,23 +15,20 @@ pub const fn largest_max_leb128_len() -> usize { macro_rules! impl_write_unsigned_leb128 { ($fn_name:ident, $int_ty:ty) => { #[inline] - pub fn $fn_name( - out: &mut [::std::mem::MaybeUninit; max_leb128_len::<$int_ty>()], - mut value: $int_ty, - ) -> &[u8] { + pub fn $fn_name(out: &mut [u8; max_leb128_len::<$int_ty>()], mut value: $int_ty) -> usize { let mut i = 0; loop { if value < 0x80 { unsafe { - *out.get_unchecked_mut(i).as_mut_ptr() = value as u8; + *out.get_unchecked_mut(i) = value as u8; } i += 1; break; } else { unsafe { - *out.get_unchecked_mut(i).as_mut_ptr() = ((value & 0x7f) | 0x80) as u8; + *out.get_unchecked_mut(i) = ((value & 0x7f) | 0x80) as u8; } value >>= 7; @@ -39,7 +36,7 @@ macro_rules! impl_write_unsigned_leb128 { } } - unsafe { ::std::mem::MaybeUninit::slice_assume_init_ref(&out.get_unchecked(..i)) } + i } }; } @@ -87,10 +84,7 @@ impl_read_unsigned_leb128!(read_usize_leb128, usize); macro_rules! impl_write_signed_leb128 { ($fn_name:ident, $int_ty:ty) => { #[inline] - pub fn $fn_name( - out: &mut [::std::mem::MaybeUninit; max_leb128_len::<$int_ty>()], - mut value: $int_ty, - ) -> &[u8] { + pub fn $fn_name(out: &mut [u8; max_leb128_len::<$int_ty>()], mut value: $int_ty) -> usize { let mut i = 0; loop { @@ -104,7 +98,7 @@ macro_rules! impl_write_signed_leb128 { } unsafe { - *out.get_unchecked_mut(i).as_mut_ptr() = byte; + *out.get_unchecked_mut(i) = byte; } i += 1; @@ -114,7 +108,7 @@ macro_rules! impl_write_signed_leb128 { } } - unsafe { ::std::mem::MaybeUninit::slice_assume_init_ref(&out.get_unchecked(..i)) } + i } }; } diff --git a/compiler/rustc_serialize/src/lib.rs b/compiler/rustc_serialize/src/lib.rs index ce8503918b4f..dd40b3cf0283 100644 --- a/compiler/rustc_serialize/src/lib.rs +++ b/compiler/rustc_serialize/src/lib.rs @@ -17,6 +17,9 @@ Core encoding and decoding interfaces. #![feature(new_uninit)] #![feature(allocator_api)] #![feature(ptr_sub_ptr)] +#![feature(slice_first_last_chunk)] +#![feature(inline_const)] +#![feature(const_option)] #![cfg_attr(test, feature(test))] #![allow(rustc::internal)] #![deny(rustc::untranslatable_diagnostic)] diff --git a/compiler/rustc_serialize/src/opaque.rs b/compiler/rustc_serialize/src/opaque.rs index f1b7e8d9ae09..fcd35f2ea57b 100644 --- a/compiler/rustc_serialize/src/opaque.rs +++ b/compiler/rustc_serialize/src/opaque.rs @@ -3,10 +3,8 @@ use crate::serialize::{Decodable, Decoder, Encodable, Encoder}; use std::fs::File; use std::io::{self, Write}; use std::marker::PhantomData; -use std::mem::MaybeUninit; use std::ops::Range; use std::path::Path; -use std::ptr; // ----------------------------------------------------------------------------- // Encoder @@ -24,10 +22,9 @@ const BUF_SIZE: usize = 8192; /// size of the buffer, rather than the full length of the encoded data, and /// because it doesn't need to reallocate memory along the way. pub struct FileEncoder { - /// The input buffer. For adequate performance, we need more control over - /// buffering than `BufWriter` offers. If `BufWriter` ever offers a raw - /// buffer access API, we can use it, and remove `buf` and `buffered`. - buf: Box<[MaybeUninit]>, + /// The input buffer. For adequate performance, we need to be able to write + /// directly to the unwritten region of the buffer, without calling copy_from_slice. + buf: Box<[u8; BUF_SIZE]>, buffered: usize, flushed: usize, file: File, @@ -38,15 +35,11 @@ pub struct FileEncoder { impl FileEncoder { pub fn new>(path: P) -> io::Result { - // Create the file for reading and writing, because some encoders do both - // (e.g. the metadata encoder when -Zmeta-stats is enabled) - let file = File::options().read(true).write(true).create(true).truncate(true).open(path)?; - Ok(FileEncoder { - buf: Box::new_uninit_slice(BUF_SIZE), + buf: vec![0u8; BUF_SIZE].into_boxed_slice().try_into().unwrap(), buffered: 0, flushed: 0, - file, + file: File::create(path)?, res: Ok(()), }) } @@ -54,94 +47,20 @@ impl FileEncoder { #[inline] pub fn position(&self) -> usize { // Tracking position this way instead of having a `self.position` field - // means that we don't have to update the position on every write call. + // means that we only need to update `self.buffered` on a write call, + // as opposed to updating `self.position` and `self.buffered`. self.flushed + self.buffered } - pub fn flush(&mut self) { - // This is basically a copy of `BufWriter::flush`. If `BufWriter` ever - // offers a raw buffer access API, we can use it, and remove this. - - /// Helper struct to ensure the buffer is updated after all the writes - /// are complete. It tracks the number of written bytes and drains them - /// all from the front of the buffer when dropped. - struct BufGuard<'a> { - buffer: &'a mut [u8], - encoder_buffered: &'a mut usize, - encoder_flushed: &'a mut usize, - flushed: usize, - } - - impl<'a> BufGuard<'a> { - fn new( - buffer: &'a mut [u8], - encoder_buffered: &'a mut usize, - encoder_flushed: &'a mut usize, - ) -> Self { - assert_eq!(buffer.len(), *encoder_buffered); - Self { buffer, encoder_buffered, encoder_flushed, flushed: 0 } - } - - /// The unwritten part of the buffer - fn remaining(&self) -> &[u8] { - &self.buffer[self.flushed..] - } - - /// Flag some bytes as removed from the front of the buffer - fn consume(&mut self, amt: usize) { - self.flushed += amt; - } - - /// true if all of the bytes have been written - fn done(&self) -> bool { - self.flushed >= *self.encoder_buffered - } - } - - impl Drop for BufGuard<'_> { - fn drop(&mut self) { - if self.flushed > 0 { - if self.done() { - *self.encoder_flushed += *self.encoder_buffered; - *self.encoder_buffered = 0; - } else { - self.buffer.copy_within(self.flushed.., 0); - *self.encoder_flushed += self.flushed; - *self.encoder_buffered -= self.flushed; - } - } - } - } - - // If we've already had an error, do nothing. It'll get reported after - // `finish` is called. - if self.res.is_err() { - return; - } - - let mut guard = BufGuard::new( - unsafe { MaybeUninit::slice_assume_init_mut(&mut self.buf[..self.buffered]) }, - &mut self.buffered, - &mut self.flushed, - ); - - while !guard.done() { - match self.file.write(guard.remaining()) { - Ok(0) => { - self.res = Err(io::Error::new( - io::ErrorKind::WriteZero, - "failed to write the buffered data", - )); - return; - } - Ok(n) => guard.consume(n), - Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} - Err(e) => { - self.res = Err(e); - return; - } - } + #[cold] + #[inline(never)] + pub fn flush(&mut self) -> &mut [u8; BUF_SIZE] { + if self.res.is_ok() { + self.res = self.file.write_all(&self.buf[..self.buffered]); } + self.flushed += self.buffered; + self.buffered = 0; + &mut self.buf } pub fn file(&self) -> &File { @@ -149,99 +68,64 @@ impl FileEncoder { } #[inline] - fn write_one(&mut self, value: u8) { - let mut buffered = self.buffered; + fn buffer_empty(&mut self) -> &mut [u8] { + // SAFETY: self.buffered is inbounds as an invariant of the type + unsafe { self.buf.get_unchecked_mut(self.buffered..) } + } - if std::intrinsics::unlikely(buffered + 1 > BUF_SIZE) { - self.flush(); - buffered = 0; + #[cold] + #[inline(never)] + fn write_all_cold_path(&mut self, buf: &[u8]) { + if let Some(dest) = self.flush().get_mut(..buf.len()) { + dest.copy_from_slice(buf); + self.buffered += buf.len(); + } else { + if self.res.is_ok() { + self.res = self.file.write_all(buf); + } + self.flushed += buf.len(); } - - // SAFETY: The above check and `flush` ensures that there is enough - // room to write the input to the buffer. - unsafe { - *MaybeUninit::slice_as_mut_ptr(&mut self.buf).add(buffered) = value; - } - - self.buffered = buffered + 1; } #[inline] fn write_all(&mut self, buf: &[u8]) { - let buf_len = buf.len(); - - if std::intrinsics::likely(buf_len <= BUF_SIZE) { - let mut buffered = self.buffered; - - if std::intrinsics::unlikely(buffered + buf_len > BUF_SIZE) { - self.flush(); - buffered = 0; - } - - // SAFETY: The above check and `flush` ensures that there is enough - // room to write the input to the buffer. - unsafe { - let src = buf.as_ptr(); - let dst = MaybeUninit::slice_as_mut_ptr(&mut self.buf).add(buffered); - ptr::copy_nonoverlapping(src, dst, buf_len); - } - - self.buffered = buffered + buf_len; + if let Some(dest) = self.buffer_empty().get_mut(..buf.len()) { + dest.copy_from_slice(buf); + self.buffered += buf.len(); } else { - self.write_all_unbuffered(buf); + self.write_all_cold_path(buf); } } - fn write_all_unbuffered(&mut self, mut buf: &[u8]) { - // If we've already had an error, do nothing. It'll get reported after - // `finish` is called. - if self.res.is_err() { - return; - } - - if self.buffered > 0 { + /// Write up to `N` bytes to this encoder. + /// + /// Whenever possible, use this function to do writes whose length has a small and + /// compile-time constant upper bound. + #[inline] + pub fn write_with(&mut self, mut visitor: V) + where + V: FnMut(&mut [u8; N]) -> usize, + { + let flush_threshold = const { BUF_SIZE.checked_sub(N).unwrap() }; + if std::intrinsics::unlikely(self.buffered > flush_threshold) { self.flush(); } - - // This is basically a copy of `Write::write_all` but also updates our - // `self.flushed`. It's necessary because `Write::write_all` does not - // return the number of bytes written when an error is encountered, and - // without that, we cannot accurately update `self.flushed` on error. - while !buf.is_empty() { - match self.file.write(buf) { - Ok(0) => { - self.res = Err(io::Error::new( - io::ErrorKind::WriteZero, - "failed to write whole buffer", - )); - return; - } - Ok(n) => { - buf = &buf[n..]; - self.flushed += n; - } - Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} - Err(e) => { - self.res = Err(e); - return; - } - } - } + // SAFETY: We checked above that that N < self.buffer_empty().len(), + // and if isn't, flush ensures that our empty buffer is now BUF_SIZE. + // We produce a post-mono error if N > BUF_SIZE. + let buf = unsafe { self.buffer_empty().first_chunk_mut::().unwrap_unchecked() }; + let written = visitor(buf); + debug_assert!(written <= N); + // We have to ensure that an errant visitor cannot cause self.buffered to exeed BUF_SIZE. + self.buffered += written.min(N); } pub fn finish(mut self) -> Result { self.flush(); - - let res = std::mem::replace(&mut self.res, Ok(())); - res.map(|()| self.position()) - } -} - -impl Drop for FileEncoder { - fn drop(&mut self) { - // Likely to be a no-op, because `finish` should have been called and - // it also flushes. But do it just in case. - let _result = self.flush(); + match self.res { + Ok(()) => Ok(self.position()), + Err(e) => Err(e), + } } } @@ -250,25 +134,7 @@ macro_rules! write_leb128 { #[inline] fn $this_fn(&mut self, v: $int_ty) { const MAX_ENCODED_LEN: usize = $crate::leb128::max_leb128_len::<$int_ty>(); - - let mut buffered = self.buffered; - - // This can't overflow because BUF_SIZE and MAX_ENCODED_LEN are both - // quite small. - if std::intrinsics::unlikely(buffered + MAX_ENCODED_LEN > BUF_SIZE) { - self.flush(); - buffered = 0; - } - - // SAFETY: The above check and flush ensures that there is enough - // room to write the encoded value to the buffer. - let buf = unsafe { - &mut *(self.buf.as_mut_ptr().add(buffered) - as *mut [MaybeUninit; MAX_ENCODED_LEN]) - }; - - let encoded = leb128::$write_leb_fn(buf, v); - self.buffered = buffered + encoded.len(); + self.write_with::(|buf| leb128::$write_leb_fn(buf, v)) } }; } @@ -281,12 +147,18 @@ impl Encoder for FileEncoder { #[inline] fn emit_u16(&mut self, v: u16) { - self.write_all(&v.to_le_bytes()); + self.write_with(|buf| { + *buf = v.to_le_bytes(); + 2 + }); } #[inline] fn emit_u8(&mut self, v: u8) { - self.write_one(v); + self.write_with(|buf: &mut [u8; 1]| { + buf[0] = v; + 1 + }); } write_leb128!(emit_isize, isize, write_isize_leb128); @@ -296,7 +168,10 @@ impl Encoder for FileEncoder { #[inline] fn emit_i16(&mut self, v: i16) { - self.write_all(&v.to_le_bytes()); + self.write_with(|buf| { + *buf = v.to_le_bytes(); + 2 + }); } #[inline] @@ -495,7 +370,10 @@ impl Encodable for IntEncodedWithFixedSize { #[inline] fn encode(&self, e: &mut FileEncoder) { let _start_pos = e.position(); - e.emit_raw_bytes(&self.0.to_le_bytes()); + e.write_with(|buf| { + *buf = self.0.to_le_bytes(); + buf.len() + }); let _end_pos = e.position(); debug_assert_eq!((_end_pos - _start_pos), IntEncodedWithFixedSize::ENCODED_SIZE); } diff --git a/compiler/rustc_serialize/tests/leb128.rs b/compiler/rustc_serialize/tests/leb128.rs index 7872e7784311..dc9b32a968b5 100644 --- a/compiler/rustc_serialize/tests/leb128.rs +++ b/compiler/rustc_serialize/tests/leb128.rs @@ -1,8 +1,4 @@ -#![feature(maybe_uninit_slice)] -#![feature(maybe_uninit_uninit_array)] - use rustc_serialize::leb128::*; -use std::mem::MaybeUninit; use rustc_serialize::Decoder; macro_rules! impl_test_unsigned_leb128 { @@ -24,9 +20,10 @@ macro_rules! impl_test_unsigned_leb128 { let mut stream = Vec::new(); + let mut buf = Default::default(); for &x in &values { - let mut buf = MaybeUninit::uninit_array(); - stream.extend($write_fn_name(&mut buf, x)); + let n = $write_fn_name(&mut buf, x); + stream.extend(&buf[..n]); } let mut decoder = rustc_serialize::opaque::MemDecoder::new(&stream, 0); @@ -70,9 +67,10 @@ macro_rules! impl_test_signed_leb128 { let mut stream = Vec::new(); + let mut buf = Default::default(); for &x in &values { - let mut buf = MaybeUninit::uninit_array(); - stream.extend($write_fn_name(&mut buf, x)); + let n = $write_fn_name(&mut buf, x); + stream.extend(&buf[..n]); } let mut decoder = rustc_serialize::opaque::MemDecoder::new(&stream, 0); From 207b6091d4d25db310a28f0f6bc6db50368dd00b Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 11 Sep 2023 08:49:57 -0400 Subject: [PATCH 039/111] Refactor `thread_info` to remove the `RefCell` `thread_info` currently uses `RefCell`-based initialization. Refactor this to use `OnceCell` instead which is more performant and better suits the needs of one-time initialization. --- library/std/src/sys_common/thread_info.rs | 37 +++++++++++++---------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/library/std/src/sys_common/thread_info.rs b/library/std/src/sys_common/thread_info.rs index 88d937a7db15..8d51732e0358 100644 --- a/library/std/src/sys_common/thread_info.rs +++ b/library/std/src/sys_common/thread_info.rs @@ -1,46 +1,51 @@ #![allow(dead_code)] // stack_guard isn't used right now on all platforms -use crate::cell::RefCell; +use crate::cell::OnceCell; use crate::sys::thread::guard::Guard; use crate::thread::Thread; struct ThreadInfo { - stack_guard: Option, - thread: Thread, + stack_guard: OnceCell, + thread: OnceCell, } -thread_local! { static THREAD_INFO: RefCell> = const { RefCell::new(None) } } +thread_local! { + static THREAD_INFO: ThreadInfo = const { ThreadInfo { + stack_guard: OnceCell::new(), + thread: OnceCell::new() + } }; +} impl ThreadInfo { fn with(f: F) -> Option where - F: FnOnce(&mut ThreadInfo) -> R, + F: FnOnce(&Thread, &OnceCell) -> R, { THREAD_INFO .try_with(move |thread_info| { - let mut thread_info = thread_info.borrow_mut(); - let thread_info = thread_info.get_or_insert_with(|| ThreadInfo { - stack_guard: None, - thread: Thread::new(None), - }); - f(thread_info) + let thread = thread_info.thread.get_or_init(|| Thread::new(None)); + f(thread, &thread_info.stack_guard) }) .ok() } } pub fn current_thread() -> Option { - ThreadInfo::with(|info| info.thread.clone()) + ThreadInfo::with(|thread, _| thread.clone()) } pub fn stack_guard() -> Option { - ThreadInfo::with(|info| info.stack_guard.clone()).and_then(|o| o) + ThreadInfo::with(|_, guard| guard.get().cloned()).flatten() } +/// Set new thread info, panicking if it has already been initialized +#[allow(unreachable_code, unreachable_patterns)] // some platforms don't use stack_guard pub fn set(stack_guard: Option, thread: Thread) { THREAD_INFO.with(move |thread_info| { - let mut thread_info = thread_info.borrow_mut(); - rtassert!(thread_info.is_none()); - *thread_info = Some(ThreadInfo { stack_guard, thread }); + rtassert!(thread_info.stack_guard.get().is_none() && thread_info.thread.get().is_none()); + if let Some(guard) = stack_guard { + thread_info.stack_guard.set(guard).unwrap(); + } + thread_info.thread.set(thread).unwrap(); }); } From ce19bc39640f85b2ffffff3bd31cf0bec5a06165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mi=C4=85sko?= Date: Sun, 10 Sep 2023 00:00:00 +0000 Subject: [PATCH 040/111] Use no_core for KCFI tests to exercise them in CI --- tests/codegen/sanitizer/kcfi-add-kcfi-flag.rs | 13 +++++++++++- tests/ui/sanitize/cfg.rs | 21 ++++++++++++++----- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/tests/codegen/sanitizer/kcfi-add-kcfi-flag.rs b/tests/codegen/sanitizer/kcfi-add-kcfi-flag.rs index c2eb852aec3c..6d466b93c406 100644 --- a/tests/codegen/sanitizer/kcfi-add-kcfi-flag.rs +++ b/tests/codegen/sanitizer/kcfi-add-kcfi-flag.rs @@ -1,9 +1,20 @@ // Verifies that "kcfi" module flag is added. // -// needs-sanitizer-kcfi +// revisions: aarch64 x86_64 +// [aarch64] compile-flags: --target aarch64-unknown-none +// [aarch64] needs-llvm-components: aarch64 +// [x86_64] compile-flags: --target x86_64-unknown-none +// [x86_64] needs-llvm-components: x86 // compile-flags: -Ctarget-feature=-crt-static -Zsanitizer=kcfi +#![feature(no_core, lang_items)] #![crate_type="lib"] +#![no_core] + +#[lang="sized"] +trait Sized { } +#[lang="copy"] +trait Copy { } pub fn foo() { } diff --git a/tests/ui/sanitize/cfg.rs b/tests/ui/sanitize/cfg.rs index 523de1ceaee3..f8ccf3b042db 100644 --- a/tests/ui/sanitize/cfg.rs +++ b/tests/ui/sanitize/cfg.rs @@ -1,15 +1,15 @@ // Verifies that when compiling with -Zsanitizer=option, // the `#[cfg(sanitize = "option")]` attribute is configured. -// needs-sanitizer-support // check-pass // revisions: address cfi kcfi leak memory thread //[address]needs-sanitizer-address //[address]compile-flags: -Zsanitizer=address --cfg address //[cfi]needs-sanitizer-cfi -//[cfi]compile-flags: -Zsanitizer=cfi --cfg cfi -Clto -//[kcfi]needs-sanitizer-kcfi -//[kcfi]compile-flags: -Zsanitizer=kcfi --cfg kcfi +//[cfi]compile-flags: -Zsanitizer=cfi --cfg cfi +//[cfi]compile-flags: -Clto -Ccodegen-units=1 -Ctarget-feature=-crt-static +//[kcfi]needs-llvm-components: x86 +//[kcfi]compile-flags: -Zsanitizer=kcfi --cfg kcfi --target x86_64-unknown-none //[leak]needs-sanitizer-leak //[leak]compile-flags: -Zsanitizer=leak --cfg leak //[memory]needs-sanitizer-memory @@ -17,7 +17,14 @@ //[thread]needs-sanitizer-thread //[thread]compile-flags: -Zsanitizer=thread --cfg thread -#![feature(cfg_sanitize)] +#![feature(cfg_sanitize, no_core, lang_items)] +#![crate_type="lib"] +#![no_core] + +#[lang="sized"] +trait Sized { } +#[lang="copy"] +trait Copy { } #[cfg(all(sanitize = "address", address))] fn main() {} @@ -36,3 +43,7 @@ fn main() {} #[cfg(all(sanitize = "thread", thread))] fn main() {} + +pub fn check() { + main(); +} From 272cd384e8d1006240ecaed5077fe2990f576dbb Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Mon, 4 Sep 2023 17:35:46 -0400 Subject: [PATCH 041/111] Fall back to an unoptimized implementation in read_binary_file if File::metadata lies --- compiler/rustc_span/src/source_map.rs | 33 +++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_span/src/source_map.rs b/compiler/rustc_span/src/source_map.rs index 68727a6c40ea..0b575c13adf2 100644 --- a/compiler/rustc_span/src/source_map.rs +++ b/compiler/rustc_span/src/source_map.rs @@ -127,10 +127,39 @@ impl FileLoader for RealFileLoader { let mut bytes = Lrc::new_uninit_slice(len as usize); let mut buf = BorrowedBuf::from(Lrc::get_mut(&mut bytes).unwrap()); - file.read_buf_exact(buf.unfilled())?; + match file.read_buf_exact(buf.unfilled()) { + Ok(()) => {} + Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => { + drop(bytes); + return fs::read(path).map(Vec::into); + } + Err(e) => return Err(e), + } // SAFETY: If the read_buf_exact call returns Ok(()), then we have // read len bytes and initialized the buffer. - Ok(unsafe { bytes.assume_init() }) + let bytes = unsafe { bytes.assume_init() }; + + // At this point, we've read all the bytes that filesystem metadata reported exist. + // But we are not guaranteed to be at the end of the file, because we did not attempt to do + // a read with a non-zero-sized buffer and get Ok(0). + // So we do small read to a fixed-size buffer. If the read returns no bytes then we're + // already done, and we just return the Lrc we built above. + // If the read returns bytes however, we just fall back to reading into a Vec then turning + // that into an Lrc, losing our nice peak memory behavior. This fallback code path should + // be rarely exercised. + + let mut probe = [0u8; 32]; + let n = loop { + match file.read(&mut probe) { + Ok(0) => return Ok(bytes), + Err(e) if e.kind() == io::ErrorKind::Interrupted => continue, + Err(e) => return Err(e), + Ok(n) => break n, + } + }; + let mut bytes: Vec = bytes.iter().copied().chain(probe[..n].iter().copied()).collect(); + file.read_to_end(&mut bytes)?; + Ok(bytes.into()) } } From 8fbd78ccea0e462be3d3745923d5aed8a451dc62 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 13 Sep 2023 17:33:40 +0000 Subject: [PATCH 042/111] Detect cycle errors hidden by opaques during monomorphization --- .../src/normalize_projection_ty.rs | 26 +++++++++++- .../indirect-recursion-issue-112047.rs | 38 ++++++++++++++++++ .../indirect-recursion-issue-112047.stderr | 5 +++ .../indirect-recursion-issue-112047.rs | 29 ++++++++++++++ .../indirect-recursion-issue-112047.stderr | 5 +++ .../mututally-recursive-overflow.rs | 40 +++++++++++++++++++ .../mututally-recursive-overflow.stderr | 5 +++ 7 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 tests/ui/async-await/in-trait/indirect-recursion-issue-112047.rs create mode 100644 tests/ui/async-await/in-trait/indirect-recursion-issue-112047.stderr create mode 100644 tests/ui/type-alias-impl-trait/indirect-recursion-issue-112047.rs create mode 100644 tests/ui/type-alias-impl-trait/indirect-recursion-issue-112047.stderr create mode 100644 tests/ui/type-alias-impl-trait/mututally-recursive-overflow.rs create mode 100644 tests/ui/type-alias-impl-trait/mututally-recursive-overflow.stderr diff --git a/compiler/rustc_traits/src/normalize_projection_ty.rs b/compiler/rustc_traits/src/normalize_projection_ty.rs index 0dbac56b47dd..01bb1ca70eb4 100644 --- a/compiler/rustc_traits/src/normalize_projection_ty.rs +++ b/compiler/rustc_traits/src/normalize_projection_ty.rs @@ -3,10 +3,13 @@ use rustc_infer::infer::TyCtxtInferExt; use rustc_middle::query::Providers; use rustc_middle::ty::{ParamEnvAnd, TyCtxt}; use rustc_trait_selection::infer::InferCtxtBuilderExt; +use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt; use rustc_trait_selection::traits::query::{ normalize::NormalizationResult, CanonicalProjectionGoal, NoSolution, }; -use rustc_trait_selection::traits::{self, ObligationCause, SelectionContext}; +use rustc_trait_selection::traits::{ + self, FulfillmentErrorCode, ObligationCause, SelectionContext, +}; use std::sync::atomic::Ordering; pub(crate) fn provide(p: &mut Providers) { @@ -40,6 +43,27 @@ fn normalize_projection_ty<'tcx>( &mut obligations, ); ocx.register_obligations(obligations); + // #112047: With projections and opaques, we are able to create opaques that + // are recursive (given some substitution of the opaque's type variables). + // In that case, we may only realize a cycle error when calling + // `normalize_erasing_regions` in mono. + if !ocx.infcx.next_trait_solver() { + let errors = ocx.select_where_possible(); + if !errors.is_empty() { + // Rustdoc may attempt to normalize type alias types which are not + // well-formed. Rustdoc also normalizes types that are just not + // well-formed, since we don't do as much HIR analysis (checking + // that impl vars are constrained by the signature, for example). + if !tcx.sess.opts.actually_rustdoc { + for error in &errors { + if let FulfillmentErrorCode::CodeCycle(cycle) = &error.code { + ocx.infcx.err_ctxt().report_overflow_obligation_cycle(cycle); + } + } + } + return Err(NoSolution); + } + } // FIXME(associated_const_equality): All users of normalize_projection_ty expected // a type, but there is the possibility it could've been a const now. Maybe change // it to a Term later? diff --git a/tests/ui/async-await/in-trait/indirect-recursion-issue-112047.rs b/tests/ui/async-await/in-trait/indirect-recursion-issue-112047.rs new file mode 100644 index 000000000000..85d17ddff94d --- /dev/null +++ b/tests/ui/async-await/in-trait/indirect-recursion-issue-112047.rs @@ -0,0 +1,38 @@ +// edition: 2021 +// build-fail +//~^^ ERROR overflow evaluating the requirement `::{opaque#0} == _` + +#![feature(async_fn_in_trait)] + +fn main() { + let _ = async { + A.first().await.second().await; + }; +} + +pub trait First { + type Second: Second; + async fn first(self) -> Self::Second; +} + +struct A; + +impl First for A { + type Second = A; + async fn first(self) -> Self::Second { + A + } +} + +pub trait Second { + async fn second(self); +} + +impl Second for C +where + C: First, +{ + async fn second(self) { + self.first().await.second().await; + } +} diff --git a/tests/ui/async-await/in-trait/indirect-recursion-issue-112047.stderr b/tests/ui/async-await/in-trait/indirect-recursion-issue-112047.stderr new file mode 100644 index 000000000000..3f487a6e5fe5 --- /dev/null +++ b/tests/ui/async-await/in-trait/indirect-recursion-issue-112047.stderr @@ -0,0 +1,5 @@ +error[E0275]: overflow evaluating the requirement `::{opaque#0} == _` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0275`. diff --git a/tests/ui/type-alias-impl-trait/indirect-recursion-issue-112047.rs b/tests/ui/type-alias-impl-trait/indirect-recursion-issue-112047.rs new file mode 100644 index 000000000000..baa22e1ce180 --- /dev/null +++ b/tests/ui/type-alias-impl-trait/indirect-recursion-issue-112047.rs @@ -0,0 +1,29 @@ +// edition: 2021 +// build-fail +//~^^ ERROR overflow evaluating the requirement `<() as Recur>::Recur == _` + +#![feature(impl_trait_in_assoc_type)] + +use core::future::Future; + +trait Recur { + type Recur: Future; + + fn recur(self) -> Self::Recur; +} + +async fn recur(t: impl Recur) { + t.recur().await; +} + +impl Recur for () { + type Recur = impl Future; + + fn recur(self) -> Self::Recur { + async move { recur(self).await; } + } +} + +fn main() { + recur(()); +} diff --git a/tests/ui/type-alias-impl-trait/indirect-recursion-issue-112047.stderr b/tests/ui/type-alias-impl-trait/indirect-recursion-issue-112047.stderr new file mode 100644 index 000000000000..0238694c24d3 --- /dev/null +++ b/tests/ui/type-alias-impl-trait/indirect-recursion-issue-112047.stderr @@ -0,0 +1,5 @@ +error[E0275]: overflow evaluating the requirement `<() as Recur>::Recur == _` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0275`. diff --git a/tests/ui/type-alias-impl-trait/mututally-recursive-overflow.rs b/tests/ui/type-alias-impl-trait/mututally-recursive-overflow.rs new file mode 100644 index 000000000000..1ccd1b0cbad4 --- /dev/null +++ b/tests/ui/type-alias-impl-trait/mututally-recursive-overflow.rs @@ -0,0 +1,40 @@ +// edition: 2021 +// build-fail +//~^^ ERROR overflow evaluating the requirement `<() as B>::Assoc == _` + +#![feature(rustc_attrs)] +#![feature(impl_trait_in_assoc_type)] + +#[rustc_coinductive] +trait A { + type Assoc; + + fn test() -> Self::Assoc; +} + +#[rustc_coinductive] +trait B { + type Assoc; + + fn test() -> Self::Assoc; +} + +impl B for T { + type Assoc = impl Sized; + + fn test() -> ::Assoc { + ::test() + } +} + +fn main() { + <() as A>::test(); +} + +impl A for T { + type Assoc = impl Sized; + + fn test() -> ::Assoc { + ::test() + } +} diff --git a/tests/ui/type-alias-impl-trait/mututally-recursive-overflow.stderr b/tests/ui/type-alias-impl-trait/mututally-recursive-overflow.stderr new file mode 100644 index 000000000000..49c59f7eb37a --- /dev/null +++ b/tests/ui/type-alias-impl-trait/mututally-recursive-overflow.stderr @@ -0,0 +1,5 @@ +error[E0275]: overflow evaluating the requirement `<() as B>::Assoc == _` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0275`. From 68687e253597a9fee97b5cc638c93a1f90ee8097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20S=C3=A1nchez=20Mu=C3=B1oz?= Date: Wed, 13 Sep 2023 19:41:21 +0200 Subject: [PATCH 043/111] rustc_middle: add `Scalar::from_i8` and `Scalar::from_i16` and use them in Miri --- compiler/rustc_middle/src/mir/interpret/value.rs | 10 ++++++++++ src/tools/miri/src/shims/x86/sse2.rs | 14 +++++--------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_middle/src/mir/interpret/value.rs b/compiler/rustc_middle/src/mir/interpret/value.rs index 5345a6588032..b6f399993d25 100644 --- a/compiler/rustc_middle/src/mir/interpret/value.rs +++ b/compiler/rustc_middle/src/mir/interpret/value.rs @@ -266,6 +266,16 @@ impl Scalar { .unwrap_or_else(|| bug!("Signed value {:#x} does not fit in {} bits", i, size.bits())) } + #[inline] + pub fn from_i8(i: i8) -> Self { + Self::from_int(i, Size::from_bits(8)) + } + + #[inline] + pub fn from_i16(i: i16) -> Self { + Self::from_int(i, Size::from_bits(16)) + } + #[inline] pub fn from_i32(i: i32) -> Self { Self::from_int(i, Size::from_bits(32)) diff --git a/src/tools/miri/src/shims/x86/sse2.rs b/src/tools/miri/src/shims/x86/sse2.rs index 5b42339e6480..0b918eab0c0c 100644 --- a/src/tools/miri/src/shims/x86/sse2.rs +++ b/src/tools/miri/src/shims/x86/sse2.rs @@ -5,7 +5,6 @@ use rustc_apfloat::{ use rustc_middle::ty::layout::LayoutOf as _; use rustc_middle::ty::Ty; use rustc_span::Symbol; -use rustc_target::abi::Size; use rustc_target::spec::abi::Abi; use super::FloatCmpOp; @@ -112,7 +111,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Values are expanded from i16 to i32, so multiplication cannot overflow. let res = i32::from(left).checked_mul(i32::from(right)).unwrap() >> 16; - this.write_scalar(Scalar::from_int(res, Size::from_bits(16)), &dest)?; + this.write_scalar(Scalar::from_i16(res.try_into().unwrap()), &dest)?; } } // Used to implement the _mm_mulhi_epu16 function. @@ -431,11 +430,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let right_res = i8::try_from(right).unwrap_or(if right < 0 { i8::MIN } else { i8::MAX }); - this.write_scalar(Scalar::from_int(left_res, Size::from_bits(8)), &left_dest)?; - this.write_scalar( - Scalar::from_int(right_res, Size::from_bits(8)), - &right_dest, - )?; + this.write_scalar(Scalar::from_i8(left_res.try_into().unwrap()), &left_dest)?; + this.write_scalar(Scalar::from_i8(right_res.try_into().unwrap()), &right_dest)?; } } // Used to implement the _mm_packus_epi16 function. @@ -495,9 +491,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let right_res = i16::try_from(right).unwrap_or(if right < 0 { i16::MIN } else { i16::MAX }); - this.write_scalar(Scalar::from_int(left_res, Size::from_bits(16)), &left_dest)?; + this.write_scalar(Scalar::from_i16(left_res.try_into().unwrap()), &left_dest)?; this.write_scalar( - Scalar::from_int(right_res, Size::from_bits(16)), + Scalar::from_i16(right_res.try_into().unwrap()), &right_dest, )?; } From b8044774ffc863f559cf563baa9d7e8dbe4faac8 Mon Sep 17 00:00:00 2001 From: dirreke Date: Thu, 14 Sep 2023 16:56:49 +0800 Subject: [PATCH 044/111] Bump libc to 0.2.148 --- Cargo.lock | 4 ++-- library/std/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 386b57e6a44e..4e5071cba14c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2066,9 +2066,9 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" dependencies = [ "rustc-std-workspace-core", ] diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index 33c9c6e63c15..416e43e17e98 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -17,7 +17,7 @@ cfg-if = { version = "1.0", features = ['rustc-dep-of-std'] } panic_unwind = { path = "../panic_unwind", optional = true } panic_abort = { path = "../panic_abort" } core = { path = "../core", public = true } -libc = { version = "0.2.146", default-features = false, features = ['rustc-dep-of-std'], public = true } +libc = { version = "0.2.148", default-features = false, features = ['rustc-dep-of-std'], public = true } compiler_builtins = { version = "0.1.100" } profiler_builtins = { path = "../profiler_builtins", optional = true } unwind = { path = "../unwind" } From 21418429fdb9cc823253d47d5d87294230854489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20S=C3=A1nchez=20Mu=C3=B1oz?= Date: Wed, 13 Sep 2023 20:17:59 +0200 Subject: [PATCH 045/111] miri: reduce code duplication in SSE/SSE2 bin_op_* functions --- .../rustc_middle/src/mir/interpret/value.rs | 12 +- src/tools/miri/src/helpers.rs | 17 ++ src/tools/miri/src/shims/intrinsics/simd.rs | 19 +-- src/tools/miri/src/shims/x86/mod.rs | 158 +++++++++++++++++- src/tools/miri/src/shims/x86/sse.rs | 152 +---------------- src/tools/miri/src/shims/x86/sse2.rs | 146 +--------------- 6 files changed, 194 insertions(+), 310 deletions(-) diff --git a/compiler/rustc_middle/src/mir/interpret/value.rs b/compiler/rustc_middle/src/mir/interpret/value.rs index b6f399993d25..f288869289ce 100644 --- a/compiler/rustc_middle/src/mir/interpret/value.rs +++ b/compiler/rustc_middle/src/mir/interpret/value.rs @@ -503,16 +503,20 @@ impl<'tcx, Prov: Provenance> Scalar { Ok(i64::try_from(b).unwrap()) } + #[inline] + pub fn to_float(self) -> InterpResult<'tcx, F> { + // Going through `to_uint` to check size and truncation. + Ok(F::from_bits(self.to_uint(Size::from_bits(F::BITS))?)) + } + #[inline] pub fn to_f32(self) -> InterpResult<'tcx, Single> { - // Going through `u32` to check size and truncation. - Ok(Single::from_bits(self.to_u32()?.into())) + self.to_float() } #[inline] pub fn to_f64(self) -> InterpResult<'tcx, Double> { - // Going through `u64` to check size and truncation. - Ok(Double::from_bits(self.to_u64()?.into())) + self.to_float() } } diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 0c7e82781476..7805fe25bcd4 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -1152,3 +1152,20 @@ pub fn get_local_crates(tcx: TyCtxt<'_>) -> Vec { pub fn target_os_is_unix(target_os: &str) -> bool { matches!(target_os, "linux" | "macos" | "freebsd" | "android") } + +pub(crate) fn bool_to_simd_element(b: bool, size: Size) -> Scalar { + // SIMD uses all-1 as pattern for "true". In two's complement, + // -1 has all its bits set to one and `from_int` will truncate or + // sign-extend it to `size` as required. + let val = if b { -1 } else { 0 }; + Scalar::from_int(val, size) +} + +pub(crate) fn simd_element_to_bool(elem: ImmTy<'_, Provenance>) -> InterpResult<'_, bool> { + let val = elem.to_scalar().to_int(elem.layout.size)?; + Ok(match val { + 0 => false, + -1 => true, + _ => throw_ub_format!("each element of a SIMD mask must be all-0-bits or all-1-bits"), + }) +} diff --git a/src/tools/miri/src/shims/intrinsics/simd.rs b/src/tools/miri/src/shims/intrinsics/simd.rs index dd8c4a4f6eca..626ead378e7b 100644 --- a/src/tools/miri/src/shims/intrinsics/simd.rs +++ b/src/tools/miri/src/shims/intrinsics/simd.rs @@ -1,10 +1,10 @@ use rustc_apfloat::{Float, Round}; use rustc_middle::ty::layout::{HasParamEnv, LayoutOf}; use rustc_middle::{mir, ty, ty::FloatTy}; -use rustc_target::abi::{Endian, HasDataLayout, Size}; +use rustc_target::abi::{Endian, HasDataLayout}; use crate::*; -use helpers::check_arg_count; +use helpers::{bool_to_simd_element, check_arg_count, simd_element_to_bool}; impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { @@ -612,21 +612,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } -fn bool_to_simd_element(b: bool, size: Size) -> Scalar { - // SIMD uses all-1 as pattern for "true" - let val = if b { -1 } else { 0 }; - Scalar::from_int(val, size) -} - -fn simd_element_to_bool(elem: ImmTy<'_, Provenance>) -> InterpResult<'_, bool> { - let val = elem.to_scalar().to_int(elem.layout.size)?; - Ok(match val { - 0 => false, - -1 => true, - _ => throw_ub_format!("each element of a SIMD mask must be all-0-bits or all-1-bits"), - }) -} - fn simd_bitmask_index(idx: u32, vec_len: u32, endianness: Endian) -> u32 { assert!(idx < vec_len); match endianness { diff --git a/src/tools/miri/src/shims/x86/mod.rs b/src/tools/miri/src/shims/x86/mod.rs index 62f5eb1baf7c..ccc729aae1a2 100644 --- a/src/tools/miri/src/shims/x86/mod.rs +++ b/src/tools/miri/src/shims/x86/mod.rs @@ -1,4 +1,8 @@ -use crate::InterpResult; +use rustc_middle::mir; +use rustc_target::abi::Size; + +use crate::*; +use helpers::bool_to_simd_element; pub(super) mod sse; pub(super) mod sse2; @@ -43,3 +47,155 @@ impl FloatCmpOp { } } } + +#[derive(Copy, Clone)] +enum FloatBinOp { + /// Arithmetic operation + Arith(mir::BinOp), + /// Comparison + Cmp(FloatCmpOp), + /// Minimum value (with SSE semantics) + /// + /// + /// + /// + /// + Min, + /// Maximum value (with SSE semantics) + /// + /// + /// + /// + /// + Max, +} + +/// Performs `which` scalar operation on `left` and `right` and returns +/// the result. +fn bin_op_float<'tcx, F: rustc_apfloat::Float>( + this: &crate::MiriInterpCx<'_, 'tcx>, + which: FloatBinOp, + left: &ImmTy<'tcx, Provenance>, + right: &ImmTy<'tcx, Provenance>, +) -> InterpResult<'tcx, Scalar> { + match which { + FloatBinOp::Arith(which) => { + let (res, _overflow, _ty) = this.overflowing_binary_op(which, left, right)?; + Ok(res) + } + FloatBinOp::Cmp(which) => { + let left = left.to_scalar().to_float::()?; + let right = right.to_scalar().to_float::()?; + // FIXME: Make sure that these operations match the semantics + // of cmpps/cmpss/cmppd/cmpsd + let res = match which { + FloatCmpOp::Eq => left == right, + FloatCmpOp::Lt => left < right, + FloatCmpOp::Le => left <= right, + FloatCmpOp::Unord => left.is_nan() || right.is_nan(), + FloatCmpOp::Neq => left != right, + FloatCmpOp::Nlt => !(left < right), + FloatCmpOp::Nle => !(left <= right), + FloatCmpOp::Ord => !left.is_nan() && !right.is_nan(), + }; + Ok(bool_to_simd_element(res, Size::from_bits(F::BITS))) + } + FloatBinOp::Min => { + let left_scalar = left.to_scalar(); + let left = left_scalar.to_float::()?; + let right_scalar = right.to_scalar(); + let right = right_scalar.to_float::()?; + // SSE semantics to handle zero and NaN. Note that `x == F::ZERO` + // is true when `x` is either +0 or -0. + if (left == F::ZERO && right == F::ZERO) + || left.is_nan() + || right.is_nan() + || left >= right + { + Ok(right_scalar) + } else { + Ok(left_scalar) + } + } + FloatBinOp::Max => { + let left_scalar = left.to_scalar(); + let left = left_scalar.to_float::()?; + let right_scalar = right.to_scalar(); + let right = right_scalar.to_float::()?; + // SSE semantics to handle zero and NaN. Note that `x == F::ZERO` + // is true when `x` is either +0 or -0. + if (left == F::ZERO && right == F::ZERO) + || left.is_nan() + || right.is_nan() + || left <= right + { + Ok(right_scalar) + } else { + Ok(left_scalar) + } + } + } +} + +/// Performs `which` operation on the first component of `left` and `right` +/// and copies the other components from `left`. The result is stored in `dest`. +fn bin_op_simd_float_first<'tcx, F: rustc_apfloat::Float>( + this: &mut crate::MiriInterpCx<'_, 'tcx>, + which: FloatBinOp, + left: &OpTy<'tcx, Provenance>, + right: &OpTy<'tcx, Provenance>, + dest: &PlaceTy<'tcx, Provenance>, +) -> InterpResult<'tcx, ()> { + let (left, left_len) = this.operand_to_simd(left)?; + let (right, right_len) = this.operand_to_simd(right)?; + let (dest, dest_len) = this.place_to_simd(dest)?; + + assert_eq!(dest_len, left_len); + assert_eq!(dest_len, right_len); + + let res0 = bin_op_float::( + this, + which, + &this.read_immediate(&this.project_index(&left, 0)?)?, + &this.read_immediate(&this.project_index(&right, 0)?)?, + )?; + this.write_scalar(res0, &this.project_index(&dest, 0)?)?; + + for i in 1..dest_len { + this.copy_op( + &this.project_index(&left, i)?, + &this.project_index(&dest, i)?, + /*allow_transmute*/ false, + )?; + } + + Ok(()) +} + +/// Performs `which` operation on each component of `left` and +/// `right`, storing the result is stored in `dest`. +fn bin_op_simd_float_all<'tcx, F: rustc_apfloat::Float>( + this: &mut crate::MiriInterpCx<'_, 'tcx>, + which: FloatBinOp, + left: &OpTy<'tcx, Provenance>, + right: &OpTy<'tcx, Provenance>, + dest: &PlaceTy<'tcx, Provenance>, +) -> InterpResult<'tcx, ()> { + let (left, left_len) = this.operand_to_simd(left)?; + let (right, right_len) = this.operand_to_simd(right)?; + let (dest, dest_len) = this.place_to_simd(dest)?; + + assert_eq!(dest_len, left_len); + assert_eq!(dest_len, right_len); + + for i in 0..dest_len { + let left = this.read_immediate(&this.project_index(&left, i)?)?; + let right = this.read_immediate(&this.project_index(&right, i)?)?; + let dest = this.project_index(&dest, i)?; + + let res = bin_op_float::(this, which, &left, &right)?; + this.write_scalar(res, &dest)?; + } + + Ok(()) +} diff --git a/src/tools/miri/src/shims/x86/sse.rs b/src/tools/miri/src/shims/x86/sse.rs index ff4bd3697065..dcfe190a4edf 100644 --- a/src/tools/miri/src/shims/x86/sse.rs +++ b/src/tools/miri/src/shims/x86/sse.rs @@ -5,7 +5,7 @@ use rustc_target::spec::abi::Abi; use rand::Rng as _; -use super::FloatCmpOp; +use super::{bin_op_simd_float_all, bin_op_simd_float_first, FloatBinOp, FloatCmpOp}; use crate::*; use shims::foreign_items::EmulateByNameResult; @@ -45,7 +45,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { _ => unreachable!(), }; - bin_op_ss(this, which, left, right, dest)?; + bin_op_simd_float_first::(this, which, left, right, dest)?; } // Used to implement _mm_min_ps and _mm_max_ps functions. // Note that the semantics are a bit different from Rust simd_min @@ -62,7 +62,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { _ => unreachable!(), }; - bin_op_ps(this, which, left, right, dest)?; + bin_op_simd_float_all::(this, which, left, right, dest)?; } // Used to implement _mm_{sqrt,rcp,rsqrt}_ss functions. // Performs the operations on the first component of `op` and @@ -106,7 +106,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { "llvm.x86.sse.cmp.ss", )?); - bin_op_ss(this, which, left, right, dest)?; + bin_op_simd_float_first::(this, which, left, right, dest)?; } // Used to implement the _mm_cmp_ps function. // Performs a comparison operation on each component of `left` @@ -121,7 +121,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { "llvm.x86.sse.cmp.ps", )?); - bin_op_ps(this, which, left, right, dest)?; + bin_op_simd_float_all::(this, which, left, right, dest)?; } // Used to implement _mm_{,u}comi{eq,lt,le,gt,ge,neq}_ss functions. // Compares the first component of `left` and `right` and returns @@ -281,148 +281,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } -#[derive(Copy, Clone)] -enum FloatBinOp { - /// Arithmetic operation - Arith(mir::BinOp), - /// Comparison - Cmp(FloatCmpOp), - /// Minimum value (with SSE semantics) - /// - /// - /// - Min, - /// Maximum value (with SSE semantics) - /// - /// - /// - Max, -} - -/// Performs `which` scalar operation on `left` and `right` and returns -/// the result. -fn bin_op_f32<'tcx>( - this: &crate::MiriInterpCx<'_, 'tcx>, - which: FloatBinOp, - left: &ImmTy<'tcx, Provenance>, - right: &ImmTy<'tcx, Provenance>, -) -> InterpResult<'tcx, Scalar> { - match which { - FloatBinOp::Arith(which) => { - let (res, _, _) = this.overflowing_binary_op(which, left, right)?; - Ok(res) - } - FloatBinOp::Cmp(which) => { - let left = left.to_scalar().to_f32()?; - let right = right.to_scalar().to_f32()?; - // FIXME: Make sure that these operations match the semantics of cmpps - let res = match which { - FloatCmpOp::Eq => left == right, - FloatCmpOp::Lt => left < right, - FloatCmpOp::Le => left <= right, - FloatCmpOp::Unord => left.is_nan() || right.is_nan(), - FloatCmpOp::Neq => left != right, - FloatCmpOp::Nlt => !(left < right), - FloatCmpOp::Nle => !(left <= right), - FloatCmpOp::Ord => !left.is_nan() && !right.is_nan(), - }; - Ok(Scalar::from_u32(if res { u32::MAX } else { 0 })) - } - FloatBinOp::Min => { - let left = left.to_scalar().to_f32()?; - let right = right.to_scalar().to_f32()?; - // SSE semantics to handle zero and NaN. Note that `x == Single::ZERO` - // is true when `x` is either +0 or -0. - if (left == Single::ZERO && right == Single::ZERO) - || left.is_nan() - || right.is_nan() - || left >= right - { - Ok(Scalar::from_f32(right)) - } else { - Ok(Scalar::from_f32(left)) - } - } - FloatBinOp::Max => { - let left = left.to_scalar().to_f32()?; - let right = right.to_scalar().to_f32()?; - // SSE semantics to handle zero and NaN. Note that `x == Single::ZERO` - // is true when `x` is either +0 or -0. - if (left == Single::ZERO && right == Single::ZERO) - || left.is_nan() - || right.is_nan() - || left <= right - { - Ok(Scalar::from_f32(right)) - } else { - Ok(Scalar::from_f32(left)) - } - } - } -} - -/// Performs `which` operation on the first component of `left` and `right` -/// and copies the other components from `left`. The result is stored in `dest`. -fn bin_op_ss<'tcx>( - this: &mut crate::MiriInterpCx<'_, 'tcx>, - which: FloatBinOp, - left: &OpTy<'tcx, Provenance>, - right: &OpTy<'tcx, Provenance>, - dest: &PlaceTy<'tcx, Provenance>, -) -> InterpResult<'tcx, ()> { - let (left, left_len) = this.operand_to_simd(left)?; - let (right, right_len) = this.operand_to_simd(right)?; - let (dest, dest_len) = this.place_to_simd(dest)?; - - assert_eq!(dest_len, left_len); - assert_eq!(dest_len, right_len); - - let res0 = bin_op_f32( - this, - which, - &this.read_immediate(&this.project_index(&left, 0)?)?, - &this.read_immediate(&this.project_index(&right, 0)?)?, - )?; - this.write_scalar(res0, &this.project_index(&dest, 0)?)?; - - for i in 1..dest_len { - let left = this.read_immediate(&this.project_index(&left, i)?)?; - let dest = this.project_index(&dest, i)?; - - this.write_immediate(*left, &dest)?; - } - - Ok(()) -} - -/// Performs `which` operation on each component of `left` and -/// `right`, storing the result is stored in `dest`. -fn bin_op_ps<'tcx>( - this: &mut crate::MiriInterpCx<'_, 'tcx>, - which: FloatBinOp, - left: &OpTy<'tcx, Provenance>, - right: &OpTy<'tcx, Provenance>, - dest: &PlaceTy<'tcx, Provenance>, -) -> InterpResult<'tcx, ()> { - let (left, left_len) = this.operand_to_simd(left)?; - let (right, right_len) = this.operand_to_simd(right)?; - let (dest, dest_len) = this.place_to_simd(dest)?; - - assert_eq!(dest_len, left_len); - assert_eq!(dest_len, right_len); - - for i in 0..dest_len { - let left = this.read_immediate(&this.project_index(&left, i)?)?; - let right = this.read_immediate(&this.project_index(&right, i)?)?; - let dest = this.project_index(&dest, i)?; - - let res = bin_op_f32(this, which, &left, &right)?; - this.write_scalar(res, &dest)?; - } - - Ok(()) -} - #[derive(Copy, Clone)] enum FloatUnaryOp { /// sqrt(x) diff --git a/src/tools/miri/src/shims/x86/sse2.rs b/src/tools/miri/src/shims/x86/sse2.rs index 0b918eab0c0c..c6496fa195d1 100644 --- a/src/tools/miri/src/shims/x86/sse2.rs +++ b/src/tools/miri/src/shims/x86/sse2.rs @@ -7,7 +7,7 @@ use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::spec::abi::Abi; -use super::FloatCmpOp; +use super::{bin_op_simd_float_all, bin_op_simd_float_first, FloatBinOp, FloatCmpOp}; use crate::*; use shims::foreign_items::EmulateByNameResult; @@ -513,7 +513,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { _ => unreachable!(), }; - bin_op_sd(this, which, left, right, dest)?; + bin_op_simd_float_first::(this, which, left, right, dest)?; } // Used to implement _mm_min_pd and _mm_max_pd functions. // Note that the semantics are a bit different from Rust simd_min @@ -530,7 +530,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { _ => unreachable!(), }; - bin_op_pd(this, which, left, right, dest)?; + bin_op_simd_float_all::(this, which, left, right, dest)?; } // Used to implement _mm_sqrt_sd functions. // Performs the operations on the first component of `op` and @@ -589,7 +589,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { "llvm.x86.sse2.cmp.sd", )?); - bin_op_sd(this, which, left, right, dest)?; + bin_op_simd_float_first::(this, which, left, right, dest)?; } // Used to implement the _mm_cmp*_pd functions. // Performs a comparison operation on each component of `left` @@ -604,7 +604,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { "llvm.x86.sse2.cmp.pd", )?); - bin_op_pd(this, which, left, right, dest)?; + bin_op_simd_float_all::(this, which, left, right, dest)?; } // Used to implement _mm_{,u}comi{eq,lt,le,gt,ge,neq}_sd functions. // Compares the first component of `left` and `right` and returns @@ -840,139 +840,3 @@ fn extract_first_u64<'tcx>( // Get the first u64 from the array this.read_scalar(&this.project_index(&op, 0)?)?.to_u64() } - -#[derive(Copy, Clone)] -enum FloatBinOp { - /// Comparison - Cmp(FloatCmpOp), - /// Minimum value (with SSE semantics) - /// - /// - /// - Min, - /// Maximum value (with SSE semantics) - /// - /// - /// - Max, -} - -/// Performs `which` scalar operation on `left` and `right` and returns -/// the result. -// FIXME make this generic over apfloat type to reduce code duplicaton with bin_op_f32 -fn bin_op_f64<'tcx>( - which: FloatBinOp, - left: &ImmTy<'tcx, Provenance>, - right: &ImmTy<'tcx, Provenance>, -) -> InterpResult<'tcx, Scalar> { - match which { - FloatBinOp::Cmp(which) => { - let left = left.to_scalar().to_f64()?; - let right = right.to_scalar().to_f64()?; - // FIXME: Make sure that these operations match the semantics of cmppd - let res = match which { - FloatCmpOp::Eq => left == right, - FloatCmpOp::Lt => left < right, - FloatCmpOp::Le => left <= right, - FloatCmpOp::Unord => left.is_nan() || right.is_nan(), - FloatCmpOp::Neq => left != right, - FloatCmpOp::Nlt => !(left < right), - FloatCmpOp::Nle => !(left <= right), - FloatCmpOp::Ord => !left.is_nan() && !right.is_nan(), - }; - Ok(Scalar::from_u64(if res { u64::MAX } else { 0 })) - } - FloatBinOp::Min => { - let left = left.to_scalar().to_f64()?; - let right = right.to_scalar().to_f64()?; - // SSE semantics to handle zero and NaN. Note that `x == Single::ZERO` - // is true when `x` is either +0 or -0. - if (left == Double::ZERO && right == Double::ZERO) - || left.is_nan() - || right.is_nan() - || left >= right - { - Ok(Scalar::from_f64(right)) - } else { - Ok(Scalar::from_f64(left)) - } - } - FloatBinOp::Max => { - let left = left.to_scalar().to_f64()?; - let right = right.to_scalar().to_f64()?; - // SSE semantics to handle zero and NaN. Note that `x == Single::ZERO` - // is true when `x` is either +0 or -0. - if (left == Double::ZERO && right == Double::ZERO) - || left.is_nan() - || right.is_nan() - || left <= right - { - Ok(Scalar::from_f64(right)) - } else { - Ok(Scalar::from_f64(left)) - } - } - } -} - -/// Performs `which` operation on the first component of `left` and `right` -/// and copies the other components from `left`. The result is stored in `dest`. -fn bin_op_sd<'tcx>( - this: &mut crate::MiriInterpCx<'_, 'tcx>, - which: FloatBinOp, - left: &OpTy<'tcx, Provenance>, - right: &OpTy<'tcx, Provenance>, - dest: &PlaceTy<'tcx, Provenance>, -) -> InterpResult<'tcx, ()> { - let (left, left_len) = this.operand_to_simd(left)?; - let (right, right_len) = this.operand_to_simd(right)?; - let (dest, dest_len) = this.place_to_simd(dest)?; - - assert_eq!(dest_len, left_len); - assert_eq!(dest_len, right_len); - - let res0 = bin_op_f64( - which, - &this.read_immediate(&this.project_index(&left, 0)?)?, - &this.read_immediate(&this.project_index(&right, 0)?)?, - )?; - this.write_scalar(res0, &this.project_index(&dest, 0)?)?; - - for i in 1..dest_len { - this.copy_op( - &this.project_index(&left, i)?, - &this.project_index(&dest, i)?, - /*allow_transmute*/ false, - )?; - } - - Ok(()) -} - -/// Performs `which` operation on each component of `left` and -/// `right`, storing the result is stored in `dest`. -fn bin_op_pd<'tcx>( - this: &mut crate::MiriInterpCx<'_, 'tcx>, - which: FloatBinOp, - left: &OpTy<'tcx, Provenance>, - right: &OpTy<'tcx, Provenance>, - dest: &PlaceTy<'tcx, Provenance>, -) -> InterpResult<'tcx, ()> { - let (left, left_len) = this.operand_to_simd(left)?; - let (right, right_len) = this.operand_to_simd(right)?; - let (dest, dest_len) = this.place_to_simd(dest)?; - - assert_eq!(dest_len, left_len); - assert_eq!(dest_len, right_len); - - for i in 0..dest_len { - let left = this.read_immediate(&this.project_index(&left, i)?)?; - let right = this.read_immediate(&this.project_index(&right, i)?)?; - let dest = this.project_index(&dest, i)?; - - let res = bin_op_f64(which, &left, &right)?; - this.write_scalar(res, &dest)?; - } - - Ok(()) -} From 98310bed8e09817e518e6b82fcb3f9b2c4b89675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20S=C3=A1nchez=20Mu=C3=B1oz?= Date: Wed, 13 Sep 2023 20:30:24 +0200 Subject: [PATCH 046/111] miri: reduce code duplication in SSE cvtsi2ss/cvtsi642ss --- src/tools/miri/src/shims/x86/sse.rs | 49 +++++++---------------------- 1 file changed, 12 insertions(+), 37 deletions(-) diff --git a/src/tools/miri/src/shims/x86/sse.rs b/src/tools/miri/src/shims/x86/sse.rs index dcfe190a4edf..ab90e292fcb6 100644 --- a/src/tools/miri/src/shims/x86/sse.rs +++ b/src/tools/miri/src/shims/x86/sse.rs @@ -204,12 +204,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_scalar(res, dest)?; } - // Used to implement the _mm_cvtsi32_ss function. - // Converts `right` from i32 to f32. Returns a SIMD vector with + // Used to implement the _mm_cvtsi32_ss and _mm_cvtsi64_ss functions. + // Converts `right` from i32/i64 to f32. Returns a SIMD vector with // the result in the first component and the remaining components // are copied from `left`. // https://www.felixcloutier.com/x86/cvtsi2ss - "cvtsi2ss" => { + "cvtsi2ss" | "cvtsi642ss" => { let [left, right] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; @@ -218,42 +218,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { assert_eq!(dest_len, left_len); - let right = this.read_scalar(right)?.to_i32()?; - - let res0 = Scalar::from_f32(Single::from_i128(right.into()).value); - this.write_scalar(res0, &this.project_index(&dest, 0)?)?; + let right = this.read_immediate(right)?; + let dest0 = this.project_index(&dest, 0)?; + let res0 = this.int_to_int_or_float(&right, dest0.layout.ty)?; + this.write_immediate(res0, &dest0)?; for i in 1..dest_len { - let left = this.read_immediate(&this.project_index(&left, i)?)?; - let dest = this.project_index(&dest, i)?; - - this.write_immediate(*left, &dest)?; - } - } - // Used to implement the _mm_cvtsi64_ss function. - // Converts `right` from i64 to f32. Returns a SIMD vector with - // the result in the first component and the remaining components - // are copied from `left`. - // https://www.felixcloutier.com/x86/cvtsi2ss - "cvtsi642ss" => { - let [left, right] = - this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - - let (left, left_len) = this.operand_to_simd(left)?; - let (dest, dest_len) = this.place_to_simd(dest)?; - - assert_eq!(dest_len, left_len); - - let right = this.read_scalar(right)?.to_i64()?; - - let res0 = Scalar::from_f32(Single::from_i128(right.into()).value); - this.write_scalar(res0, &this.project_index(&dest, 0)?)?; - - for i in 1..dest_len { - let left = this.read_immediate(&this.project_index(&left, i)?)?; - let dest = this.project_index(&dest, i)?; - - this.write_immediate(*left, &dest)?; + this.copy_op( + &this.project_index(&left, i)?, + &this.project_index(&dest, i)?, + /*allow_transmute*/ false, + )?; } } // Used to implement the _mm_movemask_ps function. From ff685416dbb52367df5e8d32ae95bea49a10f229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20S=C3=A1nchez=20Mu=C3=B1oz?= Date: Wed, 13 Sep 2023 20:34:03 +0200 Subject: [PATCH 047/111] miri: use `copy_op` in `unary_op_ss` --- src/tools/miri/src/shims/x86/sse.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/tools/miri/src/shims/x86/sse.rs b/src/tools/miri/src/shims/x86/sse.rs index ab90e292fcb6..05717216636f 100644 --- a/src/tools/miri/src/shims/x86/sse.rs +++ b/src/tools/miri/src/shims/x86/sse.rs @@ -343,10 +343,11 @@ fn unary_op_ss<'tcx>( this.write_scalar(res0, &this.project_index(&dest, 0)?)?; for i in 1..dest_len { - let op = this.read_immediate(&this.project_index(&op, i)?)?; - let dest = this.project_index(&dest, i)?; - - this.write_immediate(*op, &dest)?; + this.copy_op( + &this.project_index(&op, i)?, + &this.project_index(&dest, i)?, + /*allow_transmute*/ false, + )?; } Ok(()) From 9bd648bf130a2dfb7b7f386cbc6ad33f33c8b4d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20S=C3=A1nchez=20Mu=C3=B1oz?= Date: Wed, 13 Sep 2023 20:50:18 +0200 Subject: [PATCH 048/111] miri: reduce code duplication in SSE/SSE2 cvt{,t}s{s,d}2si{,64} --- src/tools/miri/src/shims/x86/sse.rs | 38 +++++----------------------- src/tools/miri/src/shims/x86/sse2.rs | 38 +++++----------------------- 2 files changed, 14 insertions(+), 62 deletions(-) diff --git a/src/tools/miri/src/shims/x86/sse.rs b/src/tools/miri/src/shims/x86/sse.rs index 05717216636f..30ad088206a1 100644 --- a/src/tools/miri/src/shims/x86/sse.rs +++ b/src/tools/miri/src/shims/x86/sse.rs @@ -154,9 +154,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { }; this.write_scalar(Scalar::from_i32(i32::from(res)), dest)?; } - // Use to implement _mm_cvtss_si32 and _mm_cvttss_si32. - // Converts the first component of `op` from f32 to i32. - "cvtss2si" | "cvttss2si" => { + // Use to implement the _mm_cvtss_si32, _mm_cvttss_si32, + // _mm_cvtss_si64 and _mm_cvttss_si64 functions. + // Converts the first component of `op` from f32 to i32/i64. + "cvtss2si" | "cvttss2si" | "cvtss2si64" | "cvttss2si64" => { let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let (op, _) = this.operand_to_simd(op)?; @@ -165,41 +166,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let rnd = match unprefixed_name { // "current SSE rounding mode", assume nearest // https://www.felixcloutier.com/x86/cvtss2si - "cvtss2si" => rustc_apfloat::Round::NearestTiesToEven, + "cvtss2si" | "cvtss2si64" => rustc_apfloat::Round::NearestTiesToEven, // always truncate // https://www.felixcloutier.com/x86/cvttss2si - "cvttss2si" => rustc_apfloat::Round::TowardZero, + "cvttss2si" | "cvttss2si64" => rustc_apfloat::Round::TowardZero, _ => unreachable!(), }; let res = this.float_to_int_checked(op, dest.layout.ty, rnd).unwrap_or_else(|| { // Fallback to minimum acording to SSE semantics. - Scalar::from_i32(i32::MIN) - }); - - this.write_scalar(res, dest)?; - } - // Use to implement _mm_cvtss_si64 and _mm_cvttss_si64. - // Converts the first component of `op` from f32 to i64. - "cvtss2si64" | "cvttss2si64" => { - let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let (op, _) = this.operand_to_simd(op)?; - - let op = this.read_scalar(&this.project_index(&op, 0)?)?.to_f32()?; - - let rnd = match unprefixed_name { - // "current SSE rounding mode", assume nearest - // https://www.felixcloutier.com/x86/cvtss2si - "cvtss2si64" => rustc_apfloat::Round::NearestTiesToEven, - // always truncate - // https://www.felixcloutier.com/x86/cvttss2si - "cvttss2si64" => rustc_apfloat::Round::TowardZero, - _ => unreachable!(), - }; - - let res = this.float_to_int_checked(op, dest.layout.ty, rnd).unwrap_or_else(|| { - // Fallback to minimum acording to SSE semantics. - Scalar::from_i64(i64::MIN) + Scalar::from_int(dest.layout.size.signed_int_min(), dest.layout.size) }); this.write_scalar(res, dest)?; diff --git a/src/tools/miri/src/shims/x86/sse2.rs b/src/tools/miri/src/shims/x86/sse2.rs index c6496fa195d1..d2370d3c1f2f 100644 --- a/src/tools/miri/src/shims/x86/sse2.rs +++ b/src/tools/miri/src/shims/x86/sse2.rs @@ -722,9 +722,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_scalar(Scalar::from_i32(0), &dest)?; } } - // Use to implement the _mm_cvtsd_si32 and _mm_cvttsd_si32 functions. - // Converts the first component of `op` from f64 to i32. - "cvtsd2si" | "cvttsd2si" => { + // Use to implement the _mm_cvtsd_si32, _mm_cvttsd_si32, + // _mm_cvtsd_si64 and _mm_cvttsd_si64 functions. + // Converts the first component of `op` from f64 to i32/i64. + "cvtsd2si" | "cvttsd2si" | "cvtsd2si64" | "cvttsd2si64" => { let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let (op, _) = this.operand_to_simd(op)?; @@ -733,41 +734,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let rnd = match unprefixed_name { // "current SSE rounding mode", assume nearest // https://www.felixcloutier.com/x86/cvtsd2si - "cvtsd2si" => rustc_apfloat::Round::NearestTiesToEven, + "cvtsd2si" | "cvtsd2si64" => rustc_apfloat::Round::NearestTiesToEven, // always truncate // https://www.felixcloutier.com/x86/cvttsd2si - "cvttsd2si" => rustc_apfloat::Round::TowardZero, + "cvttsd2si" | "cvttsd2si64" => rustc_apfloat::Round::TowardZero, _ => unreachable!(), }; let res = this.float_to_int_checked(op, dest.layout.ty, rnd).unwrap_or_else(|| { // Fallback to minimum acording to SSE semantics. - Scalar::from_i32(i32::MIN) - }); - - this.write_scalar(res, dest)?; - } - // Use to implement the _mm_cvtsd_si64 and _mm_cvttsd_si64 functions. - // Converts the first component of `op` from f64 to i64. - "cvtsd2si64" | "cvttsd2si64" => { - let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let (op, _) = this.operand_to_simd(op)?; - - let op = this.read_scalar(&this.project_index(&op, 0)?)?.to_f64()?; - - let rnd = match unprefixed_name { - // "current SSE rounding mode", assume nearest - // https://www.felixcloutier.com/x86/cvtsd2si - "cvtsd2si64" => rustc_apfloat::Round::NearestTiesToEven, - // always truncate - // https://www.felixcloutier.com/x86/cvttsd2si - "cvttsd2si64" => rustc_apfloat::Round::TowardZero, - _ => unreachable!(), - }; - - let res = this.float_to_int_checked(op, dest.layout.ty, rnd).unwrap_or_else(|| { - // Fallback to minimum acording to SSE semantics. - Scalar::from_i64(i64::MIN) + Scalar::from_int(dest.layout.size.signed_int_min(), dest.layout.size) }); this.write_scalar(res, dest)?; From e634c188dd4089320a5ca8a99683d8a1f084c3e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20S=C3=A1nchez=20Mu=C3=B1oz?= Date: Wed, 13 Sep 2023 21:02:11 +0200 Subject: [PATCH 049/111] miri: reduce code duplication in SSE2 cvtpd2ps/cvtps2pd --- src/tools/miri/src/shims/x86/sse2.rs | 49 ++++++++-------------------- 1 file changed, 14 insertions(+), 35 deletions(-) diff --git a/src/tools/miri/src/shims/x86/sse2.rs b/src/tools/miri/src/shims/x86/sse2.rs index d2370d3c1f2f..a0280fd0122c 100644 --- a/src/tools/miri/src/shims/x86/sse2.rs +++ b/src/tools/miri/src/shims/x86/sse2.rs @@ -1,6 +1,6 @@ use rustc_apfloat::{ ieee::{Double, Single}, - Float as _, FloatConvert as _, + Float as _, }; use rustc_middle::ty::layout::LayoutOf as _; use rustc_middle::ty::Ty; @@ -637,52 +637,31 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { }; this.write_scalar(Scalar::from_i32(i32::from(res)), dest)?; } - // Used to implement the _mm_cvtpd_ps function. - // Converts packed f32 to packed f64. - "cvtpd2ps" => { + // Used to implement the _mm_cvtpd_ps and _mm_cvtps_pd functions. + // Converts packed f32/f64 to packed f64/f32. + "cvtpd2ps" | "cvtps2pd" => { let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let (op, op_len) = this.operand_to_simd(op)?; let (dest, dest_len) = this.place_to_simd(dest)?; - // op is f64x2, dest is f32x4 - assert_eq!(op_len, 2); - assert_eq!(dest_len, 4); - - for i in 0..op_len { - let op = this.read_scalar(&this.project_index(&op, i)?)?.to_f64()?; + // For cvtpd2ps: op is f64x2, dest is f32x4 + // For cvtps2pd: op is f32x4, dest is f64x2 + // In either case, the two first values are converted + for i in 0..op_len.min(dest_len) { + let op = this.read_immediate(&this.project_index(&op, i)?)?; let dest = this.project_index(&dest, i)?; - let res = op.convert(/*loses_info*/ &mut false).value; - this.write_scalar(Scalar::from_f32(res), &dest)?; + let res = this.float_to_float_or_int(&op, dest.layout.ty)?; + this.write_immediate(res, &dest)?; } - // Fill the remaining with zeros + // For f32 -> f64, ignore the remaining + // For f64 -> f32, fill the remaining with zeros for i in op_len..dest_len { let dest = this.project_index(&dest, i)?; - this.write_scalar(Scalar::from_u32(0), &dest)?; + this.write_scalar(Scalar::from_int(0, dest.layout.size), &dest)?; } } - // Used to implement the _mm_cvtps_pd function. - // Converts packed f64 to packed f32. - "cvtps2pd" => { - let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - - let (op, op_len) = this.operand_to_simd(op)?; - let (dest, dest_len) = this.place_to_simd(dest)?; - - // op is f32x4, dest is f64x2 - assert_eq!(op_len, 4); - assert_eq!(dest_len, 2); - - for i in 0..dest_len { - let op = this.read_scalar(&this.project_index(&op, i)?)?.to_f32()?; - let dest = this.project_index(&dest, i)?; - - let res = op.convert(/*loses_info*/ &mut false).value; - this.write_scalar(Scalar::from_f64(res), &dest)?; - } - // the two remaining f32 are ignored - } // Used to implement the _mm_cvtpd_epi32 and _mm_cvttpd_epi32 functions. // Converts packed f64 to packed i32. "cvtpd2dq" | "cvttpd2dq" => { From 4bb8a8b804d89902c2cf9be0446605306d1f22df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20S=C3=A1nchez=20Mu=C3=B1oz?= Date: Wed, 13 Sep 2023 21:03:07 +0200 Subject: [PATCH 050/111] miri: fix comment --- src/tools/miri/src/shims/x86/sse2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/src/shims/x86/sse2.rs b/src/tools/miri/src/shims/x86/sse2.rs index a0280fd0122c..098409d6e350 100644 --- a/src/tools/miri/src/shims/x86/sse2.rs +++ b/src/tools/miri/src/shims/x86/sse2.rs @@ -465,7 +465,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } // Used to implement the _mm_packs_epi32 function. - // Converts two 16-bit integer vectors to a single 8-bit integer + // Converts two 32-bit integer vectors to a single 16-bit integer // vector with signed saturation. "packssdw.128" => { let [left, right] = From 8d590f2fa84c0bb7bf423dfc3ea64b229e2f7346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20S=C3=A1nchez=20Mu=C3=B1oz?= Date: Wed, 13 Sep 2023 21:51:40 +0200 Subject: [PATCH 051/111] miri: reduce code duplication in SSE2 pavg.b and pavg.w --- src/tools/miri/src/helpers.rs | 20 +++++++- src/tools/miri/src/shims/x86/sse2.rs | 72 +++++++++++++--------------- 2 files changed, 53 insertions(+), 39 deletions(-) diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 7805fe25bcd4..bbd905be0a92 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -14,7 +14,7 @@ use rustc_middle::mir; use rustc_middle::ty::{ self, layout::{IntegerExt as _, LayoutOf, TyAndLayout}, - Ty, TyCtxt, + IntTy, Ty, TyCtxt, UintTy, }; use rustc_span::{def_id::CrateNum, sym, Span, Symbol}; use rustc_target::abi::{Align, FieldIdx, FieldsShape, Integer, Size, Variants}; @@ -1067,6 +1067,24 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ), } } + + /// Returns an integer type that is twice wide as `ty` + fn get_twice_wide_int_ty(&self, ty: Ty<'tcx>) -> Ty<'tcx> { + let this = self.eval_context_ref(); + match ty.kind() { + // Unsigned + ty::Uint(UintTy::U8) => this.tcx.types.u16, + ty::Uint(UintTy::U16) => this.tcx.types.u32, + ty::Uint(UintTy::U32) => this.tcx.types.u64, + ty::Uint(UintTy::U64) => this.tcx.types.u128, + // Signed + ty::Int(IntTy::I8) => this.tcx.types.i16, + ty::Int(IntTy::I16) => this.tcx.types.i32, + ty::Int(IntTy::I32) => this.tcx.types.i64, + ty::Int(IntTy::I64) => this.tcx.types.i128, + _ => span_bug!(this.cur_span(), "unexpected type: {ty:?}"), + } + } } impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { diff --git a/src/tools/miri/src/shims/x86/sse2.rs b/src/tools/miri/src/shims/x86/sse2.rs index 098409d6e350..28aebc8cba87 100644 --- a/src/tools/miri/src/shims/x86/sse2.rs +++ b/src/tools/miri/src/shims/x86/sse2.rs @@ -2,6 +2,7 @@ use rustc_apfloat::{ ieee::{Double, Single}, Float as _, }; +use rustc_middle::mir; use rustc_middle::ty::layout::LayoutOf as _; use rustc_middle::ty::Ty; use rustc_span::Symbol; @@ -36,9 +37,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Intrinsincs sufixed with "epiX" or "epuX" operate with X-bit signed or unsigned // vectors. match unprefixed_name { - // Used to implement the _mm_avg_epu8 function. - // Averages packed unsigned 8-bit integers in `left` and `right`. - "pavg.b" => { + // Used to implement the _mm_avg_epu8 and _mm_avg_epu16 functions. + // Averages packed unsigned 8/16-bit integers in `left` and `right`. + "pavg.b" | "pavg.w" => { let [left, right] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; @@ -50,46 +51,41 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { assert_eq!(dest_len, right_len); for i in 0..dest_len { - let left = this.read_scalar(&this.project_index(&left, i)?)?.to_u8()?; - let right = this.read_scalar(&this.project_index(&right, i)?)?.to_u8()?; + let left = this.read_immediate(&this.project_index(&left, i)?)?; + let right = this.read_immediate(&this.project_index(&right, i)?)?; let dest = this.project_index(&dest, i)?; - // Values are expanded from u8 to u16, so adds cannot overflow. - let res = u16::from(left) - .checked_add(u16::from(right)) - .unwrap() - .checked_add(1) - .unwrap() - / 2; - this.write_scalar(Scalar::from_u8(res.try_into().unwrap()), &dest)?; - } - } - // Used to implement the _mm_avg_epu16 function. - // Averages packed unsigned 16-bit integers in `left` and `right`. - "pavg.w" => { - let [left, right] = - this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + // Widen the operands to avoid overflow + let twice_wide_ty = this.get_twice_wide_int_ty(left.layout.ty); + let twice_wide_layout = this.layout_of(twice_wide_ty)?; + let left = this.int_to_int_or_float(&left, twice_wide_ty)?; + let right = this.int_to_int_or_float(&right, twice_wide_ty)?; - let (left, left_len) = this.operand_to_simd(left)?; - let (right, right_len) = this.operand_to_simd(right)?; - let (dest, dest_len) = this.place_to_simd(dest)?; + // Calculate left + right + 1 + let (added, _overflow, _ty) = this.overflowing_binary_op( + mir::BinOp::Add, + &ImmTy::from_immediate(left, twice_wide_layout), + &ImmTy::from_immediate(right, twice_wide_layout), + )?; + let (added, _overflow, _ty) = this.overflowing_binary_op( + mir::BinOp::Add, + &ImmTy::from_scalar(added, twice_wide_layout), + &ImmTy::from_uint(1u32, twice_wide_layout), + )?; - assert_eq!(dest_len, left_len); - assert_eq!(dest_len, right_len); + // Calculate (left + right + 1) / 2 + let (divided, _overflow, _ty) = this.overflowing_binary_op( + mir::BinOp::Div, + &ImmTy::from_scalar(added, twice_wide_layout), + &ImmTy::from_uint(2u32, twice_wide_layout), + )?; - for i in 0..dest_len { - let left = this.read_scalar(&this.project_index(&left, i)?)?.to_u16()?; - let right = this.read_scalar(&this.project_index(&right, i)?)?.to_u16()?; - let dest = this.project_index(&dest, i)?; - - // Values are expanded from u16 to u32, so adds cannot overflow. - let res = u32::from(left) - .checked_add(u32::from(right)) - .unwrap() - .checked_add(1) - .unwrap() - / 2; - this.write_scalar(Scalar::from_u16(res.try_into().unwrap()), &dest)?; + // Narrow back to the original type + let res = this.int_to_int_or_float( + &ImmTy::from_scalar(divided, twice_wide_layout), + dest.layout.ty, + )?; + this.write_immediate(res, &dest)?; } } // Used to implement the _mm_mulhi_epi16 function. From 9fbbfd2e396a2d1c66168af9db80a806bcc5a32a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20S=C3=A1nchez=20Mu=C3=B1oz?= Date: Wed, 13 Sep 2023 21:58:28 +0200 Subject: [PATCH 052/111] miri: reduce code duplication in SSE2 pmulh.w and pmulhu.w Not really a saving in terms of lines of code, but at least the logic is de-duplicated --- src/tools/miri/src/shims/x86/sse2.rs | 54 ++++++++++++++-------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/tools/miri/src/shims/x86/sse2.rs b/src/tools/miri/src/shims/x86/sse2.rs index 28aebc8cba87..b68690a835c2 100644 --- a/src/tools/miri/src/shims/x86/sse2.rs +++ b/src/tools/miri/src/shims/x86/sse2.rs @@ -88,8 +88,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_immediate(res, &dest)?; } } - // Used to implement the _mm_mulhi_epi16 function. - "pmulh.w" => { + // Used to implement the _mm_mulhi_epi16 and _mm_mulhi_epu16 functions. + "pmulh.w" | "pmulhu.w" => { let [left, right] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; @@ -101,35 +101,35 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { assert_eq!(dest_len, right_len); for i in 0..dest_len { - let left = this.read_scalar(&this.project_index(&left, i)?)?.to_i16()?; - let right = this.read_scalar(&this.project_index(&right, i)?)?.to_i16()?; + let left = this.read_immediate(&this.project_index(&left, i)?)?; + let right = this.read_immediate(&this.project_index(&right, i)?)?; let dest = this.project_index(&dest, i)?; - // Values are expanded from i16 to i32, so multiplication cannot overflow. - let res = i32::from(left).checked_mul(i32::from(right)).unwrap() >> 16; - this.write_scalar(Scalar::from_i16(res.try_into().unwrap()), &dest)?; - } - } - // Used to implement the _mm_mulhi_epu16 function. - "pmulhu.w" => { - let [left, right] = - this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + // Widen the operands to avoid overflow + let twice_wide_ty = this.get_twice_wide_int_ty(left.layout.ty); + let twice_wide_layout = this.layout_of(twice_wide_ty)?; + let left = this.int_to_int_or_float(&left, twice_wide_ty)?; + let right = this.int_to_int_or_float(&right, twice_wide_ty)?; - let (left, left_len) = this.operand_to_simd(left)?; - let (right, right_len) = this.operand_to_simd(right)?; - let (dest, dest_len) = this.place_to_simd(dest)?; + // Multiply + let (multiplied, _overflow, _ty) = this.overflowing_binary_op( + mir::BinOp::Mul, + &ImmTy::from_immediate(left, twice_wide_layout), + &ImmTy::from_immediate(right, twice_wide_layout), + )?; + // Keep the high half + let (high, _overflow, _ty) = this.overflowing_binary_op( + mir::BinOp::Shr, + &ImmTy::from_scalar(multiplied, twice_wide_layout), + &ImmTy::from_uint(dest.layout.size.bits(), twice_wide_layout), + )?; - assert_eq!(dest_len, left_len); - assert_eq!(dest_len, right_len); - - for i in 0..dest_len { - let left = this.read_scalar(&this.project_index(&left, i)?)?.to_u16()?; - let right = this.read_scalar(&this.project_index(&right, i)?)?.to_u16()?; - let dest = this.project_index(&dest, i)?; - - // Values are expanded from u16 to u32, so multiplication cannot overflow. - let res = u32::from(left).checked_mul(u32::from(right)).unwrap() >> 16; - this.write_scalar(Scalar::from_u16(res.try_into().unwrap()), &dest)?; + // Narrow back to the original type + let res = this.int_to_int_or_float( + &ImmTy::from_scalar(high, twice_wide_layout), + dest.layout.ty, + )?; + this.write_immediate(res, &dest)?; } } // Used to implement the _mm_mul_epu32 function. From a6d87246f6d1cee63d773311536ace19d1df78a3 Mon Sep 17 00:00:00 2001 From: The 8472 Date: Mon, 18 Sep 2023 21:51:34 +0200 Subject: [PATCH 053/111] panic when encountering an illegal cpumask --- library/std/src/sys/unix/thread.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/std/src/sys/unix/thread.rs b/library/std/src/sys/unix/thread.rs index 7c242d4d3345..2afec897a884 100644 --- a/library/std/src/sys/unix/thread.rs +++ b/library/std/src/sys/unix/thread.rs @@ -324,8 +324,10 @@ pub fn available_parallelism() -> io::Result { if libc::sched_getaffinity(0, mem::size_of::(), &mut set) == 0 { let count = libc::CPU_COUNT(&set) as usize; let count = count.min(quota); - // SAFETY: affinity mask can't be empty and the quota gets clamped to a minimum of 1 - return Ok(NonZeroUsize::new_unchecked(count)); + // reported to occur on MIPS kernels older than our minimum supported kernel version for those targets + let count = NonZeroUsize::new(count) + .expect("CPU count must be > 0. This may be a bug in sched_getaffinity(); try upgrading the kernel."); + return Ok(count); } } } From 6f47a858f27a9e9546eaf9368b2a4fba3a4d74b8 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 18 Sep 2023 22:09:51 +0200 Subject: [PATCH 054/111] Use old parser if `custom_code_classes_in_docs` feature is not enabled --- src/librustdoc/html/markdown.rs | 239 ++++++++++++++++++-------------- 1 file changed, 135 insertions(+), 104 deletions(-) diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 59958fbaef91..7528393f0f8c 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -868,7 +868,7 @@ impl<'tcx> ExtraInfo<'tcx> { #[derive(Eq, PartialEq, Clone, Debug)] pub(crate) struct LangString { - original: String, + pub(crate) original: String, pub(crate) should_panic: bool, pub(crate) no_run: bool, pub(crate) ignore: Ignore, @@ -1158,6 +1158,29 @@ impl<'a, 'tcx> Iterator for TagIterator<'a, 'tcx> { } } +fn tokens(string: &str) -> impl Iterator> { + // Pandoc, which Rust once used for generating documentation, + // expects lang strings to be surrounded by `{}` and for each token + // to be proceeded by a `.`. Since some of these lang strings are still + // loose in the wild, we strip a pair of surrounding `{}` from the lang + // string and a leading `.` from each token. + + let string = string.trim(); + + let first = string.chars().next(); + let last = string.chars().last(); + + let string = + if first == Some('{') && last == Some('}') { &string[1..string.len() - 1] } else { string }; + + string + .split(|c| c == ',' || c == ' ' || c == '\t') + .map(str::trim) + .map(|token| token.strip_prefix('.').unwrap_or(token)) + .filter(|token| !token.is_empty()) + .map(|token| LangStringToken::LangToken(token)) +} + impl Default for LangString { fn default() -> Self { Self { @@ -1208,122 +1231,130 @@ impl LangString { data.original = string.to_owned(); - for token in TagIterator::new(string, extra) { - match token { - LangStringToken::LangToken("should_panic") => { - data.should_panic = true; - seen_rust_tags = !seen_other_tags; - } - LangStringToken::LangToken("no_run") => { - data.no_run = true; - seen_rust_tags = !seen_other_tags; - } - LangStringToken::LangToken("ignore") => { - data.ignore = Ignore::All; - seen_rust_tags = !seen_other_tags; - } - LangStringToken::LangToken(x) if x.starts_with("ignore-") => { - if enable_per_target_ignores { - ignores.push(x.trim_start_matches("ignore-").to_owned()); + let mut call = |tokens: &mut dyn Iterator>| { + for token in tokens { + match token { + LangStringToken::LangToken("should_panic") => { + data.should_panic = true; seen_rust_tags = !seen_other_tags; } - } - LangStringToken::LangToken("rust") => { - data.rust = true; - seen_rust_tags = true; - } - LangStringToken::LangToken("custom") => { - if custom_code_classes_in_docs { - seen_custom_tag = true; - } else { - seen_other_tags = true; + LangStringToken::LangToken("no_run") => { + data.no_run = true; + seen_rust_tags = !seen_other_tags; } - } - LangStringToken::LangToken("test_harness") => { - data.test_harness = true; - seen_rust_tags = !seen_other_tags || seen_rust_tags; - } - LangStringToken::LangToken("compile_fail") => { - data.compile_fail = true; - seen_rust_tags = !seen_other_tags || seen_rust_tags; - data.no_run = true; - } - LangStringToken::LangToken(x) if x.starts_with("edition") => { - data.edition = x[7..].parse::().ok(); - } - LangStringToken::LangToken(x) - if allow_error_code_check && x.starts_with('E') && x.len() == 5 => - { - if x[1..].parse::().is_ok() { - data.error_codes.push(x.to_owned()); + LangStringToken::LangToken("ignore") => { + data.ignore = Ignore::All; + seen_rust_tags = !seen_other_tags; + } + LangStringToken::LangToken(x) if x.starts_with("ignore-") => { + if enable_per_target_ignores { + ignores.push(x.trim_start_matches("ignore-").to_owned()); + seen_rust_tags = !seen_other_tags; + } + } + LangStringToken::LangToken("rust") => { + data.rust = true; + seen_rust_tags = true; + } + LangStringToken::LangToken("custom") => { + if custom_code_classes_in_docs { + seen_custom_tag = true; + } else { + seen_other_tags = true; + } + } + LangStringToken::LangToken("test_harness") => { + data.test_harness = true; seen_rust_tags = !seen_other_tags || seen_rust_tags; - } else { - seen_other_tags = true; } - } - LangStringToken::LangToken(x) if extra.is_some() => { - let s = x.to_lowercase(); - if let Some((flag, help)) = if s == "compile-fail" - || s == "compile_fail" - || s == "compilefail" + LangStringToken::LangToken("compile_fail") => { + data.compile_fail = true; + seen_rust_tags = !seen_other_tags || seen_rust_tags; + data.no_run = true; + } + LangStringToken::LangToken(x) if x.starts_with("edition") => { + data.edition = x[7..].parse::().ok(); + } + LangStringToken::LangToken(x) + if allow_error_code_check && x.starts_with('E') && x.len() == 5 => { - Some(( - "compile_fail", - "the code block will either not be tested if not marked as a rust one \ - or won't fail if it compiles successfully", - )) - } else if s == "should-panic" || s == "should_panic" || s == "shouldpanic" { - Some(( - "should_panic", - "the code block will either not be tested if not marked as a rust one \ - or won't fail if it doesn't panic when running", - )) - } else if s == "no-run" || s == "no_run" || s == "norun" { - Some(( - "no_run", - "the code block will either not be tested if not marked as a rust one \ - or will be run (which you might not want)", - )) - } else if s == "test-harness" || s == "test_harness" || s == "testharness" { - Some(( - "test_harness", - "the code block will either not be tested if not marked as a rust one \ - or the code will be wrapped inside a main function", - )) - } else { - None - } { - if let Some(extra) = extra { - extra.error_invalid_codeblock_attr_with_help( - format!("unknown attribute `{x}`. Did you mean `{flag}`?"), - help, - ); + if x[1..].parse::().is_ok() { + data.error_codes.push(x.to_owned()); + seen_rust_tags = !seen_other_tags || seen_rust_tags; + } else { + seen_other_tags = true; } } - seen_other_tags = true; - data.unknown.push(x.to_owned()); - } - LangStringToken::LangToken(x) => { - seen_other_tags = true; - data.unknown.push(x.to_owned()); - } - LangStringToken::KeyValueAttribute(key, value) => { - if custom_code_classes_in_docs { - if key == "class" { - data.added_classes.push(value.to_owned()); - } else if let Some(extra) = extra { - extra.error_invalid_codeblock_attr(format!( - "unsupported attribute `{key}`" - )); + LangStringToken::LangToken(x) if extra.is_some() => { + let s = x.to_lowercase(); + if let Some((flag, help)) = if s == "compile-fail" + || s == "compile_fail" + || s == "compilefail" + { + Some(( + "compile_fail", + "the code block will either not be tested if not marked as a rust one \ + or won't fail if it compiles successfully", + )) + } else if s == "should-panic" || s == "should_panic" || s == "shouldpanic" { + Some(( + "should_panic", + "the code block will either not be tested if not marked as a rust one \ + or won't fail if it doesn't panic when running", + )) + } else if s == "no-run" || s == "no_run" || s == "norun" { + Some(( + "no_run", + "the code block will either not be tested if not marked as a rust one \ + or will be run (which you might not want)", + )) + } else if s == "test-harness" || s == "test_harness" || s == "testharness" { + Some(( + "test_harness", + "the code block will either not be tested if not marked as a rust one \ + or the code will be wrapped inside a main function", + )) + } else { + None + } { + if let Some(extra) = extra { + extra.error_invalid_codeblock_attr_with_help( + format!("unknown attribute `{x}`. Did you mean `{flag}`?"), + help, + ); + } } - } else { seen_other_tags = true; + data.unknown.push(x.to_owned()); + } + LangStringToken::LangToken(x) => { + seen_other_tags = true; + data.unknown.push(x.to_owned()); + } + LangStringToken::KeyValueAttribute(key, value) => { + if custom_code_classes_in_docs { + if key == "class" { + data.added_classes.push(value.to_owned()); + } else if let Some(extra) = extra { + extra.error_invalid_codeblock_attr(format!( + "unsupported attribute `{key}`" + )); + } + } else { + seen_other_tags = true; + } + } + LangStringToken::ClassAttribute(class) => { + data.added_classes.push(class.to_owned()); } - } - LangStringToken::ClassAttribute(class) => { - data.added_classes.push(class.to_owned()); } } + }; + + if custom_code_classes_in_docs { + call(&mut TagIterator::new(string, extra).into_iter()) + } else { + call(&mut tokens(string)) } // ignore-foo overrides ignore From 55ce976e06e1dc9590a41cdc262688d4db8d93e4 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 17 Aug 2023 17:43:38 +0000 Subject: [PATCH 055/111] Failing test --- .../dont_inline_type_id.call.Inline.diff | 37 +++++++++++++++++++ tests/mir-opt/dont_inline_type_id.rs | 15 ++++++++ 2 files changed, 52 insertions(+) create mode 100644 tests/mir-opt/dont_inline_type_id.call.Inline.diff create mode 100644 tests/mir-opt/dont_inline_type_id.rs diff --git a/tests/mir-opt/dont_inline_type_id.call.Inline.diff b/tests/mir-opt/dont_inline_type_id.call.Inline.diff new file mode 100644 index 000000000000..d67235ddbd2b --- /dev/null +++ b/tests/mir-opt/dont_inline_type_id.call.Inline.diff @@ -0,0 +1,37 @@ +- // MIR for `call` before Inline ++ // MIR for `call` after Inline + + fn call(_1: &T) -> TypeId { + debug s => _1; + let mut _0: std::any::TypeId; + let mut _2: &T; ++ scope 1 (inlined ::type_id) { ++ debug self => _2; ++ scope 2 (inlined TypeId::of::) { ++ let _3: u128; ++ let mut _4: u128; ++ scope 3 { ++ debug t => _3; ++ } ++ } ++ } + + bb0: { + StorageLive(_2); + _2 = &(*_1); +- _0 = ::type_id(move _2) -> [return: bb1, unwind unreachable]; ++ StorageLive(_3); ++ _3 = std::intrinsics::type_id::() -> [return: bb1, unwind unreachable]; + } + + bb1: { ++ StorageLive(_4); ++ _4 = _3; ++ _0 = TypeId { t: move _4 }; ++ StorageDead(_4); ++ StorageDead(_3); + StorageDead(_2); + return; + } + } + diff --git a/tests/mir-opt/dont_inline_type_id.rs b/tests/mir-opt/dont_inline_type_id.rs new file mode 100644 index 000000000000..6f4c21a3ab1c --- /dev/null +++ b/tests/mir-opt/dont_inline_type_id.rs @@ -0,0 +1,15 @@ +// unit-test: Inline +// compile-flags: --crate-type=lib -C panic=abort + +use std::any::Any; +use std::any::TypeId; + +struct A { + a: i32, + b: T, +} + +// EMIT_MIR dont_inline_type_id.call.Inline.diff +fn call(s: &T) -> TypeId { + s.type_id() +} From 976d377f7f4778010294bbe83a518c1b34d2a233 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 7 Sep 2023 02:06:20 +0000 Subject: [PATCH 056/111] Explain HRTB + infer limitations of old solver --- .../src/traits/error_reporting/mod.rs | 2 + .../src/traits/error_reporting/suggestions.rs | 73 +++++++++++++++++++ ...elf-auto-trait-issue-109924.current.stderr | 8 ++ .../bugs/issue-88460.stderr | 8 ++ .../issue-62529-3.stderr | 8 ++ .../normalize-under-binder/issue-90950.stderr | 8 ++ .../norm-before-method-resolution.stderr | 5 ++ 7 files changed, 112 insertions(+) diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs index 135410691f1a..7d754cbafa34 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs @@ -986,6 +986,8 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } } + self.explain_hrtb_projection(&mut err, trait_predicate, obligation.param_env, &obligation.cause); + // Return early if the trait is Debug or Display and the invocation // originates within a standard library macro, because the output // is otherwise overwhelming and unhelpful (see #85844 for an diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs index d6ac72087158..a08ebe5a9ea8 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs @@ -406,6 +406,14 @@ pub trait TypeErrCtxtExt<'tcx> { candidate_impls: &[ImplCandidate<'tcx>], span: Span, ); + + fn explain_hrtb_projection( + &self, + diag: &mut Diagnostic, + pred: ty::PolyTraitPredicate<'tcx>, + param_env: ty::ParamEnv<'tcx>, + cause: &ObligationCause<'tcx>, + ); } fn predicate_constraint(generics: &hir::Generics<'_>, pred: ty::Predicate<'_>) -> (Span, String) { @@ -4027,6 +4035,71 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } } } + + fn explain_hrtb_projection( + &self, + diag: &mut Diagnostic, + pred: ty::PolyTraitPredicate<'tcx>, + param_env: ty::ParamEnv<'tcx>, + cause: &ObligationCause<'tcx>, + ) { + if pred.skip_binder().has_escaping_bound_vars() && pred.skip_binder().has_non_region_infer() + { + self.probe(|_| { + let ocx = ObligationCtxt::new(self); + let pred = self.instantiate_binder_with_placeholders(pred); + let pred = ocx.normalize(&ObligationCause::dummy(), param_env, pred); + ocx.register_obligation(Obligation::new( + self.tcx, + ObligationCause::dummy(), + param_env, + pred, + )); + if !ocx.select_where_possible().is_empty() { + // encountered errors. + return; + } + + if let ObligationCauseCode::FunctionArgumentObligation { + call_hir_id, + arg_hir_id, + parent_code: _, + } = cause.code() + { + let arg_span = self.tcx.hir().span(*arg_hir_id); + let mut sp: MultiSpan = arg_span.into(); + + sp.push_span_label( + arg_span, + "the trait solver is unable to infer the \ + generic types that should be inferred from this argument", + ); + sp.push_span_label( + self.tcx.hir().span(*call_hir_id), + "add turbofish arguments to this call to \ + specify the types manually, even if it's redundant", + ); + diag.span_note( + sp, + "this is a known limitation of the trait solver that \ + will be lifted in the future", + ); + } else { + let mut sp: MultiSpan = cause.span.into(); + sp.push_span_label( + cause.span, + "try adding turbofish arguments to this expression to \ + specify the types manually, even if it's redundant", + ); + diag.span_note( + sp, + "this is a known limitation of the trait solver that \ + will be lifted in the future", + ); + } + }); + } + } } /// Add a hint to add a missing borrow or remove an unnecessary one. diff --git a/tests/ui/async-await/return-type-notation/normalizing-self-auto-trait-issue-109924.current.stderr b/tests/ui/async-await/return-type-notation/normalizing-self-auto-trait-issue-109924.current.stderr index 3b63ec45804e..8f45902035e9 100644 --- a/tests/ui/async-await/return-type-notation/normalizing-self-auto-trait-issue-109924.current.stderr +++ b/tests/ui/async-await/return-type-notation/normalizing-self-auto-trait-issue-109924.current.stderr @@ -16,6 +16,14 @@ LL | build(Bar); | required by a bound introduced by this call | = help: the trait `for<'a> Send` is not implemented for `impl Future { <_ as Foo>::bar() }` +note: this is a known limitation of the trait solver that will be lifted in the future + --> $DIR/normalizing-self-auto-trait-issue-109924.rs:23:11 + | +LL | build(Bar); + | ------^^^- + | | | + | | the trait solver is unable to infer the generic types that should be inferred from this argument + | add turbofish arguments to this call to specify the types manually, even if it's redundant note: required by a bound in `build` --> $DIR/normalizing-self-auto-trait-issue-109924.rs:20:39 | diff --git a/tests/ui/generic-associated-types/bugs/issue-88460.stderr b/tests/ui/generic-associated-types/bugs/issue-88460.stderr index a2047f103d48..b9e2c4186c14 100644 --- a/tests/ui/generic-associated-types/bugs/issue-88460.stderr +++ b/tests/ui/generic-associated-types/bugs/issue-88460.stderr @@ -7,6 +7,14 @@ LL | test(Foo); | required by a bound introduced by this call | = help: the trait `Marker` is implemented for `()` +note: this is a known limitation of the trait solver that will be lifted in the future + --> $DIR/issue-88460.rs:28:10 + | +LL | test(Foo); + | -----^^^- + | | | + | | the trait solver is unable to infer the generic types that should be inferred from this argument + | add turbofish arguments to this call to specify the types manually, even if it's redundant note: required by a bound in `test` --> $DIR/issue-88460.rs:15:27 | diff --git a/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-62529-3.stderr b/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-62529-3.stderr index b30dd36d2ad6..2cc2bb2bbc3c 100644 --- a/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-62529-3.stderr +++ b/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-62529-3.stderr @@ -8,6 +8,14 @@ LL | call(f, ()); | = note: expected a closure with arguments `((),)` found a closure with arguments `(<_ as ATC<'a>>::Type,)` +note: this is a known limitation of the trait solver that will be lifted in the future + --> $DIR/issue-62529-3.rs:25:14 + | +LL | call(f, ()); + | -----^----- + | | | + | | the trait solver is unable to infer the generic types that should be inferred from this argument + | add turbofish arguments to this call to specify the types manually, even if it's redundant note: required by a bound in `call` --> $DIR/issue-62529-3.rs:9:36 | diff --git a/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-90950.stderr b/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-90950.stderr index 5be33bccdc31..55eaef78634c 100644 --- a/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-90950.stderr +++ b/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-90950.stderr @@ -7,6 +7,14 @@ LL | upcast(y) | required by a bound introduced by this call | = help: the trait `IsCovariant<'a>` is implemented for `std::borrow::Cow<'a, T>` +note: this is a known limitation of the trait solver that will be lifted in the future + --> $DIR/issue-90950.rs:50:12 + | +LL | upcast(y) + | -------^- + | | | + | | the trait solver is unable to infer the generic types that should be inferred from this argument + | add turbofish arguments to this call to specify the types manually, even if it's redundant note: required by a bound in `upcast` --> $DIR/issue-90950.rs:27:42 | diff --git a/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/norm-before-method-resolution.stderr b/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/norm-before-method-resolution.stderr index 73388a72574e..081dbb67df85 100644 --- a/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/norm-before-method-resolution.stderr +++ b/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/norm-before-method-resolution.stderr @@ -4,6 +4,11 @@ error[E0277]: the trait bound `for<'a> <_ as Trait<'a>>::Out: Copy` is not satis LL | let _: () = weird_bound(); | ^^^^^^^^^^^ the trait `for<'a> Copy` is not implemented for `<_ as Trait<'a>>::Out` | +note: this is a known limitation of the trait solver that will be lifted in the future + --> $DIR/norm-before-method-resolution.rs:22:17 + | +LL | let _: () = weird_bound(); + | ^^^^^^^^^^^ try adding turbofish arguments to this expression to specify the types manually, even if it's redundant note: required by a bound in `weird_bound` --> $DIR/norm-before-method-resolution.rs:18:40 | From a30ad3a5a6362c31b16d3bd3f21bbae1315bfaec Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Mon, 21 Aug 2023 22:39:34 +0000 Subject: [PATCH 057/111] Don't resolve generic instances if they may be shadowed by dyn --- compiler/rustc_middle/src/ty/sty.rs | 27 +++++++++++++++++++ compiler/rustc_ty_utils/src/instance.rs | 25 ++++++++++++++++- .../dont_inline_type_id.call.Inline.diff | 19 +------------ tests/mir-opt/dont_inline_type_id.rs | 2 +- ...line_generically_if_sized.call.Inline.diff | 24 +++++++++++++++++ tests/mir-opt/inline_generically_if_sized.rs | 17 ++++++++++++ .../dont-propagate-generic-instance-2.rs | 26 ++++++++++++++++++ .../dont-propagate-generic-instance.rs | 24 +++++++++++++++++ 8 files changed, 144 insertions(+), 20 deletions(-) create mode 100644 tests/mir-opt/inline_generically_if_sized.call.Inline.diff create mode 100644 tests/mir-opt/inline_generically_if_sized.rs create mode 100644 tests/ui/const_prop/dont-propagate-generic-instance-2.rs create mode 100644 tests/ui/const_prop/dont-propagate-generic-instance.rs diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index edab7fe58bae..a14d09bbb700 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -2945,6 +2945,33 @@ impl<'tcx> Ty<'tcx> { _ => false, } } + + pub fn is_known_rigid(self) -> bool { + match self.kind() { + Bool + | Char + | Int(_) + | Uint(_) + | Float(_) + | Adt(_, _) + | Foreign(_) + | Str + | Array(_, _) + | Slice(_) + | RawPtr(_) + | Ref(_, _, _) + | FnDef(_, _) + | FnPtr(_) + | Dynamic(_, _, _) + | Closure(_, _) + | Generator(_, _, _) + | GeneratorWitness(_) + | GeneratorWitnessMIR(_, _) + | Never + | Tuple(_) => true, + Error(_) | Infer(_) | Alias(_, _) | Param(_) | Bound(_, _) | Placeholder(_) => false, + } + } } /// Extra information about why we ended up with a particular variance. diff --git a/compiler/rustc_ty_utils/src/instance.rs b/compiler/rustc_ty_utils/src/instance.rs index da2958bf56e8..91f1c21310e3 100644 --- a/compiler/rustc_ty_utils/src/instance.rs +++ b/compiler/rustc_ty_utils/src/instance.rs @@ -141,11 +141,34 @@ fn resolve_associated_item<'tcx>( false } }; - if !eligible { return Ok(None); } + // HACK: We may have overlapping `dyn Trait` built-in impls and + // user-provided blanket impls. Detect that case here, and return + // ambiguity. + // + // This should not affect totally monomorphized contexts, only + // resolve calls that happen polymorphically, such as the mir-inliner + // and const-prop (and also some lints). + let self_ty = rcvr_args.type_at(0); + if !self_ty.is_known_rigid() { + let predicates = tcx + .predicates_of(impl_data.impl_def_id) + .instantiate(tcx, impl_data.args) + .predicates; + let sized_def_id = tcx.lang_items().sized_trait(); + // If we find a `Self: Sized` bound on the item, then we know + // that `dyn Trait` can certainly never apply here. + if !predicates.into_iter().filter_map(ty::Clause::as_trait_clause).any(|clause| { + Some(clause.def_id()) == sized_def_id + && clause.skip_binder().self_ty() == self_ty + }) { + return Ok(None); + } + } + // Any final impl is required to define all associated items. if !leaf_def.item.defaultness(tcx).has_value() { let guard = tcx.sess.delay_span_bug( diff --git a/tests/mir-opt/dont_inline_type_id.call.Inline.diff b/tests/mir-opt/dont_inline_type_id.call.Inline.diff index d67235ddbd2b..80cfe07b0cbf 100644 --- a/tests/mir-opt/dont_inline_type_id.call.Inline.diff +++ b/tests/mir-opt/dont_inline_type_id.call.Inline.diff @@ -5,31 +5,14 @@ debug s => _1; let mut _0: std::any::TypeId; let mut _2: &T; -+ scope 1 (inlined ::type_id) { -+ debug self => _2; -+ scope 2 (inlined TypeId::of::) { -+ let _3: u128; -+ let mut _4: u128; -+ scope 3 { -+ debug t => _3; -+ } -+ } -+ } bb0: { StorageLive(_2); _2 = &(*_1); -- _0 = ::type_id(move _2) -> [return: bb1, unwind unreachable]; -+ StorageLive(_3); -+ _3 = std::intrinsics::type_id::() -> [return: bb1, unwind unreachable]; + _0 = ::type_id(move _2) -> [return: bb1, unwind unreachable]; } bb1: { -+ StorageLive(_4); -+ _4 = _3; -+ _0 = TypeId { t: move _4 }; -+ StorageDead(_4); -+ StorageDead(_3); StorageDead(_2); return; } diff --git a/tests/mir-opt/dont_inline_type_id.rs b/tests/mir-opt/dont_inline_type_id.rs index 6f4c21a3ab1c..d8a566360944 100644 --- a/tests/mir-opt/dont_inline_type_id.rs +++ b/tests/mir-opt/dont_inline_type_id.rs @@ -10,6 +10,6 @@ struct A { } // EMIT_MIR dont_inline_type_id.call.Inline.diff -fn call(s: &T) -> TypeId { +pub fn call(s: &T) -> TypeId { s.type_id() } diff --git a/tests/mir-opt/inline_generically_if_sized.call.Inline.diff b/tests/mir-opt/inline_generically_if_sized.call.Inline.diff new file mode 100644 index 000000000000..0cf4565dc994 --- /dev/null +++ b/tests/mir-opt/inline_generically_if_sized.call.Inline.diff @@ -0,0 +1,24 @@ +- // MIR for `call` before Inline ++ // MIR for `call` after Inline + + fn call(_1: &T) -> i32 { + debug s => _1; + let mut _0: i32; + let mut _2: &T; ++ scope 1 (inlined ::bar) { ++ debug self => _2; ++ } + + bb0: { + StorageLive(_2); + _2 = &(*_1); +- _0 = ::bar(move _2) -> [return: bb1, unwind unreachable]; +- } +- +- bb1: { ++ _0 = const 0_i32; + StorageDead(_2); + return; + } + } + diff --git a/tests/mir-opt/inline_generically_if_sized.rs b/tests/mir-opt/inline_generically_if_sized.rs new file mode 100644 index 000000000000..1acfff7a56b7 --- /dev/null +++ b/tests/mir-opt/inline_generically_if_sized.rs @@ -0,0 +1,17 @@ +// unit-test: Inline +// compile-flags: --crate-type=lib -C panic=abort + +trait Foo { + fn bar(&self) -> i32; +} + +impl Foo for T { + fn bar(&self) -> i32 { + 0 + } +} + +// EMIT_MIR inline_generically_if_sized.call.Inline.diff +pub fn call(s: &T) -> i32 { + s.bar() +} diff --git a/tests/ui/const_prop/dont-propagate-generic-instance-2.rs b/tests/ui/const_prop/dont-propagate-generic-instance-2.rs new file mode 100644 index 000000000000..e5525af23f0e --- /dev/null +++ b/tests/ui/const_prop/dont-propagate-generic-instance-2.rs @@ -0,0 +1,26 @@ +// run-pass + +#![feature(inline_const)] + +// Makes sure we don't propagate generic instances of `Self: ?Sized` blanket impls. +// This is relevant when we have an overlapping impl and builtin dyn instance. +// See for more context. + +trait Trait { + fn foo(&self) -> &'static str; +} + +impl Trait for T { + fn foo(&self) -> &'static str { + std::any::type_name::() + } +} + +fn bar() -> fn(&T) -> &'static str { + const { Trait::foo as fn(&T) -> &'static str } + // If const prop were to propagate the instance +} + +fn main() { + assert_eq!("i32", bar::()(&1i32)); +} diff --git a/tests/ui/const_prop/dont-propagate-generic-instance.rs b/tests/ui/const_prop/dont-propagate-generic-instance.rs new file mode 100644 index 000000000000..5994961b887b --- /dev/null +++ b/tests/ui/const_prop/dont-propagate-generic-instance.rs @@ -0,0 +1,24 @@ +// run-pass + +// Makes sure we don't propagate generic instances of `Self: ?Sized` blanket impls. +// This is relevant when we have an overlapping impl and builtin dyn instance. +// See for more context. + +trait Trait { + fn foo(&self) -> &'static str; +} + +impl Trait for T { + fn foo(&self) -> &'static str { + std::any::type_name::() + } +} + +const fn bar() -> fn(&T) -> &'static str { + Trait::foo + // If const prop were to propagate the instance +} + +fn main() { + assert_eq!("i32", bar::()(&1i32)); +} From 49f5b17cdb79548a4eef69b7a5559fbd4e1a946d Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 14 Sep 2023 23:25:34 +0200 Subject: [PATCH 058/111] more MIR const types to separate file --- compiler/rustc_middle/src/mir/consts.rs | 414 +++++++++++++++++++++++ compiler/rustc_middle/src/mir/mod.rs | 417 +----------------------- 2 files changed, 422 insertions(+), 409 deletions(-) create mode 100644 compiler/rustc_middle/src/mir/consts.rs diff --git a/compiler/rustc_middle/src/mir/consts.rs b/compiler/rustc_middle/src/mir/consts.rs new file mode 100644 index 000000000000..92c6828a0b40 --- /dev/null +++ b/compiler/rustc_middle/src/mir/consts.rs @@ -0,0 +1,414 @@ +use std::fmt::{self, Debug, Display, Formatter}; + +use rustc_hir; +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::{self as hir}; +use rustc_span::Span; +use rustc_target::abi::Size; + +use crate::mir::interpret::{ConstValue, ErrorHandled, GlobalAlloc, Scalar}; +use crate::mir::{interpret, pretty_print_const, pretty_print_const_value, Promoted}; +use crate::ty::{self, List, Ty, TyCtxt}; +use crate::ty::{GenericArgs, GenericArgsRef}; +use crate::ty::{ScalarInt, UserTypeAnnotationIndex}; + +/////////////////////////////////////////////////////////////////////////// +/// Constants +/// +/// Two constants are equal if they are the same constant. Note that +/// this does not necessarily mean that they are `==` in Rust. In +/// particular, one must be wary of `NaN`! + +#[derive(Clone, Copy, PartialEq, TyEncodable, TyDecodable, Hash, HashStable)] +#[derive(TypeFoldable, TypeVisitable)] +pub struct Constant<'tcx> { + pub span: Span, + + /// Optional user-given type: for something like + /// `collect::>`, this would be present and would + /// indicate that `Vec<_>` was explicitly specified. + /// + /// Needed for NLL to impose user-given type constraints. + pub user_ty: Option, + + pub literal: ConstantKind<'tcx>, +} + +#[derive(Clone, Copy, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable, Debug)] +#[derive(TypeFoldable, TypeVisitable)] +pub enum ConstantKind<'tcx> { + /// This constant came from the type system. + /// + /// Any way of turning `ty::Const` into `ConstValue` should go through `valtree_to_const_val`; + /// this ensures that we consistently produce "clean" values without data in the padding or + /// anything like that. + Ty(ty::Const<'tcx>), + + /// An unevaluated mir constant which is not part of the type system. + Unevaluated(UnevaluatedConst<'tcx>, Ty<'tcx>), + + /// This constant cannot go back into the type system, as it represents + /// something the type system cannot handle (e.g. pointers). + Val(interpret::ConstValue<'tcx>, Ty<'tcx>), +} + +impl<'tcx> Constant<'tcx> { + pub fn check_static_ptr(&self, tcx: TyCtxt<'_>) -> Option { + match self.literal.try_to_scalar() { + Some(Scalar::Ptr(ptr, _size)) => match tcx.global_alloc(ptr.provenance) { + GlobalAlloc::Static(def_id) => { + assert!(!tcx.is_thread_local_static(def_id)); + Some(def_id) + } + _ => None, + }, + _ => None, + } + } + #[inline] + pub fn ty(&self) -> Ty<'tcx> { + self.literal.ty() + } +} + +impl<'tcx> ConstantKind<'tcx> { + #[inline(always)] + pub fn ty(&self) -> Ty<'tcx> { + match self { + ConstantKind::Ty(c) => c.ty(), + ConstantKind::Val(_, ty) | ConstantKind::Unevaluated(_, ty) => *ty, + } + } + + #[inline] + pub fn try_to_scalar(self) -> Option { + match self { + ConstantKind::Ty(c) => match c.kind() { + ty::ConstKind::Value(valtree) => match valtree { + ty::ValTree::Leaf(scalar_int) => Some(Scalar::Int(scalar_int)), + ty::ValTree::Branch(_) => None, + }, + _ => None, + }, + ConstantKind::Val(val, _) => val.try_to_scalar(), + ConstantKind::Unevaluated(..) => None, + } + } + + #[inline] + pub fn try_to_scalar_int(self) -> Option { + self.try_to_scalar()?.try_to_int().ok() + } + + #[inline] + pub fn try_to_bits(self, size: Size) -> Option { + self.try_to_scalar_int()?.to_bits(size).ok() + } + + #[inline] + pub fn try_to_bool(self) -> Option { + self.try_to_scalar_int()?.try_into().ok() + } + + #[inline] + pub fn eval( + self, + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + span: Option, + ) -> Result, ErrorHandled> { + match self { + ConstantKind::Ty(c) => { + // We want to consistently have a "clean" value for type system constants (i.e., no + // data hidden in the padding), so we always go through a valtree here. + let val = c.eval(tcx, param_env, span)?; + Ok(tcx.valtree_to_const_val((self.ty(), val))) + } + ConstantKind::Unevaluated(uneval, _) => { + // FIXME: We might want to have a `try_eval`-like function on `Unevaluated` + tcx.const_eval_resolve(param_env, uneval, span) + } + ConstantKind::Val(val, _) => Ok(val), + } + } + + /// Normalizes the constant to a value or an error if possible. + #[inline] + pub fn normalize(self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> Self { + match self.eval(tcx, param_env, None) { + Ok(val) => Self::Val(val, self.ty()), + Err(ErrorHandled::Reported(guar, _span)) => { + Self::Ty(ty::Const::new_error(tcx, guar.into(), self.ty())) + } + Err(ErrorHandled::TooGeneric(_span)) => self, + } + } + + #[inline] + pub fn try_eval_scalar( + self, + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + ) -> Option { + self.eval(tcx, param_env, None).ok()?.try_to_scalar() + } + + #[inline] + pub fn try_eval_scalar_int( + self, + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + ) -> Option { + self.try_eval_scalar(tcx, param_env)?.try_to_int().ok() + } + + #[inline] + pub fn try_eval_bits( + &self, + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + ty: Ty<'tcx>, + ) -> Option { + let int = self.try_eval_scalar_int(tcx, param_env)?; + assert_eq!(self.ty(), ty); + let size = tcx.layout_of(param_env.with_reveal_all_normalized(tcx).and(ty)).ok()?.size; + int.to_bits(size).ok() + } + + /// Panics if the value cannot be evaluated or doesn't contain a valid integer of the given type. + #[inline] + pub fn eval_bits(self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>) -> u128 { + self.try_eval_bits(tcx, param_env, ty) + .unwrap_or_else(|| bug!("expected bits of {:#?}, got {:#?}", ty, self)) + } + + #[inline] + pub fn try_eval_target_usize( + self, + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + ) -> Option { + self.try_eval_scalar_int(tcx, param_env)?.try_to_target_usize(tcx).ok() + } + + #[inline] + /// Panics if the value cannot be evaluated or doesn't contain a valid `usize`. + pub fn eval_target_usize(self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> u64 { + self.try_eval_target_usize(tcx, param_env) + .unwrap_or_else(|| bug!("expected usize, got {:#?}", self)) + } + + #[inline] + pub fn try_eval_bool(self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> Option { + self.try_eval_scalar_int(tcx, param_env)?.try_into().ok() + } + + #[inline] + pub fn from_value(val: ConstValue<'tcx>, ty: Ty<'tcx>) -> Self { + Self::Val(val, ty) + } + + pub fn from_bits( + tcx: TyCtxt<'tcx>, + bits: u128, + param_env_ty: ty::ParamEnvAnd<'tcx, Ty<'tcx>>, + ) -> Self { + let size = tcx + .layout_of(param_env_ty) + .unwrap_or_else(|e| { + bug!("could not compute layout for {:?}: {:?}", param_env_ty.value, e) + }) + .size; + let cv = ConstValue::Scalar(Scalar::from_uint(bits, size)); + + Self::Val(cv, param_env_ty.value) + } + + #[inline] + pub fn from_bool(tcx: TyCtxt<'tcx>, v: bool) -> Self { + let cv = ConstValue::from_bool(v); + Self::Val(cv, tcx.types.bool) + } + + #[inline] + pub fn zero_sized(ty: Ty<'tcx>) -> Self { + let cv = ConstValue::ZeroSized; + Self::Val(cv, ty) + } + + pub fn from_usize(tcx: TyCtxt<'tcx>, n: u64) -> Self { + let ty = tcx.types.usize; + Self::from_bits(tcx, n as u128, ty::ParamEnv::empty().and(ty)) + } + + #[inline] + pub fn from_scalar(_tcx: TyCtxt<'tcx>, s: Scalar, ty: Ty<'tcx>) -> Self { + let val = ConstValue::Scalar(s); + Self::Val(val, ty) + } + + /// Literals are converted to `ConstantKindVal`, const generic parameters are eagerly + /// converted to a constant, everything else becomes `Unevaluated`. + #[instrument(skip(tcx), level = "debug", ret)] + pub fn from_anon_const( + tcx: TyCtxt<'tcx>, + def: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + ) -> Self { + let body_id = match tcx.hir().get_by_def_id(def) { + hir::Node::AnonConst(ac) => ac.body, + _ => { + span_bug!(tcx.def_span(def), "from_anon_const can only process anonymous constants") + } + }; + + let expr = &tcx.hir().body(body_id).value; + debug!(?expr); + + // Unwrap a block, so that e.g. `{ P }` is recognised as a parameter. Const arguments + // currently have to be wrapped in curly brackets, so it's necessary to special-case. + let expr = match &expr.kind { + hir::ExprKind::Block(block, _) if block.stmts.is_empty() && block.expr.is_some() => { + block.expr.as_ref().unwrap() + } + _ => expr, + }; + debug!("expr.kind: {:?}", expr.kind); + + let ty = tcx.type_of(def).instantiate_identity(); + debug!(?ty); + + // FIXME(const_generics): We currently have to special case parameters because `min_const_generics` + // does not provide the parents generics to anonymous constants. We still allow generic const + // parameters by themselves however, e.g. `N`. These constants would cause an ICE if we were to + // ever try to substitute the generic parameters in their bodies. + // + // While this doesn't happen as these constants are always used as `ty::ConstKind::Param`, it does + // cause issues if we were to remove that special-case and try to evaluate the constant instead. + use hir::{def::DefKind::ConstParam, def::Res, ExprKind, Path, QPath}; + match expr.kind { + ExprKind::Path(QPath::Resolved(_, &Path { res: Res::Def(ConstParam, def_id), .. })) => { + // Find the name and index of the const parameter by indexing the generics of + // the parent item and construct a `ParamConst`. + let item_def_id = tcx.parent(def_id); + let generics = tcx.generics_of(item_def_id); + let index = generics.param_def_id_to_index[&def_id]; + let name = tcx.item_name(def_id); + let ty_const = ty::Const::new_param(tcx, ty::ParamConst::new(index, name), ty); + debug!(?ty_const); + + return Self::Ty(ty_const); + } + _ => {} + } + + let hir_id = tcx.hir().local_def_id_to_hir_id(def); + let parent_args = if let Some(parent_hir_id) = tcx.hir().opt_parent_id(hir_id) + && let Some(parent_did) = parent_hir_id.as_owner() + { + GenericArgs::identity_for_item(tcx, parent_did) + } else { + List::empty() + }; + debug!(?parent_args); + + let did = def.to_def_id(); + let child_args = GenericArgs::identity_for_item(tcx, did); + let args = tcx.mk_args_from_iter(parent_args.into_iter().chain(child_args.into_iter())); + debug!(?args); + + let span = tcx.def_span(def); + let uneval = UnevaluatedConst::new(did, args); + debug!(?span, ?param_env); + + match tcx.const_eval_resolve(param_env, uneval, Some(span)) { + Ok(val) => { + debug!("evaluated const value"); + Self::Val(val, ty) + } + Err(_) => { + debug!("error encountered during evaluation"); + // Error was handled in `const_eval_resolve`. Here we just create a + // new unevaluated const and error hard later in codegen + Self::Unevaluated( + UnevaluatedConst { + def: did, + args: GenericArgs::identity_for_item(tcx, did), + promoted: None, + }, + ty, + ) + } + } + } + + pub fn from_ty_const(c: ty::Const<'tcx>, tcx: TyCtxt<'tcx>) -> Self { + match c.kind() { + ty::ConstKind::Value(valtree) => { + // Make sure that if `c` is normalized, then the return value is normalized. + let const_val = tcx.valtree_to_const_val((c.ty(), valtree)); + Self::Val(const_val, c.ty()) + } + _ => Self::Ty(c), + } + } +} + +/// An unevaluated (potentially generic) constant used in MIR. +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, TyEncodable, TyDecodable)] +#[derive(Hash, HashStable, TypeFoldable, TypeVisitable)] +pub struct UnevaluatedConst<'tcx> { + pub def: DefId, + pub args: GenericArgsRef<'tcx>, + pub promoted: Option, +} + +impl<'tcx> UnevaluatedConst<'tcx> { + #[inline] + pub fn shrink(self) -> ty::UnevaluatedConst<'tcx> { + assert_eq!(self.promoted, None); + ty::UnevaluatedConst { def: self.def, args: self.args } + } +} + +impl<'tcx> UnevaluatedConst<'tcx> { + #[inline] + pub fn new(def: DefId, args: GenericArgsRef<'tcx>) -> UnevaluatedConst<'tcx> { + UnevaluatedConst { def, args, promoted: Default::default() } + } + + #[inline] + pub fn from_instance(instance: ty::Instance<'tcx>) -> Self { + UnevaluatedConst::new(instance.def_id(), instance.args) + } +} + +impl<'tcx> Debug for Constant<'tcx> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + write!(fmt, "{self}") + } +} + +impl<'tcx> Display for Constant<'tcx> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + match self.ty().kind() { + ty::FnDef(..) => {} + _ => write!(fmt, "const ")?, + } + Display::fmt(&self.literal, fmt) + } +} + +impl<'tcx> Display for ConstantKind<'tcx> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + match *self { + ConstantKind::Ty(c) => pretty_print_const(c, fmt, true), + ConstantKind::Val(val, ty) => pretty_print_const_value(val, ty, fmt), + // FIXME(valtrees): Correctly print mir constants. + ConstantKind::Unevaluated(..) => { + fmt.write_str("_")?; + Ok(()) + } + } + } +} diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs index b80327338687..3a79e1d31b46 100644 --- a/compiler/rustc_middle/src/mir/mod.rs +++ b/compiler/rustc_middle/src/mir/mod.rs @@ -2,9 +2,7 @@ //! //! [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/mir/index.html -use crate::mir::interpret::{ - AllocRange, ConstAllocation, ConstValue, ErrorHandled, GlobalAlloc, Scalar, -}; +use crate::mir::interpret::{AllocRange, ErrorHandled, ConstAllocation, ConstValue, Scalar}; use crate::mir::visit::MirVisitable; use crate::ty::codec::{TyDecoder, TyEncoder}; use crate::ty::fold::{FallibleTypeFolder, TypeFoldable}; @@ -12,13 +10,13 @@ use crate::ty::print::with_no_trimmed_paths; use crate::ty::print::{FmtPrinter, Printer}; use crate::ty::visit::TypeVisitableExt; use crate::ty::{self, List, Ty, TyCtxt}; -use crate::ty::{AdtDef, InstanceDef, ScalarInt, UserTypeAnnotationIndex}; -use crate::ty::{GenericArg, GenericArgs, GenericArgsRef}; +use crate::ty::{AdtDef, InstanceDef, UserTypeAnnotationIndex}; +use crate::ty::{GenericArg, GenericArgsRef}; use rustc_data_structures::captures::Captures; use rustc_errors::{DiagnosticArgValue, DiagnosticMessage, ErrorGuaranteed, IntoDiagnosticArg}; use rustc_hir::def::{CtorKind, Namespace}; -use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID}; +use rustc_hir::def_id::{DefId, CRATE_DEF_ID}; use rustc_hir::{self, GeneratorKind, ImplicitSelfKind}; use rustc_hir::{self as hir, HirId}; use rustc_session::Session; @@ -39,7 +37,7 @@ use either::Either; use std::borrow::Cow; use std::cell::RefCell; use std::collections::hash_map::Entry; -use std::fmt::{self, Debug, Display, Formatter, Write}; +use std::fmt::{self, Debug, Formatter, Write}; use std::ops::{Index, IndexMut}; use std::{iter, mem}; @@ -47,6 +45,8 @@ pub use self::query::*; pub use basic_blocks::BasicBlocks; mod basic_blocks; +mod consts; +pub use consts::*; pub mod coverage; mod generic_graph; pub mod generic_graphviz; @@ -60,7 +60,7 @@ pub mod spanview; mod syntax; pub use syntax::*; pub mod tcx; -pub mod terminator; +mod terminator; pub use terminator::*; pub mod traversal; @@ -2300,377 +2300,6 @@ impl<'tcx> Debug for Rvalue<'tcx> { } } -/////////////////////////////////////////////////////////////////////////// -/// Constants -/// -/// Two constants are equal if they are the same constant. Note that -/// this does not necessarily mean that they are `==` in Rust. In -/// particular, one must be wary of `NaN`! - -#[derive(Clone, Copy, PartialEq, TyEncodable, TyDecodable, Hash, HashStable)] -#[derive(TypeFoldable, TypeVisitable)] -pub struct Constant<'tcx> { - pub span: Span, - - /// Optional user-given type: for something like - /// `collect::>`, this would be present and would - /// indicate that `Vec<_>` was explicitly specified. - /// - /// Needed for NLL to impose user-given type constraints. - pub user_ty: Option, - - pub literal: ConstantKind<'tcx>, -} - -#[derive(Clone, Copy, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable, Debug)] -#[derive(TypeFoldable, TypeVisitable)] -pub enum ConstantKind<'tcx> { - /// This constant came from the type system. - /// - /// Any way of turning `ty::Const` into `ConstValue` should go through `valtree_to_const_val`; - /// this ensures that we consistently produce "clean" values without data in the padding or - /// anything like that. - Ty(ty::Const<'tcx>), - - /// An unevaluated mir constant which is not part of the type system. - Unevaluated(UnevaluatedConst<'tcx>, Ty<'tcx>), - - /// This constant cannot go back into the type system, as it represents - /// something the type system cannot handle (e.g. pointers). - Val(interpret::ConstValue<'tcx>, Ty<'tcx>), -} - -impl<'tcx> Constant<'tcx> { - pub fn check_static_ptr(&self, tcx: TyCtxt<'_>) -> Option { - match self.literal.try_to_scalar() { - Some(Scalar::Ptr(ptr, _size)) => match tcx.global_alloc(ptr.provenance) { - GlobalAlloc::Static(def_id) => { - assert!(!tcx.is_thread_local_static(def_id)); - Some(def_id) - } - _ => None, - }, - _ => None, - } - } - #[inline] - pub fn ty(&self) -> Ty<'tcx> { - self.literal.ty() - } -} - -impl<'tcx> ConstantKind<'tcx> { - #[inline(always)] - pub fn ty(&self) -> Ty<'tcx> { - match self { - ConstantKind::Ty(c) => c.ty(), - ConstantKind::Val(_, ty) | ConstantKind::Unevaluated(_, ty) => *ty, - } - } - - #[inline] - pub fn try_to_scalar(self) -> Option { - match self { - ConstantKind::Ty(c) => match c.kind() { - ty::ConstKind::Value(valtree) => match valtree { - ty::ValTree::Leaf(scalar_int) => Some(Scalar::Int(scalar_int)), - ty::ValTree::Branch(_) => None, - }, - _ => None, - }, - ConstantKind::Val(val, _) => val.try_to_scalar(), - ConstantKind::Unevaluated(..) => None, - } - } - - #[inline] - pub fn try_to_scalar_int(self) -> Option { - self.try_to_scalar()?.try_to_int().ok() - } - - #[inline] - pub fn try_to_bits(self, size: Size) -> Option { - self.try_to_scalar_int()?.to_bits(size).ok() - } - - #[inline] - pub fn try_to_bool(self) -> Option { - self.try_to_scalar_int()?.try_into().ok() - } - - #[inline] - pub fn eval( - self, - tcx: TyCtxt<'tcx>, - param_env: ty::ParamEnv<'tcx>, - span: Option, - ) -> Result, ErrorHandled> { - match self { - ConstantKind::Ty(c) => { - // We want to consistently have a "clean" value for type system constants (i.e., no - // data hidden in the padding), so we always go through a valtree here. - let val = c.eval(tcx, param_env, span)?; - Ok(tcx.valtree_to_const_val((self.ty(), val))) - } - ConstantKind::Unevaluated(uneval, _) => { - // FIXME: We might want to have a `try_eval`-like function on `Unevaluated` - tcx.const_eval_resolve(param_env, uneval, span) - } - ConstantKind::Val(val, _) => Ok(val), - } - } - - /// Normalizes the constant to a value or an error if possible. - #[inline] - pub fn normalize(self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> Self { - match self.eval(tcx, param_env, None) { - Ok(val) => Self::Val(val, self.ty()), - Err(ErrorHandled::Reported(guar, _span)) => { - Self::Ty(ty::Const::new_error(tcx, guar.into(), self.ty())) - } - Err(ErrorHandled::TooGeneric(_span)) => self, - } - } - - #[inline] - pub fn try_eval_scalar( - self, - tcx: TyCtxt<'tcx>, - param_env: ty::ParamEnv<'tcx>, - ) -> Option { - self.eval(tcx, param_env, None).ok()?.try_to_scalar() - } - - #[inline] - pub fn try_eval_scalar_int( - self, - tcx: TyCtxt<'tcx>, - param_env: ty::ParamEnv<'tcx>, - ) -> Option { - self.try_eval_scalar(tcx, param_env)?.try_to_int().ok() - } - - #[inline] - pub fn try_eval_bits( - &self, - tcx: TyCtxt<'tcx>, - param_env: ty::ParamEnv<'tcx>, - ty: Ty<'tcx>, - ) -> Option { - let int = self.try_eval_scalar_int(tcx, param_env)?; - assert_eq!(self.ty(), ty); - let size = tcx.layout_of(param_env.with_reveal_all_normalized(tcx).and(ty)).ok()?.size; - int.to_bits(size).ok() - } - - /// Panics if the value cannot be evaluated or doesn't contain a valid integer of the given type. - #[inline] - pub fn eval_bits(self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>) -> u128 { - self.try_eval_bits(tcx, param_env, ty) - .unwrap_or_else(|| bug!("expected bits of {:#?}, got {:#?}", ty, self)) - } - - #[inline] - pub fn try_eval_target_usize( - self, - tcx: TyCtxt<'tcx>, - param_env: ty::ParamEnv<'tcx>, - ) -> Option { - self.try_eval_scalar_int(tcx, param_env)?.try_to_target_usize(tcx).ok() - } - - #[inline] - /// Panics if the value cannot be evaluated or doesn't contain a valid `usize`. - pub fn eval_target_usize(self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> u64 { - self.try_eval_target_usize(tcx, param_env) - .unwrap_or_else(|| bug!("expected usize, got {:#?}", self)) - } - - #[inline] - pub fn try_eval_bool(self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> Option { - self.try_eval_scalar_int(tcx, param_env)?.try_into().ok() - } - - #[inline] - pub fn from_value(val: ConstValue<'tcx>, ty: Ty<'tcx>) -> Self { - Self::Val(val, ty) - } - - pub fn from_bits( - tcx: TyCtxt<'tcx>, - bits: u128, - param_env_ty: ty::ParamEnvAnd<'tcx, Ty<'tcx>>, - ) -> Self { - let size = tcx - .layout_of(param_env_ty) - .unwrap_or_else(|e| { - bug!("could not compute layout for {:?}: {:?}", param_env_ty.value, e) - }) - .size; - let cv = ConstValue::Scalar(Scalar::from_uint(bits, size)); - - Self::Val(cv, param_env_ty.value) - } - - #[inline] - pub fn from_bool(tcx: TyCtxt<'tcx>, v: bool) -> Self { - let cv = ConstValue::from_bool(v); - Self::Val(cv, tcx.types.bool) - } - - #[inline] - pub fn zero_sized(ty: Ty<'tcx>) -> Self { - let cv = ConstValue::ZeroSized; - Self::Val(cv, ty) - } - - pub fn from_usize(tcx: TyCtxt<'tcx>, n: u64) -> Self { - let ty = tcx.types.usize; - Self::from_bits(tcx, n as u128, ty::ParamEnv::empty().and(ty)) - } - - #[inline] - pub fn from_scalar(_tcx: TyCtxt<'tcx>, s: Scalar, ty: Ty<'tcx>) -> Self { - let val = ConstValue::Scalar(s); - Self::Val(val, ty) - } - - /// Literals are converted to `ConstantKindVal`, const generic parameters are eagerly - /// converted to a constant, everything else becomes `Unevaluated`. - #[instrument(skip(tcx), level = "debug", ret)] - pub fn from_anon_const( - tcx: TyCtxt<'tcx>, - def: LocalDefId, - param_env: ty::ParamEnv<'tcx>, - ) -> Self { - let body_id = match tcx.hir().get_by_def_id(def) { - hir::Node::AnonConst(ac) => ac.body, - _ => { - span_bug!(tcx.def_span(def), "from_anon_const can only process anonymous constants") - } - }; - - let expr = &tcx.hir().body(body_id).value; - debug!(?expr); - - // Unwrap a block, so that e.g. `{ P }` is recognised as a parameter. Const arguments - // currently have to be wrapped in curly brackets, so it's necessary to special-case. - let expr = match &expr.kind { - hir::ExprKind::Block(block, _) if block.stmts.is_empty() && block.expr.is_some() => { - block.expr.as_ref().unwrap() - } - _ => expr, - }; - debug!("expr.kind: {:?}", expr.kind); - - let ty = tcx.type_of(def).instantiate_identity(); - debug!(?ty); - - // FIXME(const_generics): We currently have to special case parameters because `min_const_generics` - // does not provide the parents generics to anonymous constants. We still allow generic const - // parameters by themselves however, e.g. `N`. These constants would cause an ICE if we were to - // ever try to substitute the generic parameters in their bodies. - // - // While this doesn't happen as these constants are always used as `ty::ConstKind::Param`, it does - // cause issues if we were to remove that special-case and try to evaluate the constant instead. - use hir::{def::DefKind::ConstParam, def::Res, ExprKind, Path, QPath}; - match expr.kind { - ExprKind::Path(QPath::Resolved(_, &Path { res: Res::Def(ConstParam, def_id), .. })) => { - // Find the name and index of the const parameter by indexing the generics of - // the parent item and construct a `ParamConst`. - let item_def_id = tcx.parent(def_id); - let generics = tcx.generics_of(item_def_id); - let index = generics.param_def_id_to_index[&def_id]; - let name = tcx.item_name(def_id); - let ty_const = ty::Const::new_param(tcx, ty::ParamConst::new(index, name), ty); - debug!(?ty_const); - - return Self::Ty(ty_const); - } - _ => {} - } - - let hir_id = tcx.hir().local_def_id_to_hir_id(def); - let parent_args = if let Some(parent_hir_id) = tcx.hir().opt_parent_id(hir_id) - && let Some(parent_did) = parent_hir_id.as_owner() - { - GenericArgs::identity_for_item(tcx, parent_did) - } else { - List::empty() - }; - debug!(?parent_args); - - let did = def.to_def_id(); - let child_args = GenericArgs::identity_for_item(tcx, did); - let args = tcx.mk_args_from_iter(parent_args.into_iter().chain(child_args.into_iter())); - debug!(?args); - - let span = tcx.def_span(def); - let uneval = UnevaluatedConst::new(did, args); - debug!(?span, ?param_env); - - match tcx.const_eval_resolve(param_env, uneval, Some(span)) { - Ok(val) => { - debug!("evaluated const value"); - Self::Val(val, ty) - } - Err(_) => { - debug!("error encountered during evaluation"); - // Error was handled in `const_eval_resolve`. Here we just create a - // new unevaluated const and error hard later in codegen - Self::Unevaluated( - UnevaluatedConst { - def: did, - args: GenericArgs::identity_for_item(tcx, did), - promoted: None, - }, - ty, - ) - } - } - } - - pub fn from_ty_const(c: ty::Const<'tcx>, tcx: TyCtxt<'tcx>) -> Self { - match c.kind() { - ty::ConstKind::Value(valtree) => { - // Make sure that if `c` is normalized, then the return value is normalized. - let const_val = tcx.valtree_to_const_val((c.ty(), valtree)); - Self::Val(const_val, c.ty()) - } - _ => Self::Ty(c), - } - } -} - -/// An unevaluated (potentially generic) constant used in MIR. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, TyEncodable, TyDecodable)] -#[derive(Hash, HashStable, TypeFoldable, TypeVisitable)] -pub struct UnevaluatedConst<'tcx> { - pub def: DefId, - pub args: GenericArgsRef<'tcx>, - pub promoted: Option, -} - -impl<'tcx> UnevaluatedConst<'tcx> { - #[inline] - pub fn shrink(self) -> ty::UnevaluatedConst<'tcx> { - assert_eq!(self.promoted, None); - ty::UnevaluatedConst { def: self.def, args: self.args } - } -} - -impl<'tcx> UnevaluatedConst<'tcx> { - #[inline] - pub fn new(def: DefId, args: GenericArgsRef<'tcx>) -> UnevaluatedConst<'tcx> { - UnevaluatedConst { def, args, promoted: Default::default() } - } - - #[inline] - pub fn from_instance(instance: ty::Instance<'tcx>) -> Self { - UnevaluatedConst::new(instance.def_id(), instance.args) - } -} - /// A collection of projections into user types. /// /// They are projections because a binding can occur a part of a @@ -2830,36 +2459,6 @@ rustc_index::newtype_index! { pub struct Promoted {} } -impl<'tcx> Debug for Constant<'tcx> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - write!(fmt, "{self}") - } -} - -impl<'tcx> Display for Constant<'tcx> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - match self.ty().kind() { - ty::FnDef(..) => {} - _ => write!(fmt, "const ")?, - } - Display::fmt(&self.literal, fmt) - } -} - -impl<'tcx> Display for ConstantKind<'tcx> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - match *self { - ConstantKind::Ty(c) => pretty_print_const(c, fmt, true), - ConstantKind::Val(val, ty) => pretty_print_const_value(val, ty, fmt), - // FIXME(valtrees): Correctly print mir constants. - ConstantKind::Unevaluated(..) => { - fmt.write_str("_")?; - Ok(()) - } - } - } -} - fn pretty_print_const<'tcx>( c: ty::Const<'tcx>, fmt: &mut Formatter<'_>, From 10a822be385be021ee717adad58b6290f149d829 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 14 Sep 2023 23:33:42 +0200 Subject: [PATCH 059/111] move some MIR const pretty-printing into pretty.rs --- compiler/rustc_middle/src/mir/consts.rs | 4 +- compiler/rustc_middle/src/mir/mod.rs | 181 +------------------ compiler/rustc_middle/src/mir/pretty.rs | 153 ++++++++++++++++ compiler/rustc_middle/src/ty/print/pretty.rs | 15 ++ 4 files changed, 176 insertions(+), 177 deletions(-) diff --git a/compiler/rustc_middle/src/mir/consts.rs b/compiler/rustc_middle/src/mir/consts.rs index 92c6828a0b40..c80419d4c09b 100644 --- a/compiler/rustc_middle/src/mir/consts.rs +++ b/compiler/rustc_middle/src/mir/consts.rs @@ -7,8 +7,8 @@ use rustc_span::Span; use rustc_target::abi::Size; use crate::mir::interpret::{ConstValue, ErrorHandled, GlobalAlloc, Scalar}; -use crate::mir::{interpret, pretty_print_const, pretty_print_const_value, Promoted}; -use crate::ty::{self, List, Ty, TyCtxt}; +use crate::mir::{interpret, pretty_print_const_value, Promoted}; +use crate::ty::{self, print::pretty_print_const, List, Ty, TyCtxt}; use crate::ty::{GenericArgs, GenericArgsRef}; use crate::ty::{ScalarInt, UserTypeAnnotationIndex}; diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs index 3a79e1d31b46..fb6dbe34a895 100644 --- a/compiler/rustc_middle/src/mir/mod.rs +++ b/compiler/rustc_middle/src/mir/mod.rs @@ -6,7 +6,7 @@ use crate::mir::interpret::{AllocRange, ErrorHandled, ConstAllocation, ConstValu use crate::mir::visit::MirVisitable; use crate::ty::codec::{TyDecoder, TyEncoder}; use crate::ty::fold::{FallibleTypeFolder, TypeFoldable}; -use crate::ty::print::with_no_trimmed_paths; +use crate::ty::print::{pretty_print_const, with_no_trimmed_paths}; use crate::ty::print::{FmtPrinter, Printer}; use crate::ty::visit::TypeVisitableExt; use crate::ty::{self, List, Ty, TyCtxt}; @@ -20,7 +20,7 @@ use rustc_hir::def_id::{DefId, CRATE_DEF_ID}; use rustc_hir::{self, GeneratorKind, ImplicitSelfKind}; use rustc_hir::{self as hir, HirId}; use rustc_session::Session; -use rustc_target::abi::{FieldIdx, Size, VariantIdx}; +use rustc_target::abi::{FieldIdx, VariantIdx}; use polonius_engine::Atom; pub use rustc_ast::Mutability; @@ -46,7 +46,6 @@ pub use basic_blocks::BasicBlocks; mod basic_blocks; mod consts; -pub use consts::*; pub mod coverage; mod generic_graph; pub mod generic_graphviz; @@ -58,10 +57,8 @@ pub mod pretty; mod query; pub mod spanview; mod syntax; -pub use syntax::*; pub mod tcx; mod terminator; -pub use terminator::*; pub mod traversal; mod type_foldable; @@ -72,6 +69,10 @@ pub use self::graphviz::write_mir_graphviz; pub use self::pretty::{ create_dump_file, display_allocation, dump_enabled, dump_mir, write_mir_pretty, PassWhere, }; +pub use consts::*; +pub use pretty::pretty_print_const_value; +pub use syntax::*; +pub use terminator::*; /// Types for locals pub type LocalDecls<'tcx> = IndexSlice>; @@ -2459,176 +2460,6 @@ rustc_index::newtype_index! { pub struct Promoted {} } -fn pretty_print_const<'tcx>( - c: ty::Const<'tcx>, - fmt: &mut Formatter<'_>, - print_types: bool, -) -> fmt::Result { - use crate::ty::print::PrettyPrinter; - ty::tls::with(|tcx| { - let literal = tcx.lift(c).unwrap(); - let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS); - cx.print_alloc_ids = true; - let cx = cx.pretty_print_const(literal, print_types)?; - fmt.write_str(&cx.into_buffer())?; - Ok(()) - }) -} - -fn pretty_print_byte_str(fmt: &mut Formatter<'_>, byte_str: &[u8]) -> fmt::Result { - write!(fmt, "b\"{}\"", byte_str.escape_ascii()) -} - -fn comma_sep<'tcx>( - fmt: &mut Formatter<'_>, - elems: Vec<(ConstValue<'tcx>, Ty<'tcx>)>, -) -> fmt::Result { - let mut first = true; - for (ct, ty) in elems { - if !first { - fmt.write_str(", ")?; - } - pretty_print_const_value(ct, ty, fmt)?; - first = false; - } - Ok(()) -} - -// FIXME: Move that into `mir/pretty.rs`. -fn pretty_print_const_value<'tcx>( - ct: ConstValue<'tcx>, - ty: Ty<'tcx>, - fmt: &mut Formatter<'_>, -) -> fmt::Result { - use crate::ty::print::PrettyPrinter; - - ty::tls::with(|tcx| { - let ct = tcx.lift(ct).unwrap(); - let ty = tcx.lift(ty).unwrap(); - - if tcx.sess.verbose() { - fmt.write_str(&format!("ConstValue({ct:?}: {ty})"))?; - return Ok(()); - } - - let u8_type = tcx.types.u8; - match (ct, ty.kind()) { - // Byte/string slices, printed as (byte) string literals. - (_, ty::Ref(_, inner_ty, _)) if matches!(inner_ty.kind(), ty::Str) => { - if let Some(data) = ct.try_get_slice_bytes_for_diagnostics(tcx) { - fmt.write_str(&format!("{:?}", String::from_utf8_lossy(data)))?; - return Ok(()); - } - } - (_, ty::Ref(_, inner_ty, _)) if matches!(inner_ty.kind(), ty::Slice(t) if *t == u8_type) => { - if let Some(data) = ct.try_get_slice_bytes_for_diagnostics(tcx) { - pretty_print_byte_str(fmt, data)?; - return Ok(()); - } - } - (ConstValue::Indirect { alloc_id, offset }, ty::Array(t, n)) if *t == u8_type => { - let n = n.try_to_target_usize(tcx).unwrap(); - let alloc = tcx.global_alloc(alloc_id).unwrap_memory(); - // cast is ok because we already checked for pointer size (32 or 64 bit) above - let range = AllocRange { start: offset, size: Size::from_bytes(n) }; - let byte_str = alloc.inner().get_bytes_strip_provenance(&tcx, range).unwrap(); - fmt.write_str("*")?; - pretty_print_byte_str(fmt, byte_str)?; - return Ok(()); - } - // Aggregates, printed as array/tuple/struct/variant construction syntax. - // - // NB: the `has_non_region_param` check ensures that we can use - // the `destructure_const` query with an empty `ty::ParamEnv` without - // introducing ICEs (e.g. via `layout_of`) from missing bounds. - // E.g. `transmute([0usize; 2]): (u8, *mut T)` needs to know `T: Sized` - // to be able to destructure the tuple into `(0u8, *mut T)` - (_, ty::Array(..) | ty::Tuple(..) | ty::Adt(..)) if !ty.has_non_region_param() => { - let ct = tcx.lift(ct).unwrap(); - let ty = tcx.lift(ty).unwrap(); - if let Some(contents) = tcx.try_destructure_mir_constant_for_diagnostics((ct, ty)) { - let fields: Vec<(ConstValue<'_>, Ty<'_>)> = contents.fields.to_vec(); - match *ty.kind() { - ty::Array(..) => { - fmt.write_str("[")?; - comma_sep(fmt, fields)?; - fmt.write_str("]")?; - } - ty::Tuple(..) => { - fmt.write_str("(")?; - comma_sep(fmt, fields)?; - if contents.fields.len() == 1 { - fmt.write_str(",")?; - } - fmt.write_str(")")?; - } - ty::Adt(def, _) if def.variants().is_empty() => { - fmt.write_str(&format!("{{unreachable(): {ty}}}"))?; - } - ty::Adt(def, args) => { - let variant_idx = contents - .variant - .expect("destructed mir constant of adt without variant idx"); - let variant_def = &def.variant(variant_idx); - let args = tcx.lift(args).unwrap(); - let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS); - cx.print_alloc_ids = true; - let cx = cx.print_value_path(variant_def.def_id, args)?; - fmt.write_str(&cx.into_buffer())?; - - match variant_def.ctor_kind() { - Some(CtorKind::Const) => {} - Some(CtorKind::Fn) => { - fmt.write_str("(")?; - comma_sep(fmt, fields)?; - fmt.write_str(")")?; - } - None => { - fmt.write_str(" {{ ")?; - let mut first = true; - for (field_def, (ct, ty)) in - iter::zip(&variant_def.fields, fields) - { - if !first { - fmt.write_str(", ")?; - } - write!(fmt, "{}: ", field_def.name)?; - pretty_print_const_value(ct, ty, fmt)?; - first = false; - } - fmt.write_str(" }}")?; - } - } - } - _ => unreachable!(), - } - return Ok(()); - } - } - (ConstValue::Scalar(scalar), _) => { - let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS); - cx.print_alloc_ids = true; - let ty = tcx.lift(ty).unwrap(); - cx = cx.pretty_print_const_scalar(scalar, ty)?; - fmt.write_str(&cx.into_buffer())?; - return Ok(()); - } - (ConstValue::ZeroSized, ty::FnDef(d, s)) => { - let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS); - cx.print_alloc_ids = true; - let cx = cx.print_value_path(*d, s)?; - fmt.write_str(&cx.into_buffer())?; - return Ok(()); - } - // FIXME(oli-obk): also pretty print arrays and other aggregate constants by reading - // their fields instead of just dumping the memory. - _ => {} - } - // Fall back to debug pretty printing for invalid constants. - write!(fmt, "{ct:?}: {ty}") - }) -} - /// `Location` represents the position of the start of the statement; or, if /// `statement_index` equals the number of statements, then the start of the /// terminator. diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index c4fae27a7a53..56b59fe52046 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -1085,6 +1085,159 @@ pub fn dump_mir_def_ids(tcx: TyCtxt<'_>, single: Option) -> Vec { } } +fn pretty_print_byte_str(fmt: &mut Formatter<'_>, byte_str: &[u8]) -> fmt::Result { + write!(fmt, "b\"{}\"", byte_str.escape_ascii()) +} + +fn comma_sep<'tcx>( + fmt: &mut Formatter<'_>, + elems: Vec<(ConstValue<'tcx>, Ty<'tcx>)>, +) -> fmt::Result { + let mut first = true; + for (ct, ty) in elems { + if !first { + fmt.write_str(", ")?; + } + pretty_print_const_value(ct, ty, fmt)?; + first = false; + } + Ok(()) +} + +pub fn pretty_print_const_value<'tcx>( + ct: ConstValue<'tcx>, + ty: Ty<'tcx>, + fmt: &mut Formatter<'_>, +) -> fmt::Result { + use crate::ty::print::PrettyPrinter; + + ty::tls::with(|tcx| { + let ct = tcx.lift(ct).unwrap(); + let ty = tcx.lift(ty).unwrap(); + + if tcx.sess.verbose() { + fmt.write_str(&format!("ConstValue({ct:?}: {ty})"))?; + return Ok(()); + } + + let u8_type = tcx.types.u8; + match (ct, ty.kind()) { + // Byte/string slices, printed as (byte) string literals. + (_, ty::Ref(_, inner_ty, _)) if matches!(inner_ty.kind(), ty::Str) => { + if let Some(data) = ct.try_get_slice_bytes_for_diagnostics(tcx) { + fmt.write_str(&format!("{:?}", String::from_utf8_lossy(data)))?; + return Ok(()); + } + } + (_, ty::Ref(_, inner_ty, _)) if matches!(inner_ty.kind(), ty::Slice(t) if *t == u8_type) => { + if let Some(data) = ct.try_get_slice_bytes_for_diagnostics(tcx) { + pretty_print_byte_str(fmt, data)?; + return Ok(()); + } + } + (ConstValue::Indirect { alloc_id, offset }, ty::Array(t, n)) if *t == u8_type => { + let n = n.try_to_target_usize(tcx).unwrap(); + let alloc = tcx.global_alloc(alloc_id).unwrap_memory(); + // cast is ok because we already checked for pointer size (32 or 64 bit) above + let range = AllocRange { start: offset, size: Size::from_bytes(n) }; + let byte_str = alloc.inner().get_bytes_strip_provenance(&tcx, range).unwrap(); + fmt.write_str("*")?; + pretty_print_byte_str(fmt, byte_str)?; + return Ok(()); + } + // Aggregates, printed as array/tuple/struct/variant construction syntax. + // + // NB: the `has_non_region_param` check ensures that we can use + // the `destructure_const` query with an empty `ty::ParamEnv` without + // introducing ICEs (e.g. via `layout_of`) from missing bounds. + // E.g. `transmute([0usize; 2]): (u8, *mut T)` needs to know `T: Sized` + // to be able to destructure the tuple into `(0u8, *mut T)` + (_, ty::Array(..) | ty::Tuple(..) | ty::Adt(..)) if !ty.has_non_region_param() => { + let ct = tcx.lift(ct).unwrap(); + let ty = tcx.lift(ty).unwrap(); + if let Some(contents) = tcx.try_destructure_mir_constant_for_diagnostics((ct, ty)) { + let fields: Vec<(ConstValue<'_>, Ty<'_>)> = contents.fields.to_vec(); + match *ty.kind() { + ty::Array(..) => { + fmt.write_str("[")?; + comma_sep(fmt, fields)?; + fmt.write_str("]")?; + } + ty::Tuple(..) => { + fmt.write_str("(")?; + comma_sep(fmt, fields)?; + if contents.fields.len() == 1 { + fmt.write_str(",")?; + } + fmt.write_str(")")?; + } + ty::Adt(def, _) if def.variants().is_empty() => { + fmt.write_str(&format!("{{unreachable(): {ty}}}"))?; + } + ty::Adt(def, args) => { + let variant_idx = contents + .variant + .expect("destructed mir constant of adt without variant idx"); + let variant_def = &def.variant(variant_idx); + let args = tcx.lift(args).unwrap(); + let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS); + cx.print_alloc_ids = true; + let cx = cx.print_value_path(variant_def.def_id, args)?; + fmt.write_str(&cx.into_buffer())?; + + match variant_def.ctor_kind() { + Some(CtorKind::Const) => {} + Some(CtorKind::Fn) => { + fmt.write_str("(")?; + comma_sep(fmt, fields)?; + fmt.write_str(")")?; + } + None => { + fmt.write_str(" {{ ")?; + let mut first = true; + for (field_def, (ct, ty)) in + iter::zip(&variant_def.fields, fields) + { + if !first { + fmt.write_str(", ")?; + } + write!(fmt, "{}: ", field_def.name)?; + pretty_print_const_value(ct, ty, fmt)?; + first = false; + } + fmt.write_str(" }}")?; + } + } + } + _ => unreachable!(), + } + return Ok(()); + } + } + (ConstValue::Scalar(scalar), _) => { + let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS); + cx.print_alloc_ids = true; + let ty = tcx.lift(ty).unwrap(); + cx = cx.pretty_print_const_scalar(scalar, ty)?; + fmt.write_str(&cx.into_buffer())?; + return Ok(()); + } + (ConstValue::ZeroSized, ty::FnDef(d, s)) => { + let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS); + cx.print_alloc_ids = true; + let cx = cx.print_value_path(*d, s)?; + fmt.write_str(&cx.into_buffer())?; + return Ok(()); + } + // FIXME(oli-obk): also pretty print arrays and other aggregate constants by reading + // their fields instead of just dumping the memory. + _ => {} + } + // Fall back to debug pretty printing for invalid constants. + write!(fmt, "{ct:?}: {ty}") + }) +} + /// Calc converted u64 decimal into hex and return it's length in chars /// /// ```ignore (cannot-test-private-function) diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs index e1d4e43841db..d958e25e6298 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -1713,6 +1713,21 @@ pub trait PrettyPrinter<'tcx>: } } +pub fn pretty_print_const<'tcx>( + c: ty::Const<'tcx>, + fmt: &mut fmt::Formatter<'_>, + print_types: bool, +) -> fmt::Result { + ty::tls::with(|tcx| { + let literal = tcx.lift(c).unwrap(); + let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS); + cx.print_alloc_ids = true; + let cx = cx.pretty_print_const(literal, print_types)?; + fmt.write_str(&cx.into_buffer())?; + Ok(()) + }) +} + // HACK(eddyb) boxed to avoid moving around a large struct by-value. pub struct FmtPrinter<'a, 'tcx>(Box>); From 57444cf9f31b304d9759b63112f0957315592922 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 15 Sep 2023 08:21:40 +0200 Subject: [PATCH 060/111] use pretty_print_const_value from MIR constant 'extra' printing --- compiler/rustc_data_structures/src/lib.rs | 19 ++ compiler/rustc_middle/src/mir/mod.rs | 2 +- compiler/rustc_middle/src/mir/pretty.rs | 282 +++++++++--------- compiler/rustc_middle/src/ty/print/pretty.rs | 2 +- ...ans.outer.PreCodegen.after.panic-abort.mir | 2 +- ...ns.outer.PreCodegen.after.panic-unwind.mir | 2 +- 6 files changed, 167 insertions(+), 142 deletions(-) diff --git a/compiler/rustc_data_structures/src/lib.rs b/compiler/rustc_data_structures/src/lib.rs index bee5a89c4c73..7d037ddfa986 100644 --- a/compiler/rustc_data_structures/src/lib.rs +++ b/compiler/rustc_data_structures/src/lib.rs @@ -47,6 +47,8 @@ extern crate cfg_if; #[macro_use] extern crate rustc_macros; +use std::fmt; + pub use rustc_index::static_assert_size; #[inline(never)] @@ -126,6 +128,23 @@ impl Drop for OnDrop { } } +/// Turns a closure that takes an `&mut Formatter` into something that can be display-formatted. +pub fn make_display(f: impl Fn(&mut fmt::Formatter<'_>) -> fmt::Result) -> impl fmt::Display { + struct Printer { + f: F, + } + impl fmt::Display for Printer + where + F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result, + { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + (self.f)(fmt) + } + } + + Printer { f } +} + // See comments in src/librustc_middle/lib.rs #[doc(hidden)] pub fn __noop_fix_for_27438() {} diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs index fb6dbe34a895..7707c4247036 100644 --- a/compiler/rustc_middle/src/mir/mod.rs +++ b/compiler/rustc_middle/src/mir/mod.rs @@ -70,7 +70,7 @@ pub use self::pretty::{ create_dump_file, display_allocation, dump_enabled, dump_mir, write_mir_pretty, PassWhere, }; pub use consts::*; -pub use pretty::pretty_print_const_value; +use pretty::pretty_print_const_value; pub use syntax::*; pub use terminator::*; diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index 56b59fe52046..39af95d62f86 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -455,26 +455,27 @@ impl<'tcx> Visitor<'tcx> for ExtraComments<'tcx> { self.push(&format!("+ user_ty: {user_ty:?}")); } - // FIXME: this is a poor version of `pretty_print_const_value`. - let fmt_val = |val: &ConstValue<'tcx>| match val { - ConstValue::ZeroSized => "".to_string(), - ConstValue::Scalar(s) => format!("Scalar({s:?})"), - ConstValue::Slice { .. } => "Slice(..)".to_string(), - ConstValue::Indirect { .. } => "ByRef(..)".to_string(), + let fmt_val = |val: ConstValue<'tcx>, ty: Ty<'tcx>| { + let tcx = self.tcx; + rustc_data_structures::make_display(move |fmt| { + pretty_print_const_value_tcx(tcx, val, ty, fmt) + }) }; + // FIXME: call pretty_print_const_valtree? let fmt_valtree = |valtree: &ty::ValTree<'tcx>| match valtree { - ty::ValTree::Leaf(leaf) => format!("ValTree::Leaf({leaf:?})"), - ty::ValTree::Branch(_) => "ValTree::Branch(..)".to_string(), + ty::ValTree::Leaf(leaf) => format!("Leaf({leaf:?})"), + ty::ValTree::Branch(_) => "Branch(..)".to_string(), }; let val = match literal { ConstantKind::Ty(ct) => match ct.kind() { - ty::ConstKind::Param(p) => format!("Param({p})"), + ty::ConstKind::Param(p) => format!("ty::Param({p})"), ty::ConstKind::Unevaluated(uv) => { - format!("Unevaluated({}, {:?})", self.tcx.def_path_str(uv.def), uv.args,) + format!("ty::Unevaluated({}, {:?})", self.tcx.def_path_str(uv.def), uv.args,) } - ty::ConstKind::Value(val) => format!("Value({})", fmt_valtree(&val)), + ty::ConstKind::Value(val) => format!("ty::Valtree({})", fmt_valtree(&val)), + // No `ty::` prefix since we also use this to represent errors from `mir::Unevaluated`. ty::ConstKind::Error(_) => "Error".to_string(), // These variants shouldn't exist in the MIR. ty::ConstKind::Placeholder(_) @@ -490,10 +491,7 @@ impl<'tcx> Visitor<'tcx> for ExtraComments<'tcx> { uv.promoted, ) } - // To keep the diffs small, we render this like we render `ty::Const::Value`. - // - // This changes once `ty::Const::Value` is represented using valtrees. - ConstantKind::Val(val, _) => format!("Value({})", fmt_val(&val)), + ConstantKind::Val(val, ty) => format!("Value({})", fmt_val(*val, *ty)), }; // This reflects what `Const` looked liked before `val` was renamed @@ -1090,6 +1088,7 @@ fn pretty_print_byte_str(fmt: &mut Formatter<'_>, byte_str: &[u8]) -> fmt::Resul } fn comma_sep<'tcx>( + tcx: TyCtxt<'tcx>, fmt: &mut Formatter<'_>, elems: Vec<(ConstValue<'tcx>, Ty<'tcx>)>, ) -> fmt::Result { @@ -1098,143 +1097,150 @@ fn comma_sep<'tcx>( if !first { fmt.write_str(", ")?; } - pretty_print_const_value(ct, ty, fmt)?; + pretty_print_const_value_tcx(tcx, ct, ty, fmt)?; first = false; } Ok(()) } -pub fn pretty_print_const_value<'tcx>( +fn pretty_print_const_value_tcx<'tcx>( + tcx: TyCtxt<'tcx>, ct: ConstValue<'tcx>, ty: Ty<'tcx>, fmt: &mut Formatter<'_>, ) -> fmt::Result { use crate::ty::print::PrettyPrinter; + if tcx.sess.verbose() { + fmt.write_str(&format!("ConstValue({ct:?}: {ty})"))?; + return Ok(()); + } + + let u8_type = tcx.types.u8; + match (ct, ty.kind()) { + // Byte/string slices, printed as (byte) string literals. + (_, ty::Ref(_, inner_ty, _)) if matches!(inner_ty.kind(), ty::Str) => { + if let Some(data) = ct.try_get_slice_bytes_for_diagnostics(tcx) { + fmt.write_str(&format!("{:?}", String::from_utf8_lossy(data)))?; + return Ok(()); + } + } + (_, ty::Ref(_, inner_ty, _)) if matches!(inner_ty.kind(), ty::Slice(t) if *t == u8_type) => { + if let Some(data) = ct.try_get_slice_bytes_for_diagnostics(tcx) { + pretty_print_byte_str(fmt, data)?; + return Ok(()); + } + } + (ConstValue::Indirect { alloc_id, offset }, ty::Array(t, n)) if *t == u8_type => { + let n = n.try_to_target_usize(tcx).unwrap(); + let alloc = tcx.global_alloc(alloc_id).unwrap_memory(); + // cast is ok because we already checked for pointer size (32 or 64 bit) above + let range = AllocRange { start: offset, size: Size::from_bytes(n) }; + let byte_str = alloc.inner().get_bytes_strip_provenance(&tcx, range).unwrap(); + fmt.write_str("*")?; + pretty_print_byte_str(fmt, byte_str)?; + return Ok(()); + } + // Aggregates, printed as array/tuple/struct/variant construction syntax. + // + // NB: the `has_non_region_param` check ensures that we can use + // the `destructure_const` query with an empty `ty::ParamEnv` without + // introducing ICEs (e.g. via `layout_of`) from missing bounds. + // E.g. `transmute([0usize; 2]): (u8, *mut T)` needs to know `T: Sized` + // to be able to destructure the tuple into `(0u8, *mut T)` + (_, ty::Array(..) | ty::Tuple(..) | ty::Adt(..)) if !ty.has_non_region_param() => { + let ct = tcx.lift(ct).unwrap(); + let ty = tcx.lift(ty).unwrap(); + if let Some(contents) = tcx.try_destructure_mir_constant_for_diagnostics((ct, ty)) { + let fields: Vec<(ConstValue<'_>, Ty<'_>)> = contents.fields.to_vec(); + match *ty.kind() { + ty::Array(..) => { + fmt.write_str("[")?; + comma_sep(tcx, fmt, fields)?; + fmt.write_str("]")?; + } + ty::Tuple(..) => { + fmt.write_str("(")?; + comma_sep(tcx, fmt, fields)?; + if contents.fields.len() == 1 { + fmt.write_str(",")?; + } + fmt.write_str(")")?; + } + ty::Adt(def, _) if def.variants().is_empty() => { + fmt.write_str(&format!("{{unreachable(): {ty}}}"))?; + } + ty::Adt(def, args) => { + let variant_idx = contents + .variant + .expect("destructed mir constant of adt without variant idx"); + let variant_def = &def.variant(variant_idx); + let args = tcx.lift(args).unwrap(); + let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS); + cx.print_alloc_ids = true; + let cx = cx.print_value_path(variant_def.def_id, args)?; + fmt.write_str(&cx.into_buffer())?; + + match variant_def.ctor_kind() { + Some(CtorKind::Const) => {} + Some(CtorKind::Fn) => { + fmt.write_str("(")?; + comma_sep(tcx, fmt, fields)?; + fmt.write_str(")")?; + } + None => { + fmt.write_str(" {{ ")?; + let mut first = true; + for (field_def, (ct, ty)) in iter::zip(&variant_def.fields, fields) + { + if !first { + fmt.write_str(", ")?; + } + write!(fmt, "{}: ", field_def.name)?; + pretty_print_const_value_tcx(tcx, ct, ty, fmt)?; + first = false; + } + fmt.write_str(" }}")?; + } + } + } + _ => unreachable!(), + } + return Ok(()); + } + } + (ConstValue::Scalar(scalar), _) => { + let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS); + cx.print_alloc_ids = true; + let ty = tcx.lift(ty).unwrap(); + cx = cx.pretty_print_const_scalar(scalar, ty)?; + fmt.write_str(&cx.into_buffer())?; + return Ok(()); + } + (ConstValue::ZeroSized, ty::FnDef(d, s)) => { + let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS); + cx.print_alloc_ids = true; + let cx = cx.print_value_path(*d, s)?; + fmt.write_str(&cx.into_buffer())?; + return Ok(()); + } + // FIXME(oli-obk): also pretty print arrays and other aggregate constants by reading + // their fields instead of just dumping the memory. + _ => {} + } + // Fall back to debug pretty printing for invalid constants. + write!(fmt, "{ct:?}: {ty}") +} + +pub(crate) fn pretty_print_const_value<'tcx>( + ct: ConstValue<'tcx>, + ty: Ty<'tcx>, + fmt: &mut Formatter<'_>, +) -> fmt::Result { ty::tls::with(|tcx| { let ct = tcx.lift(ct).unwrap(); let ty = tcx.lift(ty).unwrap(); - - if tcx.sess.verbose() { - fmt.write_str(&format!("ConstValue({ct:?}: {ty})"))?; - return Ok(()); - } - - let u8_type = tcx.types.u8; - match (ct, ty.kind()) { - // Byte/string slices, printed as (byte) string literals. - (_, ty::Ref(_, inner_ty, _)) if matches!(inner_ty.kind(), ty::Str) => { - if let Some(data) = ct.try_get_slice_bytes_for_diagnostics(tcx) { - fmt.write_str(&format!("{:?}", String::from_utf8_lossy(data)))?; - return Ok(()); - } - } - (_, ty::Ref(_, inner_ty, _)) if matches!(inner_ty.kind(), ty::Slice(t) if *t == u8_type) => { - if let Some(data) = ct.try_get_slice_bytes_for_diagnostics(tcx) { - pretty_print_byte_str(fmt, data)?; - return Ok(()); - } - } - (ConstValue::Indirect { alloc_id, offset }, ty::Array(t, n)) if *t == u8_type => { - let n = n.try_to_target_usize(tcx).unwrap(); - let alloc = tcx.global_alloc(alloc_id).unwrap_memory(); - // cast is ok because we already checked for pointer size (32 or 64 bit) above - let range = AllocRange { start: offset, size: Size::from_bytes(n) }; - let byte_str = alloc.inner().get_bytes_strip_provenance(&tcx, range).unwrap(); - fmt.write_str("*")?; - pretty_print_byte_str(fmt, byte_str)?; - return Ok(()); - } - // Aggregates, printed as array/tuple/struct/variant construction syntax. - // - // NB: the `has_non_region_param` check ensures that we can use - // the `destructure_const` query with an empty `ty::ParamEnv` without - // introducing ICEs (e.g. via `layout_of`) from missing bounds. - // E.g. `transmute([0usize; 2]): (u8, *mut T)` needs to know `T: Sized` - // to be able to destructure the tuple into `(0u8, *mut T)` - (_, ty::Array(..) | ty::Tuple(..) | ty::Adt(..)) if !ty.has_non_region_param() => { - let ct = tcx.lift(ct).unwrap(); - let ty = tcx.lift(ty).unwrap(); - if let Some(contents) = tcx.try_destructure_mir_constant_for_diagnostics((ct, ty)) { - let fields: Vec<(ConstValue<'_>, Ty<'_>)> = contents.fields.to_vec(); - match *ty.kind() { - ty::Array(..) => { - fmt.write_str("[")?; - comma_sep(fmt, fields)?; - fmt.write_str("]")?; - } - ty::Tuple(..) => { - fmt.write_str("(")?; - comma_sep(fmt, fields)?; - if contents.fields.len() == 1 { - fmt.write_str(",")?; - } - fmt.write_str(")")?; - } - ty::Adt(def, _) if def.variants().is_empty() => { - fmt.write_str(&format!("{{unreachable(): {ty}}}"))?; - } - ty::Adt(def, args) => { - let variant_idx = contents - .variant - .expect("destructed mir constant of adt without variant idx"); - let variant_def = &def.variant(variant_idx); - let args = tcx.lift(args).unwrap(); - let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS); - cx.print_alloc_ids = true; - let cx = cx.print_value_path(variant_def.def_id, args)?; - fmt.write_str(&cx.into_buffer())?; - - match variant_def.ctor_kind() { - Some(CtorKind::Const) => {} - Some(CtorKind::Fn) => { - fmt.write_str("(")?; - comma_sep(fmt, fields)?; - fmt.write_str(")")?; - } - None => { - fmt.write_str(" {{ ")?; - let mut first = true; - for (field_def, (ct, ty)) in - iter::zip(&variant_def.fields, fields) - { - if !first { - fmt.write_str(", ")?; - } - write!(fmt, "{}: ", field_def.name)?; - pretty_print_const_value(ct, ty, fmt)?; - first = false; - } - fmt.write_str(" }}")?; - } - } - } - _ => unreachable!(), - } - return Ok(()); - } - } - (ConstValue::Scalar(scalar), _) => { - let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS); - cx.print_alloc_ids = true; - let ty = tcx.lift(ty).unwrap(); - cx = cx.pretty_print_const_scalar(scalar, ty)?; - fmt.write_str(&cx.into_buffer())?; - return Ok(()); - } - (ConstValue::ZeroSized, ty::FnDef(d, s)) => { - let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS); - cx.print_alloc_ids = true; - let cx = cx.print_value_path(*d, s)?; - fmt.write_str(&cx.into_buffer())?; - return Ok(()); - } - // FIXME(oli-obk): also pretty print arrays and other aggregate constants by reading - // their fields instead of just dumping the memory. - _ => {} - } - // Fall back to debug pretty printing for invalid constants. - write!(fmt, "{ct:?}: {ty}") + pretty_print_const_value_tcx(tcx, ct, ty, fmt) }) } diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs index d958e25e6298..0ee63e92848f 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -1713,7 +1713,7 @@ pub trait PrettyPrinter<'tcx>: } } -pub fn pretty_print_const<'tcx>( +pub(crate) fn pretty_print_const<'tcx>( c: ty::Const<'tcx>, fmt: &mut fmt::Formatter<'_>, print_types: bool, diff --git a/tests/mir-opt/pre-codegen/spans.outer.PreCodegen.after.panic-abort.mir b/tests/mir-opt/pre-codegen/spans.outer.PreCodegen.after.panic-abort.mir index 1d3405a9afef..18a663d9f9e9 100644 --- a/tests/mir-opt/pre-codegen/spans.outer.PreCodegen.after.panic-abort.mir +++ b/tests/mir-opt/pre-codegen/spans.outer.PreCodegen.after.panic-abort.mir @@ -11,7 +11,7 @@ fn outer(_1: u8) -> u8 { _0 = inner(move _2) -> [return: bb1, unwind unreachable]; // scope 0 at $DIR/spans.rs:10:5: 10:14 // mir::Constant // + span: $DIR/spans.rs:10:5: 10:10 - // + literal: Const { ty: for<'a> fn(&'a u8) -> u8 {inner}, val: Value() } + // + literal: Const { ty: for<'a> fn(&'a u8) -> u8 {inner}, val: Value(inner) } } bb1: { diff --git a/tests/mir-opt/pre-codegen/spans.outer.PreCodegen.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/spans.outer.PreCodegen.after.panic-unwind.mir index 285b6e413146..1c02fb72bcd6 100644 --- a/tests/mir-opt/pre-codegen/spans.outer.PreCodegen.after.panic-unwind.mir +++ b/tests/mir-opt/pre-codegen/spans.outer.PreCodegen.after.panic-unwind.mir @@ -11,7 +11,7 @@ fn outer(_1: u8) -> u8 { _0 = inner(move _2) -> [return: bb1, unwind continue]; // scope 0 at $DIR/spans.rs:10:5: 10:14 // mir::Constant // + span: $DIR/spans.rs:10:5: 10:10 - // + literal: Const { ty: for<'a> fn(&'a u8) -> u8 {inner}, val: Value() } + // + literal: Const { ty: for<'a> fn(&'a u8) -> u8 {inner}, val: Value(inner) } } bb1: { From be8f5f6e7fba05d9761b1cb8dc2bcd0901942312 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 15 Sep 2023 08:49:37 +0200 Subject: [PATCH 061/111] organize mir pretty.rs and move more things into it; move statement-related things out of mir/mod.rs --- compiler/rustc_middle/src/mir/mod.rs | 923 +---------------- compiler/rustc_middle/src/mir/pretty.rs | 1008 ++++++++++++++----- compiler/rustc_middle/src/mir/statement.rs | 441 ++++++++ compiler/rustc_middle/src/mir/syntax.rs | 24 +- compiler/rustc_middle/src/mir/terminator.rs | 368 +++---- 5 files changed, 1393 insertions(+), 1371 deletions(-) create mode 100644 compiler/rustc_middle/src/mir/statement.rs diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs index 7707c4247036..cf134a0e5e7b 100644 --- a/compiler/rustc_middle/src/mir/mod.rs +++ b/compiler/rustc_middle/src/mir/mod.rs @@ -37,7 +37,7 @@ use either::Either; use std::borrow::Cow; use std::cell::RefCell; use std::collections::hash_map::Entry; -use std::fmt::{self, Debug, Formatter, Write}; +use std::fmt::{self, Debug, Formatter}; use std::ops::{Index, IndexMut}; use std::{iter, mem}; @@ -56,6 +56,7 @@ pub mod patch; pub mod pretty; mod query; pub mod spanview; +mod statement; mod syntax; pub mod tcx; mod terminator; @@ -71,6 +72,7 @@ pub use self::pretty::{ }; pub use consts::*; use pretty::pretty_print_const_value; +pub use statement::*; pub use syntax::*; pub use terminator::*; @@ -1150,20 +1152,6 @@ pub struct VarDebugInfo<'tcx> { pub argument_index: Option, } -impl Debug for VarDebugInfo<'_> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - if let Some(box VarDebugInfoFragment { ty, ref projection }) = self.composite { - pre_fmt_projection(&projection[..], fmt)?; - write!(fmt, "({}: {})", self.name, ty)?; - post_fmt_projection(&projection[..], fmt)?; - } else { - write!(fmt, "{}", self.name)?; - } - - write!(fmt, " => {:?}", self.value) - } -} - /////////////////////////////////////////////////////////////////////////// // BasicBlock @@ -1320,576 +1308,6 @@ impl<'tcx> BasicBlockData<'tcx> { } } -impl AssertKind { - /// Returns true if this an overflow checking assertion controlled by -C overflow-checks. - pub fn is_optional_overflow_check(&self) -> bool { - use AssertKind::*; - use BinOp::*; - matches!(self, OverflowNeg(..) | Overflow(Add | Sub | Mul | Shl | Shr, ..)) - } - - /// Get the message that is printed at runtime when this assertion fails. - /// - /// The caller is expected to handle `BoundsCheck` and `MisalignedPointerDereference` by - /// invoking the appropriate lang item (panic_bounds_check/panic_misaligned_pointer_dereference) - /// instead of printing a static message. - pub fn description(&self) -> &'static str { - use AssertKind::*; - match self { - Overflow(BinOp::Add, _, _) => "attempt to add with overflow", - Overflow(BinOp::Sub, _, _) => "attempt to subtract with overflow", - Overflow(BinOp::Mul, _, _) => "attempt to multiply with overflow", - Overflow(BinOp::Div, _, _) => "attempt to divide with overflow", - Overflow(BinOp::Rem, _, _) => "attempt to calculate the remainder with overflow", - OverflowNeg(_) => "attempt to negate with overflow", - Overflow(BinOp::Shr, _, _) => "attempt to shift right with overflow", - Overflow(BinOp::Shl, _, _) => "attempt to shift left with overflow", - Overflow(op, _, _) => bug!("{:?} cannot overflow", op), - DivisionByZero(_) => "attempt to divide by zero", - RemainderByZero(_) => "attempt to calculate the remainder with a divisor of zero", - ResumedAfterReturn(GeneratorKind::Gen) => "generator resumed after completion", - ResumedAfterReturn(GeneratorKind::Async(_)) => "`async fn` resumed after completion", - ResumedAfterPanic(GeneratorKind::Gen) => "generator resumed after panicking", - ResumedAfterPanic(GeneratorKind::Async(_)) => "`async fn` resumed after panicking", - BoundsCheck { .. } | MisalignedPointerDereference { .. } => { - bug!("Unexpected AssertKind") - } - } - } - - /// Format the message arguments for the `assert(cond, msg..)` terminator in MIR printing. - /// - /// Needs to be kept in sync with the run-time behavior (which is defined by - /// `AssertKind::description` and the lang items mentioned in its docs). - /// Note that we deliberately show more details here than we do at runtime, such as the actual - /// numbers that overflowed -- it is much easier to do so here than at runtime. - pub fn fmt_assert_args(&self, f: &mut W) -> fmt::Result - where - O: Debug, - { - use AssertKind::*; - match self { - BoundsCheck { ref len, ref index } => write!( - f, - "\"index out of bounds: the length is {{}} but the index is {{}}\", {len:?}, {index:?}" - ), - - OverflowNeg(op) => { - write!(f, "\"attempt to negate `{{}}`, which would overflow\", {op:?}") - } - DivisionByZero(op) => write!(f, "\"attempt to divide `{{}}` by zero\", {op:?}"), - RemainderByZero(op) => write!( - f, - "\"attempt to calculate the remainder of `{{}}` with a divisor of zero\", {op:?}" - ), - Overflow(BinOp::Add, l, r) => write!( - f, - "\"attempt to compute `{{}} + {{}}`, which would overflow\", {l:?}, {r:?}" - ), - Overflow(BinOp::Sub, l, r) => write!( - f, - "\"attempt to compute `{{}} - {{}}`, which would overflow\", {l:?}, {r:?}" - ), - Overflow(BinOp::Mul, l, r) => write!( - f, - "\"attempt to compute `{{}} * {{}}`, which would overflow\", {l:?}, {r:?}" - ), - Overflow(BinOp::Div, l, r) => write!( - f, - "\"attempt to compute `{{}} / {{}}`, which would overflow\", {l:?}, {r:?}" - ), - Overflow(BinOp::Rem, l, r) => write!( - f, - "\"attempt to compute the remainder of `{{}} % {{}}`, which would overflow\", {l:?}, {r:?}" - ), - Overflow(BinOp::Shr, _, r) => { - write!(f, "\"attempt to shift right by `{{}}`, which would overflow\", {r:?}") - } - Overflow(BinOp::Shl, _, r) => { - write!(f, "\"attempt to shift left by `{{}}`, which would overflow\", {r:?}") - } - MisalignedPointerDereference { required, found } => { - write!( - f, - "\"misaligned pointer dereference: address must be a multiple of {{}} but is {{}}\", {required:?}, {found:?}" - ) - } - _ => write!(f, "\"{}\"", self.description()), - } - } - - /// Format the diagnostic message for use in a lint (e.g. when the assertion fails during const-eval). - /// - /// Needs to be kept in sync with the run-time behavior (which is defined by - /// `AssertKind::description` and the lang items mentioned in its docs). - /// Note that we deliberately show more details here than we do at runtime, such as the actual - /// numbers that overflowed -- it is much easier to do so here than at runtime. - pub fn diagnostic_message(&self) -> DiagnosticMessage { - use crate::fluent_generated::*; - use AssertKind::*; - - match self { - BoundsCheck { .. } => middle_bounds_check, - Overflow(BinOp::Shl, _, _) => middle_assert_shl_overflow, - Overflow(BinOp::Shr, _, _) => middle_assert_shr_overflow, - Overflow(_, _, _) => middle_assert_op_overflow, - OverflowNeg(_) => middle_assert_overflow_neg, - DivisionByZero(_) => middle_assert_divide_by_zero, - RemainderByZero(_) => middle_assert_remainder_by_zero, - ResumedAfterReturn(GeneratorKind::Async(_)) => middle_assert_async_resume_after_return, - ResumedAfterReturn(GeneratorKind::Gen) => middle_assert_generator_resume_after_return, - ResumedAfterPanic(GeneratorKind::Async(_)) => middle_assert_async_resume_after_panic, - ResumedAfterPanic(GeneratorKind::Gen) => middle_assert_generator_resume_after_panic, - - MisalignedPointerDereference { .. } => middle_assert_misaligned_ptr_deref, - } - } - - pub fn add_args(self, adder: &mut dyn FnMut(Cow<'static, str>, DiagnosticArgValue<'static>)) - where - O: fmt::Debug, - { - use AssertKind::*; - - macro_rules! add { - ($name: expr, $value: expr) => { - adder($name.into(), $value.into_diagnostic_arg()); - }; - } - - match self { - BoundsCheck { len, index } => { - add!("len", format!("{len:?}")); - add!("index", format!("{index:?}")); - } - Overflow(BinOp::Shl | BinOp::Shr, _, val) - | DivisionByZero(val) - | RemainderByZero(val) - | OverflowNeg(val) => { - add!("val", format!("{val:#?}")); - } - Overflow(binop, left, right) => { - add!("op", binop.to_hir_binop().as_str()); - add!("left", format!("{left:#?}")); - add!("right", format!("{right:#?}")); - } - ResumedAfterReturn(_) | ResumedAfterPanic(_) => {} - MisalignedPointerDereference { required, found } => { - add!("required", format!("{required:#?}")); - add!("found", format!("{found:#?}")); - } - } - } -} - -/////////////////////////////////////////////////////////////////////////// -// Statements - -/// A statement in a basic block, including information about its source code. -#[derive(Clone, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)] -pub struct Statement<'tcx> { - pub source_info: SourceInfo, - pub kind: StatementKind<'tcx>, -} - -impl Statement<'_> { - /// Changes a statement to a nop. This is both faster than deleting instructions and avoids - /// invalidating statement indices in `Location`s. - pub fn make_nop(&mut self) { - self.kind = StatementKind::Nop - } - - /// Changes a statement to a nop and returns the original statement. - #[must_use = "If you don't need the statement, use `make_nop` instead"] - pub fn replace_nop(&mut self) -> Self { - Statement { - source_info: self.source_info, - kind: mem::replace(&mut self.kind, StatementKind::Nop), - } - } -} - -impl Debug for Statement<'_> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - use self::StatementKind::*; - match self.kind { - Assign(box (ref place, ref rv)) => write!(fmt, "{place:?} = {rv:?}"), - FakeRead(box (ref cause, ref place)) => { - write!(fmt, "FakeRead({cause:?}, {place:?})") - } - Retag(ref kind, ref place) => write!( - fmt, - "Retag({}{:?})", - match kind { - RetagKind::FnEntry => "[fn entry] ", - RetagKind::TwoPhase => "[2phase] ", - RetagKind::Raw => "[raw] ", - RetagKind::Default => "", - }, - place, - ), - StorageLive(ref place) => write!(fmt, "StorageLive({place:?})"), - StorageDead(ref place) => write!(fmt, "StorageDead({place:?})"), - SetDiscriminant { ref place, variant_index } => { - write!(fmt, "discriminant({place:?}) = {variant_index:?}") - } - Deinit(ref place) => write!(fmt, "Deinit({place:?})"), - PlaceMention(ref place) => { - write!(fmt, "PlaceMention({place:?})") - } - AscribeUserType(box (ref place, ref c_ty), ref variance) => { - write!(fmt, "AscribeUserType({place:?}, {variance:?}, {c_ty:?})") - } - Coverage(box self::Coverage { ref kind, code_region: Some(ref rgn) }) => { - write!(fmt, "Coverage::{kind:?} for {rgn:?}") - } - Coverage(box ref coverage) => write!(fmt, "Coverage::{:?}", coverage.kind), - Intrinsic(box ref intrinsic) => write!(fmt, "{intrinsic}"), - ConstEvalCounter => write!(fmt, "ConstEvalCounter"), - Nop => write!(fmt, "nop"), - } - } -} - -impl<'tcx> StatementKind<'tcx> { - pub fn as_assign_mut(&mut self) -> Option<&mut (Place<'tcx>, Rvalue<'tcx>)> { - match self { - StatementKind::Assign(x) => Some(x), - _ => None, - } - } - - pub fn as_assign(&self) -> Option<&(Place<'tcx>, Rvalue<'tcx>)> { - match self { - StatementKind::Assign(x) => Some(x), - _ => None, - } - } -} - -/////////////////////////////////////////////////////////////////////////// -// Places - -impl ProjectionElem { - /// Returns `true` if the target of this projection may refer to a different region of memory - /// than the base. - fn is_indirect(&self) -> bool { - match self { - Self::Deref => true, - - Self::Field(_, _) - | Self::Index(_) - | Self::OpaqueCast(_) - | Self::ConstantIndex { .. } - | Self::Subslice { .. } - | Self::Downcast(_, _) => false, - } - } - - /// Returns `true` if the target of this projection always refers to the same memory region - /// whatever the state of the program. - pub fn is_stable_offset(&self) -> bool { - match self { - Self::Deref | Self::Index(_) => false, - Self::Field(_, _) - | Self::OpaqueCast(_) - | Self::ConstantIndex { .. } - | Self::Subslice { .. } - | Self::Downcast(_, _) => true, - } - } - - /// Returns `true` if this is a `Downcast` projection with the given `VariantIdx`. - pub fn is_downcast_to(&self, v: VariantIdx) -> bool { - matches!(*self, Self::Downcast(_, x) if x == v) - } - - /// Returns `true` if this is a `Field` projection with the given index. - pub fn is_field_to(&self, f: FieldIdx) -> bool { - matches!(*self, Self::Field(x, _) if x == f) - } - - /// Returns `true` if this is accepted inside `VarDebugInfoContents::Place`. - pub fn can_use_in_debuginfo(&self) -> bool { - match self { - Self::ConstantIndex { from_end: false, .. } - | Self::Deref - | Self::Downcast(_, _) - | Self::Field(_, _) => true, - Self::ConstantIndex { from_end: true, .. } - | Self::Index(_) - | Self::OpaqueCast(_) - | Self::Subslice { .. } => false, - } - } -} - -/// Alias for projections as they appear in `UserTypeProjection`, where we -/// need neither the `V` parameter for `Index` nor the `T` for `Field`. -pub type ProjectionKind = ProjectionElem<(), ()>; - -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub struct PlaceRef<'tcx> { - pub local: Local, - pub projection: &'tcx [PlaceElem<'tcx>], -} - -// Once we stop implementing `Ord` for `DefId`, -// this impl will be unnecessary. Until then, we'll -// leave this impl in place to prevent re-adding a -// dependency on the `Ord` impl for `DefId` -impl<'tcx> !PartialOrd for PlaceRef<'tcx> {} - -impl<'tcx> Place<'tcx> { - // FIXME change this to a const fn by also making List::empty a const fn. - pub fn return_place() -> Place<'tcx> { - Place { local: RETURN_PLACE, projection: List::empty() } - } - - /// Returns `true` if this `Place` contains a `Deref` projection. - /// - /// If `Place::is_indirect` returns false, the caller knows that the `Place` refers to the - /// same region of memory as its base. - pub fn is_indirect(&self) -> bool { - self.projection.iter().any(|elem| elem.is_indirect()) - } - - /// Returns `true` if this `Place`'s first projection is `Deref`. - /// - /// This is useful because for MIR phases `AnalysisPhase::PostCleanup` and later, - /// `Deref` projections can only occur as the first projection. In that case this method - /// is equivalent to `is_indirect`, but faster. - pub fn is_indirect_first_projection(&self) -> bool { - self.as_ref().is_indirect_first_projection() - } - - /// Finds the innermost `Local` from this `Place`, *if* it is either a local itself or - /// a single deref of a local. - #[inline(always)] - pub fn local_or_deref_local(&self) -> Option { - self.as_ref().local_or_deref_local() - } - - /// If this place represents a local variable like `_X` with no - /// projections, return `Some(_X)`. - #[inline(always)] - pub fn as_local(&self) -> Option { - self.as_ref().as_local() - } - - #[inline] - pub fn as_ref(&self) -> PlaceRef<'tcx> { - PlaceRef { local: self.local, projection: &self.projection } - } - - /// Iterate over the projections in evaluation order, i.e., the first element is the base with - /// its projection and then subsequently more projections are added. - /// As a concrete example, given the place a.b.c, this would yield: - /// - (a, .b) - /// - (a.b, .c) - /// - /// Given a place without projections, the iterator is empty. - #[inline] - pub fn iter_projections( - self, - ) -> impl Iterator, PlaceElem<'tcx>)> + DoubleEndedIterator { - self.as_ref().iter_projections() - } - - /// Generates a new place by appending `more_projections` to the existing ones - /// and interning the result. - pub fn project_deeper(self, more_projections: &[PlaceElem<'tcx>], tcx: TyCtxt<'tcx>) -> Self { - if more_projections.is_empty() { - return self; - } - - self.as_ref().project_deeper(more_projections, tcx) - } -} - -impl From for Place<'_> { - #[inline] - fn from(local: Local) -> Self { - Place { local, projection: List::empty() } - } -} - -impl<'tcx> PlaceRef<'tcx> { - /// Finds the innermost `Local` from this `Place`, *if* it is either a local itself or - /// a single deref of a local. - pub fn local_or_deref_local(&self) -> Option { - match *self { - PlaceRef { local, projection: [] } - | PlaceRef { local, projection: [ProjectionElem::Deref] } => Some(local), - _ => None, - } - } - - /// Returns `true` if this `Place` contains a `Deref` projection. - /// - /// If `Place::is_indirect` returns false, the caller knows that the `Place` refers to the - /// same region of memory as its base. - pub fn is_indirect(&self) -> bool { - self.projection.iter().any(|elem| elem.is_indirect()) - } - - /// Returns `true` if this `Place`'s first projection is `Deref`. - /// - /// This is useful because for MIR phases `AnalysisPhase::PostCleanup` and later, - /// `Deref` projections can only occur as the first projection. In that case this method - /// is equivalent to `is_indirect`, but faster. - pub fn is_indirect_first_projection(&self) -> bool { - // To make sure this is not accidentally used in wrong mir phase - debug_assert!( - self.projection.is_empty() || !self.projection[1..].contains(&PlaceElem::Deref) - ); - self.projection.first() == Some(&PlaceElem::Deref) - } - - /// If this place represents a local variable like `_X` with no - /// projections, return `Some(_X)`. - #[inline] - pub fn as_local(&self) -> Option { - match *self { - PlaceRef { local, projection: [] } => Some(local), - _ => None, - } - } - - #[inline] - pub fn last_projection(&self) -> Option<(PlaceRef<'tcx>, PlaceElem<'tcx>)> { - if let &[ref proj_base @ .., elem] = self.projection { - Some((PlaceRef { local: self.local, projection: proj_base }, elem)) - } else { - None - } - } - - /// Iterate over the projections in evaluation order, i.e., the first element is the base with - /// its projection and then subsequently more projections are added. - /// As a concrete example, given the place a.b.c, this would yield: - /// - (a, .b) - /// - (a.b, .c) - /// - /// Given a place without projections, the iterator is empty. - #[inline] - pub fn iter_projections( - self, - ) -> impl Iterator, PlaceElem<'tcx>)> + DoubleEndedIterator { - self.projection.iter().enumerate().map(move |(i, proj)| { - let base = PlaceRef { local: self.local, projection: &self.projection[..i] }; - (base, *proj) - }) - } - - /// Generates a new place by appending `more_projections` to the existing ones - /// and interning the result. - pub fn project_deeper( - self, - more_projections: &[PlaceElem<'tcx>], - tcx: TyCtxt<'tcx>, - ) -> Place<'tcx> { - let mut v: Vec>; - - let new_projections = if self.projection.is_empty() { - more_projections - } else { - v = Vec::with_capacity(self.projection.len() + more_projections.len()); - v.extend(self.projection); - v.extend(more_projections); - &v - }; - - Place { local: self.local, projection: tcx.mk_place_elems(new_projections) } - } -} - -impl From for PlaceRef<'_> { - #[inline] - fn from(local: Local) -> Self { - PlaceRef { local, projection: &[] } - } -} - -impl Debug for Place<'_> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - self.as_ref().fmt(fmt) - } -} - -impl Debug for PlaceRef<'_> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - pre_fmt_projection(self.projection, fmt)?; - write!(fmt, "{:?}", self.local)?; - post_fmt_projection(self.projection, fmt) - } -} - -fn pre_fmt_projection(projection: &[PlaceElem<'_>], fmt: &mut Formatter<'_>) -> fmt::Result { - for &elem in projection.iter().rev() { - match elem { - ProjectionElem::OpaqueCast(_) - | ProjectionElem::Downcast(_, _) - | ProjectionElem::Field(_, _) => { - write!(fmt, "(").unwrap(); - } - ProjectionElem::Deref => { - write!(fmt, "(*").unwrap(); - } - ProjectionElem::Index(_) - | ProjectionElem::ConstantIndex { .. } - | ProjectionElem::Subslice { .. } => {} - } - } - - Ok(()) -} - -fn post_fmt_projection(projection: &[PlaceElem<'_>], fmt: &mut Formatter<'_>) -> fmt::Result { - for &elem in projection.iter() { - match elem { - ProjectionElem::OpaqueCast(ty) => { - write!(fmt, " as {ty})")?; - } - ProjectionElem::Downcast(Some(name), _index) => { - write!(fmt, " as {name})")?; - } - ProjectionElem::Downcast(None, index) => { - write!(fmt, " as variant#{index:?})")?; - } - ProjectionElem::Deref => { - write!(fmt, ")")?; - } - ProjectionElem::Field(field, ty) => { - with_no_trimmed_paths!(write!(fmt, ".{:?}: {})", field.index(), ty)?); - } - ProjectionElem::Index(ref index) => { - write!(fmt, "[{index:?}]")?; - } - ProjectionElem::ConstantIndex { offset, min_length, from_end: false } => { - write!(fmt, "[{offset:?} of {min_length:?}]")?; - } - ProjectionElem::ConstantIndex { offset, min_length, from_end: true } => { - write!(fmt, "[-{offset:?} of {min_length:?}]")?; - } - ProjectionElem::Subslice { from, to, from_end: true } if to == 0 => { - write!(fmt, "[{from:?}:]")?; - } - ProjectionElem::Subslice { from, to, from_end: true } if from == 0 => { - write!(fmt, "[:-{to:?}]")?; - } - ProjectionElem::Subslice { from, to, from_end: true } => { - write!(fmt, "[{from:?}:-{to:?}]")?; - } - ProjectionElem::Subslice { from, to, from_end: false } => { - write!(fmt, "[{from:?}..{to:?}]")?; - } - } - } - - Ok(()) -} - /////////////////////////////////////////////////////////////////////////// // Scopes @@ -1968,345 +1386,12 @@ pub struct SourceScopeLocalData { pub safety: Safety, } -/////////////////////////////////////////////////////////////////////////// -// Operands - -impl<'tcx> Debug for Operand<'tcx> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - use self::Operand::*; - match *self { - Constant(ref a) => write!(fmt, "{a:?}"), - Copy(ref place) => write!(fmt, "{place:?}"), - Move(ref place) => write!(fmt, "move {place:?}"), - } - } -} - -impl<'tcx> Operand<'tcx> { - /// Convenience helper to make a constant that refers to the fn - /// with given `DefId` and args. Since this is used to synthesize - /// MIR, assumes `user_ty` is None. - pub fn function_handle( - tcx: TyCtxt<'tcx>, - def_id: DefId, - args: impl IntoIterator>, - span: Span, - ) -> Self { - let ty = Ty::new_fn_def(tcx, def_id, args); - Operand::Constant(Box::new(Constant { - span, - user_ty: None, - literal: ConstantKind::Val(ConstValue::ZeroSized, ty), - })) - } - - pub fn is_move(&self) -> bool { - matches!(self, Operand::Move(..)) - } - - /// Convenience helper to make a literal-like constant from a given scalar value. - /// Since this is used to synthesize MIR, assumes `user_ty` is None. - pub fn const_from_scalar( - tcx: TyCtxt<'tcx>, - ty: Ty<'tcx>, - val: Scalar, - span: Span, - ) -> Operand<'tcx> { - debug_assert!({ - let param_env_and_ty = ty::ParamEnv::empty().and(ty); - let type_size = tcx - .layout_of(param_env_and_ty) - .unwrap_or_else(|e| panic!("could not compute layout for {ty:?}: {e:?}")) - .size; - let scalar_size = match val { - Scalar::Int(int) => int.size(), - _ => panic!("Invalid scalar type {val:?}"), - }; - scalar_size == type_size - }); - Operand::Constant(Box::new(Constant { - span, - user_ty: None, - literal: ConstantKind::Val(ConstValue::Scalar(val), ty), - })) - } - - pub fn to_copy(&self) -> Self { - match *self { - Operand::Copy(_) | Operand::Constant(_) => self.clone(), - Operand::Move(place) => Operand::Copy(place), - } - } - - /// Returns the `Place` that is the target of this `Operand`, or `None` if this `Operand` is a - /// constant. - pub fn place(&self) -> Option> { - match self { - Operand::Copy(place) | Operand::Move(place) => Some(*place), - Operand::Constant(_) => None, - } - } - - /// Returns the `Constant` that is the target of this `Operand`, or `None` if this `Operand` is a - /// place. - pub fn constant(&self) -> Option<&Constant<'tcx>> { - match self { - Operand::Constant(x) => Some(&**x), - Operand::Copy(_) | Operand::Move(_) => None, - } - } - - /// Gets the `ty::FnDef` from an operand if it's a constant function item. - /// - /// While this is unlikely in general, it's the normal case of what you'll - /// find as the `func` in a [`TerminatorKind::Call`]. - pub fn const_fn_def(&self) -> Option<(DefId, GenericArgsRef<'tcx>)> { - let const_ty = self.constant()?.literal.ty(); - if let ty::FnDef(def_id, args) = *const_ty.kind() { Some((def_id, args)) } else { None } - } -} - -/////////////////////////////////////////////////////////////////////////// -/// Rvalues - -impl<'tcx> Rvalue<'tcx> { - /// Returns true if rvalue can be safely removed when the result is unused. - #[inline] - pub fn is_safe_to_remove(&self) -> bool { - match self { - // Pointer to int casts may be side-effects due to exposing the provenance. - // While the model is undecided, we should be conservative. See - // - Rvalue::Cast(CastKind::PointerExposeAddress, _, _) => false, - - Rvalue::Use(_) - | Rvalue::CopyForDeref(_) - | Rvalue::Repeat(_, _) - | Rvalue::Ref(_, _, _) - | Rvalue::ThreadLocalRef(_) - | Rvalue::AddressOf(_, _) - | Rvalue::Len(_) - | Rvalue::Cast( - CastKind::IntToInt - | CastKind::FloatToInt - | CastKind::FloatToFloat - | CastKind::IntToFloat - | CastKind::FnPtrToPtr - | CastKind::PtrToPtr - | CastKind::PointerCoercion(_) - | CastKind::PointerFromExposedAddress - | CastKind::DynStar - | CastKind::Transmute, - _, - _, - ) - | Rvalue::BinaryOp(_, _) - | Rvalue::CheckedBinaryOp(_, _) - | Rvalue::NullaryOp(_, _) - | Rvalue::UnaryOp(_, _) - | Rvalue::Discriminant(_) - | Rvalue::Aggregate(_, _) - | Rvalue::ShallowInitBox(_, _) => true, - } - } -} - -impl BorrowKind { - pub fn mutability(&self) -> Mutability { - match *self { - BorrowKind::Shared | BorrowKind::Shallow => Mutability::Not, - BorrowKind::Mut { .. } => Mutability::Mut, - } - } - - pub fn allows_two_phase_borrow(&self) -> bool { - match *self { - BorrowKind::Shared - | BorrowKind::Shallow - | BorrowKind::Mut { kind: MutBorrowKind::Default | MutBorrowKind::ClosureCapture } => { - false - } - BorrowKind::Mut { kind: MutBorrowKind::TwoPhaseBorrow } => true, - } - } -} - -impl<'tcx> Debug for Rvalue<'tcx> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - use self::Rvalue::*; - - match *self { - Use(ref place) => write!(fmt, "{place:?}"), - Repeat(ref a, b) => { - write!(fmt, "[{a:?}; ")?; - pretty_print_const(b, fmt, false)?; - write!(fmt, "]") - } - Len(ref a) => write!(fmt, "Len({a:?})"), - Cast(ref kind, ref place, ref ty) => { - with_no_trimmed_paths!(write!(fmt, "{place:?} as {ty} ({kind:?})")) - } - BinaryOp(ref op, box (ref a, ref b)) => write!(fmt, "{op:?}({a:?}, {b:?})"), - CheckedBinaryOp(ref op, box (ref a, ref b)) => { - write!(fmt, "Checked{op:?}({a:?}, {b:?})") - } - UnaryOp(ref op, ref a) => write!(fmt, "{op:?}({a:?})"), - Discriminant(ref place) => write!(fmt, "discriminant({place:?})"), - NullaryOp(ref op, ref t) => { - let t = with_no_trimmed_paths!(format!("{}", t)); - match op { - NullOp::SizeOf => write!(fmt, "SizeOf({t})"), - NullOp::AlignOf => write!(fmt, "AlignOf({t})"), - NullOp::OffsetOf(fields) => write!(fmt, "OffsetOf({t}, {fields:?})"), - } - } - ThreadLocalRef(did) => ty::tls::with(|tcx| { - let muta = tcx.static_mutability(did).unwrap().prefix_str(); - write!(fmt, "&/*tls*/ {}{}", muta, tcx.def_path_str(did)) - }), - Ref(region, borrow_kind, ref place) => { - let kind_str = match borrow_kind { - BorrowKind::Shared => "", - BorrowKind::Shallow => "shallow ", - BorrowKind::Mut { .. } => "mut ", - }; - - // When printing regions, add trailing space if necessary. - let print_region = ty::tls::with(|tcx| { - tcx.sess.verbose() || tcx.sess.opts.unstable_opts.identify_regions - }); - let region = if print_region { - let mut region = region.to_string(); - if !region.is_empty() { - region.push(' '); - } - region - } else { - // Do not even print 'static - String::new() - }; - write!(fmt, "&{region}{kind_str}{place:?}") - } - - CopyForDeref(ref place) => write!(fmt, "deref_copy {place:#?}"), - - AddressOf(mutability, ref place) => { - let kind_str = match mutability { - Mutability::Mut => "mut", - Mutability::Not => "const", - }; - - write!(fmt, "&raw {kind_str} {place:?}") - } - - Aggregate(ref kind, ref places) => { - let fmt_tuple = |fmt: &mut Formatter<'_>, name: &str| { - let mut tuple_fmt = fmt.debug_tuple(name); - for place in places { - tuple_fmt.field(place); - } - tuple_fmt.finish() - }; - - match **kind { - AggregateKind::Array(_) => write!(fmt, "{places:?}"), - - AggregateKind::Tuple => { - if places.is_empty() { - write!(fmt, "()") - } else { - fmt_tuple(fmt, "") - } - } - - AggregateKind::Adt(adt_did, variant, args, _user_ty, _) => { - ty::tls::with(|tcx| { - let variant_def = &tcx.adt_def(adt_did).variant(variant); - let args = tcx.lift(args).expect("could not lift for printing"); - let name = FmtPrinter::new(tcx, Namespace::ValueNS) - .print_def_path(variant_def.def_id, args)? - .into_buffer(); - - match variant_def.ctor_kind() { - Some(CtorKind::Const) => fmt.write_str(&name), - Some(CtorKind::Fn) => fmt_tuple(fmt, &name), - None => { - let mut struct_fmt = fmt.debug_struct(&name); - for (field, place) in iter::zip(&variant_def.fields, places) { - struct_fmt.field(field.name.as_str(), place); - } - struct_fmt.finish() - } - } - }) - } - - AggregateKind::Closure(def_id, args) => ty::tls::with(|tcx| { - let name = if tcx.sess.opts.unstable_opts.span_free_formats { - let args = tcx.lift(args).unwrap(); - format!("[closure@{}]", tcx.def_path_str_with_args(def_id, args),) - } else { - let span = tcx.def_span(def_id); - format!( - "[closure@{}]", - tcx.sess.source_map().span_to_diagnostic_string(span) - ) - }; - let mut struct_fmt = fmt.debug_struct(&name); - - // FIXME(project-rfc-2229#48): This should be a list of capture names/places - if let Some(def_id) = def_id.as_local() - && let Some(upvars) = tcx.upvars_mentioned(def_id) - { - for (&var_id, place) in iter::zip(upvars.keys(), places) { - let var_name = tcx.hir().name(var_id); - struct_fmt.field(var_name.as_str(), place); - } - } else { - for (index, place) in places.iter().enumerate() { - struct_fmt.field(&format!("{index}"), place); - } - } - - struct_fmt.finish() - }), - - AggregateKind::Generator(def_id, _, _) => ty::tls::with(|tcx| { - let name = format!("[generator@{:?}]", tcx.def_span(def_id)); - let mut struct_fmt = fmt.debug_struct(&name); - - // FIXME(project-rfc-2229#48): This should be a list of capture names/places - if let Some(def_id) = def_id.as_local() - && let Some(upvars) = tcx.upvars_mentioned(def_id) - { - for (&var_id, place) in iter::zip(upvars.keys(), places) { - let var_name = tcx.hir().name(var_id); - struct_fmt.field(var_name.as_str(), place); - } - } else { - for (index, place) in places.iter().enumerate() { - struct_fmt.field(&format!("{index}"), place); - } - } - - struct_fmt.finish() - }), - } - } - - ShallowInitBox(ref place, ref ty) => { - with_no_trimmed_paths!(write!(fmt, "ShallowInitBox({place:?}, {ty})")) - } - } - } -} - /// A collection of projections into user types. /// /// They are projections because a binding can occur a part of a /// parent pattern that has been ascribed a type. /// -/// Its a collection because there can be multiple type ascriptions on +/// It's a collection because there can be multiple type ascriptions on /// the path from the root of the pattern down to the binding itself. /// /// An example: diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index 39af95d62f86..f41f454ba5e5 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -1,13 +1,13 @@ use std::collections::BTreeSet; -use std::fmt::Display; -use std::fmt::Write as _; +use std::fmt::{self, Debug, Display, Write as _}; use std::fs; -use std::io::{self, Write}; +use std::io::{self, Write as _}; use std::path::{Path, PathBuf}; use super::graphviz::write_mir_fn_graphviz; use super::spanview::write_mir_fn_spanview; use either::Either; +use rustc_ast::InlineAsmTemplatePiece; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def_id::DefId; use rustc_index::Idx; @@ -79,7 +79,7 @@ pub fn dump_mir<'tcx, F>( body: &Body<'tcx>, extra_data: F, ) where - F: FnMut(PassWhere, &mut dyn Write) -> io::Result<()>, + F: FnMut(PassWhere, &mut dyn io::Write) -> io::Result<()>, { if !dump_enabled(tcx, pass_name, body.source.def_id()) { return; @@ -116,7 +116,7 @@ fn dump_matched_mir_node<'tcx, F>( body: &Body<'tcx>, mut extra_data: F, ) where - F: FnMut(PassWhere, &mut dyn Write) -> io::Result<()>, + F: FnMut(PassWhere, &mut dyn io::Write) -> io::Result<()>, { let _: io::Result<()> = try { let mut file = create_dump_file(tcx, "mir", pass_num, pass_name, disambiguator, body)?; @@ -260,11 +260,14 @@ pub fn create_dump_file<'tcx>( ) } +/////////////////////////////////////////////////////////////////////////// +// Whole MIR bodies + /// Write out a human-readable textual representation for the given MIR. pub fn write_mir_pretty<'tcx>( tcx: TyCtxt<'tcx>, single: Option, - w: &mut dyn Write, + w: &mut dyn io::Write, ) -> io::Result<()> { writeln!(w, "// WARNING: This output format is intended for human consumers only")?; writeln!(w, "// and is subject to change without notice. Knock yourself out.")?; @@ -278,7 +281,7 @@ pub fn write_mir_pretty<'tcx>( writeln!(w)?; } - let render_body = |w: &mut dyn Write, body| -> io::Result<()> { + let render_body = |w: &mut dyn io::Write, body| -> io::Result<()> { write_mir_fn(tcx, body, &mut |_, _| Ok(()), w)?; for body in tcx.promoted_mir(def_id) { @@ -309,10 +312,10 @@ pub fn write_mir_fn<'tcx, F>( tcx: TyCtxt<'tcx>, body: &Body<'tcx>, extra_data: &mut F, - w: &mut dyn Write, + w: &mut dyn io::Write, ) -> io::Result<()> where - F: FnMut(PassWhere, &mut dyn Write) -> io::Result<()>, + F: FnMut(PassWhere, &mut dyn io::Write) -> io::Result<()>, { write_mir_intro(tcx, body, w)?; for block in body.basic_blocks.indices() { @@ -330,16 +333,267 @@ where Ok(()) } +/// Prints local variables in a scope tree. +fn write_scope_tree( + tcx: TyCtxt<'_>, + body: &Body<'_>, + scope_tree: &FxHashMap>, + w: &mut dyn io::Write, + parent: SourceScope, + depth: usize, +) -> io::Result<()> { + let indent = depth * INDENT.len(); + + // Local variable debuginfo. + for var_debug_info in &body.var_debug_info { + if var_debug_info.source_info.scope != parent { + // Not declared in this scope. + continue; + } + + let indented_debug_info = format!("{0:1$}debug {2:?};", INDENT, indent, var_debug_info); + + if tcx.sess.opts.unstable_opts.mir_include_spans { + writeln!( + w, + "{0:1$} // in {2}", + indented_debug_info, + ALIGN, + comment(tcx, var_debug_info.source_info), + )?; + } else { + writeln!(w, "{indented_debug_info}")?; + } + } + + // Local variable types. + for (local, local_decl) in body.local_decls.iter_enumerated() { + if (1..body.arg_count + 1).contains(&local.index()) { + // Skip over argument locals, they're printed in the signature. + continue; + } + + if local_decl.source_info.scope != parent { + // Not declared in this scope. + continue; + } + + let mut_str = local_decl.mutability.prefix_str(); + + let mut indented_decl = ty::print::with_no_trimmed_paths!(format!( + "{0:1$}let {2}{3:?}: {4}", + INDENT, indent, mut_str, local, local_decl.ty + )); + if let Some(user_ty) = &local_decl.user_ty { + for user_ty in user_ty.projections() { + write!(indented_decl, " as {user_ty:?}").unwrap(); + } + } + indented_decl.push(';'); + + let local_name = if local == RETURN_PLACE { " return place" } else { "" }; + + if tcx.sess.opts.unstable_opts.mir_include_spans { + writeln!( + w, + "{0:1$} //{2} in {3}", + indented_decl, + ALIGN, + local_name, + comment(tcx, local_decl.source_info), + )?; + } else { + writeln!(w, "{indented_decl}",)?; + } + } + + let Some(children) = scope_tree.get(&parent) else { + return Ok(()); + }; + + for &child in children { + let child_data = &body.source_scopes[child]; + assert_eq!(child_data.parent_scope, Some(parent)); + + let (special, span) = if let Some((callee, callsite_span)) = child_data.inlined { + ( + format!( + " (inlined {}{})", + if callee.def.requires_caller_location(tcx) { "#[track_caller] " } else { "" }, + callee + ), + Some(callsite_span), + ) + } else { + (String::new(), None) + }; + + let indented_header = format!("{0:1$}scope {2}{3} {{", "", indent, child.index(), special); + + if tcx.sess.opts.unstable_opts.mir_include_spans { + if let Some(span) = span { + writeln!( + w, + "{0:1$} // at {2}", + indented_header, + ALIGN, + tcx.sess.source_map().span_to_embeddable_string(span), + )?; + } else { + writeln!(w, "{indented_header}")?; + } + } else { + writeln!(w, "{indented_header}")?; + } + + write_scope_tree(tcx, body, scope_tree, w, child, depth + 1)?; + writeln!(w, "{0:1$}}}", "", depth * INDENT.len())?; + } + + Ok(()) +} + +impl Debug for VarDebugInfo<'_> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + if let Some(box VarDebugInfoFragment { ty, ref projection }) = self.composite { + pre_fmt_projection(&projection[..], fmt)?; + write!(fmt, "({}: {})", self.name, ty)?; + post_fmt_projection(&projection[..], fmt)?; + } else { + write!(fmt, "{}", self.name)?; + } + + write!(fmt, " => {:?}", self.value) + } +} + +/// Write out a human-readable textual representation of the MIR's `fn` type and the types of its +/// local variables (both user-defined bindings and compiler temporaries). +pub fn write_mir_intro<'tcx>( + tcx: TyCtxt<'tcx>, + body: &Body<'_>, + w: &mut dyn io::Write, +) -> io::Result<()> { + write_mir_sig(tcx, body, w)?; + writeln!(w, "{{")?; + + // construct a scope tree and write it out + let mut scope_tree: FxHashMap> = Default::default(); + for (index, scope_data) in body.source_scopes.iter().enumerate() { + if let Some(parent) = scope_data.parent_scope { + scope_tree.entry(parent).or_default().push(SourceScope::new(index)); + } else { + // Only the argument scope has no parent, because it's the root. + assert_eq!(index, OUTERMOST_SOURCE_SCOPE.index()); + } + } + + write_scope_tree(tcx, body, &scope_tree, w, OUTERMOST_SOURCE_SCOPE, 1)?; + + // Add an empty line before the first block is printed. + writeln!(w)?; + + Ok(()) +} + +fn write_mir_sig(tcx: TyCtxt<'_>, body: &Body<'_>, w: &mut dyn io::Write) -> io::Result<()> { + use rustc_hir::def::DefKind; + + trace!("write_mir_sig: {:?}", body.source.instance); + let def_id = body.source.def_id(); + let kind = tcx.def_kind(def_id); + let is_function = match kind { + DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(..) => true, + _ => tcx.is_closure(def_id), + }; + match (kind, body.source.promoted) { + (_, Some(i)) => write!(w, "{i:?} in ")?, + (DefKind::Const | DefKind::AssocConst, _) => write!(w, "const ")?, + (DefKind::Static(hir::Mutability::Not), _) => write!(w, "static ")?, + (DefKind::Static(hir::Mutability::Mut), _) => write!(w, "static mut ")?, + (_, _) if is_function => write!(w, "fn ")?, + (DefKind::AnonConst | DefKind::InlineConst, _) => {} // things like anon const, not an item + _ => bug!("Unexpected def kind {:?}", kind), + } + + ty::print::with_forced_impl_filename_line! { + // see notes on #41697 elsewhere + write!(w, "{}", tcx.def_path_str(def_id))? + } + + if body.source.promoted.is_none() && is_function { + write!(w, "(")?; + + // fn argument types. + for (i, arg) in body.args_iter().enumerate() { + if i != 0 { + write!(w, ", ")?; + } + write!(w, "{:?}: {}", Place::from(arg), body.local_decls[arg].ty)?; + } + + write!(w, ") -> {}", body.return_ty())?; + } else { + assert_eq!(body.arg_count, 0); + write!(w, ": {} =", body.return_ty())?; + } + + if let Some(yield_ty) = body.yield_ty() { + writeln!(w)?; + writeln!(w, "yields {yield_ty}")?; + } + + write!(w, " ")?; + // Next thing that gets printed is the opening { + + Ok(()) +} + +fn write_user_type_annotations( + tcx: TyCtxt<'_>, + body: &Body<'_>, + w: &mut dyn io::Write, +) -> io::Result<()> { + if !body.user_type_annotations.is_empty() { + writeln!(w, "| User Type Annotations")?; + } + for (index, annotation) in body.user_type_annotations.iter_enumerated() { + writeln!( + w, + "| {:?}: user_ty: {}, span: {}, inferred_ty: {}", + index.index(), + annotation.user_ty, + tcx.sess.source_map().span_to_embeddable_string(annotation.span), + with_no_trimmed_paths!(format!("{}", annotation.inferred_ty)), + )?; + } + if !body.user_type_annotations.is_empty() { + writeln!(w, "|")?; + } + Ok(()) +} + +pub fn dump_mir_def_ids(tcx: TyCtxt<'_>, single: Option) -> Vec { + if let Some(i) = single { + vec![i] + } else { + tcx.mir_keys(()).iter().map(|def_id| def_id.to_def_id()).collect() + } +} + +/////////////////////////////////////////////////////////////////////////// +// Basic blocks and their parts (statements, terminators, ...) + /// Write out a human-readable textual representation for the given basic block. pub fn write_basic_block<'tcx, F>( tcx: TyCtxt<'tcx>, block: BasicBlock, body: &Body<'tcx>, extra_data: &mut F, - w: &mut dyn Write, + w: &mut dyn io::Write, ) -> io::Result<()> where - F: FnMut(PassWhere, &mut dyn Write) -> io::Result<()>, + F: FnMut(PassWhere, &mut dyn io::Write) -> io::Result<()>, { let data = &body[block]; @@ -400,10 +654,497 @@ where writeln!(w, "{INDENT}}}") } +impl Debug for Statement<'_> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + use self::StatementKind::*; + match self.kind { + Assign(box (ref place, ref rv)) => write!(fmt, "{place:?} = {rv:?}"), + FakeRead(box (ref cause, ref place)) => { + write!(fmt, "FakeRead({cause:?}, {place:?})") + } + Retag(ref kind, ref place) => write!( + fmt, + "Retag({}{:?})", + match kind { + RetagKind::FnEntry => "[fn entry] ", + RetagKind::TwoPhase => "[2phase] ", + RetagKind::Raw => "[raw] ", + RetagKind::Default => "", + }, + place, + ), + StorageLive(ref place) => write!(fmt, "StorageLive({place:?})"), + StorageDead(ref place) => write!(fmt, "StorageDead({place:?})"), + SetDiscriminant { ref place, variant_index } => { + write!(fmt, "discriminant({place:?}) = {variant_index:?}") + } + Deinit(ref place) => write!(fmt, "Deinit({place:?})"), + PlaceMention(ref place) => { + write!(fmt, "PlaceMention({place:?})") + } + AscribeUserType(box (ref place, ref c_ty), ref variance) => { + write!(fmt, "AscribeUserType({place:?}, {variance:?}, {c_ty:?})") + } + Coverage(box self::Coverage { ref kind, code_region: Some(ref rgn) }) => { + write!(fmt, "Coverage::{kind:?} for {rgn:?}") + } + Coverage(box ref coverage) => write!(fmt, "Coverage::{:?}", coverage.kind), + Intrinsic(box ref intrinsic) => write!(fmt, "{intrinsic}"), + ConstEvalCounter => write!(fmt, "ConstEvalCounter"), + Nop => write!(fmt, "nop"), + } + } +} + +impl<'tcx> Debug for TerminatorKind<'tcx> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + self.fmt_head(fmt)?; + let successor_count = self.successors().count(); + let labels = self.fmt_successor_labels(); + assert_eq!(successor_count, labels.len()); + + // `Cleanup` is already included in successors + let show_unwind = !matches!(self.unwind(), None | Some(UnwindAction::Cleanup(_))); + let fmt_unwind = |fmt: &mut Formatter<'_>| -> fmt::Result { + write!(fmt, "unwind ")?; + match self.unwind() { + // Not needed or included in successors + None | Some(UnwindAction::Cleanup(_)) => unreachable!(), + Some(UnwindAction::Continue) => write!(fmt, "continue"), + Some(UnwindAction::Unreachable) => write!(fmt, "unreachable"), + Some(UnwindAction::Terminate(reason)) => { + write!(fmt, "terminate({})", reason.as_short_str()) + } + } + }; + + match (successor_count, show_unwind) { + (0, false) => Ok(()), + (0, true) => { + write!(fmt, " -> ")?; + fmt_unwind(fmt) + } + (1, false) => write!(fmt, " -> {:?}", self.successors().next().unwrap()), + _ => { + write!(fmt, " -> [")?; + for (i, target) in self.successors().enumerate() { + if i > 0 { + write!(fmt, ", ")?; + } + write!(fmt, "{}: {:?}", labels[i], target)?; + } + if show_unwind { + write!(fmt, ", ")?; + fmt_unwind(fmt)?; + } + write!(fmt, "]") + } + } + } +} + +impl<'tcx> TerminatorKind<'tcx> { + /// Writes the "head" part of the terminator; that is, its name and the data it uses to pick the + /// successor basic block, if any. The only information not included is the list of possible + /// successors, which may be rendered differently between the text and the graphviz format. + pub fn fmt_head(&self, fmt: &mut W) -> fmt::Result { + use self::TerminatorKind::*; + match self { + Goto { .. } => write!(fmt, "goto"), + SwitchInt { discr, .. } => write!(fmt, "switchInt({discr:?})"), + Return => write!(fmt, "return"), + GeneratorDrop => write!(fmt, "generator_drop"), + UnwindResume => write!(fmt, "resume"), + UnwindTerminate(reason) => { + write!(fmt, "abort({})", reason.as_short_str()) + } + Yield { value, resume_arg, .. } => write!(fmt, "{resume_arg:?} = yield({value:?})"), + Unreachable => write!(fmt, "unreachable"), + Drop { place, .. } => write!(fmt, "drop({place:?})"), + Call { func, args, destination, .. } => { + write!(fmt, "{destination:?} = ")?; + write!(fmt, "{func:?}(")?; + for (index, arg) in args.iter().enumerate() { + if index > 0 { + write!(fmt, ", ")?; + } + write!(fmt, "{arg:?}")?; + } + write!(fmt, ")") + } + Assert { cond, expected, msg, .. } => { + write!(fmt, "assert(")?; + if !expected { + write!(fmt, "!")?; + } + write!(fmt, "{cond:?}, ")?; + msg.fmt_assert_args(fmt)?; + write!(fmt, ")") + } + FalseEdge { .. } => write!(fmt, "falseEdge"), + FalseUnwind { .. } => write!(fmt, "falseUnwind"), + InlineAsm { template, ref operands, options, .. } => { + write!(fmt, "asm!(\"{}\"", InlineAsmTemplatePiece::to_string(template))?; + for op in operands { + write!(fmt, ", ")?; + let print_late = |&late| if late { "late" } else { "" }; + match op { + InlineAsmOperand::In { reg, value } => { + write!(fmt, "in({reg}) {value:?}")?; + } + InlineAsmOperand::Out { reg, late, place: Some(place) } => { + write!(fmt, "{}out({}) {:?}", print_late(late), reg, place)?; + } + InlineAsmOperand::Out { reg, late, place: None } => { + write!(fmt, "{}out({}) _", print_late(late), reg)?; + } + InlineAsmOperand::InOut { + reg, + late, + in_value, + out_place: Some(out_place), + } => { + write!( + fmt, + "in{}out({}) {:?} => {:?}", + print_late(late), + reg, + in_value, + out_place + )?; + } + InlineAsmOperand::InOut { reg, late, in_value, out_place: None } => { + write!(fmt, "in{}out({}) {:?} => _", print_late(late), reg, in_value)?; + } + InlineAsmOperand::Const { value } => { + write!(fmt, "const {value:?}")?; + } + InlineAsmOperand::SymFn { value } => { + write!(fmt, "sym_fn {value:?}")?; + } + InlineAsmOperand::SymStatic { def_id } => { + write!(fmt, "sym_static {def_id:?}")?; + } + } + } + write!(fmt, ", options({options:?}))") + } + } + } + + /// Returns the list of labels for the edges to the successor basic blocks. + pub fn fmt_successor_labels(&self) -> Vec> { + use self::TerminatorKind::*; + match *self { + Return | UnwindResume | UnwindTerminate(_) | Unreachable | GeneratorDrop => vec![], + Goto { .. } => vec!["".into()], + SwitchInt { ref targets, .. } => targets + .values + .iter() + .map(|&u| Cow::Owned(u.to_string())) + .chain(iter::once("otherwise".into())) + .collect(), + Call { target: Some(_), unwind: UnwindAction::Cleanup(_), .. } => { + vec!["return".into(), "unwind".into()] + } + Call { target: Some(_), unwind: _, .. } => vec!["return".into()], + Call { target: None, unwind: UnwindAction::Cleanup(_), .. } => vec!["unwind".into()], + Call { target: None, unwind: _, .. } => vec![], + Yield { drop: Some(_), .. } => vec!["resume".into(), "drop".into()], + Yield { drop: None, .. } => vec!["resume".into()], + Drop { unwind: UnwindAction::Cleanup(_), .. } => vec!["return".into(), "unwind".into()], + Drop { unwind: _, .. } => vec!["return".into()], + Assert { unwind: UnwindAction::Cleanup(_), .. } => { + vec!["success".into(), "unwind".into()] + } + Assert { unwind: _, .. } => vec!["success".into()], + FalseEdge { .. } => vec!["real".into(), "imaginary".into()], + FalseUnwind { unwind: UnwindAction::Cleanup(_), .. } => { + vec!["real".into(), "unwind".into()] + } + FalseUnwind { unwind: _, .. } => vec!["real".into()], + InlineAsm { destination: Some(_), unwind: UnwindAction::Cleanup(_), .. } => { + vec!["return".into(), "unwind".into()] + } + InlineAsm { destination: Some(_), unwind: _, .. } => { + vec!["return".into()] + } + InlineAsm { destination: None, unwind: UnwindAction::Cleanup(_), .. } => { + vec!["unwind".into()] + } + InlineAsm { destination: None, unwind: _, .. } => vec![], + } + } +} + +impl<'tcx> Debug for Rvalue<'tcx> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + use self::Rvalue::*; + + match *self { + Use(ref place) => write!(fmt, "{place:?}"), + Repeat(ref a, b) => { + write!(fmt, "[{a:?}; ")?; + pretty_print_const(b, fmt, false)?; + write!(fmt, "]") + } + Len(ref a) => write!(fmt, "Len({a:?})"), + Cast(ref kind, ref place, ref ty) => { + with_no_trimmed_paths!(write!(fmt, "{place:?} as {ty} ({kind:?})")) + } + BinaryOp(ref op, box (ref a, ref b)) => write!(fmt, "{op:?}({a:?}, {b:?})"), + CheckedBinaryOp(ref op, box (ref a, ref b)) => { + write!(fmt, "Checked{op:?}({a:?}, {b:?})") + } + UnaryOp(ref op, ref a) => write!(fmt, "{op:?}({a:?})"), + Discriminant(ref place) => write!(fmt, "discriminant({place:?})"), + NullaryOp(ref op, ref t) => { + let t = with_no_trimmed_paths!(format!("{}", t)); + match op { + NullOp::SizeOf => write!(fmt, "SizeOf({t})"), + NullOp::AlignOf => write!(fmt, "AlignOf({t})"), + NullOp::OffsetOf(fields) => write!(fmt, "OffsetOf({t}, {fields:?})"), + } + } + ThreadLocalRef(did) => ty::tls::with(|tcx| { + let muta = tcx.static_mutability(did).unwrap().prefix_str(); + write!(fmt, "&/*tls*/ {}{}", muta, tcx.def_path_str(did)) + }), + Ref(region, borrow_kind, ref place) => { + let kind_str = match borrow_kind { + BorrowKind::Shared => "", + BorrowKind::Shallow => "shallow ", + BorrowKind::Mut { .. } => "mut ", + }; + + // When printing regions, add trailing space if necessary. + let print_region = ty::tls::with(|tcx| { + tcx.sess.verbose() || tcx.sess.opts.unstable_opts.identify_regions + }); + let region = if print_region { + let mut region = region.to_string(); + if !region.is_empty() { + region.push(' '); + } + region + } else { + // Do not even print 'static + String::new() + }; + write!(fmt, "&{region}{kind_str}{place:?}") + } + + CopyForDeref(ref place) => write!(fmt, "deref_copy {place:#?}"), + + AddressOf(mutability, ref place) => { + let kind_str = match mutability { + Mutability::Mut => "mut", + Mutability::Not => "const", + }; + + write!(fmt, "&raw {kind_str} {place:?}") + } + + Aggregate(ref kind, ref places) => { + let fmt_tuple = |fmt: &mut Formatter<'_>, name: &str| { + let mut tuple_fmt = fmt.debug_tuple(name); + for place in places { + tuple_fmt.field(place); + } + tuple_fmt.finish() + }; + + match **kind { + AggregateKind::Array(_) => write!(fmt, "{places:?}"), + + AggregateKind::Tuple => { + if places.is_empty() { + write!(fmt, "()") + } else { + fmt_tuple(fmt, "") + } + } + + AggregateKind::Adt(adt_did, variant, args, _user_ty, _) => { + ty::tls::with(|tcx| { + let variant_def = &tcx.adt_def(adt_did).variant(variant); + let args = tcx.lift(args).expect("could not lift for printing"); + let name = FmtPrinter::new(tcx, Namespace::ValueNS) + .print_def_path(variant_def.def_id, args)? + .into_buffer(); + + match variant_def.ctor_kind() { + Some(CtorKind::Const) => fmt.write_str(&name), + Some(CtorKind::Fn) => fmt_tuple(fmt, &name), + None => { + let mut struct_fmt = fmt.debug_struct(&name); + for (field, place) in iter::zip(&variant_def.fields, places) { + struct_fmt.field(field.name.as_str(), place); + } + struct_fmt.finish() + } + } + }) + } + + AggregateKind::Closure(def_id, args) => ty::tls::with(|tcx| { + let name = if tcx.sess.opts.unstable_opts.span_free_formats { + let args = tcx.lift(args).unwrap(); + format!("[closure@{}]", tcx.def_path_str_with_args(def_id, args),) + } else { + let span = tcx.def_span(def_id); + format!( + "[closure@{}]", + tcx.sess.source_map().span_to_diagnostic_string(span) + ) + }; + let mut struct_fmt = fmt.debug_struct(&name); + + // FIXME(project-rfc-2229#48): This should be a list of capture names/places + if let Some(def_id) = def_id.as_local() + && let Some(upvars) = tcx.upvars_mentioned(def_id) + { + for (&var_id, place) in iter::zip(upvars.keys(), places) { + let var_name = tcx.hir().name(var_id); + struct_fmt.field(var_name.as_str(), place); + } + } else { + for (index, place) in places.iter().enumerate() { + struct_fmt.field(&format!("{index}"), place); + } + } + + struct_fmt.finish() + }), + + AggregateKind::Generator(def_id, _, _) => ty::tls::with(|tcx| { + let name = format!("[generator@{:?}]", tcx.def_span(def_id)); + let mut struct_fmt = fmt.debug_struct(&name); + + // FIXME(project-rfc-2229#48): This should be a list of capture names/places + if let Some(def_id) = def_id.as_local() + && let Some(upvars) = tcx.upvars_mentioned(def_id) + { + for (&var_id, place) in iter::zip(upvars.keys(), places) { + let var_name = tcx.hir().name(var_id); + struct_fmt.field(var_name.as_str(), place); + } + } else { + for (index, place) in places.iter().enumerate() { + struct_fmt.field(&format!("{index}"), place); + } + } + + struct_fmt.finish() + }), + } + } + + ShallowInitBox(ref place, ref ty) => { + with_no_trimmed_paths!(write!(fmt, "ShallowInitBox({place:?}, {ty})")) + } + } + } +} + +impl<'tcx> Debug for Operand<'tcx> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + use self::Operand::*; + match *self { + Constant(ref a) => write!(fmt, "{a:?}"), + Copy(ref place) => write!(fmt, "{place:?}"), + Move(ref place) => write!(fmt, "move {place:?}"), + } + } +} + +impl Debug for Place<'_> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + self.as_ref().fmt(fmt) + } +} + +impl Debug for PlaceRef<'_> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + pre_fmt_projection(self.projection, fmt)?; + write!(fmt, "{:?}", self.local)?; + post_fmt_projection(self.projection, fmt) + } +} + +fn pre_fmt_projection(projection: &[PlaceElem<'_>], fmt: &mut Formatter<'_>) -> fmt::Result { + for &elem in projection.iter().rev() { + match elem { + ProjectionElem::OpaqueCast(_) + | ProjectionElem::Downcast(_, _) + | ProjectionElem::Field(_, _) => { + write!(fmt, "(").unwrap(); + } + ProjectionElem::Deref => { + write!(fmt, "(*").unwrap(); + } + ProjectionElem::Index(_) + | ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } => {} + } + } + + Ok(()) +} + +fn post_fmt_projection(projection: &[PlaceElem<'_>], fmt: &mut Formatter<'_>) -> fmt::Result { + for &elem in projection.iter() { + match elem { + ProjectionElem::OpaqueCast(ty) => { + write!(fmt, " as {ty})")?; + } + ProjectionElem::Downcast(Some(name), _index) => { + write!(fmt, " as {name})")?; + } + ProjectionElem::Downcast(None, index) => { + write!(fmt, " as variant#{index:?})")?; + } + ProjectionElem::Deref => { + write!(fmt, ")")?; + } + ProjectionElem::Field(field, ty) => { + with_no_trimmed_paths!(write!(fmt, ".{:?}: {})", field.index(), ty)?); + } + ProjectionElem::Index(ref index) => { + write!(fmt, "[{index:?}]")?; + } + ProjectionElem::ConstantIndex { offset, min_length, from_end: false } => { + write!(fmt, "[{offset:?} of {min_length:?}]")?; + } + ProjectionElem::ConstantIndex { offset, min_length, from_end: true } => { + write!(fmt, "[-{offset:?} of {min_length:?}]")?; + } + ProjectionElem::Subslice { from, to, from_end: true } if to == 0 => { + write!(fmt, "[{from:?}:]")?; + } + ProjectionElem::Subslice { from, to, from_end: true } if from == 0 => { + write!(fmt, "[:-{to:?}]")?; + } + ProjectionElem::Subslice { from, to, from_end: true } => { + write!(fmt, "[{from:?}:-{to:?}]")?; + } + ProjectionElem::Subslice { from, to, from_end: false } => { + write!(fmt, "[{from:?}..{to:?}]")?; + } + } + } + + Ok(()) +} + /// After we print the main statement, we sometimes dump extra /// information. There's often a lot of little things "nuzzled up" in /// a statement. -fn write_extra<'tcx, F>(tcx: TyCtxt<'tcx>, write: &mut dyn Write, mut visit_op: F) -> io::Result<()> +fn write_extra<'tcx, F>( + tcx: TyCtxt<'tcx>, + write: &mut dyn io::Write, + mut visit_op: F, +) -> io::Result<()> where F: FnMut(&mut ExtraComments<'tcx>), { @@ -534,161 +1275,15 @@ fn comment(tcx: TyCtxt<'_>, SourceInfo { span, scope }: SourceInfo) -> String { format!("scope {} at {}", scope.index(), location,) } -/// Prints local variables in a scope tree. -fn write_scope_tree( - tcx: TyCtxt<'_>, - body: &Body<'_>, - scope_tree: &FxHashMap>, - w: &mut dyn Write, - parent: SourceScope, - depth: usize, -) -> io::Result<()> { - let indent = depth * INDENT.len(); - - // Local variable debuginfo. - for var_debug_info in &body.var_debug_info { - if var_debug_info.source_info.scope != parent { - // Not declared in this scope. - continue; - } - - let indented_debug_info = format!("{0:1$}debug {2:?};", INDENT, indent, var_debug_info); - - if tcx.sess.opts.unstable_opts.mir_include_spans { - writeln!( - w, - "{0:1$} // in {2}", - indented_debug_info, - ALIGN, - comment(tcx, var_debug_info.source_info), - )?; - } else { - writeln!(w, "{indented_debug_info}")?; - } - } - - // Local variable types. - for (local, local_decl) in body.local_decls.iter_enumerated() { - if (1..body.arg_count + 1).contains(&local.index()) { - // Skip over argument locals, they're printed in the signature. - continue; - } - - if local_decl.source_info.scope != parent { - // Not declared in this scope. - continue; - } - - let mut_str = local_decl.mutability.prefix_str(); - - let mut indented_decl = ty::print::with_no_trimmed_paths!(format!( - "{0:1$}let {2}{3:?}: {4}", - INDENT, indent, mut_str, local, local_decl.ty - )); - if let Some(user_ty) = &local_decl.user_ty { - for user_ty in user_ty.projections() { - write!(indented_decl, " as {user_ty:?}").unwrap(); - } - } - indented_decl.push(';'); - - let local_name = if local == RETURN_PLACE { " return place" } else { "" }; - - if tcx.sess.opts.unstable_opts.mir_include_spans { - writeln!( - w, - "{0:1$} //{2} in {3}", - indented_decl, - ALIGN, - local_name, - comment(tcx, local_decl.source_info), - )?; - } else { - writeln!(w, "{indented_decl}",)?; - } - } - - let Some(children) = scope_tree.get(&parent) else { - return Ok(()); - }; - - for &child in children { - let child_data = &body.source_scopes[child]; - assert_eq!(child_data.parent_scope, Some(parent)); - - let (special, span) = if let Some((callee, callsite_span)) = child_data.inlined { - ( - format!( - " (inlined {}{})", - if callee.def.requires_caller_location(tcx) { "#[track_caller] " } else { "" }, - callee - ), - Some(callsite_span), - ) - } else { - (String::new(), None) - }; - - let indented_header = format!("{0:1$}scope {2}{3} {{", "", indent, child.index(), special); - - if tcx.sess.opts.unstable_opts.mir_include_spans { - if let Some(span) = span { - writeln!( - w, - "{0:1$} // at {2}", - indented_header, - ALIGN, - tcx.sess.source_map().span_to_embeddable_string(span), - )?; - } else { - writeln!(w, "{indented_header}")?; - } - } else { - writeln!(w, "{indented_header}")?; - } - - write_scope_tree(tcx, body, scope_tree, w, child, depth + 1)?; - writeln!(w, "{0:1$}}}", "", depth * INDENT.len())?; - } - - Ok(()) -} - -/// Write out a human-readable textual representation of the MIR's `fn` type and the types of its -/// local variables (both user-defined bindings and compiler temporaries). -pub fn write_mir_intro<'tcx>( - tcx: TyCtxt<'tcx>, - body: &Body<'_>, - w: &mut dyn Write, -) -> io::Result<()> { - write_mir_sig(tcx, body, w)?; - writeln!(w, "{{")?; - - // construct a scope tree and write it out - let mut scope_tree: FxHashMap> = Default::default(); - for (index, scope_data) in body.source_scopes.iter().enumerate() { - if let Some(parent) = scope_data.parent_scope { - scope_tree.entry(parent).or_default().push(SourceScope::new(index)); - } else { - // Only the argument scope has no parent, because it's the root. - assert_eq!(index, OUTERMOST_SOURCE_SCOPE.index()); - } - } - - write_scope_tree(tcx, body, &scope_tree, w, OUTERMOST_SOURCE_SCOPE, 1)?; - - // Add an empty line before the first block is printed. - writeln!(w)?; - - Ok(()) -} +/////////////////////////////////////////////////////////////////////////// +// Allocations /// Find all `AllocId`s mentioned (recursively) in the MIR body and print their corresponding /// allocations. pub fn write_allocations<'tcx>( tcx: TyCtxt<'tcx>, body: &Body<'_>, - w: &mut dyn Write, + w: &mut dyn io::Write, ) -> io::Result<()> { fn alloc_ids_from_alloc( alloc: ConstAllocation<'_>, @@ -737,7 +1332,7 @@ pub fn write_allocations<'tcx>( let mut todo: Vec<_> = seen.iter().copied().collect(); while let Some(id) = todo.pop() { let mut write_allocation_track_relocs = - |w: &mut dyn Write, alloc: ConstAllocation<'tcx>| -> io::Result<()> { + |w: &mut dyn io::Write, alloc: ConstAllocation<'tcx>| -> io::Result<()> { // `.rev()` because we are popping them from the back of the `todo` vector. for id in alloc_ids_from_alloc(alloc).rev() { if seen.insert(id) { @@ -998,90 +1593,8 @@ pub fn write_allocation_bytes<'tcx, Prov: Provenance, Extra, Bytes: AllocBytes>( Ok(()) } -fn write_mir_sig(tcx: TyCtxt<'_>, body: &Body<'_>, w: &mut dyn Write) -> io::Result<()> { - use rustc_hir::def::DefKind; - - trace!("write_mir_sig: {:?}", body.source.instance); - let def_id = body.source.def_id(); - let kind = tcx.def_kind(def_id); - let is_function = match kind { - DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(..) => true, - _ => tcx.is_closure(def_id), - }; - match (kind, body.source.promoted) { - (_, Some(i)) => write!(w, "{i:?} in ")?, - (DefKind::Const | DefKind::AssocConst, _) => write!(w, "const ")?, - (DefKind::Static(hir::Mutability::Not), _) => write!(w, "static ")?, - (DefKind::Static(hir::Mutability::Mut), _) => write!(w, "static mut ")?, - (_, _) if is_function => write!(w, "fn ")?, - (DefKind::AnonConst | DefKind::InlineConst, _) => {} // things like anon const, not an item - _ => bug!("Unexpected def kind {:?}", kind), - } - - ty::print::with_forced_impl_filename_line! { - // see notes on #41697 elsewhere - write!(w, "{}", tcx.def_path_str(def_id))? - } - - if body.source.promoted.is_none() && is_function { - write!(w, "(")?; - - // fn argument types. - for (i, arg) in body.args_iter().enumerate() { - if i != 0 { - write!(w, ", ")?; - } - write!(w, "{:?}: {}", Place::from(arg), body.local_decls[arg].ty)?; - } - - write!(w, ") -> {}", body.return_ty())?; - } else { - assert_eq!(body.arg_count, 0); - write!(w, ": {} =", body.return_ty())?; - } - - if let Some(yield_ty) = body.yield_ty() { - writeln!(w)?; - writeln!(w, "yields {yield_ty}")?; - } - - write!(w, " ")?; - // Next thing that gets printed is the opening { - - Ok(()) -} - -fn write_user_type_annotations( - tcx: TyCtxt<'_>, - body: &Body<'_>, - w: &mut dyn Write, -) -> io::Result<()> { - if !body.user_type_annotations.is_empty() { - writeln!(w, "| User Type Annotations")?; - } - for (index, annotation) in body.user_type_annotations.iter_enumerated() { - writeln!( - w, - "| {:?}: user_ty: {}, span: {}, inferred_ty: {}", - index.index(), - annotation.user_ty, - tcx.sess.source_map().span_to_embeddable_string(annotation.span), - with_no_trimmed_paths!(format!("{}", annotation.inferred_ty)), - )?; - } - if !body.user_type_annotations.is_empty() { - writeln!(w, "|")?; - } - Ok(()) -} - -pub fn dump_mir_def_ids(tcx: TyCtxt<'_>, single: Option) -> Vec { - if let Some(i) = single { - vec![i] - } else { - tcx.mir_keys(()).iter().map(|def_id| def_id.to_def_id()).collect() - } -} +/////////////////////////////////////////////////////////////////////////// +// Constants fn pretty_print_byte_str(fmt: &mut Formatter<'_>, byte_str: &[u8]) -> fmt::Result { write!(fmt, "b\"{}\"", byte_str.escape_ascii()) @@ -1244,6 +1757,9 @@ pub(crate) fn pretty_print_const_value<'tcx>( }) } +/////////////////////////////////////////////////////////////////////////// +// Miscellaneous + /// Calc converted u64 decimal into hex and return it's length in chars /// /// ```ignore (cannot-test-private-function) diff --git a/compiler/rustc_middle/src/mir/statement.rs b/compiler/rustc_middle/src/mir/statement.rs new file mode 100644 index 000000000000..25534f469604 --- /dev/null +++ b/compiler/rustc_middle/src/mir/statement.rs @@ -0,0 +1,441 @@ +/// Functionality for statements, operands, places, and things that appear in them. +use super::*; + +/////////////////////////////////////////////////////////////////////////// +// Statements + +/// A statement in a basic block, including information about its source code. +#[derive(Clone, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)] +pub struct Statement<'tcx> { + pub source_info: SourceInfo, + pub kind: StatementKind<'tcx>, +} + +impl Statement<'_> { + /// Changes a statement to a nop. This is both faster than deleting instructions and avoids + /// invalidating statement indices in `Location`s. + pub fn make_nop(&mut self) { + self.kind = StatementKind::Nop + } + + /// Changes a statement to a nop and returns the original statement. + #[must_use = "If you don't need the statement, use `make_nop` instead"] + pub fn replace_nop(&mut self) -> Self { + Statement { + source_info: self.source_info, + kind: mem::replace(&mut self.kind, StatementKind::Nop), + } + } +} + +impl<'tcx> StatementKind<'tcx> { + pub fn as_assign_mut(&mut self) -> Option<&mut (Place<'tcx>, Rvalue<'tcx>)> { + match self { + StatementKind::Assign(x) => Some(x), + _ => None, + } + } + + pub fn as_assign(&self) -> Option<&(Place<'tcx>, Rvalue<'tcx>)> { + match self { + StatementKind::Assign(x) => Some(x), + _ => None, + } + } +} + +/////////////////////////////////////////////////////////////////////////// +// Places + +impl ProjectionElem { + /// Returns `true` if the target of this projection may refer to a different region of memory + /// than the base. + fn is_indirect(&self) -> bool { + match self { + Self::Deref => true, + + Self::Field(_, _) + | Self::Index(_) + | Self::OpaqueCast(_) + | Self::ConstantIndex { .. } + | Self::Subslice { .. } + | Self::Downcast(_, _) => false, + } + } + + /// Returns `true` if the target of this projection always refers to the same memory region + /// whatever the state of the program. + pub fn is_stable_offset(&self) -> bool { + match self { + Self::Deref | Self::Index(_) => false, + Self::Field(_, _) + | Self::OpaqueCast(_) + | Self::ConstantIndex { .. } + | Self::Subslice { .. } + | Self::Downcast(_, _) => true, + } + } + + /// Returns `true` if this is a `Downcast` projection with the given `VariantIdx`. + pub fn is_downcast_to(&self, v: VariantIdx) -> bool { + matches!(*self, Self::Downcast(_, x) if x == v) + } + + /// Returns `true` if this is a `Field` projection with the given index. + pub fn is_field_to(&self, f: FieldIdx) -> bool { + matches!(*self, Self::Field(x, _) if x == f) + } + + /// Returns `true` if this is accepted inside `VarDebugInfoContents::Place`. + pub fn can_use_in_debuginfo(&self) -> bool { + match self { + Self::ConstantIndex { from_end: false, .. } + | Self::Deref + | Self::Downcast(_, _) + | Self::Field(_, _) => true, + Self::ConstantIndex { from_end: true, .. } + | Self::Index(_) + | Self::OpaqueCast(_) + | Self::Subslice { .. } => false, + } + } +} + +/// Alias for projections as they appear in `UserTypeProjection`, where we +/// need neither the `V` parameter for `Index` nor the `T` for `Field`. +pub type ProjectionKind = ProjectionElem<(), ()>; + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct PlaceRef<'tcx> { + pub local: Local, + pub projection: &'tcx [PlaceElem<'tcx>], +} + +// Once we stop implementing `Ord` for `DefId`, +// this impl will be unnecessary. Until then, we'll +// leave this impl in place to prevent re-adding a +// dependency on the `Ord` impl for `DefId` +impl<'tcx> !PartialOrd for PlaceRef<'tcx> {} + +impl<'tcx> Place<'tcx> { + // FIXME change this to a const fn by also making List::empty a const fn. + pub fn return_place() -> Place<'tcx> { + Place { local: RETURN_PLACE, projection: List::empty() } + } + + /// Returns `true` if this `Place` contains a `Deref` projection. + /// + /// If `Place::is_indirect` returns false, the caller knows that the `Place` refers to the + /// same region of memory as its base. + pub fn is_indirect(&self) -> bool { + self.projection.iter().any(|elem| elem.is_indirect()) + } + + /// Returns `true` if this `Place`'s first projection is `Deref`. + /// + /// This is useful because for MIR phases `AnalysisPhase::PostCleanup` and later, + /// `Deref` projections can only occur as the first projection. In that case this method + /// is equivalent to `is_indirect`, but faster. + pub fn is_indirect_first_projection(&self) -> bool { + self.as_ref().is_indirect_first_projection() + } + + /// Finds the innermost `Local` from this `Place`, *if* it is either a local itself or + /// a single deref of a local. + #[inline(always)] + pub fn local_or_deref_local(&self) -> Option { + self.as_ref().local_or_deref_local() + } + + /// If this place represents a local variable like `_X` with no + /// projections, return `Some(_X)`. + #[inline(always)] + pub fn as_local(&self) -> Option { + self.as_ref().as_local() + } + + #[inline] + pub fn as_ref(&self) -> PlaceRef<'tcx> { + PlaceRef { local: self.local, projection: &self.projection } + } + + /// Iterate over the projections in evaluation order, i.e., the first element is the base with + /// its projection and then subsequently more projections are added. + /// As a concrete example, given the place a.b.c, this would yield: + /// - (a, .b) + /// - (a.b, .c) + /// + /// Given a place without projections, the iterator is empty. + #[inline] + pub fn iter_projections( + self, + ) -> impl Iterator, PlaceElem<'tcx>)> + DoubleEndedIterator { + self.as_ref().iter_projections() + } + + /// Generates a new place by appending `more_projections` to the existing ones + /// and interning the result. + pub fn project_deeper(self, more_projections: &[PlaceElem<'tcx>], tcx: TyCtxt<'tcx>) -> Self { + if more_projections.is_empty() { + return self; + } + + self.as_ref().project_deeper(more_projections, tcx) + } +} + +impl From for Place<'_> { + #[inline] + fn from(local: Local) -> Self { + Place { local, projection: List::empty() } + } +} + +impl<'tcx> PlaceRef<'tcx> { + /// Finds the innermost `Local` from this `Place`, *if* it is either a local itself or + /// a single deref of a local. + pub fn local_or_deref_local(&self) -> Option { + match *self { + PlaceRef { local, projection: [] } + | PlaceRef { local, projection: [ProjectionElem::Deref] } => Some(local), + _ => None, + } + } + + /// Returns `true` if this `Place` contains a `Deref` projection. + /// + /// If `Place::is_indirect` returns false, the caller knows that the `Place` refers to the + /// same region of memory as its base. + pub fn is_indirect(&self) -> bool { + self.projection.iter().any(|elem| elem.is_indirect()) + } + + /// Returns `true` if this `Place`'s first projection is `Deref`. + /// + /// This is useful because for MIR phases `AnalysisPhase::PostCleanup` and later, + /// `Deref` projections can only occur as the first projection. In that case this method + /// is equivalent to `is_indirect`, but faster. + pub fn is_indirect_first_projection(&self) -> bool { + // To make sure this is not accidentally used in wrong mir phase + debug_assert!( + self.projection.is_empty() || !self.projection[1..].contains(&PlaceElem::Deref) + ); + self.projection.first() == Some(&PlaceElem::Deref) + } + + /// If this place represents a local variable like `_X` with no + /// projections, return `Some(_X)`. + #[inline] + pub fn as_local(&self) -> Option { + match *self { + PlaceRef { local, projection: [] } => Some(local), + _ => None, + } + } + + #[inline] + pub fn last_projection(&self) -> Option<(PlaceRef<'tcx>, PlaceElem<'tcx>)> { + if let &[ref proj_base @ .., elem] = self.projection { + Some((PlaceRef { local: self.local, projection: proj_base }, elem)) + } else { + None + } + } + + /// Iterate over the projections in evaluation order, i.e., the first element is the base with + /// its projection and then subsequently more projections are added. + /// As a concrete example, given the place a.b.c, this would yield: + /// - (a, .b) + /// - (a.b, .c) + /// + /// Given a place without projections, the iterator is empty. + #[inline] + pub fn iter_projections( + self, + ) -> impl Iterator, PlaceElem<'tcx>)> + DoubleEndedIterator { + self.projection.iter().enumerate().map(move |(i, proj)| { + let base = PlaceRef { local: self.local, projection: &self.projection[..i] }; + (base, *proj) + }) + } + + /// Generates a new place by appending `more_projections` to the existing ones + /// and interning the result. + pub fn project_deeper( + self, + more_projections: &[PlaceElem<'tcx>], + tcx: TyCtxt<'tcx>, + ) -> Place<'tcx> { + let mut v: Vec>; + + let new_projections = if self.projection.is_empty() { + more_projections + } else { + v = Vec::with_capacity(self.projection.len() + more_projections.len()); + v.extend(self.projection); + v.extend(more_projections); + &v + }; + + Place { local: self.local, projection: tcx.mk_place_elems(new_projections) } + } +} + +impl From for PlaceRef<'_> { + #[inline] + fn from(local: Local) -> Self { + PlaceRef { local, projection: &[] } + } +} + +/////////////////////////////////////////////////////////////////////////// +// Operands + +impl<'tcx> Operand<'tcx> { + /// Convenience helper to make a constant that refers to the fn + /// with given `DefId` and args. Since this is used to synthesize + /// MIR, assumes `user_ty` is None. + pub fn function_handle( + tcx: TyCtxt<'tcx>, + def_id: DefId, + args: impl IntoIterator>, + span: Span, + ) -> Self { + let ty = Ty::new_fn_def(tcx, def_id, args); + Operand::Constant(Box::new(Constant { + span, + user_ty: None, + literal: ConstantKind::Val(ConstValue::ZeroSized, ty), + })) + } + + pub fn is_move(&self) -> bool { + matches!(self, Operand::Move(..)) + } + + /// Convenience helper to make a literal-like constant from a given scalar value. + /// Since this is used to synthesize MIR, assumes `user_ty` is None. + pub fn const_from_scalar( + tcx: TyCtxt<'tcx>, + ty: Ty<'tcx>, + val: Scalar, + span: Span, + ) -> Operand<'tcx> { + debug_assert!({ + let param_env_and_ty = ty::ParamEnv::empty().and(ty); + let type_size = tcx + .layout_of(param_env_and_ty) + .unwrap_or_else(|e| panic!("could not compute layout for {ty:?}: {e:?}")) + .size; + let scalar_size = match val { + Scalar::Int(int) => int.size(), + _ => panic!("Invalid scalar type {val:?}"), + }; + scalar_size == type_size + }); + Operand::Constant(Box::new(Constant { + span, + user_ty: None, + literal: ConstantKind::Val(ConstValue::Scalar(val), ty), + })) + } + + pub fn to_copy(&self) -> Self { + match *self { + Operand::Copy(_) | Operand::Constant(_) => self.clone(), + Operand::Move(place) => Operand::Copy(place), + } + } + + /// Returns the `Place` that is the target of this `Operand`, or `None` if this `Operand` is a + /// constant. + pub fn place(&self) -> Option> { + match self { + Operand::Copy(place) | Operand::Move(place) => Some(*place), + Operand::Constant(_) => None, + } + } + + /// Returns the `Constant` that is the target of this `Operand`, or `None` if this `Operand` is a + /// place. + pub fn constant(&self) -> Option<&Constant<'tcx>> { + match self { + Operand::Constant(x) => Some(&**x), + Operand::Copy(_) | Operand::Move(_) => None, + } + } + + /// Gets the `ty::FnDef` from an operand if it's a constant function item. + /// + /// While this is unlikely in general, it's the normal case of what you'll + /// find as the `func` in a [`TerminatorKind::Call`]. + pub fn const_fn_def(&self) -> Option<(DefId, GenericArgsRef<'tcx>)> { + let const_ty = self.constant()?.literal.ty(); + if let ty::FnDef(def_id, args) = *const_ty.kind() { Some((def_id, args)) } else { None } + } +} + +/////////////////////////////////////////////////////////////////////////// +/// Rvalues + +impl<'tcx> Rvalue<'tcx> { + /// Returns true if rvalue can be safely removed when the result is unused. + #[inline] + pub fn is_safe_to_remove(&self) -> bool { + match self { + // Pointer to int casts may be side-effects due to exposing the provenance. + // While the model is undecided, we should be conservative. See + // + Rvalue::Cast(CastKind::PointerExposeAddress, _, _) => false, + + Rvalue::Use(_) + | Rvalue::CopyForDeref(_) + | Rvalue::Repeat(_, _) + | Rvalue::Ref(_, _, _) + | Rvalue::ThreadLocalRef(_) + | Rvalue::AddressOf(_, _) + | Rvalue::Len(_) + | Rvalue::Cast( + CastKind::IntToInt + | CastKind::FloatToInt + | CastKind::FloatToFloat + | CastKind::IntToFloat + | CastKind::FnPtrToPtr + | CastKind::PtrToPtr + | CastKind::PointerCoercion(_) + | CastKind::PointerFromExposedAddress + | CastKind::DynStar + | CastKind::Transmute, + _, + _, + ) + | Rvalue::BinaryOp(_, _) + | Rvalue::CheckedBinaryOp(_, _) + | Rvalue::NullaryOp(_, _) + | Rvalue::UnaryOp(_, _) + | Rvalue::Discriminant(_) + | Rvalue::Aggregate(_, _) + | Rvalue::ShallowInitBox(_, _) => true, + } + } +} + +impl BorrowKind { + pub fn mutability(&self) -> Mutability { + match *self { + BorrowKind::Shared | BorrowKind::Shallow => Mutability::Not, + BorrowKind::Mut { .. } => Mutability::Mut, + } + } + + pub fn allows_two_phase_borrow(&self) -> bool { + match *self { + BorrowKind::Shared + | BorrowKind::Shallow + | BorrowKind::Mut { kind: MutBorrowKind::Default | MutBorrowKind::ClosureCapture } => { + false + } + BorrowKind::Mut { kind: MutBorrowKind::TwoPhaseBorrow } => true, + } + } +} diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 79b64a491f4e..f99084d0eb7a 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -3,7 +3,7 @@ //! This is in a dedicated file so that changes to this file can be reviewed more carefully. //! The intention is that this file only contains datatype declarations, no code. -use super::{BasicBlock, Constant, Local, SwitchTargets, UserTypeProjection}; +use super::{BasicBlock, Constant, Local, UserTypeProjection}; use crate::mir::coverage::{CodeRegion, CoverageKind}; use crate::traits::Reveal; @@ -24,6 +24,7 @@ use rustc_span::def_id::LocalDefId; use rustc_span::symbol::Symbol; use rustc_span::Span; use rustc_target::asm::InlineAsmRegOrRegClass; +use smallvec::SmallVec; /// Represents the "flavors" of MIR. /// @@ -828,6 +829,27 @@ impl TerminatorKind<'_> { } } +#[derive(Debug, Clone, TyEncodable, TyDecodable, Hash, HashStable, PartialEq)] +pub struct SwitchTargets { + /// Possible values. The locations to branch to in each case + /// are found in the corresponding indices from the `targets` vector. + pub(super) values: SmallVec<[u128; 1]>, + + /// Possible branch sites. The last element of this vector is used + /// for the otherwise branch, so targets.len() == values.len() + 1 + /// should hold. + // + // This invariant is quite non-obvious and also could be improved. + // One way to make this invariant is to have something like this instead: + // + // branches: Vec<(ConstInt, BasicBlock)>, + // otherwise: Option // exhaustive if None + // + // However we’ve decided to keep this as-is until we figure a case + // where some other approach seems to be strictly better than other. + pub(super) targets: SmallVec<[BasicBlock; 2]>, +} + /// Action to be taken when a stack unwind happens. #[derive(Copy, Clone, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)] #[derive(TypeFoldable, TypeVisitable)] diff --git a/compiler/rustc_middle/src/mir/terminator.rs b/compiler/rustc_middle/src/mir/terminator.rs index 7eddf13b3fb5..02aab4a892d0 100644 --- a/compiler/rustc_middle/src/mir/terminator.rs +++ b/compiler/rustc_middle/src/mir/terminator.rs @@ -1,39 +1,16 @@ +/// Functionality for terminators and helper types that appear in terminators. use rustc_hir::LangItem; use smallvec::SmallVec; use super::{BasicBlock, InlineAsmOperand, Operand, SourceInfo, TerminatorKind, UnwindAction}; -use rustc_ast::InlineAsmTemplatePiece; pub use rustc_ast::Mutability; use rustc_macros::HashStable; -use std::borrow::Cow; -use std::fmt::{self, Debug, Formatter, Write}; use std::iter; use std::slice; pub use super::query::*; use super::*; -#[derive(Debug, Clone, TyEncodable, TyDecodable, Hash, HashStable, PartialEq)] -pub struct SwitchTargets { - /// Possible values. The locations to branch to in each case - /// are found in the corresponding indices from the `targets` vector. - values: SmallVec<[u128; 1]>, - - /// Possible branch sites. The last element of this vector is used - /// for the otherwise branch, so targets.len() == values.len() + 1 - /// should hold. - // - // This invariant is quite non-obvious and also could be improved. - // One way to make this invariant is to have something like this instead: - // - // branches: Vec<(ConstInt, BasicBlock)>, - // otherwise: Option // exhaustive if None - // - // However we’ve decided to keep this as-is until we figure a case - // where some other approach seems to be strictly better than other. - targets: SmallVec<[BasicBlock; 2]>, -} - impl SwitchTargets { /// Creates switch targets from an iterator of values and target blocks. /// @@ -135,6 +112,168 @@ impl UnwindTerminateReason { } } +impl AssertKind { + /// Returns true if this an overflow checking assertion controlled by -C overflow-checks. + pub fn is_optional_overflow_check(&self) -> bool { + use AssertKind::*; + use BinOp::*; + matches!(self, OverflowNeg(..) | Overflow(Add | Sub | Mul | Shl | Shr, ..)) + } + + /// Get the message that is printed at runtime when this assertion fails. + /// + /// The caller is expected to handle `BoundsCheck` and `MisalignedPointerDereference` by + /// invoking the appropriate lang item (panic_bounds_check/panic_misaligned_pointer_dereference) + /// instead of printing a static message. + pub fn description(&self) -> &'static str { + use AssertKind::*; + match self { + Overflow(BinOp::Add, _, _) => "attempt to add with overflow", + Overflow(BinOp::Sub, _, _) => "attempt to subtract with overflow", + Overflow(BinOp::Mul, _, _) => "attempt to multiply with overflow", + Overflow(BinOp::Div, _, _) => "attempt to divide with overflow", + Overflow(BinOp::Rem, _, _) => "attempt to calculate the remainder with overflow", + OverflowNeg(_) => "attempt to negate with overflow", + Overflow(BinOp::Shr, _, _) => "attempt to shift right with overflow", + Overflow(BinOp::Shl, _, _) => "attempt to shift left with overflow", + Overflow(op, _, _) => bug!("{:?} cannot overflow", op), + DivisionByZero(_) => "attempt to divide by zero", + RemainderByZero(_) => "attempt to calculate the remainder with a divisor of zero", + ResumedAfterReturn(GeneratorKind::Gen) => "generator resumed after completion", + ResumedAfterReturn(GeneratorKind::Async(_)) => "`async fn` resumed after completion", + ResumedAfterPanic(GeneratorKind::Gen) => "generator resumed after panicking", + ResumedAfterPanic(GeneratorKind::Async(_)) => "`async fn` resumed after panicking", + BoundsCheck { .. } | MisalignedPointerDereference { .. } => { + bug!("Unexpected AssertKind") + } + } + } + + /// Format the message arguments for the `assert(cond, msg..)` terminator in MIR printing. + /// + /// Needs to be kept in sync with the run-time behavior (which is defined by + /// `AssertKind::description` and the lang items mentioned in its docs). + /// Note that we deliberately show more details here than we do at runtime, such as the actual + /// numbers that overflowed -- it is much easier to do so here than at runtime. + pub fn fmt_assert_args(&self, f: &mut W) -> fmt::Result + where + O: Debug, + { + use AssertKind::*; + match self { + BoundsCheck { ref len, ref index } => write!( + f, + "\"index out of bounds: the length is {{}} but the index is {{}}\", {len:?}, {index:?}" + ), + + OverflowNeg(op) => { + write!(f, "\"attempt to negate `{{}}`, which would overflow\", {op:?}") + } + DivisionByZero(op) => write!(f, "\"attempt to divide `{{}}` by zero\", {op:?}"), + RemainderByZero(op) => write!( + f, + "\"attempt to calculate the remainder of `{{}}` with a divisor of zero\", {op:?}" + ), + Overflow(BinOp::Add, l, r) => write!( + f, + "\"attempt to compute `{{}} + {{}}`, which would overflow\", {l:?}, {r:?}" + ), + Overflow(BinOp::Sub, l, r) => write!( + f, + "\"attempt to compute `{{}} - {{}}`, which would overflow\", {l:?}, {r:?}" + ), + Overflow(BinOp::Mul, l, r) => write!( + f, + "\"attempt to compute `{{}} * {{}}`, which would overflow\", {l:?}, {r:?}" + ), + Overflow(BinOp::Div, l, r) => write!( + f, + "\"attempt to compute `{{}} / {{}}`, which would overflow\", {l:?}, {r:?}" + ), + Overflow(BinOp::Rem, l, r) => write!( + f, + "\"attempt to compute the remainder of `{{}} % {{}}`, which would overflow\", {l:?}, {r:?}" + ), + Overflow(BinOp::Shr, _, r) => { + write!(f, "\"attempt to shift right by `{{}}`, which would overflow\", {r:?}") + } + Overflow(BinOp::Shl, _, r) => { + write!(f, "\"attempt to shift left by `{{}}`, which would overflow\", {r:?}") + } + MisalignedPointerDereference { required, found } => { + write!( + f, + "\"misaligned pointer dereference: address must be a multiple of {{}} but is {{}}\", {required:?}, {found:?}" + ) + } + _ => write!(f, "\"{}\"", self.description()), + } + } + + /// Format the diagnostic message for use in a lint (e.g. when the assertion fails during const-eval). + /// + /// Needs to be kept in sync with the run-time behavior (which is defined by + /// `AssertKind::description` and the lang items mentioned in its docs). + /// Note that we deliberately show more details here than we do at runtime, such as the actual + /// numbers that overflowed -- it is much easier to do so here than at runtime. + pub fn diagnostic_message(&self) -> DiagnosticMessage { + use crate::fluent_generated::*; + use AssertKind::*; + + match self { + BoundsCheck { .. } => middle_bounds_check, + Overflow(BinOp::Shl, _, _) => middle_assert_shl_overflow, + Overflow(BinOp::Shr, _, _) => middle_assert_shr_overflow, + Overflow(_, _, _) => middle_assert_op_overflow, + OverflowNeg(_) => middle_assert_overflow_neg, + DivisionByZero(_) => middle_assert_divide_by_zero, + RemainderByZero(_) => middle_assert_remainder_by_zero, + ResumedAfterReturn(GeneratorKind::Async(_)) => middle_assert_async_resume_after_return, + ResumedAfterReturn(GeneratorKind::Gen) => middle_assert_generator_resume_after_return, + ResumedAfterPanic(GeneratorKind::Async(_)) => middle_assert_async_resume_after_panic, + ResumedAfterPanic(GeneratorKind::Gen) => middle_assert_generator_resume_after_panic, + + MisalignedPointerDereference { .. } => middle_assert_misaligned_ptr_deref, + } + } + + pub fn add_args(self, adder: &mut dyn FnMut(Cow<'static, str>, DiagnosticArgValue<'static>)) + where + O: fmt::Debug, + { + use AssertKind::*; + + macro_rules! add { + ($name: expr, $value: expr) => { + adder($name.into(), $value.into_diagnostic_arg()); + }; + } + + match self { + BoundsCheck { len, index } => { + add!("len", format!("{len:?}")); + add!("index", format!("{index:?}")); + } + Overflow(BinOp::Shl | BinOp::Shr, _, val) + | DivisionByZero(val) + | RemainderByZero(val) + | OverflowNeg(val) => { + add!("val", format!("{val:#?}")); + } + Overflow(binop, left, right) => { + add!("op", binop.to_hir_binop().as_str()); + add!("left", format!("{left:#?}")); + add!("right", format!("{right:#?}")); + } + ResumedAfterReturn(_) | ResumedAfterPanic(_) => {} + MisalignedPointerDereference { required, found } => { + add!("required", format!("{required:#?}")); + add!("found", format!("{found:#?}")); + } + } + } +} + #[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)] pub struct Terminator<'tcx> { pub source_info: SourceInfo, @@ -299,187 +438,6 @@ impl<'tcx> TerminatorKind<'tcx> { } } -impl<'tcx> Debug for TerminatorKind<'tcx> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - self.fmt_head(fmt)?; - let successor_count = self.successors().count(); - let labels = self.fmt_successor_labels(); - assert_eq!(successor_count, labels.len()); - - // `Cleanup` is already included in successors - let show_unwind = !matches!(self.unwind(), None | Some(UnwindAction::Cleanup(_))); - let fmt_unwind = |fmt: &mut Formatter<'_>| -> fmt::Result { - write!(fmt, "unwind ")?; - match self.unwind() { - // Not needed or included in successors - None | Some(UnwindAction::Cleanup(_)) => unreachable!(), - Some(UnwindAction::Continue) => write!(fmt, "continue"), - Some(UnwindAction::Unreachable) => write!(fmt, "unreachable"), - Some(UnwindAction::Terminate(reason)) => { - write!(fmt, "terminate({})", reason.as_short_str()) - } - } - }; - - match (successor_count, show_unwind) { - (0, false) => Ok(()), - (0, true) => { - write!(fmt, " -> ")?; - fmt_unwind(fmt) - } - (1, false) => write!(fmt, " -> {:?}", self.successors().next().unwrap()), - _ => { - write!(fmt, " -> [")?; - for (i, target) in self.successors().enumerate() { - if i > 0 { - write!(fmt, ", ")?; - } - write!(fmt, "{}: {:?}", labels[i], target)?; - } - if show_unwind { - write!(fmt, ", ")?; - fmt_unwind(fmt)?; - } - write!(fmt, "]") - } - } - } -} - -impl<'tcx> TerminatorKind<'tcx> { - /// Writes the "head" part of the terminator; that is, its name and the data it uses to pick the - /// successor basic block, if any. The only information not included is the list of possible - /// successors, which may be rendered differently between the text and the graphviz format. - pub fn fmt_head(&self, fmt: &mut W) -> fmt::Result { - use self::TerminatorKind::*; - match self { - Goto { .. } => write!(fmt, "goto"), - SwitchInt { discr, .. } => write!(fmt, "switchInt({discr:?})"), - Return => write!(fmt, "return"), - GeneratorDrop => write!(fmt, "generator_drop"), - UnwindResume => write!(fmt, "resume"), - UnwindTerminate(reason) => { - write!(fmt, "abort({})", reason.as_short_str()) - } - Yield { value, resume_arg, .. } => write!(fmt, "{resume_arg:?} = yield({value:?})"), - Unreachable => write!(fmt, "unreachable"), - Drop { place, .. } => write!(fmt, "drop({place:?})"), - Call { func, args, destination, .. } => { - write!(fmt, "{destination:?} = ")?; - write!(fmt, "{func:?}(")?; - for (index, arg) in args.iter().enumerate() { - if index > 0 { - write!(fmt, ", ")?; - } - write!(fmt, "{arg:?}")?; - } - write!(fmt, ")") - } - Assert { cond, expected, msg, .. } => { - write!(fmt, "assert(")?; - if !expected { - write!(fmt, "!")?; - } - write!(fmt, "{cond:?}, ")?; - msg.fmt_assert_args(fmt)?; - write!(fmt, ")") - } - FalseEdge { .. } => write!(fmt, "falseEdge"), - FalseUnwind { .. } => write!(fmt, "falseUnwind"), - InlineAsm { template, ref operands, options, .. } => { - write!(fmt, "asm!(\"{}\"", InlineAsmTemplatePiece::to_string(template))?; - for op in operands { - write!(fmt, ", ")?; - let print_late = |&late| if late { "late" } else { "" }; - match op { - InlineAsmOperand::In { reg, value } => { - write!(fmt, "in({reg}) {value:?}")?; - } - InlineAsmOperand::Out { reg, late, place: Some(place) } => { - write!(fmt, "{}out({}) {:?}", print_late(late), reg, place)?; - } - InlineAsmOperand::Out { reg, late, place: None } => { - write!(fmt, "{}out({}) _", print_late(late), reg)?; - } - InlineAsmOperand::InOut { - reg, - late, - in_value, - out_place: Some(out_place), - } => { - write!( - fmt, - "in{}out({}) {:?} => {:?}", - print_late(late), - reg, - in_value, - out_place - )?; - } - InlineAsmOperand::InOut { reg, late, in_value, out_place: None } => { - write!(fmt, "in{}out({}) {:?} => _", print_late(late), reg, in_value)?; - } - InlineAsmOperand::Const { value } => { - write!(fmt, "const {value:?}")?; - } - InlineAsmOperand::SymFn { value } => { - write!(fmt, "sym_fn {value:?}")?; - } - InlineAsmOperand::SymStatic { def_id } => { - write!(fmt, "sym_static {def_id:?}")?; - } - } - } - write!(fmt, ", options({options:?}))") - } - } - } - - /// Returns the list of labels for the edges to the successor basic blocks. - pub fn fmt_successor_labels(&self) -> Vec> { - use self::TerminatorKind::*; - match *self { - Return | UnwindResume | UnwindTerminate(_) | Unreachable | GeneratorDrop => vec![], - Goto { .. } => vec!["".into()], - SwitchInt { ref targets, .. } => targets - .values - .iter() - .map(|&u| Cow::Owned(u.to_string())) - .chain(iter::once("otherwise".into())) - .collect(), - Call { target: Some(_), unwind: UnwindAction::Cleanup(_), .. } => { - vec!["return".into(), "unwind".into()] - } - Call { target: Some(_), unwind: _, .. } => vec!["return".into()], - Call { target: None, unwind: UnwindAction::Cleanup(_), .. } => vec!["unwind".into()], - Call { target: None, unwind: _, .. } => vec![], - Yield { drop: Some(_), .. } => vec!["resume".into(), "drop".into()], - Yield { drop: None, .. } => vec!["resume".into()], - Drop { unwind: UnwindAction::Cleanup(_), .. } => vec!["return".into(), "unwind".into()], - Drop { unwind: _, .. } => vec!["return".into()], - Assert { unwind: UnwindAction::Cleanup(_), .. } => { - vec!["success".into(), "unwind".into()] - } - Assert { unwind: _, .. } => vec!["success".into()], - FalseEdge { .. } => vec!["real".into(), "imaginary".into()], - FalseUnwind { unwind: UnwindAction::Cleanup(_), .. } => { - vec!["real".into(), "unwind".into()] - } - FalseUnwind { unwind: _, .. } => vec!["real".into()], - InlineAsm { destination: Some(_), unwind: UnwindAction::Cleanup(_), .. } => { - vec!["return".into(), "unwind".into()] - } - InlineAsm { destination: Some(_), unwind: _, .. } => { - vec!["return".into()] - } - InlineAsm { destination: None, unwind: UnwindAction::Cleanup(_), .. } => { - vec!["unwind".into()] - } - InlineAsm { destination: None, unwind: _, .. } => vec![], - } - } -} - #[derive(Copy, Clone, Debug)] pub enum TerminatorEdges<'mir, 'tcx> { /// For terminators that have no successor, like `return`. From 5a0a1ff0cdb37ffb5fe970a4bcbde9df4ef1d602 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 16 Sep 2023 09:36:22 +0200 Subject: [PATCH 062/111] move ConstValue into mir this way we have mir::ConstValue and ty::ValTree as reasonably parallel --- .../rustc_codegen_cranelift/src/constant.rs | 3 +- compiler/rustc_codegen_ssa/src/common.rs | 6 +- .../rustc_codegen_ssa/src/mir/constant.rs | 4 +- compiler/rustc_codegen_ssa/src/mir/operand.rs | 6 +- .../src/const_eval/eval_queries.rs | 7 +- .../rustc_const_eval/src/const_eval/mod.rs | 8 +- .../src/const_eval/valtrees.rs | 13 +- .../src/interpret/intrinsics.rs | 6 +- .../rustc_const_eval/src/interpret/operand.rs | 15 +- .../rustc_const_eval/src/interpret/place.rs | 9 +- compiler/rustc_middle/src/mir/consts.rs | 165 +++++++++++++++++- .../rustc_middle/src/mir/interpret/error.rs | 4 +- .../rustc_middle/src/mir/interpret/mod.rs | 2 +- .../rustc_middle/src/mir/interpret/value.rs | 158 +---------------- compiler/rustc_middle/src/mir/mod.rs | 2 +- compiler/rustc_middle/src/mir/pretty.rs | 4 +- compiler/rustc_middle/src/mir/query.rs | 3 +- compiler/rustc_middle/src/query/erase.rs | 16 +- compiler/rustc_middle/src/query/keys.rs | 5 +- compiler/rustc_middle/src/query/mod.rs | 8 +- .../src/build/custom/parse/instruction.rs | 2 +- .../src/build/expr/as_constant.rs | 4 +- compiler/rustc_mir_build/src/build/mod.rs | 1 - .../rustc_mir_build/src/thir/pattern/mod.rs | 6 +- .../src/check_alignment.rs | 2 +- .../rustc_mir_transform/src/const_prop.rs | 4 +- .../src/dataflow_const_prop.rs | 2 +- .../rustc_mir_transform/src/large_enums.rs | 2 +- .../rustc_mir_transform/src/remove_zsts.rs | 1 - compiler/rustc_monomorphize/src/collector.rs | 13 +- compiler/rustc_smir/src/rustc_smir/alloc.rs | 5 +- src/librustdoc/clean/utils.rs | 9 +- src/tools/clippy/clippy_utils/src/consts.rs | 2 +- src/tools/clippy/clippy_utils/src/ty.rs | 2 +- 34 files changed, 245 insertions(+), 254 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/src/constant.rs b/compiler/rustc_codegen_cranelift/src/constant.rs index 0d9bd3cf2408..02468684ba05 100644 --- a/compiler/rustc_codegen_cranelift/src/constant.rs +++ b/compiler/rustc_codegen_cranelift/src/constant.rs @@ -2,7 +2,8 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; -use rustc_middle::mir::interpret::{read_target_uint, AllocId, ConstValue, GlobalAlloc, Scalar}; +use rustc_middle::mir::interpret::{read_target_uint, AllocId, GlobalAlloc, Scalar}; +use rustc_middle::mir::ConstValue; use cranelift_module::*; diff --git a/compiler/rustc_codegen_ssa/src/common.rs b/compiler/rustc_codegen_ssa/src/common.rs index 5a68075991f1..641ac3eb8087 100644 --- a/compiler/rustc_codegen_ssa/src/common.rs +++ b/compiler/rustc_codegen_ssa/src/common.rs @@ -1,7 +1,7 @@ #![allow(non_camel_case_types)] use rustc_hir::LangItem; -use rustc_middle::mir::interpret::ConstValue; +use rustc_middle::mir; use rustc_middle::ty::{self, layout::TyAndLayout, Ty, TyCtxt}; use rustc_span::Span; @@ -194,10 +194,10 @@ pub fn shift_mask_val<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( pub fn asm_const_to_str<'tcx>( tcx: TyCtxt<'tcx>, sp: Span, - const_value: ConstValue<'tcx>, + const_value: mir::ConstValue<'tcx>, ty_and_layout: TyAndLayout<'tcx>, ) -> String { - let ConstValue::Scalar(scalar) = const_value else { + let mir::ConstValue::Scalar(scalar) = const_value else { span_bug!(sp, "expected Scalar for promoted asm const, but got {:#?}", const_value) }; let value = scalar.assert_bits(ty_and_layout.size); diff --git a/compiler/rustc_codegen_ssa/src/mir/constant.rs b/compiler/rustc_codegen_ssa/src/mir/constant.rs index 263b41ed880f..7fec4047a95c 100644 --- a/compiler/rustc_codegen_ssa/src/mir/constant.rs +++ b/compiler/rustc_codegen_ssa/src/mir/constant.rs @@ -2,7 +2,7 @@ use crate::errors; use crate::mir::operand::OperandRef; use crate::traits::*; use rustc_middle::mir; -use rustc_middle::mir::interpret::{ConstValue, ErrorHandled}; +use rustc_middle::mir::interpret::ErrorHandled; use rustc_middle::ty::layout::HasTyCtxt; use rustc_middle::ty::{self, Ty}; use rustc_target::abi::Abi; @@ -20,7 +20,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { OperandRef::from_const(bx, val, ty) } - pub fn eval_mir_constant(&self, constant: &mir::Constant<'tcx>) -> ConstValue<'tcx> { + pub fn eval_mir_constant(&self, constant: &mir::Constant<'tcx>) -> mir::ConstValue<'tcx> { self.monomorphize(constant.literal) .eval(self.cx.tcx(), ty::ParamEnv::reveal_all(), Some(constant.span)) .expect("erroneous constant not captured by required_consts") diff --git a/compiler/rustc_codegen_ssa/src/mir/operand.rs b/compiler/rustc_codegen_ssa/src/mir/operand.rs index e192d16ff388..926a0bed0da8 100644 --- a/compiler/rustc_codegen_ssa/src/mir/operand.rs +++ b/compiler/rustc_codegen_ssa/src/mir/operand.rs @@ -6,8 +6,8 @@ use crate::glue; use crate::traits::*; use crate::MemFlags; -use rustc_middle::mir; -use rustc_middle::mir::interpret::{alloc_range, ConstValue, Pointer, Scalar}; +use rustc_middle::mir::interpret::{alloc_range, Pointer, Scalar}; +use rustc_middle::mir::{self, ConstValue}; use rustc_middle::ty::layout::{LayoutOf, TyAndLayout}; use rustc_middle::ty::Ty; use rustc_target::abi::{self, Abi, Align, Size}; @@ -86,7 +86,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { pub fn from_const>( bx: &mut Bx, - val: ConstValue<'tcx>, + val: mir::ConstValue<'tcx>, ty: Ty<'tcx>, ) -> Self { let layout = bx.layout_of(ty); diff --git a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs index 807794a2a599..de78b4eb5047 100644 --- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs +++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs @@ -4,9 +4,9 @@ use crate::errors::ConstEvalError; use either::{Left, Right}; use rustc_hir::def::DefKind; -use rustc_middle::mir; use rustc_middle::mir::interpret::{ErrorHandled, InterpErrorInfo}; use rustc_middle::mir::pretty::write_allocation_bytes; +use rustc_middle::mir::{self, ConstAlloc, ConstValue}; use rustc_middle::traits::Reveal; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::print::with_no_trimmed_paths; @@ -18,9 +18,8 @@ use super::{CanAccessStatics, CompileTimeEvalContext, CompileTimeInterpreter}; use crate::errors; use crate::interpret::eval_nullary_intrinsic; use crate::interpret::{ - intern_const_alloc_recursive, ConstAlloc, ConstValue, CtfeValidationMode, GlobalId, Immediate, - InternKind, InterpCx, InterpError, InterpResult, MPlaceTy, MemoryKind, OpTy, RefTracking, - StackPopCleanup, + intern_const_alloc_recursive, CtfeValidationMode, GlobalId, Immediate, InternKind, InterpCx, + InterpError, InterpResult, MPlaceTy, MemoryKind, OpTy, RefTracking, StackPopCleanup, }; // Returns a pointer to where the result lives diff --git a/compiler/rustc_const_eval/src/const_eval/mod.rs b/compiler/rustc_const_eval/src/const_eval/mod.rs index 5327fa5ce39b..886d7972a152 100644 --- a/compiler/rustc_const_eval/src/const_eval/mod.rs +++ b/compiler/rustc_const_eval/src/const_eval/mod.rs @@ -1,7 +1,7 @@ // Not in interpret to make sure we do not use private implementation details use crate::errors::MaxNumNodesInConstErr; -use crate::interpret::{intern_const_alloc_recursive, ConstValue, InternKind, InterpCx, Scalar}; +use crate::interpret::{intern_const_alloc_recursive, InternKind, InterpCx, Scalar}; use rustc_middle::mir; use rustc_middle::mir::interpret::{EvalToValTreeResult, GlobalId}; use rustc_middle::ty::{self, Ty, TyCtxt}; @@ -22,7 +22,7 @@ pub(crate) use valtrees::{const_to_valtree_inner, valtree_to_const_value}; pub(crate) fn const_caller_location( tcx: TyCtxt<'_>, (file, line, col): (Symbol, u32, u32), -) -> ConstValue<'_> { +) -> mir::ConstValue<'_> { trace!("const_caller_location: {}:{}:{}", file, line, col); let mut ecx = mk_eval_cx(tcx, DUMMY_SP, ty::ParamEnv::reveal_all(), CanAccessStatics::No); @@ -30,7 +30,7 @@ pub(crate) fn const_caller_location( if intern_const_alloc_recursive(&mut ecx, InternKind::Constant, &loc_place).is_err() { bug!("intern_const_alloc_recursive should not error in this case") } - ConstValue::Scalar(Scalar::from_maybe_pointer(loc_place.ptr(), &tcx)) + mir::ConstValue::Scalar(Scalar::from_maybe_pointer(loc_place.ptr(), &tcx)) } // We forbid type-level constants that contain more than `VALTREE_MAX_NODES` nodes. @@ -87,7 +87,7 @@ pub(crate) fn eval_to_valtree<'tcx>( #[instrument(skip(tcx), level = "debug")] pub(crate) fn try_destructure_mir_constant_for_diagnostics<'tcx>( tcx: TyCtxt<'tcx>, - val: ConstValue<'tcx>, + val: mir::ConstValue<'tcx>, ty: Ty<'tcx>, ) -> Option> { let param_env = ty::ParamEnv::reveal_all(); diff --git a/compiler/rustc_const_eval/src/const_eval/valtrees.rs b/compiler/rustc_const_eval/src/const_eval/valtrees.rs index 1675b824c525..2fba7455cb2f 100644 --- a/compiler/rustc_const_eval/src/const_eval/valtrees.rs +++ b/compiler/rustc_const_eval/src/const_eval/valtrees.rs @@ -4,9 +4,10 @@ use super::{ValTreeCreationError, ValTreeCreationResult, VALTREE_MAX_NODES}; use crate::const_eval::CanAccessStatics; use crate::interpret::MPlaceTy; use crate::interpret::{ - intern_const_alloc_recursive, ConstValue, ImmTy, Immediate, InternKind, MemPlaceMeta, - MemoryKind, PlaceTy, Projectable, Scalar, + intern_const_alloc_recursive, ImmTy, Immediate, InternKind, MemPlaceMeta, MemoryKind, PlaceTy, + Projectable, Scalar, }; +use rustc_middle::mir; use rustc_middle::ty::layout::{LayoutCx, LayoutOf, TyAndLayout}; use rustc_middle::ty::{self, ScalarInt, Ty, TyCtxt}; use rustc_span::source_map::DUMMY_SP; @@ -206,7 +207,7 @@ pub fn valtree_to_const_value<'tcx>( tcx: TyCtxt<'tcx>, param_env_ty: ty::ParamEnvAnd<'tcx, Ty<'tcx>>, valtree: ty::ValTree<'tcx>, -) -> ConstValue<'tcx> { +) -> mir::ConstValue<'tcx> { // Basic idea: We directly construct `Scalar` values from trivial `ValTree`s // (those for constants with type bool, int, uint, float or char). // For all other types we create an `MPlace` and fill that by walking @@ -219,10 +220,10 @@ pub fn valtree_to_const_value<'tcx>( match ty.kind() { ty::FnDef(..) => { assert!(valtree.unwrap_branch().is_empty()); - ConstValue::ZeroSized + mir::ConstValue::ZeroSized } ty::Bool | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Char => match valtree { - ty::ValTree::Leaf(scalar_int) => ConstValue::Scalar(Scalar::Int(scalar_int)), + ty::ValTree::Leaf(scalar_int) => mir::ConstValue::Scalar(Scalar::Int(scalar_int)), ty::ValTree::Branch(_) => bug!( "ValTrees for Bool, Int, Uint, Float or Char should have the form ValTree::Leaf" ), @@ -237,7 +238,7 @@ pub fn valtree_to_const_value<'tcx>( let layout = tcx.layout_of(param_env_ty).unwrap(); if layout.is_zst() { // Fast path to avoid some allocations. - return ConstValue::ZeroSized; + return mir::ConstValue::ZeroSized; } if layout.abi.is_scalar() && (matches!(ty.kind(), ty::Tuple(_)) diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 3b58f66353b2..f08f1437918f 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -5,10 +5,8 @@ use rustc_hir::def_id::DefId; use rustc_middle::mir::{ self, - interpret::{ - Allocation, ConstAllocation, ConstValue, GlobalId, InterpResult, PointerArithmetic, Scalar, - }, - BinOp, NonDivergingIntrinsic, + interpret::{Allocation, ConstAllocation, GlobalId, InterpResult, PointerArithmetic, Scalar}, + BinOp, ConstValue, NonDivergingIntrinsic, }; use rustc_middle::ty; use rustc_middle::ty::layout::{LayoutOf as _, ValidityRequirement}; diff --git a/compiler/rustc_const_eval/src/interpret/operand.rs b/compiler/rustc_const_eval/src/interpret/operand.rs index 082c0f5b84e5..788b50d7c4af 100644 --- a/compiler/rustc_const_eval/src/interpret/operand.rs +++ b/compiler/rustc_const_eval/src/interpret/operand.rs @@ -13,9 +13,8 @@ use rustc_middle::{mir, ty}; use rustc_target::abi::{self, Abi, Align, HasDataLayout, Size}; use super::{ - alloc_range, from_known_layout, mir_assign_valid_types, AllocId, ConstValue, Frame, InterpCx, - InterpResult, MPlaceTy, Machine, MemPlace, MemPlaceMeta, PlaceTy, Pointer, Projectable, - Provenance, Scalar, + alloc_range, from_known_layout, mir_assign_valid_types, AllocId, Frame, InterpCx, InterpResult, + MPlaceTy, Machine, MemPlace, MemPlaceMeta, PlaceTy, Pointer, Projectable, Provenance, Scalar, }; /// An `Immediate` represents a single immediate self-contained Rust value. @@ -702,7 +701,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { pub(crate) fn const_val_to_op( &self, - val_val: ConstValue<'tcx>, + val_val: mir::ConstValue<'tcx>, ty: Ty<'tcx>, layout: Option>, ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { @@ -715,15 +714,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { }; let layout = from_known_layout(self.tcx, self.param_env, layout, || self.layout_of(ty))?; let op = match val_val { - ConstValue::Indirect { alloc_id, offset } => { + mir::ConstValue::Indirect { alloc_id, offset } => { // We rely on mutability being set correctly in that allocation to prevent writes // where none should happen. let ptr = self.global_base_pointer(Pointer::new(alloc_id, offset))?; Operand::Indirect(MemPlace::from_ptr(ptr.into())) } - ConstValue::Scalar(x) => Operand::Immediate(adjust_scalar(x)?.into()), - ConstValue::ZeroSized => Operand::Immediate(Immediate::Uninit), - ConstValue::Slice { data, start, end } => { + mir::ConstValue::Scalar(x) => Operand::Immediate(adjust_scalar(x)?.into()), + mir::ConstValue::ZeroSized => Operand::Immediate(Immediate::Uninit), + mir::ConstValue::Slice { data, start, end } => { // We rely on mutability being set correctly in `data` to prevent writes // where none should happen. let ptr = Pointer::new( diff --git a/compiler/rustc_const_eval/src/interpret/place.rs b/compiler/rustc_const_eval/src/interpret/place.rs index 0f66df5c30df..fb9aa9d3abe6 100644 --- a/compiler/rustc_const_eval/src/interpret/place.rs +++ b/compiler/rustc_const_eval/src/interpret/place.rs @@ -9,16 +9,15 @@ use either::{Either, Left, Right}; use rustc_ast::Mutability; use rustc_index::IndexSlice; use rustc_middle::mir; -use rustc_middle::mir::interpret::PointerArithmetic; use rustc_middle::ty; use rustc_middle::ty::layout::{LayoutOf, TyAndLayout}; use rustc_middle::ty::Ty; use rustc_target::abi::{Abi, Align, FieldIdx, HasDataLayout, Size, FIRST_VARIANT}; use super::{ - alloc_range, mir_assign_valid_types, AllocId, AllocRef, AllocRefMut, CheckInAllocMsg, - ConstAlloc, ImmTy, Immediate, InterpCx, InterpResult, Machine, MemoryKind, OpTy, Operand, - Pointer, Projectable, Provenance, Readable, Scalar, + alloc_range, mir_assign_valid_types, AllocId, AllocRef, AllocRefMut, CheckInAllocMsg, ImmTy, + Immediate, InterpCx, InterpResult, Machine, MemoryKind, OpTy, Operand, Pointer, + PointerArithmetic, Projectable, Provenance, Readable, Scalar, }; #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)] @@ -1037,7 +1036,7 @@ where pub fn raw_const_to_mplace( &self, - raw: ConstAlloc<'tcx>, + raw: mir::ConstAlloc<'tcx>, ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> { // This must be an allocation in `tcx` let _ = self.tcx.global_alloc(raw.alloc_id); diff --git a/compiler/rustc_middle/src/mir/consts.rs b/compiler/rustc_middle/src/mir/consts.rs index c80419d4c09b..8ea951d0c6ee 100644 --- a/compiler/rustc_middle/src/mir/consts.rs +++ b/compiler/rustc_middle/src/mir/consts.rs @@ -4,14 +4,169 @@ use rustc_hir; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::{self as hir}; use rustc_span::Span; -use rustc_target::abi::Size; +use rustc_target::abi::{HasDataLayout, Size}; -use crate::mir::interpret::{ConstValue, ErrorHandled, GlobalAlloc, Scalar}; -use crate::mir::{interpret, pretty_print_const_value, Promoted}; +use crate::mir::interpret::{ + alloc_range, AllocId, ConstAllocation, ErrorHandled, GlobalAlloc, Scalar, +}; +use crate::mir::{pretty_print_const_value, Promoted}; use crate::ty::{self, print::pretty_print_const, List, Ty, TyCtxt}; use crate::ty::{GenericArgs, GenericArgsRef}; use crate::ty::{ScalarInt, UserTypeAnnotationIndex}; +/////////////////////////////////////////////////////////////////////////// +/// Evaluated Constants + +/// Represents the result of const evaluation via the `eval_to_allocation` query. +/// Not to be confused with `ConstAllocation`, which directly refers to the underlying data! +/// Here we indirect via an `AllocId`. +#[derive(Copy, Clone, HashStable, TyEncodable, TyDecodable, Debug, Hash, Eq, PartialEq)] +pub struct ConstAlloc<'tcx> { + /// The value lives here, at offset 0, and that allocation definitely is an `AllocKind::Memory` + /// (so you can use `AllocMap::unwrap_memory`). + pub alloc_id: AllocId, + pub ty: Ty<'tcx>, +} + +/// Represents a constant value in Rust. `Scalar` and `Slice` are optimizations for +/// array length computations, enum discriminants and the pattern matching logic. +#[derive(Copy, Clone, Debug, Eq, PartialEq, TyEncodable, TyDecodable, Hash)] +#[derive(HashStable, Lift)] +pub enum ConstValue<'tcx> { + /// Used for types with `layout::abi::Scalar` ABI. + /// + /// Not using the enum `Value` to encode that this must not be `Uninit`. + Scalar(Scalar), + + /// Only for ZSTs. + ZeroSized, + + /// Used for `&[u8]` and `&str`. + /// + /// This is worth an optimized representation since Rust has literals of these types. + /// Not having to indirect those through an `AllocId` (or two, if we used `Indirect`) has shown + /// measurable performance improvements on stress tests. + Slice { data: ConstAllocation<'tcx>, start: usize, end: usize }, + + /// A value not representable by the other variants; needs to be stored in-memory. + /// + /// Must *not* be used for scalars or ZST, but having `&str` or other slices in this variant is fine. + Indirect { + /// The backing memory of the value. May contain more memory than needed for just the value + /// if this points into some other larger ConstValue. + /// + /// We use an `AllocId` here instead of a `ConstAllocation<'tcx>` to make sure that when a + /// raw constant (which is basically just an `AllocId`) is turned into a `ConstValue` and + /// back, we can preserve the original `AllocId`. + alloc_id: AllocId, + /// Offset into `alloc` + offset: Size, + }, +} + +#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] +static_assert_size!(ConstValue<'_>, 32); + +impl<'tcx> ConstValue<'tcx> { + #[inline] + pub fn try_to_scalar(&self) -> Option> { + match *self { + ConstValue::Indirect { .. } | ConstValue::Slice { .. } | ConstValue::ZeroSized => None, + ConstValue::Scalar(val) => Some(val), + } + } + + pub fn try_to_scalar_int(&self) -> Option { + self.try_to_scalar()?.try_to_int().ok() + } + + pub fn try_to_bits(&self, size: Size) -> Option { + self.try_to_scalar_int()?.to_bits(size).ok() + } + + pub fn try_to_bool(&self) -> Option { + self.try_to_scalar_int()?.try_into().ok() + } + + pub fn try_to_target_usize(&self, tcx: TyCtxt<'tcx>) -> Option { + self.try_to_scalar_int()?.try_to_target_usize(tcx).ok() + } + + pub fn try_to_bits_for_ty( + &self, + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + ty: Ty<'tcx>, + ) -> Option { + let size = tcx.layout_of(param_env.with_reveal_all_normalized(tcx).and(ty)).ok()?.size; + self.try_to_bits(size) + } + + pub fn from_bool(b: bool) -> Self { + ConstValue::Scalar(Scalar::from_bool(b)) + } + + pub fn from_u64(i: u64) -> Self { + ConstValue::Scalar(Scalar::from_u64(i)) + } + + pub fn from_u128(i: u128) -> Self { + ConstValue::Scalar(Scalar::from_u128(i)) + } + + pub fn from_target_usize(i: u64, cx: &impl HasDataLayout) -> Self { + ConstValue::Scalar(Scalar::from_target_usize(i, cx)) + } + + /// Must only be called on constants of type `&str` or `&[u8]`! + pub fn try_get_slice_bytes_for_diagnostics(&self, tcx: TyCtxt<'tcx>) -> Option<&'tcx [u8]> { + let (data, start, end) = match self { + ConstValue::Scalar(_) | ConstValue::ZeroSized => { + bug!("`try_get_slice_bytes` on non-slice constant") + } + &ConstValue::Slice { data, start, end } => (data, start, end), + &ConstValue::Indirect { alloc_id, offset } => { + // The reference itself is stored behind an indirection. + // Load the reference, and then load the actual slice contents. + let a = tcx.global_alloc(alloc_id).unwrap_memory().inner(); + let ptr_size = tcx.data_layout.pointer_size; + if a.size() < offset + 2 * ptr_size { + // (partially) dangling reference + return None; + } + // Read the wide pointer components. + let ptr = a + .read_scalar( + &tcx, + alloc_range(offset, ptr_size), + /* read_provenance */ true, + ) + .ok()?; + let ptr = ptr.to_pointer(&tcx).ok()?; + let len = a + .read_scalar( + &tcx, + alloc_range(offset + ptr_size, ptr_size), + /* read_provenance */ false, + ) + .ok()?; + let len = len.to_target_usize(&tcx).ok()?; + let len: usize = len.try_into().ok()?; + if len == 0 { + return Some(&[]); + } + // Non-empty slice, must have memory. We know this is a relative pointer. + let (inner_alloc_id, offset) = ptr.into_parts(); + let data = tcx.global_alloc(inner_alloc_id?).unwrap_memory(); + (data, offset.bytes_usize(), offset.bytes_usize() + len) + } + }; + + // This is for diagnostics only, so we are okay to use `inspect_with_uninit_and_ptr_outside_interpreter`. + Some(data.inner().inspect_with_uninit_and_ptr_outside_interpreter(start..end)) + } +} + /////////////////////////////////////////////////////////////////////////// /// Constants /// @@ -49,7 +204,7 @@ pub enum ConstantKind<'tcx> { /// This constant cannot go back into the type system, as it represents /// something the type system cannot handle (e.g. pointers). - Val(interpret::ConstValue<'tcx>, Ty<'tcx>), + Val(ConstValue<'tcx>, Ty<'tcx>), } impl<'tcx> Constant<'tcx> { @@ -116,7 +271,7 @@ impl<'tcx> ConstantKind<'tcx> { tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, span: Option, - ) -> Result, ErrorHandled> { + ) -> Result, ErrorHandled> { match self { ConstantKind::Ty(c) => { // We want to consistently have a "clean" value for type system constants (i.e., no diff --git a/compiler/rustc_middle/src/mir/interpret/error.rs b/compiler/rustc_middle/src/mir/interpret/error.rs index eb4614745d56..bc464aca5f3c 100644 --- a/compiler/rustc_middle/src/mir/interpret/error.rs +++ b/compiler/rustc_middle/src/mir/interpret/error.rs @@ -1,7 +1,7 @@ -use super::{AllocId, AllocRange, ConstAlloc, Pointer, Scalar}; +use super::{AllocId, AllocRange, Pointer, Scalar}; use crate::error; -use crate::mir::interpret::ConstValue; +use crate::mir::{ConstAlloc, ConstValue}; use crate::query::TyCtxtAt; use crate::ty::{layout, tls, Ty, TyCtxt, ValTree}; diff --git a/compiler/rustc_middle/src/mir/interpret/mod.rs b/compiler/rustc_middle/src/mir/interpret/mod.rs index 8c00746a1805..d21f82f04f6a 100644 --- a/compiler/rustc_middle/src/mir/interpret/mod.rs +++ b/compiler/rustc_middle/src/mir/interpret/mod.rs @@ -149,7 +149,7 @@ pub use self::error::{ UnsupportedOpInfo, ValidationErrorInfo, ValidationErrorKind, }; -pub use self::value::{ConstAlloc, ConstValue, Scalar}; +pub use self::value::Scalar; pub use self::allocation::{ alloc_range, AllocBytes, AllocError, AllocRange, AllocResult, Allocation, ConstAllocation, diff --git a/compiler/rustc_middle/src/mir/interpret/value.rs b/compiler/rustc_middle/src/mir/interpret/value.rs index c169733ad742..85394f6b7b88 100644 --- a/compiler/rustc_middle/src/mir/interpret/value.rs +++ b/compiler/rustc_middle/src/mir/interpret/value.rs @@ -9,163 +9,9 @@ use rustc_apfloat::{ use rustc_macros::HashStable; use rustc_target::abi::{HasDataLayout, Size}; -use crate::{ - mir::interpret::alloc_range, - ty::{ParamEnv, ScalarInt, Ty, TyCtxt}, -}; +use crate::ty::ScalarInt; -use super::{ - AllocId, ConstAllocation, InterpResult, Pointer, PointerArithmetic, Provenance, - ScalarSizeMismatch, -}; - -/// Represents the result of const evaluation via the `eval_to_allocation` query. -#[derive(Copy, Clone, HashStable, TyEncodable, TyDecodable, Debug, Hash, Eq, PartialEq)] -pub struct ConstAlloc<'tcx> { - /// The value lives here, at offset 0, and that allocation definitely is an `AllocKind::Memory` - /// (so you can use `AllocMap::unwrap_memory`). - pub alloc_id: AllocId, - pub ty: Ty<'tcx>, -} - -/// Represents a constant value in Rust. `Scalar` and `Slice` are optimizations for -/// array length computations, enum discriminants and the pattern matching logic. -#[derive(Copy, Clone, Debug, Eq, PartialEq, TyEncodable, TyDecodable, Hash)] -#[derive(HashStable, Lift)] -pub enum ConstValue<'tcx> { - /// Used for types with `layout::abi::Scalar` ABI. - /// - /// Not using the enum `Value` to encode that this must not be `Uninit`. - Scalar(Scalar), - - /// Only for ZSTs. - ZeroSized, - - /// Used for `&[u8]` and `&str`. - /// - /// This is worth an optimized representation since Rust has literals of these types. - /// Not having to indirect those through an `AllocId` (or two, if we used `Indirect`) has shown - /// measurable performance improvements on stress tests. - Slice { data: ConstAllocation<'tcx>, start: usize, end: usize }, - - /// A value not representable by the other variants; needs to be stored in-memory. - /// - /// Must *not* be used for scalars or ZST, but having `&str` or other slices in this variant is fine. - Indirect { - /// The backing memory of the value. May contain more memory than needed for just the value - /// if this points into some other larger ConstValue. - /// - /// We use an `AllocId` here instead of a `ConstAllocation<'tcx>` to make sure that when a - /// raw constant (which is basically just an `AllocId`) is turned into a `ConstValue` and - /// back, we can preserve the original `AllocId`. - alloc_id: AllocId, - /// Offset into `alloc` - offset: Size, - }, -} - -#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] -static_assert_size!(ConstValue<'_>, 32); - -impl<'tcx> ConstValue<'tcx> { - #[inline] - pub fn try_to_scalar(&self) -> Option> { - match *self { - ConstValue::Indirect { .. } | ConstValue::Slice { .. } | ConstValue::ZeroSized => None, - ConstValue::Scalar(val) => Some(val), - } - } - - pub fn try_to_scalar_int(&self) -> Option { - self.try_to_scalar()?.try_to_int().ok() - } - - pub fn try_to_bits(&self, size: Size) -> Option { - self.try_to_scalar_int()?.to_bits(size).ok() - } - - pub fn try_to_bool(&self) -> Option { - self.try_to_scalar_int()?.try_into().ok() - } - - pub fn try_to_target_usize(&self, tcx: TyCtxt<'tcx>) -> Option { - self.try_to_scalar_int()?.try_to_target_usize(tcx).ok() - } - - pub fn try_to_bits_for_ty( - &self, - tcx: TyCtxt<'tcx>, - param_env: ParamEnv<'tcx>, - ty: Ty<'tcx>, - ) -> Option { - let size = tcx.layout_of(param_env.with_reveal_all_normalized(tcx).and(ty)).ok()?.size; - self.try_to_bits(size) - } - - pub fn from_bool(b: bool) -> Self { - ConstValue::Scalar(Scalar::from_bool(b)) - } - - pub fn from_u64(i: u64) -> Self { - ConstValue::Scalar(Scalar::from_u64(i)) - } - - pub fn from_u128(i: u128) -> Self { - ConstValue::Scalar(Scalar::from_u128(i)) - } - - pub fn from_target_usize(i: u64, cx: &impl HasDataLayout) -> Self { - ConstValue::Scalar(Scalar::from_target_usize(i, cx)) - } - - /// Must only be called on constants of type `&str` or `&[u8]`! - pub fn try_get_slice_bytes_for_diagnostics(&self, tcx: TyCtxt<'tcx>) -> Option<&'tcx [u8]> { - let (data, start, end) = match self { - ConstValue::Scalar(_) | ConstValue::ZeroSized => { - bug!("`try_get_slice_bytes` on non-slice constant") - } - &ConstValue::Slice { data, start, end } => (data, start, end), - &ConstValue::Indirect { alloc_id, offset } => { - // The reference itself is stored behind an indirection. - // Load the reference, and then load the actual slice contents. - let a = tcx.global_alloc(alloc_id).unwrap_memory().inner(); - let ptr_size = tcx.data_layout.pointer_size; - if a.size() < offset + 2 * ptr_size { - // (partially) dangling reference - return None; - } - // Read the wide pointer components. - let ptr = a - .read_scalar( - &tcx, - alloc_range(offset, ptr_size), - /* read_provenance */ true, - ) - .ok()?; - let ptr = ptr.to_pointer(&tcx).ok()?; - let len = a - .read_scalar( - &tcx, - alloc_range(offset + ptr_size, ptr_size), - /* read_provenance */ false, - ) - .ok()?; - let len = len.to_target_usize(&tcx).ok()?; - let len: usize = len.try_into().ok()?; - if len == 0 { - return Some(&[]); - } - // Non-empty slice, must have memory. We know this is a relative pointer. - let (inner_alloc_id, offset) = ptr.into_parts(); - let data = tcx.global_alloc(inner_alloc_id?).unwrap_memory(); - (data, offset.bytes_usize(), offset.bytes_usize() + len) - } - }; - - // This is for diagnostics only, so we are okay to use `inspect_with_uninit_and_ptr_outside_interpreter`. - Some(data.inner().inspect_with_uninit_and_ptr_outside_interpreter(start..end)) - } -} +use super::{AllocId, InterpResult, Pointer, PointerArithmetic, Provenance, ScalarSizeMismatch}; /// A `Scalar` represents an immediate, primitive value existing outside of a /// `memory::Allocation`. It is in many ways like a small chunk of an `Allocation`, up to 16 bytes in diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs index cf134a0e5e7b..87180b56baa2 100644 --- a/compiler/rustc_middle/src/mir/mod.rs +++ b/compiler/rustc_middle/src/mir/mod.rs @@ -2,7 +2,7 @@ //! //! [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/mir/index.html -use crate::mir::interpret::{AllocRange, ErrorHandled, ConstAllocation, ConstValue, Scalar}; +use crate::mir::interpret::{AllocRange, ConstAllocation, ErrorHandled, Scalar}; use crate::mir::visit::MirVisitable; use crate::ty::codec::{TyDecoder, TyEncoder}; use crate::ty::fold::{FallibleTypeFolder, TypeFoldable}; diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index f41f454ba5e5..632f159a7a88 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -12,8 +12,8 @@ use rustc_data_structures::fx::FxHashMap; use rustc_hir::def_id::DefId; use rustc_index::Idx; use rustc_middle::mir::interpret::{ - alloc_range, read_target_uint, AllocBytes, AllocId, Allocation, ConstAllocation, ConstValue, - GlobalAlloc, Pointer, Provenance, + alloc_range, read_target_uint, AllocBytes, AllocId, Allocation, ConstAllocation, GlobalAlloc, + Pointer, Provenance, }; use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::*; diff --git a/compiler/rustc_middle/src/mir/query.rs b/compiler/rustc_middle/src/mir/query.rs index 0c80610b308a..c157b7052abb 100644 --- a/compiler/rustc_middle/src/mir/query.rs +++ b/compiler/rustc_middle/src/mir/query.rs @@ -1,6 +1,5 @@ //! Values computed by queries that use MIR. -use crate::mir::interpret::ConstValue; use crate::ty::{self, OpaqueHiddenType, Ty, TyCtxt}; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::unord::UnordSet; @@ -16,7 +15,7 @@ use smallvec::SmallVec; use std::cell::Cell; use std::fmt::{self, Debug}; -use super::SourceInfo; +use super::{ConstValue, SourceInfo}; #[derive(Copy, Clone, PartialEq, TyEncodable, TyDecodable, HashStable, Debug)] pub enum UnsafetyViolationKind { diff --git a/compiler/rustc_middle/src/query/erase.rs b/compiler/rustc_middle/src/query/erase.rs index 9b666222ad09..247fcd20c6ca 100644 --- a/compiler/rustc_middle/src/query/erase.rs +++ b/compiler/rustc_middle/src/query/erase.rs @@ -121,16 +121,12 @@ impl EraseType for Result, mir::interpret::LitToConstError [u8; size_of::, mir::interpret::LitToConstError>>()]; } -impl EraseType for Result, mir::interpret::ErrorHandled> { - type Result = [u8; size_of::< - Result, mir::interpret::ErrorHandled>, - >()]; +impl EraseType for Result, mir::interpret::ErrorHandled> { + type Result = [u8; size_of::, mir::interpret::ErrorHandled>>()]; } -impl EraseType for Result, mir::interpret::ErrorHandled> { - type Result = [u8; size_of::< - Result, mir::interpret::ErrorHandled>, - >()]; +impl EraseType for Result, mir::interpret::ErrorHandled> { + type Result = [u8; size_of::, mir::interpret::ErrorHandled>>()]; } impl EraseType for Result>, mir::interpret::ErrorHandled> { @@ -317,8 +313,8 @@ tcx_lifetime! { rustc_middle::middle::exported_symbols::ExportedSymbol, rustc_middle::mir::ConstantKind, rustc_middle::mir::DestructuredConstant, - rustc_middle::mir::interpret::ConstAlloc, - rustc_middle::mir::interpret::ConstValue, + rustc_middle::mir::ConstAlloc, + rustc_middle::mir::ConstValue, rustc_middle::mir::interpret::GlobalId, rustc_middle::mir::interpret::LitToConstInput, rustc_middle::traits::query::MethodAutoderefStepsResult, diff --git a/compiler/rustc_middle/src/query/keys.rs b/compiler/rustc_middle/src/query/keys.rs index 01bdc4c9904e..af4c57e702f4 100644 --- a/compiler/rustc_middle/src/query/keys.rs +++ b/compiler/rustc_middle/src/query/keys.rs @@ -2,7 +2,6 @@ use crate::infer::canonical::Canonical; use crate::mir; -use crate::mir::interpret::ConstValue; use crate::traits; use crate::ty::fast_reject::SimplifiedType; use crate::ty::layout::{TyAndLayout, ValidityRequirement}; @@ -369,7 +368,7 @@ impl<'tcx> Key for (ty::Const<'tcx>, FieldIdx) { } } -impl<'tcx> Key for (ConstValue<'tcx>, Ty<'tcx>) { +impl<'tcx> Key for (mir::ConstValue<'tcx>, Ty<'tcx>) { type CacheSelector = DefaultCacheSelector; fn default_span(&self, _: TyCtxt<'_>) -> Span { @@ -377,7 +376,7 @@ impl<'tcx> Key for (ConstValue<'tcx>, Ty<'tcx>) { } } -impl<'tcx> Key for mir::interpret::ConstAlloc<'tcx> { +impl<'tcx> Key for mir::ConstAlloc<'tcx> { type CacheSelector = DefaultCacheSelector; fn default_span(&self, _: TyCtxt<'_>) -> Span { diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 9e358ea4eba7..b5529568ff2d 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -21,7 +21,7 @@ use crate::middle::stability::{self, DeprecationEntry}; use crate::mir; use crate::mir::interpret::GlobalId; use crate::mir::interpret::{ - ConstValue, EvalToAllocationRawResult, EvalToConstValueResult, EvalToValTreeResult, + EvalToAllocationRawResult, EvalToConstValueResult, EvalToValTreeResult, }; use crate::mir::interpret::{LitToConstError, LitToConstInput}; use crate::mir::mono::CodegenUnit; @@ -1091,7 +1091,7 @@ rustc_queries! { } /// Converts a type level constant value into `ConstValue` - query valtree_to_const_val(key: (Ty<'tcx>, ty::ValTree<'tcx>)) -> ConstValue<'tcx> { + query valtree_to_const_val(key: (Ty<'tcx>, ty::ValTree<'tcx>)) -> mir::ConstValue<'tcx> { desc { "converting type-level constant value to mir constant value"} } @@ -1104,14 +1104,14 @@ rustc_queries! { /// Tries to destructure an `mir::ConstantKind` ADT or array into its variant index /// and its field values. This should only be used for pretty printing. query try_destructure_mir_constant_for_diagnostics( - key: (ConstValue<'tcx>, Ty<'tcx>) + key: (mir::ConstValue<'tcx>, Ty<'tcx>) ) -> Option> { desc { "destructuring MIR constant"} no_hash eval_always } - query const_caller_location(key: (rustc_span::Symbol, u32, u32)) -> ConstValue<'tcx> { + query const_caller_location(key: (rustc_span::Symbol, u32, u32)) -> mir::ConstValue<'tcx> { desc { "getting a &core::panic::Location referring to a span" } } diff --git a/compiler/rustc_mir_build/src/build/custom/parse/instruction.rs b/compiler/rustc_mir_build/src/build/custom/parse/instruction.rs index da5f2b1bcc99..0eff2df13662 100644 --- a/compiler/rustc_mir_build/src/build/custom/parse/instruction.rs +++ b/compiler/rustc_mir_build/src/build/custom/parse/instruction.rs @@ -1,4 +1,4 @@ -use rustc_middle::mir::interpret::{ConstValue, Scalar}; +use rustc_middle::mir::interpret::Scalar; use rustc_middle::mir::tcx::PlaceTy; use rustc_middle::ty::cast::mir_cast_kind; use rustc_middle::{mir::*, thir::*, ty}; diff --git a/compiler/rustc_mir_build/src/build/expr/as_constant.rs b/compiler/rustc_mir_build/src/build/expr/as_constant.rs index aaa37446e24d..22712fd3df08 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_constant.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_constant.rs @@ -3,9 +3,7 @@ use crate::build::{parse_float_into_constval, Builder}; use rustc_ast as ast; use rustc_middle::mir; -use rustc_middle::mir::interpret::{ - Allocation, ConstValue, LitToConstError, LitToConstInput, Scalar, -}; +use rustc_middle::mir::interpret::{Allocation, LitToConstError, LitToConstInput, Scalar}; use rustc_middle::mir::*; use rustc_middle::thir::*; use rustc_middle::ty::{ diff --git a/compiler/rustc_mir_build/src/build/mod.rs b/compiler/rustc_mir_build/src/build/mod.rs index 4e10916ad61e..7748ffab79cc 100644 --- a/compiler/rustc_mir_build/src/build/mod.rs +++ b/compiler/rustc_mir_build/src/build/mod.rs @@ -15,7 +15,6 @@ use rustc_index::{Idx, IndexSlice, IndexVec}; use rustc_infer::infer::{InferCtxt, TyCtxtInferExt}; use rustc_middle::hir::place::PlaceBase as HirPlaceBase; use rustc_middle::middle::region; -use rustc_middle::mir::interpret::ConstValue; use rustc_middle::mir::interpret::Scalar; use rustc_middle::mir::*; use rustc_middle::thir::{ diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs index 71e6e6f3a7a7..3d9e634c8493 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs @@ -18,7 +18,7 @@ use rustc_hir::pat_util::EnumerateAndAdjustIterator; use rustc_hir::RangeEnd; use rustc_index::Idx; use rustc_middle::mir::interpret::{ - ConstValue, ErrorHandled, GlobalId, LitToConstError, LitToConstInput, Scalar, + ErrorHandled, GlobalId, LitToConstError, LitToConstInput, Scalar, }; use rustc_middle::mir::{self, ConstantKind, UserTypeProjection}; use rustc_middle::mir::{BorrowKind, Mutability}; @@ -855,8 +855,8 @@ pub(crate) fn compare_const_vals<'tcx>( ty::Float(_) | ty::Int(_) => {} // require special handling, see below _ => match (a, b) { ( - mir::ConstantKind::Val(ConstValue::Scalar(Scalar::Int(a)), _a_ty), - mir::ConstantKind::Val(ConstValue::Scalar(Scalar::Int(b)), _b_ty), + mir::ConstantKind::Val(mir::ConstValue::Scalar(Scalar::Int(a)), _a_ty), + mir::ConstantKind::Val(mir::ConstValue::Scalar(Scalar::Int(b)), _b_ty), ) => return Some(a.cmp(&b)), (mir::ConstantKind::Ty(a), mir::ConstantKind::Ty(b)) => { return Some(a.kind().cmp(&b.kind())); diff --git a/compiler/rustc_mir_transform/src/check_alignment.rs b/compiler/rustc_mir_transform/src/check_alignment.rs index 4892ace53e38..fe66a9a09946 100644 --- a/compiler/rustc_mir_transform/src/check_alignment.rs +++ b/compiler/rustc_mir_transform/src/check_alignment.rs @@ -4,7 +4,7 @@ use rustc_hir::lang_items::LangItem; use rustc_index::IndexVec; use rustc_middle::mir::*; use rustc_middle::mir::{ - interpret::{ConstValue, Scalar}, + interpret::Scalar, visit::{PlaceContext, Visitor}, }; use rustc_middle::ty::{Ty, TyCtxt, TypeAndMut}; diff --git a/compiler/rustc_mir_transform/src/const_prop.rs b/compiler/rustc_mir_transform/src/const_prop.rs index 00e3e3a8f9f8..c6aac2ca2133 100644 --- a/compiler/rustc_mir_transform/src/const_prop.rs +++ b/compiler/rustc_mir_transform/src/const_prop.rs @@ -22,8 +22,8 @@ use rustc_target::spec::abi::Abi as CallAbi; use crate::dataflow_const_prop::Patch; use crate::MirPass; use rustc_const_eval::interpret::{ - self, compile_time_machine, AllocId, ConstAllocation, ConstValue, FnArg, Frame, ImmTy, - Immediate, InterpCx, InterpResult, MemoryKind, OpTy, PlaceTy, Pointer, Scalar, StackPopCleanup, + self, compile_time_machine, AllocId, ConstAllocation, FnArg, Frame, ImmTy, Immediate, InterpCx, + InterpResult, MemoryKind, OpTy, PlaceTy, Pointer, Scalar, StackPopCleanup, }; /// The maximum number of bytes that we'll allocate space for a local or the return value. diff --git a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs index c7c5f17dfec5..cf827c988942 100644 --- a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs +++ b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs @@ -6,7 +6,7 @@ use rustc_const_eval::const_eval::CheckAlignment; use rustc_const_eval::interpret::{ImmTy, Immediate, InterpCx, OpTy, Projectable}; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::DefKind; -use rustc_middle::mir::interpret::{AllocId, ConstAllocation, ConstValue, InterpResult, Scalar}; +use rustc_middle::mir::interpret::{AllocId, ConstAllocation, InterpResult, Scalar}; use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor}; use rustc_middle::mir::*; use rustc_middle::ty::layout::TyAndLayout; diff --git a/compiler/rustc_mir_transform/src/large_enums.rs b/compiler/rustc_mir_transform/src/large_enums.rs index 8afbe418502c..fc49c9ba348d 100644 --- a/compiler/rustc_mir_transform/src/large_enums.rs +++ b/compiler/rustc_mir_transform/src/large_enums.rs @@ -153,7 +153,7 @@ impl EnumSizeOpt { span, user_ty: None, literal: ConstantKind::Val( - interpret::ConstValue::Indirect { alloc_id, offset: Size::ZERO }, + ConstValue::Indirect { alloc_id, offset: Size::ZERO }, tmp_ty, ), }; diff --git a/compiler/rustc_mir_transform/src/remove_zsts.rs b/compiler/rustc_mir_transform/src/remove_zsts.rs index c13bafa9fbb5..dcc4cd85cda9 100644 --- a/compiler/rustc_mir_transform/src/remove_zsts.rs +++ b/compiler/rustc_mir_transform/src/remove_zsts.rs @@ -1,7 +1,6 @@ //! Removes operations on ZST places, and convert ZST operands to constants. use crate::MirPass; -use rustc_middle::mir::interpret::ConstValue; use rustc_middle::mir::visit::*; use rustc_middle::mir::*; use rustc_middle::ty::{self, Ty, TyCtxt}; diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 92abd0c3b6e7..67e821dcf5a3 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -170,8 +170,7 @@ use rustc_hir as hir; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId}; use rustc_hir::lang_items::LangItem; -use rustc_middle::mir::interpret::{AllocId, ConstValue}; -use rustc_middle::mir::interpret::{ErrorHandled, GlobalAlloc, Scalar}; +use rustc_middle::mir::interpret::{AllocId, ErrorHandled, GlobalAlloc, Scalar}; use rustc_middle::mir::mono::{InstantiationMode, MonoItem}; use rustc_middle::mir::visit::Visitor as MirVisitor; use rustc_middle::mir::{self, Local, Location}; @@ -1442,13 +1441,15 @@ fn collect_used_items<'tcx>( #[instrument(skip(tcx, output), level = "debug")] fn collect_const_value<'tcx>( tcx: TyCtxt<'tcx>, - value: ConstValue<'tcx>, + value: mir::ConstValue<'tcx>, output: &mut MonoItems<'tcx>, ) { match value { - ConstValue::Scalar(Scalar::Ptr(ptr, _size)) => collect_alloc(tcx, ptr.provenance, output), - ConstValue::Indirect { alloc_id, .. } => collect_alloc(tcx, alloc_id, output), - ConstValue::Slice { data, start: _, end: _ } => { + mir::ConstValue::Scalar(Scalar::Ptr(ptr, _size)) => { + collect_alloc(tcx, ptr.provenance, output) + } + mir::ConstValue::Indirect { alloc_id, .. } => collect_alloc(tcx, alloc_id, output), + mir::ConstValue::Slice { data, start: _, end: _ } => { for &id in data.inner().provenance().ptrs().values() { collect_alloc(tcx, id, output); } diff --git a/compiler/rustc_smir/src/rustc_smir/alloc.rs b/compiler/rustc_smir/src/rustc_smir/alloc.rs index 35e65c19be05..58b695f755fb 100644 --- a/compiler/rustc_smir/src/rustc_smir/alloc.rs +++ b/compiler/rustc_smir/src/rustc_smir/alloc.rs @@ -1,4 +1,7 @@ -use rustc_middle::mir::interpret::{alloc_range, AllocRange, ConstValue, Pointer}; +use rustc_middle::mir::{ + interpret::{alloc_range, AllocRange, Pointer}, + ConstValue, +}; use crate::{ rustc_smir::{Stable, Tables}, diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index 7696ea0f4495..8cddd5f9a877 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -16,7 +16,6 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId, LOCAL_CRATE}; use rustc_metadata::rendered_const; use rustc_middle::mir; -use rustc_middle::mir::interpret::ConstValue; use rustc_middle::ty::{self, GenericArgKind, GenericArgsRef, TyCtxt}; use rustc_span::symbol::{kw, sym, Symbol}; use std::fmt::Write as _; @@ -282,8 +281,8 @@ pub(crate) fn print_evaluated_const( let ty = tcx.type_of(def_id).instantiate_identity(); match (val, ty.kind()) { (_, &ty::Ref(..)) => None, - (ConstValue::Scalar(_), &ty::Adt(_, _)) => None, - (ConstValue::Scalar(_), _) => { + (mir::ConstValue::Scalar(_), &ty::Adt(_, _)) => None, + (mir::ConstValue::Scalar(_), _) => { let const_ = mir::ConstantKind::from_value(val, ty); Some(print_const_with_custom_print_scalar(tcx, const_, underscores_and_type)) } @@ -326,14 +325,14 @@ fn print_const_with_custom_print_scalar<'tcx>( // Use a slightly different format for integer types which always shows the actual value. // For all other types, fallback to the original `pretty_print_const`. match (ct, ct.ty().kind()) { - (mir::ConstantKind::Val(ConstValue::Scalar(int), _), ty::Uint(ui)) => { + (mir::ConstantKind::Val(mir::ConstValue::Scalar(int), _), ty::Uint(ui)) => { if underscores_and_type { format!("{}{}", format_integer_with_underscore_sep(&int.to_string()), ui.name_str()) } else { int.to_string() } } - (mir::ConstantKind::Val(ConstValue::Scalar(int), _), ty::Int(i)) => { + (mir::ConstantKind::Val(mir::ConstValue::Scalar(int), _), ty::Int(i)) => { let ty = ct.ty(); let size = tcx.layout_of(ty::ParamEnv::empty().and(ty)).unwrap().size; let data = int.assert_bits(size); diff --git a/src/tools/clippy/clippy_utils/src/consts.rs b/src/tools/clippy/clippy_utils/src/consts.rs index fcb90c63a6fc..c88fce22f7fb 100644 --- a/src/tools/clippy/clippy_utils/src/consts.rs +++ b/src/tools/clippy/clippy_utils/src/consts.rs @@ -656,7 +656,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { } pub fn miri_to_const<'tcx>(lcx: &LateContext<'tcx>, result: mir::ConstantKind<'tcx>) -> Option> { - use rustc_middle::mir::interpret::ConstValue; + use rustc_middle::mir::ConstValue; match result { mir::ConstantKind::Val(ConstValue::Scalar(Scalar::Int(int)), _) => match result.ty().kind() { ty::Adt(adt_def, _) if adt_def.is_struct() => Some(Constant::Adt(result)), diff --git a/src/tools/clippy/clippy_utils/src/ty.rs b/src/tools/clippy/clippy_utils/src/ty.rs index f0b4ede35fbc..9e25d97f5a6b 100644 --- a/src/tools/clippy/clippy_utils/src/ty.rs +++ b/src/tools/clippy/clippy_utils/src/ty.rs @@ -13,7 +13,7 @@ use rustc_hir::{Expr, FnDecl, LangItem, TyKind, Unsafety}; use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::LateContext; -use rustc_middle::mir::interpret::{ConstValue, Scalar}; +use rustc_middle::mir::{ConstValue, interpret::Scalar}; use rustc_middle::traits::EvaluationResult; use rustc_middle::ty::layout::ValidityRequirement; use rustc_middle::ty::{ From d14e601661e221be8f11ea7077d23edcbd0d0036 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 16 Sep 2023 09:41:46 +0200 Subject: [PATCH 063/111] comment on the difference between mir::ConstantKind::Unevaluated and mir::ConstantKind::Ty(ty::ConstKind::Unevaluated) --- compiler/rustc_middle/src/mir/consts.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compiler/rustc_middle/src/mir/consts.rs b/compiler/rustc_middle/src/mir/consts.rs index 8ea951d0c6ee..9e86eb214c12 100644 --- a/compiler/rustc_middle/src/mir/consts.rs +++ b/compiler/rustc_middle/src/mir/consts.rs @@ -200,6 +200,11 @@ pub enum ConstantKind<'tcx> { Ty(ty::Const<'tcx>), /// An unevaluated mir constant which is not part of the type system. + /// + /// Note that `Ty(ty::ConstKind::Unevaluated)` and this variant are *not* identical! `Ty` will + /// always flow through a valtree, so all data not captured in the valtree is lost. This variant + /// directly uses the evaluated result of the given constant, including e.g. data stored in + /// padding. Unevaluated(UnevaluatedConst<'tcx>, Ty<'tcx>), /// This constant cannot go back into the type system, as it represents From 531830cecd8467735e5c6fd9caa7a28683c028fb Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Tue, 19 Sep 2023 10:46:06 +0200 Subject: [PATCH 064/111] Update to LLVM 17.0.0 This rebases our LLVM fork to 17.0.0. Fixes #115681. --- .gitmodules | 2 +- src/llvm-project | 2 +- tests/ui/match/issue-115681.rs | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 tests/ui/match/issue-115681.rs diff --git a/.gitmodules b/.gitmodules index a13a2f5e01b5..f5025097a18d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -33,7 +33,7 @@ [submodule "src/llvm-project"] path = src/llvm-project url = https://github.com/rust-lang/llvm-project.git - branch = rustc/17.0-2023-07-29 + branch = rustc/17.0-2023-09-19 shallow = true [submodule "src/doc/embedded-book"] path = src/doc/embedded-book diff --git a/src/llvm-project b/src/llvm-project index 0537f6354cff..42263494d29f 160000 --- a/src/llvm-project +++ b/src/llvm-project @@ -1 +1 @@ -Subproject commit 0537f6354cffe546cbf47f6dc9c7f82e49e86cfb +Subproject commit 42263494d29febc26d3c1ebdaa7b63677573ec47 diff --git a/tests/ui/match/issue-115681.rs b/tests/ui/match/issue-115681.rs new file mode 100644 index 000000000000..c41e808e170c --- /dev/null +++ b/tests/ui/match/issue-115681.rs @@ -0,0 +1,32 @@ +// run-pass +// compile-flags: -C opt-level=1 + +// Make sure LLVM does not miscompile this match. +fn main() { + enum Bits { + None = 0x00, + Low = 0x40, + High = 0x80, + Both = 0xC0, + } + + let value = Box::new(0x40u8); + let mut out = Box::new(0u8); + + let bits = match *value { + 0x00 => Bits::None, + 0x40 => Bits::Low, + 0x80 => Bits::High, + 0xC0 => Bits::Both, + _ => return, + }; + + match bits { + Bits::None | Bits::Low => { + *out = 1; + } + _ => (), + } + + assert_eq!(*out, 1); +} From 0944860f35b469bd427ea265a4bfe389769ab1a6 Mon Sep 17 00:00:00 2001 From: cui fliter Date: Tue, 19 Sep 2023 18:01:32 +0800 Subject: [PATCH 065/111] fix mismatched symbols Signed-off-by: cui fliter --- src/bootstrap/job.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bootstrap/job.rs b/src/bootstrap/job.rs index 4fb00f65dc19..b0a97b540ec7 100644 --- a/src/bootstrap/job.rs +++ b/src/bootstrap/job.rs @@ -134,7 +134,7 @@ pub unsafe fn setup(build: &mut Build) { // If this failed, well at least we tried! An example of DuplicateHandle // failing in the past has been when the wrong python2 package spawned this // build system (e.g., the `python2` package in MSYS instead of - // `mingw-w64-x86_64-python2`. Not sure why it failed, but the "failure + // `mingw-w64-x86_64-python2`). Not sure why it failed, but the "failure // mode" here is that we only clean everything up when the build system // dies, not when the python parent does, so not too bad. if r.is_err() { From 751ecde0640f1d079515964e72e2646e82cb25f4 Mon Sep 17 00:00:00 2001 From: msizanoen Date: Sun, 3 Sep 2023 15:58:21 +0700 Subject: [PATCH 066/111] rustc_target/riscv: Fix passing of transparent unions with only one non-ZST member This ensures that `MaybeUninit` has the same ABI as `T` when passed through an `extern "C"` function. Fixes https://github.com/rust-lang/rust/issues/115481. --- compiler/rustc_middle/src/ty/layout.rs | 4 ++++ compiler/rustc_target/src/abi/call/riscv.rs | 11 +++++++++++ compiler/rustc_target/src/abi/mod.rs | 8 ++++++++ 3 files changed, 23 insertions(+) diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs index 9a0e72d7b640..8b425ce0267d 100644 --- a/compiler/rustc_middle/src/ty/layout.rs +++ b/compiler/rustc_middle/src/ty/layout.rs @@ -1118,6 +1118,10 @@ where fn is_unit(this: TyAndLayout<'tcx>) -> bool { matches!(this.ty.kind(), ty::Tuple(list) if list.len() == 0) } + + fn is_transparent(this: TyAndLayout<'tcx>) -> bool { + matches!(this.ty.kind(), ty::Adt(def, _) if def.repr().transparent()) + } } /// Calculates whether a function's ABI can unwind or not. diff --git a/compiler/rustc_target/src/abi/call/riscv.rs b/compiler/rustc_target/src/abi/call/riscv.rs index d90dce2a0878..93a2045632a8 100644 --- a/compiler/rustc_target/src/abi/call/riscv.rs +++ b/compiler/rustc_target/src/abi/call/riscv.rs @@ -89,6 +89,17 @@ where } FieldsShape::Union(_) => { if !arg_layout.is_zst() { + if arg_layout.is_transparent() { + let non_1zst_elem = arg_layout.non_1zst_field(cx).expect("not exactly one non-1-ZST field in non-ZST repr(transparent) union").1; + return should_use_fp_conv_helper( + cx, + &non_1zst_elem, + xlen, + flen, + field1_kind, + field2_kind, + ); + } return Err(CannotUseFpConv); } } diff --git a/compiler/rustc_target/src/abi/mod.rs b/compiler/rustc_target/src/abi/mod.rs index 636adcf6b178..74fe98920c45 100644 --- a/compiler/rustc_target/src/abi/mod.rs +++ b/compiler/rustc_target/src/abi/mod.rs @@ -66,6 +66,7 @@ pub trait TyAbiInterface<'a, C>: Sized + std::fmt::Debug { fn is_never(this: TyAndLayout<'a, Self>) -> bool; fn is_tuple(this: TyAndLayout<'a, Self>) -> bool; fn is_unit(this: TyAndLayout<'a, Self>) -> bool; + fn is_transparent(this: TyAndLayout<'a, Self>) -> bool; } impl<'a, Ty> TyAndLayout<'a, Ty> { @@ -136,6 +137,13 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { Ty::is_unit(self) } + pub fn is_transparent(self) -> bool + where + Ty: TyAbiInterface<'a, C>, + { + Ty::is_transparent(self) + } + pub fn offset_of_subfield(self, cx: &C, indices: impl Iterator) -> Size where Ty: TyAbiInterface<'a, C>, From 4d4c13bbd6584fea9972da5bbbecfa51285dbe68 Mon Sep 17 00:00:00 2001 From: msizanoen Date: Tue, 19 Sep 2023 12:22:45 +0200 Subject: [PATCH 067/111] tests/ui/abi: Enable repr(transparent) union ABI tests on RISC-V --- tests/ui/abi/compatibility.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/ui/abi/compatibility.rs b/tests/ui/abi/compatibility.rs index b3e75bb8233e..d4f42cdda97a 100644 --- a/tests/ui/abi/compatibility.rs +++ b/tests/ui/abi/compatibility.rs @@ -10,7 +10,6 @@ use std::ptr::NonNull; // Hence there are `cfg` throughout this test to disable parts of it on those targets. // sparc64: https://github.com/rust-lang/rust/issues/115336 // mips64: https://github.com/rust-lang/rust/issues/115404 -// riscv64: https://github.com/rust-lang/rust/issues/115481 // loongarch64: https://github.com/rust-lang/rust/issues/115509 macro_rules! assert_abi_compatible { @@ -110,7 +109,7 @@ macro_rules! test_transparent { test_abi_compatible!(wrap1, $t, Wrapper1<$t>); test_abi_compatible!(wrap2, $t, Wrapper2<$t>); test_abi_compatible!(wrap3, $t, Wrapper3<$t>); - #[cfg(not(any(target_arch = "riscv64", target_arch = "loongarch64")))] + #[cfg(not(target_arch = "loongarch64"))] test_abi_compatible!(wrap4, $t, WrapperUnion<$t>); } }; From 494fdcd8ecf0fea910fdc17c84dbd925e80e2b75 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 18 Sep 2023 22:11:03 +0200 Subject: [PATCH 068/111] Add new rustdoc-ui test for `custom_code_classes_in_docs` feature --- .../feature-gate-custom_code_classes_in_docs.rs | 8 ++++++++ .../feature-gate-custom_code_classes_in_docs.stderr | 13 ++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.rs b/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.rs index 3f0f8b5b9f99..4fcced86a3ba 100644 --- a/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.rs +++ b/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.rs @@ -8,3 +8,11 @@ //~| NOTE see issue #79483 //~| HELP add `#![feature(custom_code_classes_in_docs)]` to the crate attributes to enable pub struct Bar; + +/// ```ASN.1 +/// int main(void) { return 0; } +/// ``` +//~^^^ WARNING custom classes in code blocks will change behaviour +//~| NOTE see issue #79483 +//~| HELP add `#![feature(custom_code_classes_in_docs)]` to the crate attributes to enable +pub struct Bar2; diff --git a/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.stderr b/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.stderr index 1a2360d9b300..c6db54b31284 100644 --- a/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.stderr +++ b/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.stderr @@ -10,5 +10,16 @@ LL | | /// ``` = help: add `#![feature(custom_code_classes_in_docs)]` to the crate attributes to enable = note: found these custom classes: class=language-c -warning: 1 warning emitted +warning: custom classes in code blocks will change behaviour + --> $DIR/feature-gate-custom_code_classes_in_docs.rs:12:1 + | +LL | / /// ```ASN.1 +LL | | /// int main(void) { return 0; } +LL | | /// ``` + | |_______^ + | + = note: see issue #79483 for more information + = help: add `#![feature(custom_code_classes_in_docs)]` to the crate attributes to enable + +warning: 2 warnings emitted From 88b070aa925be19937e59ea576dd25e47b2c26f7 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 19 Sep 2023 13:20:18 +0200 Subject: [PATCH 069/111] Return early in `check_custom_code_classes` check if the feature is enabled --- src/librustdoc/passes/check_custom_code_classes.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/librustdoc/passes/check_custom_code_classes.rs b/src/librustdoc/passes/check_custom_code_classes.rs index 1a703a4e9675..6266d3ff51d6 100644 --- a/src/librustdoc/passes/check_custom_code_classes.rs +++ b/src/librustdoc/passes/check_custom_code_classes.rs @@ -21,6 +21,10 @@ pub(crate) const CHECK_CUSTOM_CODE_CLASSES: Pass = Pass { }; pub(crate) fn check_custom_code_classes(krate: Crate, cx: &mut DocContext<'_>) -> Crate { + if cx.tcx.features().custom_code_classes_in_docs { + // Nothing to check here if the feature is enabled. + return krate; + } let mut coll = CustomCodeClassLinter { cx }; coll.fold_crate(krate) @@ -59,7 +63,7 @@ pub(crate) fn look_for_custom_classes<'tcx>(cx: &DocContext<'tcx>, item: &Item) let dox = item.attrs.doc_value(); find_codes(&dox, &mut tests, ErrorCodes::No, false, None, true, true); - if !tests.custom_classes_found.is_empty() && !cx.tcx.features().custom_code_classes_in_docs { + if !tests.custom_classes_found.is_empty() { let span = item.attr_span(cx.tcx); let sess = &cx.tcx.sess.parse_sess; let mut err = sess From b49140295c6f67015ae23c855f525e74d3bc24eb Mon Sep 17 00:00:00 2001 From: Matthew Jasper Date: Mon, 18 Sep 2023 15:18:51 +0000 Subject: [PATCH 070/111] Add more if let guard tests --- .../issues/issue-66695-static-refs.rs | 4 + .../issues/issue-67611-static-mut-refs.rs | 4 + .../missed-capture-issue-107414.rs | 9 ++ tests/ui/binding/match-beginning-vert.rs | 4 + tests/ui/drop/dynamic-drop.rs | 18 ++++ .../rfcs/rfc-2294-if-let-guard/const-expr.rs | 26 +++++ .../move-guard-if-let-chain.rs | 97 +++++++++++++++++++ .../move-guard-if-let-chain.stderr | 67 +++++++++++++ .../move-guard-if-let.rs | 41 ++++++++ .../rfc-2294-if-let-guard/type-inference.rs | 16 +++ tests/ui/unreachable-code.rs | 15 ++- 11 files changed, 297 insertions(+), 4 deletions(-) create mode 100644 tests/ui/rfcs/rfc-2294-if-let-guard/const-expr.rs create mode 100644 tests/ui/rfcs/rfc-2294-if-let-guard/move-guard-if-let-chain.rs create mode 100644 tests/ui/rfcs/rfc-2294-if-let-guard/move-guard-if-let-chain.stderr create mode 100644 tests/ui/rfcs/rfc-2294-if-let-guard/move-guard-if-let.rs create mode 100644 tests/ui/rfcs/rfc-2294-if-let-guard/type-inference.rs diff --git a/tests/ui/async-await/issues/issue-66695-static-refs.rs b/tests/ui/async-await/issues/issue-66695-static-refs.rs index f0609713b4dd..1b0e1c6c9e77 100644 --- a/tests/ui/async-await/issues/issue-66695-static-refs.rs +++ b/tests/ui/async-await/issues/issue-66695-static-refs.rs @@ -1,12 +1,15 @@ // build-pass // edition:2018 +#![feature(if_let_guard)] + static A: [i32; 5] = [1, 2, 3, 4, 5]; async fn fun() { let u = A[async { 1 }.await]; match A { i if async { true }.await => (), + i if let Some(1) = async { Some(1) }.await => (), _ => (), } } @@ -18,6 +21,7 @@ fn main() { async { match A { i if async { true }.await => (), + i if let Some(2) = async { Some(2) }.await => (), _ => (), } }; diff --git a/tests/ui/async-await/issues/issue-67611-static-mut-refs.rs b/tests/ui/async-await/issues/issue-67611-static-mut-refs.rs index c4f8f607d257..80d824d3b2ee 100644 --- a/tests/ui/async-await/issues/issue-67611-static-mut-refs.rs +++ b/tests/ui/async-await/issues/issue-67611-static-mut-refs.rs @@ -5,6 +5,8 @@ // [drop_tracking] compile-flags: -Zdrop-tracking // [drop_tracking_mir] compile-flags: -Zdrop-tracking-mir +#![feature(if_let_guard)] + static mut A: [i32; 5] = [1, 2, 3, 4, 5]; fn is_send_sync(_: T) {} @@ -14,6 +16,7 @@ async fn fun() { unsafe { match A { i if async { true }.await => (), + i if let Some(1) = async { Some(1) }.await => (), _ => (), } } @@ -27,6 +30,7 @@ fn main() { unsafe { match A { i if async { true }.await => (), + i if let Some(2) = async { Some(2) }.await => (), _ => (), } } diff --git a/tests/ui/async-await/missed-capture-issue-107414.rs b/tests/ui/async-await/missed-capture-issue-107414.rs index 0ab4f5ade981..bb14eb74b3a5 100644 --- a/tests/ui/async-await/missed-capture-issue-107414.rs +++ b/tests/ui/async-await/missed-capture-issue-107414.rs @@ -1,6 +1,8 @@ // check-pass // edition:2018 +#![feature(if_let_guard)] + fn main() {} struct StructA {} @@ -22,3 +24,10 @@ async fn ice() { _ => {} } } + +async fn if_let() { + match Some(StructB {}) { + Some(struct_b) if let true = get_struct_a_async().await.fn_taking_struct_b(&struct_b) => {} + _ => {} + } +} diff --git a/tests/ui/binding/match-beginning-vert.rs b/tests/ui/binding/match-beginning-vert.rs index 79267400b28a..93c08f0b710a 100644 --- a/tests/ui/binding/match-beginning-vert.rs +++ b/tests/ui/binding/match-beginning-vert.rs @@ -1,4 +1,7 @@ // run-pass + +#![feature(if_let_guard)] + enum Foo { A, B, @@ -13,6 +16,7 @@ fn main() { match *foo { | A => println!("A"), | B | C if 1 < 2 => println!("BC!"), + | D if let 1 = 1 => println!("D!"), | _ => {}, } } diff --git a/tests/ui/drop/dynamic-drop.rs b/tests/ui/drop/dynamic-drop.rs index 9e51d3adaaa6..caef6358ea78 100644 --- a/tests/ui/drop/dynamic-drop.rs +++ b/tests/ui/drop/dynamic-drop.rs @@ -2,6 +2,7 @@ // needs-unwind #![feature(generators, generator_trait)] +#![feature(if_let_guard)] #![allow(unused_assignments)] #![allow(unused_variables)] @@ -332,6 +333,16 @@ fn move_ref_pattern(a: &Allocator) { let (ref _a, ref mut _b, _c, mut _d) = tup; } +fn if_let_guard(a: &Allocator, c: bool, d: i32) { + let foo = if c { Some(a.alloc()) } else { None }; + + match d == 0 { + false if let Some(a) = foo => { let b = a; } + true if let true = { drop(foo.unwrap_or_else(|| a.alloc())); d == 1 } => {} + _ => {} + } +} + fn panic_after_return(a: &Allocator) -> Ptr<'_> { // Panic in the drop of `p` or `q` can leak let exceptions = vec![8, 9]; @@ -497,6 +508,13 @@ fn main() { run_test(|a| move_ref_pattern(a)); + run_test(|a| if_let_guard(a, true, 0)); + run_test(|a| if_let_guard(a, true, 1)); + run_test(|a| if_let_guard(a, true, 2)); + run_test(|a| if_let_guard(a, false, 0)); + run_test(|a| if_let_guard(a, false, 1)); + run_test(|a| if_let_guard(a, false, 2)); + run_test(|a| { panic_after_return(a); }); diff --git a/tests/ui/rfcs/rfc-2294-if-let-guard/const-expr.rs b/tests/ui/rfcs/rfc-2294-if-let-guard/const-expr.rs new file mode 100644 index 000000000000..5c42c0d8bec2 --- /dev/null +++ b/tests/ui/rfcs/rfc-2294-if-let-guard/const-expr.rs @@ -0,0 +1,26 @@ +// Ensure if let guards can be used in constant expressions. +// build-pass + +#![feature(if_let_guard)] + +const fn match_if_let(x: Option, y: Option) -> i32 { + match x { + None if let Some(a @ 5) = y => a, + Some(z) if let (Some(_), 12) = (y, z) => 2, + _ => 3, + } +} + +const ASSERTS: usize = { + assert!(match_if_let(None, Some(5)) == 5); + assert!(match_if_let(Some(12), Some(3)) == 2); + assert!(match_if_let(None, Some(4)) == 3); + assert!(match_if_let(Some(11), Some(3)) == 3); + assert!(match_if_let(Some(12), None) == 3); + assert!(match_if_let(None, None) == 3); + 0 +}; + +fn main() { + let _: [(); ASSERTS]; +} diff --git a/tests/ui/rfcs/rfc-2294-if-let-guard/move-guard-if-let-chain.rs b/tests/ui/rfcs/rfc-2294-if-let-guard/move-guard-if-let-chain.rs new file mode 100644 index 000000000000..5c333cd7795c --- /dev/null +++ b/tests/ui/rfcs/rfc-2294-if-let-guard/move-guard-if-let-chain.rs @@ -0,0 +1,97 @@ +#![feature(if_let_guard)] +#![feature(let_chains)] +#![allow(irrefutable_let_patterns)] + +fn same_pattern(c: bool) { + let x: Box<_> = Box::new(1); + + let v = (1, 2); + + match v { + (1, 2) if let y = x && c => (), + (1, 2) if let z = x => (), //~ ERROR use of moved value: `x` + _ => (), + } +} + +fn same_pattern_ok(c: bool) { + let x: Box<_> = Box::new(1); + + let v = (1, 2); + + match v { + (1, 2) if c && let y = x => (), + (1, 2) if let z = x => (), + _ => (), + } +} + +fn different_patterns(c: bool) { + let x: Box<_> = Box::new(1); + + let v = (1, 2); + + match v { + (1, _) if let y = x && c => (), + (_, 2) if let z = x => (), //~ ERROR use of moved value: `x` + _ => (), + } +} + +fn different_patterns_ok(c: bool) { + let x: Box<_> = Box::new(1); + + let v = (1, 2); + + match v { + (1, _) if c && let y = x => (), + (_, 2) if let z = x => (), + _ => (), + } +} + +fn or_pattern(c: bool) { + let x: Box<_> = Box::new(1); + + let v = (1, 2); + + match v { + (1, _) | (_, 2) if let y = x && c => (), //~ ERROR use of moved value: `x` + _ => (), + } +} + +fn or_pattern_ok(c: bool) { + let x: Box<_> = Box::new(1); + + let v = (1, 2); + + match v { + (1, _) | (_, 2) if c && let y = x => (), + _ => (), + } +} + +fn use_in_arm(c: bool) { + let x: Box<_> = Box::new(1); + + let v = (1, 2); + + match v { + (1, 2) if let y = x && c => false, + _ => { *x == 1 }, //~ ERROR use of moved value: `x` + }; +} + +fn use_in_arm_ok(c: bool) { + let x: Box<_> = Box::new(1); + + let v = (1, 2); + + match v { + (1, 2) if c && let y = x => false, + _ => { *x == 1 }, + }; +} + +fn main() {} diff --git a/tests/ui/rfcs/rfc-2294-if-let-guard/move-guard-if-let-chain.stderr b/tests/ui/rfcs/rfc-2294-if-let-guard/move-guard-if-let-chain.stderr new file mode 100644 index 000000000000..d27fde58244d --- /dev/null +++ b/tests/ui/rfcs/rfc-2294-if-let-guard/move-guard-if-let-chain.stderr @@ -0,0 +1,67 @@ +error[E0382]: use of moved value: `x` + --> $DIR/move-guard-if-let-chain.rs:12:27 + | +LL | let x: Box<_> = Box::new(1); + | - move occurs because `x` has type `Box`, which does not implement the `Copy` trait +... +LL | (1, 2) if let y = x && c => (), + | - value moved here +LL | (1, 2) if let z = x => (), + | ^ value used here after move + | +help: borrow this binding in the pattern to avoid moving the value + | +LL | (1, 2) if let ref y = x && c => (), + | +++ + +error[E0382]: use of moved value: `x` + --> $DIR/move-guard-if-let-chain.rs:36:27 + | +LL | let x: Box<_> = Box::new(1); + | - move occurs because `x` has type `Box`, which does not implement the `Copy` trait +... +LL | (1, _) if let y = x && c => (), + | - value moved here +LL | (_, 2) if let z = x => (), + | ^ value used here after move + | +help: borrow this binding in the pattern to avoid moving the value + | +LL | (1, _) if let ref y = x && c => (), + | +++ + +error[E0382]: use of moved value: `x` + --> $DIR/move-guard-if-let-chain.rs:59:36 + | +LL | let x: Box<_> = Box::new(1); + | - move occurs because `x` has type `Box`, which does not implement the `Copy` trait +... +LL | (1, _) | (_, 2) if let y = x && c => (), + | - ^ value used here after move + | | + | value moved here + | +help: borrow this binding in the pattern to avoid moving the value + | +LL | (1, _) | (_, 2) if let ref y = x && c => (), + | +++ + +error[E0382]: use of moved value: `x` + --> $DIR/move-guard-if-let-chain.rs:82:16 + | +LL | let x: Box<_> = Box::new(1); + | - move occurs because `x` has type `Box`, which does not implement the `Copy` trait +... +LL | (1, 2) if let y = x && c => false, + | - value moved here +LL | _ => { *x == 1 }, + | ^^ value used here after move + | +help: borrow this binding in the pattern to avoid moving the value + | +LL | (1, 2) if let ref y = x && c => false, + | +++ + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0382`. diff --git a/tests/ui/rfcs/rfc-2294-if-let-guard/move-guard-if-let.rs b/tests/ui/rfcs/rfc-2294-if-let-guard/move-guard-if-let.rs new file mode 100644 index 000000000000..071b86e2e149 --- /dev/null +++ b/tests/ui/rfcs/rfc-2294-if-let-guard/move-guard-if-let.rs @@ -0,0 +1,41 @@ +// Check that borrowck knows that moves in the pattern for if-let guards +// only happen when the pattern is matched. + +// build-pass + +#![feature(if_let_guard)] +#![allow(irrefutable_let_patterns)] + +fn same_pattern() { + let x: Box<_> = Box::new(1); + + let v = (1, 2); + + match v { + (1, 2) if let y = x => (), + (1, 2) if let z = x => (), + _ => (), + } +} + +fn or_pattern() { + let x: Box<_> = Box::new(1); + + let v = (1, 2); + + match v { + (1, _) | (_, 2) if let y = x => (), + _ => (), + } +} + +fn main() { + let x: Box<_> = Box::new(1); + + let v = (1, 2); + + match v { + (1, 2) if let y = x => false, + _ => { *x == 1 }, + }; +} diff --git a/tests/ui/rfcs/rfc-2294-if-let-guard/type-inference.rs b/tests/ui/rfcs/rfc-2294-if-let-guard/type-inference.rs new file mode 100644 index 000000000000..ef7a772e6c51 --- /dev/null +++ b/tests/ui/rfcs/rfc-2294-if-let-guard/type-inference.rs @@ -0,0 +1,16 @@ +// check-pass + +#![feature(if_let_guard)] + +struct S; + +fn get() -> Option { + None +} + +fn main() { + match get() { + x if let Some(S) = x => {} + _ => {} + } +} diff --git a/tests/ui/unreachable-code.rs b/tests/ui/unreachable-code.rs index 28b938edc63b..64174db7afbd 100644 --- a/tests/ui/unreachable-code.rs +++ b/tests/ui/unreachable-code.rs @@ -2,25 +2,32 @@ #![allow(unused_must_use)] #![allow(dead_code)] - #![allow(path_statements)] #![allow(unreachable_code)] #![allow(unused_variables)] +#![feature(if_let_guard)] -fn id(x: bool) -> bool { x } +fn id(x: bool) -> bool { + x +} fn call_id() { let c = panic!(); id(c); } -fn call_id_2() { id(true) && id(return); } +fn call_id_2() { + id(true) && id(return); +} -fn call_id_3() { id(return) && id(return); } +fn call_id_3() { + id(return) && id(return); +} fn ret_guard() { match 2 { x if (return) => { x; } + x if let true = return => { x; } _ => {} } } From 6bed1c60619b23ccb3011282fa1eed200f71ee0a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 19 Sep 2023 17:29:30 +0200 Subject: [PATCH 071/111] Allow more characters in custom classes --- src/librustdoc/html/markdown.rs | 36 ++++++++++++++++++++------- src/librustdoc/html/markdown/tests.rs | 21 +++++++++++++--- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 7528393f0f8c..6dbf2185e3d0 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -893,11 +893,13 @@ pub(crate) enum Ignore { /// ```eBNF /// lang-string = *(token-list / delimited-attribute-list / comment) /// -/// bareword = CHAR *(CHAR) +/// bareword = LEADINGCHAR *(CHAR) +/// bareword-without-leading-char = CHAR *(CHAR) /// quoted-string = QUOTE *(NONQUOTE) QUOTE /// token = bareword / quoted-string +/// token-without-leading-char = bareword-without-leading-char / quoted-string /// sep = COMMA/WS *(COMMA/WS) -/// attribute = (DOT token)/(token EQUAL token) +/// attribute = (DOT token)/(token EQUAL token-without-leading-char) /// attribute-list = [sep] attribute *(sep attribute) [sep] /// delimited-attribute-list = OPEN-CURLY-BRACKET attribute-list CLOSE-CURLY-BRACKET /// token-list = [sep] token *(sep token) [sep] @@ -907,8 +909,15 @@ pub(crate) enum Ignore { /// CLOSE_PARENT = ")" /// OPEN-CURLY-BRACKET = "{" /// CLOSE-CURLY-BRACKET = "}" -/// CHAR = ALPHA / DIGIT / "_" / "-" / ":" -/// QUOTE = %x22 +/// LEADINGCHAR = ALPHA | DIGIT | "_" | "-" | ":" +/// ; All ASCII punctuation except comma, quote, equals, backslash, grave (backquote) and braces. +/// ; Comma is used to separate language tokens, so it can't be used in one. +/// ; Quote is used to allow otherwise-disallowed characters in language tokens. +/// ; Equals is used to make key=value pairs in attribute blocks. +/// ; Backslash and grave are special Markdown characters. +/// ; Braces are used to start an attribute block. +/// CHAR = ALPHA | DIGIT | "_" | "-" | ":" | "." | "!" | "#" | "$" | "%" | "&" | "*" | "+" | "/" | +/// ";" | "<" | ">" | "?" | "@" | "^" | "|" | "~" /// NONQUOTE = %x09 / %x20 / %x21 / %x23-7E ; TAB / SPACE / all printable characters except `"` /// COMMA = "," /// DOT = "." @@ -932,9 +941,12 @@ pub(crate) enum LangStringToken<'a> { KeyValueAttribute(&'a str, &'a str), } -fn is_bareword_char(c: char) -> bool { +fn is_leading_char(c: char) -> bool { c == '_' || c == '-' || c == ':' || c.is_ascii_alphabetic() || c.is_ascii_digit() } +fn is_bareword_char(c: char) -> bool { + is_leading_char(c) || ".!#$%&*+/;<>?@^|~".contains(c) +} fn is_separator(c: char) -> bool { c == ' ' || c == ',' || c == '\t' } @@ -1077,7 +1089,7 @@ impl<'a, 'tcx> TagIterator<'a, 'tcx> { return self.next(); } else if c == '.' { return self.parse_class(pos); - } else if c == '"' || is_bareword_char(c) { + } else if c == '"' || is_leading_char(c) { return self.parse_key_value(c, pos); } else { self.emit_error(format!("unexpected character `{c}`")); @@ -1107,7 +1119,11 @@ impl<'a, 'tcx> TagIterator<'a, 'tcx> { return None; } let indices = self.parse_string(pos)?; - if let Some((_, c)) = self.inner.peek().copied() && c != '{' && !is_separator(c) && c != '(' { + if let Some((_, c)) = self.inner.peek().copied() && + c != '{' && + !is_separator(c) && + c != '(' + { self.emit_error(format!("expected ` `, `{{` or `,` after `\"`, found `{c}`")); return None; } @@ -1115,8 +1131,6 @@ impl<'a, 'tcx> TagIterator<'a, 'tcx> { } else if c == '{' { self.is_in_attribute_block = true; return self.next(); - } else if is_bareword_char(c) { - continue; } else if is_separator(c) { if pos != start { return Some(LangStringToken::LangToken(&self.data[start..pos])); @@ -1130,6 +1144,10 @@ impl<'a, 'tcx> TagIterator<'a, 'tcx> { return Some(LangStringToken::LangToken(&self.data[start..pos])); } return self.next(); + } else if pos == start && is_leading_char(c) { + continue; + } else if pos != start && is_bareword_char(c) { + continue; } else { self.emit_error(format!("unexpected character `{c}`")); return None; diff --git a/src/librustdoc/html/markdown/tests.rs b/src/librustdoc/html/markdown/tests.rs index 32957ac57fae..5eba1d0609f3 100644 --- a/src/librustdoc/html/markdown/tests.rs +++ b/src/librustdoc/html/markdown/tests.rs @@ -226,13 +226,28 @@ fn test_lang_string_parse() { ..Default::default() }); // error - t(LangString { original: "{.first.second}".into(), rust: true, ..Default::default() }); + t(LangString { + original: "{.first.second}".into(), + rust: true, + added_classes: vec!["first.second".into()], + ..Default::default() + }); // error t(LangString { original: "{class=first=second}".into(), rust: true, ..Default::default() }); // error - t(LangString { original: "{class=first.second}".into(), rust: true, ..Default::default() }); + t(LangString { + original: "{class=first.second}".into(), + rust: true, + added_classes: vec!["first.second".into()], + ..Default::default() + }); // error - t(LangString { original: "{class=.first}".into(), rust: true, ..Default::default() }); + t(LangString { + original: "{class=.first}".into(), + added_classes: vec![".first".into()], + rust: true, + ..Default::default() + }); t(LangString { original: r#"{class="first"}"#.into(), added_classes: vec!["first".into()], From 295ec09b63b1c8352d4a75895eb5d8bd85975b16 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 19 Sep 2023 17:29:39 +0200 Subject: [PATCH 072/111] Update tests for custom classes --- .../custom_code_classes_in_docs-warning.rs | 8 +++--- ...custom_code_classes_in_docs-warning.stderr | 26 ++++--------------- ...eature-gate-custom_code_classes_in_docs.rs | 3 --- ...re-gate-custom_code_classes_in_docs.stderr | 13 +--------- 4 files changed, 9 insertions(+), 41 deletions(-) diff --git a/tests/rustdoc-ui/custom_code_classes_in_docs-warning.rs b/tests/rustdoc-ui/custom_code_classes_in_docs-warning.rs index dd8759b7e378..5398d5833c75 100644 --- a/tests/rustdoc-ui/custom_code_classes_in_docs-warning.rs +++ b/tests/rustdoc-ui/custom_code_classes_in_docs-warning.rs @@ -57,25 +57,23 @@ pub fn foo8() {} /// ```{class=one=two} /// main; /// ``` -//~^^^ ERROR unexpected `=` +//~^^^ ERROR unexpected `=` character pub fn foo9() {} /// ```{.one.two} /// main; /// ``` -//~^^^ ERROR unexpected `.` character pub fn foo10() {} -/// ```{class=.one} +/// ```{class=(one} /// main; /// ``` -//~^^^ ERROR unexpected `.` character after `=` +//~^^^ ERROR unexpected `(` character after `=` pub fn foo11() {} /// ```{class=one.two} /// main; /// ``` -//~^^^ ERROR unexpected `.` character pub fn foo12() {} /// ```{(comment)} diff --git a/tests/rustdoc-ui/custom_code_classes_in_docs-warning.stderr b/tests/rustdoc-ui/custom_code_classes_in_docs-warning.stderr index 3e0dc4b2f7a6..14b4b3bab3fa 100644 --- a/tests/rustdoc-ui/custom_code_classes_in_docs-warning.stderr +++ b/tests/rustdoc-ui/custom_code_classes_in_docs-warning.stderr @@ -77,37 +77,21 @@ LL | | /// main; LL | | /// ``` | |_______^ -error: unexpected `.` character - --> $DIR/custom_code_classes_in_docs-warning.rs:63:1 +error: unexpected `(` character after `=` + --> $DIR/custom_code_classes_in_docs-warning.rs:68:1 | -LL | / /// ```{.one.two} -LL | | /// main; -LL | | /// ``` - | |_______^ - -error: unexpected `.` character after `=` - --> $DIR/custom_code_classes_in_docs-warning.rs:69:1 - | -LL | / /// ```{class=.one} -LL | | /// main; -LL | | /// ``` - | |_______^ - -error: unexpected `.` character - --> $DIR/custom_code_classes_in_docs-warning.rs:75:1 - | -LL | / /// ```{class=one.two} +LL | / /// ```{class=(one} LL | | /// main; LL | | /// ``` | |_______^ error: unexpected character `(` - --> $DIR/custom_code_classes_in_docs-warning.rs:81:1 + --> $DIR/custom_code_classes_in_docs-warning.rs:79:1 | LL | / /// ```{(comment)} LL | | /// main; LL | | /// ``` | |_______^ -error: aborting due to 13 previous errors +error: aborting due to 11 previous errors diff --git a/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.rs b/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.rs index 4fcced86a3ba..99263a944f81 100644 --- a/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.rs +++ b/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.rs @@ -12,7 +12,4 @@ pub struct Bar; /// ```ASN.1 /// int main(void) { return 0; } /// ``` -//~^^^ WARNING custom classes in code blocks will change behaviour -//~| NOTE see issue #79483 -//~| HELP add `#![feature(custom_code_classes_in_docs)]` to the crate attributes to enable pub struct Bar2; diff --git a/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.stderr b/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.stderr index c6db54b31284..1a2360d9b300 100644 --- a/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.stderr +++ b/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.stderr @@ -10,16 +10,5 @@ LL | | /// ``` = help: add `#![feature(custom_code_classes_in_docs)]` to the crate attributes to enable = note: found these custom classes: class=language-c -warning: custom classes in code blocks will change behaviour - --> $DIR/feature-gate-custom_code_classes_in_docs.rs:12:1 - | -LL | / /// ```ASN.1 -LL | | /// int main(void) { return 0; } -LL | | /// ``` - | |_______^ - | - = note: see issue #79483 for more information - = help: add `#![feature(custom_code_classes_in_docs)]` to the crate attributes to enable - -warning: 2 warnings emitted +warning: 1 warning emitted From 7928c7e25d0977eed31a2ac9d22c1b440b1fdfc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Tue, 19 Sep 2023 14:15:53 +0200 Subject: [PATCH 073/111] chore(Cargo.lock): bump colored and tracing-tree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reduces the amount of dependencies pulling in atty. ``` Updating colored v2.0.0 -> v2.0.4 Updating tracing-tree v0.2.3 -> v0.2.4 ``` Signed-off-by: Martin Kröning --- Cargo.lock | 14 +++++++------- src/tools/tidy/src/deps.rs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 95ac3136628c..e45f4e806029 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -627,13 +627,13 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "colored" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" dependencies = [ - "atty", + "is-terminal", "lazy_static", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -5591,11 +5591,11 @@ dependencies = [ [[package]] name = "tracing-tree" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9742d8df709837409dbb22aa25dd7769c260406f20ff48a2320b80a4a6aed0" +checksum = "92d6b63348fad3ae0439b8bebf8d38fb5bda0b115d7a8a7e6f165f12790c58c3" dependencies = [ - "atty", + "is-terminal", "nu-ansi-term", "tracing-core", "tracing-log", diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs index 410852b6a31f..843ffe2c4c33 100644 --- a/src/tools/tidy/src/deps.rs +++ b/src/tools/tidy/src/deps.rs @@ -120,7 +120,6 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[ "annotate-snippets", "ar_archive_writer", "arrayvec", - "atty", "autocfg", "bitflags", "block-buffer", @@ -181,6 +180,7 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[ "intl-memoizer", "intl_pluralrules", "io-lifetimes", + "is-terminal", "itertools", "itoa", "jobserver", From 4241f942b67407fd9fda0c393254277fd4f35fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Tue, 19 Sep 2023 14:19:52 +0200 Subject: [PATCH 074/111] chore(miri): bump env_logger to 0.10 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reduces the amount of dependencies pulling in atty. ``` Removing env_logger v0.9.3 ``` Signed-off-by: Martin Kröning --- Cargo.lock | 15 +-------------- src/tools/miri/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e45f4e806029..d9aaedb85443 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1169,19 +1169,6 @@ dependencies = [ "termcolor", ] -[[package]] -name = "env_logger" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" -dependencies = [ - "atty", - "humantime 2.1.0", - "log", - "regex", - "termcolor", -] - [[package]] name = "env_logger" version = "0.10.0" @@ -2454,7 +2441,7 @@ version = "0.1.0" dependencies = [ "colored", "ctrlc", - "env_logger 0.9.3", + "env_logger 0.10.0", "getrandom", "lazy_static", "libc", diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml index 67a2aeefa02a..2ae6f922e3a1 100644 --- a/src/tools/miri/Cargo.toml +++ b/src/tools/miri/Cargo.toml @@ -19,7 +19,7 @@ doctest = false # and no doc tests [dependencies] getrandom = { version = "0.2", features = ["std"] } -env_logger = "0.9" +env_logger = "0.10" log = "0.4" rand = "0.8" smallvec = "1.7" From 028c78c6c70eb4e9aaf67df1fe650c07209f32ea Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 19 Sep 2023 10:06:22 +0200 Subject: [PATCH 075/111] explain mysterious addition in float minimum/maximum --- library/core/src/num/f32.rs | 1 + library/core/src/num/f64.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index 3144db197074..290f649f9acd 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -957,6 +957,7 @@ impl f32 { } else if self == other { if self.is_sign_negative() && other.is_sign_positive() { self } else { other } } else { + // At least one input is NaN. Use `+` to perform NaN propagation and quieting. self + other } } diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index 20833defc41b..7569d2cd6ca9 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -968,6 +968,7 @@ impl f64 { } else if self == other { if self.is_sign_negative() && other.is_sign_positive() { self } else { other } } else { + // At least one input is NaN. Use `+` to perform NaN propagation and quieting. self + other } } From ea22adbabdd0f5fbc033101eaeed6d3e304ede08 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 15 Sep 2023 15:59:47 +0200 Subject: [PATCH 076/111] adjust constValue::Slice to work for arbitrary slice types --- .../rustc_codegen_cranelift/src/constant.rs | 12 +++---- compiler/rustc_codegen_ssa/src/mir/operand.rs | 9 ++--- .../src/const_eval/eval_queries.rs | 25 +++++++++----- .../rustc_const_eval/src/interpret/cast.rs | 5 +-- .../src/interpret/intrinsics.rs | 2 +- .../rustc_const_eval/src/interpret/operand.rs | 33 ++++++++++--------- compiler/rustc_middle/src/mir/consts.rs | 27 ++++++++++----- .../rustc_middle/src/ty/structural_impls.rs | 1 + .../src/build/expr/as_constant.rs | 6 ++-- compiler/rustc_monomorphize/src/collector.rs | 2 +- compiler/rustc_smir/src/rustc_smir/alloc.rs | 14 ++++---- src/tools/miri/src/shims/backtrace.rs | 2 +- 12 files changed, 75 insertions(+), 63 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/src/constant.rs b/compiler/rustc_codegen_cranelift/src/constant.rs index 02468684ba05..151674b2d6d8 100644 --- a/compiler/rustc_codegen_cranelift/src/constant.rs +++ b/compiler/rustc_codegen_cranelift/src/constant.rs @@ -184,15 +184,11 @@ pub(crate) fn codegen_const_value<'tcx>( .offset_i64(fx, i64::try_from(offset.bytes()).unwrap()), layout, ), - ConstValue::Slice { data, start, end } => { + ConstValue::Slice { data, meta } => { let alloc_id = fx.tcx.reserve_and_set_memory_alloc(data); - let ptr = pointer_for_allocation(fx, alloc_id) - .offset_i64(fx, i64::try_from(start).unwrap()) - .get_addr(fx); - let len = fx - .bcx - .ins() - .iconst(fx.pointer_type, i64::try_from(end.checked_sub(start).unwrap()).unwrap()); + let ptr = pointer_for_allocation(fx, alloc_id).get_addr(fx); + // FIXME: the `try_from` here can actually fail, e.g. for very long ZST slices. + let len = fx.bcx.ins().iconst(fx.pointer_type, i64::try_from(meta).unwrap()); CValue::by_val_pair(ptr, len, layout) } } diff --git a/compiler/rustc_codegen_ssa/src/mir/operand.rs b/compiler/rustc_codegen_ssa/src/mir/operand.rs index 926a0bed0da8..9205acc527e5 100644 --- a/compiler/rustc_codegen_ssa/src/mir/operand.rs +++ b/compiler/rustc_codegen_ssa/src/mir/operand.rs @@ -100,15 +100,12 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { OperandValue::Immediate(llval) } ConstValue::ZeroSized => return OperandRef::zero_sized(layout), - ConstValue::Slice { data, start, end } => { + ConstValue::Slice { data, meta } => { let Abi::ScalarPair(a_scalar, _) = layout.abi else { bug!("from_const: invalid ScalarPair layout: {:#?}", layout); }; let a = Scalar::from_pointer( - Pointer::new( - bx.tcx().reserve_and_set_memory_alloc(data), - Size::from_bytes(start), - ), + Pointer::new(bx.tcx().reserve_and_set_memory_alloc(data), Size::ZERO), &bx.tcx(), ); let a_llval = bx.scalar_to_backend( @@ -116,7 +113,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { a_scalar, bx.scalar_pair_element_backend_type(layout, 0, true), ); - let b_llval = bx.const_usize((end - start) as u64); + let b_llval = bx.const_usize(meta); OperandValue::Pair(a_llval, b_llval) } ConstValue::Indirect { alloc_id, offset } => { diff --git a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs index de78b4eb5047..3d758cd01d34 100644 --- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs +++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs @@ -151,19 +151,26 @@ pub(super) fn op_to_const<'tcx>( Immediate::Scalar(x) => ConstValue::Scalar(x), Immediate::ScalarPair(a, b) => { debug!("ScalarPair(a: {:?}, b: {:?})", a, b); - // FIXME: assert that this has an appropriate type. - // Currently we actually get here for non-[u8] slices during valtree construction! - let msg = "`op_to_const` on an immediate scalar pair must only be used on slice references to actually allocated memory"; + // This codepath solely exists for `valtree_to_const_value` to not need to generate + // a `ConstValue::Indirect` for wide references, so it is tightly restricted to just + // that case. + let pointee_ty = imm.layout.ty.builtin_deref(false).unwrap().ty; // `false` = no raw ptrs + debug_assert!( + matches!( + ecx.tcx.struct_tail_without_normalization(pointee_ty).kind(), + ty::Str | ty::Slice(..), + ), + "`ConstValue::Slice` is for slice-tailed types only, but got {}", + imm.layout.ty, + ); + let msg = "`op_to_const` on an immediate scalar pair must only be used on slice references to the beginning of an actual allocation"; // We know `offset` is relative to the allocation, so we can use `into_parts`. - // We use `ConstValue::Slice` so that we don't have to generate an allocation for - // `ConstValue::Indirect` here. let (alloc_id, offset) = a.to_pointer(ecx).expect(msg).into_parts(); let alloc_id = alloc_id.expect(msg); let data = ecx.tcx.global_alloc(alloc_id).unwrap_memory(); - let start = offset.bytes_usize(); - let len = b.to_target_usize(ecx).expect(msg); - let len: usize = len.try_into().unwrap(); - ConstValue::Slice { data, start, end: start + len } + assert!(offset == abi::Size::ZERO, "{}", msg); + let meta = b.to_target_usize(ecx).expect(msg); + ConstValue::Slice { data, meta } } Immediate::Uninit => bug!("`Uninit` is not a valid value for {}", op.layout.ty), }, diff --git a/compiler/rustc_const_eval/src/interpret/cast.rs b/compiler/rustc_const_eval/src/interpret/cast.rs index 4c826239eca4..bd897ffaafc4 100644 --- a/compiler/rustc_const_eval/src/interpret/cast.rs +++ b/compiler/rustc_const_eval/src/interpret/cast.rs @@ -351,7 +351,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { match (&src_pointee_ty.kind(), &dest_pointee_ty.kind()) { (&ty::Array(_, length), &ty::Slice(_)) => { - let ptr = self.read_scalar(src)?; + let ptr = self.read_pointer(src)?; // u64 cast is from usize to u64, which is always good let val = Immediate::new_slice( ptr, @@ -367,6 +367,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { return self.write_immediate(*val, dest); } let (old_data, old_vptr) = val.to_scalar_pair(); + let old_data = old_data.to_pointer(self)?; let old_vptr = old_vptr.to_pointer(self)?; let (ty, old_trait) = self.get_ptr_vtable(old_vptr)?; if old_trait != data_a.principal() { @@ -378,7 +379,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { (_, &ty::Dynamic(data, _, ty::Dyn)) => { // Initial cast from sized to dyn trait let vtable = self.get_vtable_ptr(src_pointee_ty, data.principal())?; - let ptr = self.read_scalar(src)?; + let ptr = self.read_pointer(src)?; let val = Immediate::new_dyn_trait(ptr, vtable, &*self.tcx); self.write_immediate(val, dest) } diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index f08f1437918f..775a834f2eee 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -62,7 +62,7 @@ pub(crate) fn eval_nullary_intrinsic<'tcx>( sym::type_name => { ensure_monomorphic_enough(tcx, tp_ty)?; let alloc = alloc_type_name(tcx, tp_ty); - ConstValue::Slice { data: alloc, start: 0, end: alloc.inner().len() } + ConstValue::Slice { data: alloc, meta: alloc.inner().size().bytes() } } sym::needs_drop => { ensure_monomorphic_enough(tcx, tp_ty)?; diff --git a/compiler/rustc_const_eval/src/interpret/operand.rs b/compiler/rustc_const_eval/src/interpret/operand.rs index 788b50d7c4af..fdda98a50e8c 100644 --- a/compiler/rustc_const_eval/src/interpret/operand.rs +++ b/compiler/rustc_const_eval/src/interpret/operand.rs @@ -43,24 +43,30 @@ impl From> for Immediate { } impl Immediate { - pub fn from_pointer(p: Pointer, cx: &impl HasDataLayout) -> Self { - Immediate::Scalar(Scalar::from_pointer(p, cx)) + pub fn from_pointer(ptr: Pointer, cx: &impl HasDataLayout) -> Self { + Immediate::Scalar(Scalar::from_pointer(ptr, cx)) } - pub fn from_maybe_pointer(p: Pointer>, cx: &impl HasDataLayout) -> Self { - Immediate::Scalar(Scalar::from_maybe_pointer(p, cx)) + pub fn from_maybe_pointer(ptr: Pointer>, cx: &impl HasDataLayout) -> Self { + Immediate::Scalar(Scalar::from_maybe_pointer(ptr, cx)) } - pub fn new_slice(val: Scalar, len: u64, cx: &impl HasDataLayout) -> Self { - Immediate::ScalarPair(val, Scalar::from_target_usize(len, cx)) + pub fn new_slice(ptr: Pointer>, len: u64, cx: &impl HasDataLayout) -> Self { + Immediate::ScalarPair( + Scalar::from_maybe_pointer(ptr, cx), + Scalar::from_target_usize(len, cx), + ) } pub fn new_dyn_trait( - val: Scalar, + val: Pointer>, vtable: Pointer>, cx: &impl HasDataLayout, ) -> Self { - Immediate::ScalarPair(val, Scalar::from_maybe_pointer(vtable, cx)) + Immediate::ScalarPair( + Scalar::from_maybe_pointer(val, cx), + Scalar::from_maybe_pointer(vtable, cx), + ) } #[inline] @@ -722,16 +728,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } mir::ConstValue::Scalar(x) => Operand::Immediate(adjust_scalar(x)?.into()), mir::ConstValue::ZeroSized => Operand::Immediate(Immediate::Uninit), - mir::ConstValue::Slice { data, start, end } => { + mir::ConstValue::Slice { data, meta } => { // We rely on mutability being set correctly in `data` to prevent writes // where none should happen. - let ptr = Pointer::new( - self.tcx.reserve_and_set_memory_alloc(data), - Size::from_bytes(start), // offset: `start` - ); + let ptr = Pointer::new(self.tcx.reserve_and_set_memory_alloc(data), Size::ZERO); Operand::Immediate(Immediate::new_slice( - Scalar::from_pointer(self.global_base_pointer(ptr)?, &*self.tcx), - u64::try_from(end.checked_sub(start).unwrap()).unwrap(), // len: `end - start` + self.global_base_pointer(ptr)?.into(), + meta, self, )) } diff --git a/compiler/rustc_middle/src/mir/consts.rs b/compiler/rustc_middle/src/mir/consts.rs index 9e86eb214c12..0115b1dcb3ec 100644 --- a/compiler/rustc_middle/src/mir/consts.rs +++ b/compiler/rustc_middle/src/mir/consts.rs @@ -41,12 +41,20 @@ pub enum ConstValue<'tcx> { /// Only for ZSTs. ZeroSized, - /// Used for `&[u8]` and `&str`. + /// Used for references to unsized types with slice tail. /// - /// This is worth an optimized representation since Rust has literals of these types. - /// Not having to indirect those through an `AllocId` (or two, if we used `Indirect`) has shown - /// measurable performance improvements on stress tests. - Slice { data: ConstAllocation<'tcx>, start: usize, end: usize }, + /// This is worth an optimized representation since Rust has literals of type `&str` and + /// `&[u8]`. Not having to indirect those through an `AllocId` (or two, if we used `Indirect`) + /// has shown measurable performance improvements on stress tests. We then reuse this + /// optimization for slice-tail types more generally during valtree-to-constval conversion. + Slice { + /// The allocation storing the slice contents. + /// This always points to the beginning of the allocation. + data: ConstAllocation<'tcx>, + /// The metadata field of the reference. + /// This is a "target usize", so we use `u64` as in the interpreter. + meta: u64, + }, /// A value not representable by the other variants; needs to be stored in-memory. /// @@ -65,7 +73,7 @@ pub enum ConstValue<'tcx> { } #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] -static_assert_size!(ConstValue<'_>, 32); +static_assert_size!(ConstValue<'_>, 24); impl<'tcx> ConstValue<'tcx> { #[inline] @@ -124,7 +132,7 @@ impl<'tcx> ConstValue<'tcx> { ConstValue::Scalar(_) | ConstValue::ZeroSized => { bug!("`try_get_slice_bytes` on non-slice constant") } - &ConstValue::Slice { data, start, end } => (data, start, end), + &ConstValue::Slice { data, meta } => (data, 0, meta), &ConstValue::Indirect { alloc_id, offset } => { // The reference itself is stored behind an indirection. // Load the reference, and then load the actual slice contents. @@ -151,18 +159,19 @@ impl<'tcx> ConstValue<'tcx> { ) .ok()?; let len = len.to_target_usize(&tcx).ok()?; - let len: usize = len.try_into().ok()?; if len == 0 { return Some(&[]); } // Non-empty slice, must have memory. We know this is a relative pointer. let (inner_alloc_id, offset) = ptr.into_parts(); let data = tcx.global_alloc(inner_alloc_id?).unwrap_memory(); - (data, offset.bytes_usize(), offset.bytes_usize() + len) + (data, offset.bytes(), offset.bytes() + len) } }; // This is for diagnostics only, so we are okay to use `inspect_with_uninit_and_ptr_outside_interpreter`. + let start = start.try_into().unwrap(); + let end = end.try_into().unwrap(); Some(data.inner().inspect_with_uninit_and_ptr_outside_interpreter(start..end)) } } diff --git a/compiler/rustc_middle/src/ty/structural_impls.rs b/compiler/rustc_middle/src/ty/structural_impls.rs index f4158597d100..268339ed4028 100644 --- a/compiler/rustc_middle/src/ty/structural_impls.rs +++ b/compiler/rustc_middle/src/ty/structural_impls.rs @@ -457,6 +457,7 @@ TrivialLiftImpls! { (), bool, usize, + u64, } // For some things about which the type library does not know, or does not diff --git a/compiler/rustc_mir_build/src/build/expr/as_constant.rs b/compiler/rustc_mir_build/src/build/expr/as_constant.rs index 22712fd3df08..d8232199e014 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_constant.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_constant.rs @@ -131,14 +131,14 @@ fn lit_to_mir_constant<'tcx>( let s = s.as_str(); let allocation = Allocation::from_bytes_byte_aligned_immutable(s.as_bytes()); let allocation = tcx.mk_const_alloc(allocation); - ConstValue::Slice { data: allocation, start: 0, end: s.len() } + ConstValue::Slice { data: allocation, meta: allocation.inner().size().bytes() } } (ast::LitKind::ByteStr(data, _), ty::Ref(_, inner_ty, _)) if matches!(inner_ty.kind(), ty::Slice(_)) => { let allocation = Allocation::from_bytes_byte_aligned_immutable(data as &[u8]); let allocation = tcx.mk_const_alloc(allocation); - ConstValue::Slice { data: allocation, start: 0, end: data.len() } + ConstValue::Slice { data: allocation, meta: allocation.inner().size().bytes() } } (ast::LitKind::ByteStr(data, _), ty::Ref(_, inner_ty, _)) if inner_ty.is_array() => { let id = tcx.allocate_bytes(data); @@ -148,7 +148,7 @@ fn lit_to_mir_constant<'tcx>( { let allocation = Allocation::from_bytes_byte_aligned_immutable(data as &[u8]); let allocation = tcx.mk_const_alloc(allocation); - ConstValue::Slice { data: allocation, start: 0, end: data.len() } + ConstValue::Slice { data: allocation, meta: allocation.inner().size().bytes() } } (ast::LitKind::Byte(n), ty::Uint(ty::UintTy::U8)) => { ConstValue::Scalar(Scalar::from_uint(*n, Size::from_bytes(1))) diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 67e821dcf5a3..c8b721077565 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -1449,7 +1449,7 @@ fn collect_const_value<'tcx>( collect_alloc(tcx, ptr.provenance, output) } mir::ConstValue::Indirect { alloc_id, .. } => collect_alloc(tcx, alloc_id, output), - mir::ConstValue::Slice { data, start: _, end: _ } => { + mir::ConstValue::Slice { data, meta: _ } => { for &id in data.inner().provenance().ptrs().values() { collect_alloc(tcx, id, output); } diff --git a/compiler/rustc_smir/src/rustc_smir/alloc.rs b/compiler/rustc_smir/src/rustc_smir/alloc.rs index 58b695f755fb..d8766cf8ce23 100644 --- a/compiler/rustc_smir/src/rustc_smir/alloc.rs +++ b/compiler/rustc_smir/src/rustc_smir/alloc.rs @@ -47,14 +47,12 @@ pub fn new_allocation<'tcx>( tables.tcx.layout_of(rustc_middle::ty::ParamEnv::empty().and(ty)).unwrap().align; new_empty_allocation(align.abi) } - ConstValue::Slice { data, start, end } => { + ConstValue::Slice { data, meta } => { let alloc_id = tables.tcx.reserve_and_set_memory_alloc(data); - let ptr = Pointer::new(alloc_id, rustc_target::abi::Size::from_bytes(start)); + let ptr = Pointer::new(alloc_id, rustc_target::abi::Size::ZERO); let scalar_ptr = rustc_middle::mir::interpret::Scalar::from_pointer(ptr, &tables.tcx); - let scalar_len = rustc_middle::mir::interpret::Scalar::from_target_usize( - (end - start) as u64, - &tables.tcx, - ); + let scalar_meta = + rustc_middle::mir::interpret::Scalar::from_target_usize(meta, &tables.tcx); let layout = tables.tcx.layout_of(rustc_middle::ty::ParamEnv::reveal_all().and(ty)).unwrap(); let mut allocation = @@ -69,8 +67,8 @@ pub fn new_allocation<'tcx>( allocation .write_scalar( &tables.tcx, - alloc_range(tables.tcx.data_layout.pointer_size, scalar_len.size()), - scalar_len, + alloc_range(tables.tcx.data_layout.pointer_size, scalar_meta.size()), + scalar_meta, ) .unwrap(); allocation.stable(tables) diff --git a/src/tools/miri/src/shims/backtrace.rs b/src/tools/miri/src/shims/backtrace.rs index bfec4833ac90..836cab3ac9da 100644 --- a/src/tools/miri/src/shims/backtrace.rs +++ b/src/tools/miri/src/shims/backtrace.rs @@ -89,7 +89,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } this.write_immediate( - Immediate::new_slice(Scalar::from_maybe_pointer(alloc.ptr(), this), len, this), + Immediate::new_slice(alloc.ptr(), len, this), dest, )?; } From 2694b84fbf2c49e1cb7583abf6f62db4a972b614 Mon Sep 17 00:00:00 2001 From: Ziru Niu Date: Tue, 5 Sep 2023 06:58:01 +0800 Subject: [PATCH 077/111] remove unneeded `ToPredicate` impls --- compiler/rustc_middle/src/ty/mod.rs | 32 ----------------------------- 1 file changed, 32 deletions(-) diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index eb8ea0bc1147..d9bd9ad5543d 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -1253,14 +1253,6 @@ impl<'tcx> ToPredicate<'tcx, Clause<'tcx>> for TraitRef<'tcx> { } } -impl<'tcx> ToPredicate<'tcx, Clause<'tcx>> for TraitPredicate<'tcx> { - #[inline(always)] - fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Clause<'tcx> { - let p: Predicate<'tcx> = self.to_predicate(tcx); - p.expect_clause() - } -} - impl<'tcx> ToPredicate<'tcx> for Binder<'tcx, TraitRef<'tcx>> { #[inline(always)] fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { @@ -1287,18 +1279,6 @@ impl<'tcx> ToPredicate<'tcx, PolyTraitPredicate<'tcx>> for Binder<'tcx, TraitRef } } -impl<'tcx> ToPredicate<'tcx, PolyTraitPredicate<'tcx>> for TraitRef<'tcx> { - fn to_predicate(self, tcx: TyCtxt<'tcx>) -> PolyTraitPredicate<'tcx> { - ty::Binder::dummy(self).to_predicate(tcx) - } -} - -impl<'tcx> ToPredicate<'tcx, PolyTraitPredicate<'tcx>> for TraitPredicate<'tcx> { - fn to_predicate(self, _tcx: TyCtxt<'tcx>) -> PolyTraitPredicate<'tcx> { - ty::Binder::dummy(self) - } -} - impl<'tcx> ToPredicate<'tcx> for PolyTraitPredicate<'tcx> { fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { self.map_bound(|p| PredicateKind::Clause(ClauseKind::Trait(p))).to_predicate(tcx) @@ -1312,12 +1292,6 @@ impl<'tcx> ToPredicate<'tcx, Clause<'tcx>> for PolyTraitPredicate<'tcx> { } } -impl<'tcx> ToPredicate<'tcx> for OutlivesPredicate, ty::Region<'tcx>> { - fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { - ty::Binder::dummy(PredicateKind::Clause(ClauseKind::RegionOutlives(self))).to_predicate(tcx) - } -} - impl<'tcx> ToPredicate<'tcx> for PolyRegionOutlivesPredicate<'tcx> { fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { self.map_bound(|p| PredicateKind::Clause(ClauseKind::RegionOutlives(p))).to_predicate(tcx) @@ -1330,12 +1304,6 @@ impl<'tcx> ToPredicate<'tcx> for OutlivesPredicate, ty::Region<'tcx>> { } } -impl<'tcx> ToPredicate<'tcx> for PolyTypeOutlivesPredicate<'tcx> { - fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { - self.map_bound(|p| PredicateKind::Clause(ClauseKind::TypeOutlives(p))).to_predicate(tcx) - } -} - impl<'tcx> ToPredicate<'tcx> for ProjectionPredicate<'tcx> { fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { ty::Binder::dummy(PredicateKind::Clause(ClauseKind::Projection(self))).to_predicate(tcx) From 3c69a107d0149a27c11490a0d67717c555eb1288 Mon Sep 17 00:00:00 2001 From: Ziru Niu Date: Tue, 5 Sep 2023 07:21:38 +0800 Subject: [PATCH 078/111] remove `impl<'tcx> ToPredicate<'tcx, Clause<'tcx>> for PolyProjectionPredicate<'tcx>` --- .../src/check/compare_impl_item.rs | 10 +++++----- compiler/rustc_middle/src/ty/mod.rs | 12 +++++------- compiler/rustc_middle/src/ty/sty.rs | 2 +- .../src/solve/project_goals.rs | 10 ++++++---- .../rustc_trait_selection/src/traits/project.rs | 2 +- compiler/rustc_ty_utils/src/ty.rs | 13 +++++-------- 6 files changed, 23 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs index 92cc9759304f..38f19aa09adf 100644 --- a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs +++ b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs @@ -18,7 +18,7 @@ use rustc_middle::ty::util::ExplicitSelf; use rustc_middle::ty::{ self, GenericArgs, Ty, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitableExt, }; -use rustc_middle::ty::{GenericParamDefKind, ToPredicate, TyCtxt}; +use rustc_middle::ty::{GenericParamDefKind, TyCtxt}; use rustc_span::{Span, DUMMY_SP}; use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt; use rustc_trait_selection::traits::outlives_bounds::InferCtxtExt as _; @@ -2196,16 +2196,16 @@ pub(super) fn check_type_bounds<'tcx>( // // impl X for T where T: X { type Y = ::Y; } } - _ => predicates.push( + _ => predicates.push(ty::Clause::from_projection_clause( + tcx, ty::Binder::bind_with_vars( ty::ProjectionPredicate { projection_ty: tcx.mk_alias_ty(trait_ty.def_id, rebased_args), term: normalize_impl_ty.into(), }, bound_vars, - ) - .to_predicate(tcx), - ), + ), + )), }; ty::ParamEnv::new(tcx.mk_clauses(&predicates), Reveal::UserFacing) }; diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index d9bd9ad5543d..0db48f2f7ecd 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -566,6 +566,11 @@ impl rustc_errors::IntoDiagnosticArg for Clause<'_> { pub struct Clause<'tcx>(Interned<'tcx, WithCachedTypeInfo>>>); impl<'tcx> Clause<'tcx> { + pub fn from_projection_clause(tcx: TyCtxt<'tcx>, pred: PolyProjectionPredicate<'tcx>) -> Self { + let pred: Predicate<'tcx> = pred.to_predicate(tcx); + pred.expect_clause() + } + pub fn as_predicate(self) -> Predicate<'tcx> { Predicate(self.0) } @@ -1323,13 +1328,6 @@ impl<'tcx> ToPredicate<'tcx, Clause<'tcx>> for ProjectionPredicate<'tcx> { } } -impl<'tcx> ToPredicate<'tcx, Clause<'tcx>> for PolyProjectionPredicate<'tcx> { - fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Clause<'tcx> { - let p: Predicate<'tcx> = self.to_predicate(tcx); - p.expect_clause() - } -} - impl<'tcx> ToPredicate<'tcx> for TraitPredicate<'tcx> { fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { PredicateKind::Clause(ClauseKind::Trait(self)).to_predicate(tcx) diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index a14d09bbb700..e3e014a3b2a8 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -725,7 +725,7 @@ impl<'tcx> PolyExistentialPredicate<'tcx> { self.rebind(tr).with_self_ty(tcx, self_ty).to_predicate(tcx) } ExistentialPredicate::Projection(p) => { - self.rebind(p.with_self_ty(tcx, self_ty)).to_predicate(tcx) + ty::Clause::from_projection_clause(tcx, self.rebind(p.with_self_ty(tcx, self_ty))) } ExistentialPredicate::AutoTrait(did) => { let generics = tcx.generics_of(did); diff --git a/compiler/rustc_trait_selection/src/solve/project_goals.rs b/compiler/rustc_trait_selection/src/solve/project_goals.rs index 3fe914e50be1..f6e781b87ba7 100644 --- a/compiler/rustc_trait_selection/src/solve/project_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/project_goals.rs @@ -346,13 +346,15 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { ty::TraitRef::from_lang_item(tcx, LangItem::Sized, DUMMY_SP, [output]) }); - let pred = tupled_inputs_and_output - .map_bound(|(inputs, output)| ty::ProjectionPredicate { + let pred = ty::Clause::from_projection_clause( + tcx, + tupled_inputs_and_output.map_bound(|(inputs, output)| ty::ProjectionPredicate { projection_ty: tcx .mk_alias_ty(goal.predicate.def_id(), [goal.predicate.self_ty(), inputs]), term: output.into(), - }) - .to_predicate(tcx); + }), + ); + // A built-in `Fn` impl only holds if the output is sized. // (FIXME: technically we only need to check this if the type is a fn ptr...) Self::consider_implied_clause(ecx, goal, pred, [goal.with(tcx, output_is_sized_pred)]) diff --git a/compiler/rustc_trait_selection/src/traits/project.rs b/compiler/rustc_trait_selection/src/traits/project.rs index 6444c01a67b5..93e8e1f4bb1e 100644 --- a/compiler/rustc_trait_selection/src/traits/project.rs +++ b/compiler/rustc_trait_selection/src/traits/project.rs @@ -1644,7 +1644,7 @@ fn assemble_candidates_from_object_ty<'cx, 'tcx>( let env_predicates = data .projection_bounds() .filter(|bound| bound.item_def_id() == obligation.predicate.def_id) - .map(|p| p.with_self_ty(tcx, object_ty).to_predicate(tcx)); + .map(|p| ty::Clause::from_projection_clause(tcx, p.with_self_ty(tcx, object_ty))); assemble_candidates_from_predicates( selcx, diff --git a/compiler/rustc_ty_utils/src/ty.rs b/compiler/rustc_ty_utils/src/ty.rs index ba0258b63cb6..2288d36df17b 100644 --- a/compiler/rustc_ty_utils/src/ty.rs +++ b/compiler/rustc_ty_utils/src/ty.rs @@ -4,7 +4,7 @@ use rustc_hir::def::DefKind; use rustc_index::bit_set::BitSet; use rustc_middle::query::Providers; use rustc_middle::ty::{ - self, EarlyBinder, ToPredicate, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor, + self, EarlyBinder, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor, }; use rustc_span::def_id::{DefId, LocalDefId, CRATE_DEF_ID}; use rustc_span::DUMMY_SP; @@ -220,13 +220,10 @@ impl<'tcx> TypeVisitor> for ImplTraitInTraitFinder<'_, 'tcx> { // strategy, then just reinterpret the associated type like an opaque :^) let default_ty = self.tcx.type_of(shifted_alias_ty.def_id).instantiate(self.tcx, shifted_alias_ty.args); - self.predicates.push( - ty::Binder::bind_with_vars( - ty::ProjectionPredicate { projection_ty: shifted_alias_ty, term: default_ty.into() }, - self.bound_vars, - ) - .to_predicate(self.tcx), - ); + self.predicates.push(ty::Clause::from_projection_clause(self.tcx, ty::Binder::bind_with_vars( + ty::ProjectionPredicate { projection_ty: shifted_alias_ty, term: default_ty.into() }, + self.bound_vars, + ))); // We walk the *un-shifted* alias ty, because we're tracking the de bruijn // binder depth, and if we were to walk `shifted_alias_ty` instead, we'd From 2243872c26ae414d492d844a1cd03d42884008bc Mon Sep 17 00:00:00 2001 From: Ethan Brierley Date: Tue, 19 Sep 2023 21:21:01 +0100 Subject: [PATCH 079/111] fix confusing let chain indentation in rustc_resolve --- compiler/rustc_resolve/src/imports.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 820ac9ef61b0..d271519a8a38 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -991,9 +991,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { if !is_prelude && let Some(max_vis) = max_vis.get() && !max_vis.is_at_least(import.expect_vis(), self.tcx) - { - self.lint_buffer.buffer_lint(UNUSED_IMPORTS, id, import.span, fluent::resolve_glob_import_doesnt_reexport); - } + { + self.lint_buffer.buffer_lint(UNUSED_IMPORTS, id, import.span, fluent::resolve_glob_import_doesnt_reexport); + } return None; } _ => unreachable!(), From ed3c63cbec0cec2f787597ec1ca36137b1ba6e08 Mon Sep 17 00:00:00 2001 From: Mark Rousskov Date: Tue, 12 Sep 2023 10:46:14 -0400 Subject: [PATCH 080/111] copy 1.72.1 release notes to master update release date to actual released date --- RELEASES.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index c5ac070b317b..ba11234b990a 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,13 @@ +Version 1.72.1 (2023-09-19) +=========================== + +- [Adjust codegen change to improve LLVM codegen](https://github.com/rust-lang/rust/pull/115236) +- [rustdoc: Fix self ty params in objects with lifetimes](https://github.com/rust-lang/rust/pull/115276) +- [Fix regression in compile times](https://github.com/rust-lang/rust/pull/114948) +- Resolve some ICE regressions in the compiler: + - [#115215](https://github.com/rust-lang/rust/pull/115215) + - [#115559](https://github.com/rust-lang/rust/pull/115559) + Version 1.72.0 (2023-08-24) ========================== From 0163768e0d98ea41432f4c144d900e4865c5be39 Mon Sep 17 00:00:00 2001 From: WANG Rui Date: Mon, 11 Sep 2023 16:05:49 +0800 Subject: [PATCH 081/111] rustc_target/loongarch: Fix passing of transparent unions with only one non-ZST member This ensures that `MaybeUninit` has the same ABI as `T` when passed through an `extern "C"` function. Fixes https://github.com/rust-lang/rust/issues/115509 --- compiler/rustc_target/src/abi/call/loongarch.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/compiler/rustc_target/src/abi/call/loongarch.rs b/compiler/rustc_target/src/abi/call/loongarch.rs index 247256f076ba..e649d58bbcab 100644 --- a/compiler/rustc_target/src/abi/call/loongarch.rs +++ b/compiler/rustc_target/src/abi/call/loongarch.rs @@ -83,6 +83,17 @@ where } FieldsShape::Union(_) => { if !arg_layout.is_zst() { + if arg_layout.is_transparent() { + let non_1zst_elem = arg_layout.non_1zst_field(cx).expect("not exactly one non-1-ZST field in non-ZST repr(transparent) union").1; + return should_use_fp_conv_helper( + cx, + &non_1zst_elem, + xlen, + flen, + field1_kind, + field2_kind, + ); + } return Err(CannotUseFpConv); } } From 10d55c3e03a833ea5cf37eea283d631505bccad6 Mon Sep 17 00:00:00 2001 From: WANG Rui Date: Wed, 20 Sep 2023 09:24:22 +0800 Subject: [PATCH 082/111] tests/ui/abi: Enable repr(transparent) union ABI tests on LoongArch64 --- tests/ui/abi/compatibility.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/ui/abi/compatibility.rs b/tests/ui/abi/compatibility.rs index d4f42cdda97a..249e81762830 100644 --- a/tests/ui/abi/compatibility.rs +++ b/tests/ui/abi/compatibility.rs @@ -10,7 +10,6 @@ use std::ptr::NonNull; // Hence there are `cfg` throughout this test to disable parts of it on those targets. // sparc64: https://github.com/rust-lang/rust/issues/115336 // mips64: https://github.com/rust-lang/rust/issues/115404 -// loongarch64: https://github.com/rust-lang/rust/issues/115509 macro_rules! assert_abi_compatible { ($name:ident, $t1:ty, $t2:ty) => { @@ -109,7 +108,6 @@ macro_rules! test_transparent { test_abi_compatible!(wrap1, $t, Wrapper1<$t>); test_abi_compatible!(wrap2, $t, Wrapper2<$t>); test_abi_compatible!(wrap3, $t, Wrapper3<$t>); - #[cfg(not(target_arch = "loongarch64"))] test_abi_compatible!(wrap4, $t, WrapperUnion<$t>); } }; From 9fbee7dcc556cf731e00298f9ed0bbe22f57e7bb Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 19 Sep 2023 19:40:36 -0700 Subject: [PATCH 083/111] rustdoc: add test cases, and fix, search tabs --- src/librustdoc/html/static/css/rustdoc.css | 12 ++- src/librustdoc/html/static/js/search.js | 9 ++- tests/rustdoc-gui/search-tab.goml | 86 +++++++++++++++++++++- 3 files changed, 100 insertions(+), 7 deletions(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 3b2366413376..47f9e6502811 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1355,6 +1355,7 @@ a.tooltip:hover::after { #search-tabs .count { font-size: 1rem; + font-variant-numeric: tabular-nums; color: var(--search-tab-title-count-color); } @@ -1637,6 +1638,13 @@ However, it's not needed with smaller screen width because the doc/code block is /* Media Queries */ +/* Make sure all the buttons line wrap at the same time */ +@media (max-width: 850px) { + #search-tabs .count { + display: block; + } +} + /* WARNING: RUSTDOC_MOBILE_BREAKPOINT MEDIA QUERY If you update this line, then you also need to update the line with the same warning @@ -1764,10 +1772,6 @@ in src-script.js display: none !important; } - #search-tabs .count { - display: block; - } - #main-content > details.toggle > summary::before, #main-content > div > details.toggle > summary::before { left: -11px; diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 407bf5f2c5fe..3dd60296e81e 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -2334,11 +2334,16 @@ ${item.displayPath}${name}\ } function makeTabHeader(tabNb, text, nbElems) { + // https://blog.horizon-eda.org/misc/2020/02/19/ui.html + const fmtNbElems = + nbElems < 10 ? `\u{2007}(${nbElems})\u{2007}\u{2007}` : + nbElems < 100 ? `\u{2007}(${nbElems})\u{2007}` : + `\u{2007}(${nbElems})`; if (searchState.currentTab === tabNb) { return ""; + "" + fmtNbElems + ""; } - return ""; + return ""; } /** diff --git a/tests/rustdoc-gui/search-tab.goml b/tests/rustdoc-gui/search-tab.goml index 7bbde3ec23d3..427201e1b5da 100644 --- a/tests/rustdoc-gui/search-tab.goml +++ b/tests/rustdoc-gui/search-tab.goml @@ -1,5 +1,5 @@ // Checking the colors of the search tab headers. -go-to: "file://" + |DOC_PATH| + "/test_docs/fn.foo.html?search=something" +go-to: "file://" + |DOC_PATH| + "/test_docs/fn.foo.html?search=foo" show-text: true define-function: ( @@ -74,3 +74,87 @@ call-function: ("check-colors", { "border_top_selected": "2px solid #0089ff", "border_top_hover": "2px solid #0089ff", }) + +// set size wide enough that the text is in a single row +set-window-size: (851, 600) + +// Check the size and count in tabs +assert-text: ("#search-tabs > button:nth-child(1) > .count", " (23) ") +assert-text: ("#search-tabs > button:nth-child(2) > .count", " (4)  ") +assert-text: ("#search-tabs > button:nth-child(3) > .count", " (0)  ") +store-property: ("#search-tabs > button:nth-child(1)", {"offsetWidth": buttonWidth}) +assert-property: ("#search-tabs > button:nth-child(2)", {"offsetWidth": |buttonWidth|}) +assert-property: ("#search-tabs > button:nth-child(3)", {"offsetWidth": |buttonWidth|}) +store-property: ("#search-tabs > button:nth-child(1) > .count", {"offsetWidth": countWidth}) +assert-property: ("#search-tabs > button:nth-child(2) > .count", {"offsetWidth": |countWidth|}) +assert-property: ("#search-tabs > button:nth-child(3) > .count", {"offsetWidth": |countWidth|}) + +// Check that counts are in a row with each other +compare-elements-position: ( + "#search-tabs > button:nth-child(1) > .count", + "#search-tabs > button:nth-child(2) > .count", + ("y") +) +compare-elements-position: ( + "#search-tabs > button:nth-child(2) > .count", + "#search-tabs > button:nth-child(3) > .count", + ("y") +) +// Check that counts are beside the titles and haven't wrapped +compare-elements-position-near: ( + "#search-tabs > button:nth-child(1)", + "#search-tabs > button:nth-child(1) > .count", + {"y": 8} +) +compare-elements-position-near: ( + "#search-tabs > button:nth-child(2)", + "#search-tabs > button:nth-child(2) > .count", + {"y": 8} +) +compare-elements-position-near: ( + "#search-tabs > button:nth-child(2)", + "#search-tabs > button:nth-child(2) > .count", + {"y": 8} +) + +// Set size narrow enough that they wrap. +// When I tested it, it wrapped at 811px, but I added some fudge factor to ensure it +// doesn't prematurely wrap with slightly different font kerning or whatever, with a +// @media query +set-window-size: (850, 600) + +// all counts and buttons still have same size +store-property: ("#search-tabs > button:nth-child(1)", {"offsetWidth": buttonWidth}) +assert-property: ("#search-tabs > button:nth-child(2)", {"offsetWidth": |buttonWidth|}) +assert-property: ("#search-tabs > button:nth-child(3)", {"offsetWidth": |buttonWidth|}) +store-property: ("#search-tabs > button:nth-child(1) > .count", {"offsetWidth": countWidth}) +assert-property: ("#search-tabs > button:nth-child(2) > .count", {"offsetWidth": |countWidth|}) +assert-property: ("#search-tabs > button:nth-child(3) > .count", {"offsetWidth": |countWidth|}) + +// Check that counts are still in a row with each other +compare-elements-position: ( + "#search-tabs > button:nth-child(1) > .count", + "#search-tabs > button:nth-child(2) > .count", + ("y") +) +compare-elements-position: ( + "#search-tabs > button:nth-child(2) > .count", + "#search-tabs > button:nth-child(3) > .count", + ("y") +) +// Check that counts are NOT beside the titles; now they have wrapped +compare-elements-position-near-false: ( + "#search-tabs > button:nth-child(1)", + "#search-tabs > button:nth-child(1) > .count", + {"y": 8} +) +compare-elements-position-near-false: ( + "#search-tabs > button:nth-child(2)", + "#search-tabs > button:nth-child(2) > .count", + {"y": 8} +) +compare-elements-position-near-false: ( + "#search-tabs > button:nth-child(2)", + "#search-tabs > button:nth-child(2) > .count", + {"y": 8} +) From 4bb9c0f79bffd578503cb7a181ceec26c2b1067b Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 20 Sep 2023 07:49:23 +0200 Subject: [PATCH 084/111] Allow anyone to set llvm-fixed-upstream Allow llvm-* to be set by unauthenticated users, which is currently llvm-main and llvm-fixed-upstream. --- triagebot.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/triagebot.toml b/triagebot.toml index 5b4e653b1004..4a84f3caa95d 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -13,7 +13,7 @@ allow-unauthenticated = [ "WG-*", "beta-nominated", "const-hack", - "llvm-main", + "llvm-*", "needs-fcp", "relnotes", "requires-*", From 670631dd2606e7cb7cc488a2f7a7331e9dfd39af Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 20 Sep 2023 08:31:33 +0200 Subject: [PATCH 085/111] Ensure `build/tmp` exists in `rustdoc_themes::get_themes` --- src/tools/rustdoc-themes/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/rustdoc-themes/main.rs b/src/tools/rustdoc-themes/main.rs index cc13df1f5ba4..1eba83a80572 100644 --- a/src/tools/rustdoc-themes/main.rs +++ b/src/tools/rustdoc-themes/main.rs @@ -1,5 +1,5 @@ use std::env::args; -use std::fs::File; +use std::fs::{create_dir_all, File}; use std::io::{BufRead, BufReader, BufWriter, Write}; use std::path::Path; use std::process::{exit, Command}; @@ -14,6 +14,7 @@ fn get_themes>(style_path: P) -> Vec { std::time::SystemTime::UNIX_EPOCH.elapsed().expect("time is after UNIX epoch").as_millis(); let mut in_theme = None; + create_dir_all("build/tmp").expect("failed to create temporary test directory"); for line in BufReader::new(File::open(style_path).expect("read rustdoc.css failed")).lines() { let line = line.expect("read line from rustdoc.css failed"); let line = line.trim(); From 3d66513fe4eaa754f868f435334acfd78e48337c Mon Sep 17 00:00:00 2001 From: Zalathar Date: Tue, 19 Sep 2023 20:26:23 +1000 Subject: [PATCH 086/111] coverage: Remove debug code from the instrumentor --- .../src/coverage/counters.rs | 42 +- .../rustc_mir_transform/src/coverage/debug.rs | 797 ------------------ .../rustc_mir_transform/src/coverage/graph.rs | 7 - .../rustc_mir_transform/src/coverage/mod.rs | 107 +-- .../rustc_mir_transform/src/coverage/spans.rs | 49 -- compiler/rustc_session/src/options.rs | 7 +- ...rage_graphviz.bar.InstrumentCoverage.0.dot | 6 - ...age_graphviz.main.InstrumentCoverage.0.dot | 13 - tests/mir-opt/coverage_graphviz.rs | 20 - 9 files changed, 18 insertions(+), 1030 deletions(-) delete mode 100644 compiler/rustc_mir_transform/src/coverage/debug.rs delete mode 100644 tests/mir-opt/coverage_graphviz.bar.InstrumentCoverage.0.dot delete mode 100644 tests/mir-opt/coverage_graphviz.main.InstrumentCoverage.0.dot delete mode 100644 tests/mir-opt/coverage_graphviz.rs diff --git a/compiler/rustc_mir_transform/src/coverage/counters.rs b/compiler/rustc_mir_transform/src/coverage/counters.rs index 3d442e5dca9f..b3a33ebae15b 100644 --- a/compiler/rustc_mir_transform/src/coverage/counters.rs +++ b/compiler/rustc_mir_transform/src/coverage/counters.rs @@ -1,10 +1,8 @@ use super::Error; -use super::debug; use super::graph; use super::spans; -use debug::{DebugCounters, NESTED_INDENT}; use graph::{BasicCoverageBlock, BcbBranch, CoverageGraph, TraverseCoverageGraphWithLoops}; use spans::CoverageSpan; @@ -16,6 +14,8 @@ use rustc_middle::mir::coverage::*; use std::fmt::{self, Debug}; +const NESTED_INDENT: &str = " "; + /// The coverage counter or counter expression associated with a particular /// BCB node or BCB edge. #[derive(Clone)] @@ -75,8 +75,6 @@ pub(super) struct CoverageCounters { /// BCB/edge, but are needed as operands to more complex expressions. /// These are always [`BcbCounter::Expression`]. pub(super) intermediate_expressions: Vec, - - pub debug_counters: DebugCounters, } impl CoverageCounters { @@ -91,17 +89,9 @@ impl CoverageCounters { bcb_edge_counters: FxHashMap::default(), bcb_has_incoming_edge_counters: BitSet::new_empty(num_bcbs), intermediate_expressions: Vec::new(), - - debug_counters: DebugCounters::new(), } } - /// Activate the `DebugCounters` data structures, to provide additional debug formatting - /// features when formatting [`BcbCounter`] (counter) values. - pub fn enable_debug(&mut self) { - self.debug_counters.enable(); - } - /// Makes [`BcbCounter`] `Counter`s and `Expressions` for the `BasicCoverageBlock`s directly or /// indirectly associated with `CoverageSpans`, and accumulates additional `Expression`s /// representing intermediate values. @@ -113,15 +103,12 @@ impl CoverageCounters { MakeBcbCounters::new(self, basic_coverage_blocks).make_bcb_counters(coverage_spans) } - fn make_counter(&mut self, debug_block_label_fn: F) -> BcbCounter + fn make_counter(&mut self, _debug_block_label_fn: F) -> BcbCounter where F: Fn() -> Option, { - let counter = BcbCounter::Counter { id: self.next_counter() }; - if self.debug_counters.is_enabled() { - self.debug_counters.add_counter(&counter, (debug_block_label_fn)()); - } - counter + let id = self.next_counter(); + BcbCounter::Counter { id } } fn make_expression( @@ -129,28 +116,17 @@ impl CoverageCounters { lhs: Operand, op: Op, rhs: Operand, - debug_block_label_fn: F, + _debug_block_label_fn: F, ) -> BcbCounter where F: Fn() -> Option, { let id = self.next_expression(); - let expression = BcbCounter::Expression { id, lhs, op, rhs }; - if self.debug_counters.is_enabled() { - self.debug_counters.add_counter(&expression, (debug_block_label_fn)()); - } - expression + BcbCounter::Expression { id, lhs, op, rhs } } pub fn make_identity_counter(&mut self, counter_operand: Operand) -> BcbCounter { - let some_debug_block_label = if self.debug_counters.is_enabled() { - self.debug_counters.some_block_label(counter_operand).cloned() - } else { - None - }; - self.make_expression(counter_operand, Op::Add, Operand::Zero, || { - some_debug_block_label.clone() - }) + self.make_expression(counter_operand, Op::Add, Operand::Zero, || unreachable!()) } /// Counter IDs start from one and go up. @@ -713,6 +689,6 @@ impl<'a> MakeBcbCounters<'a> { #[inline] fn format_counter(&self, counter_kind: &BcbCounter) -> String { - self.coverage_counters.debug_counters.format_counter(counter_kind) + format!("{counter_kind:?}") } } diff --git a/compiler/rustc_mir_transform/src/coverage/debug.rs b/compiler/rustc_mir_transform/src/coverage/debug.rs deleted file mode 100644 index bb1f16aa8bef..000000000000 --- a/compiler/rustc_mir_transform/src/coverage/debug.rs +++ /dev/null @@ -1,797 +0,0 @@ -//! The `InstrumentCoverage` MIR pass implementation includes debugging tools and options -//! to help developers understand and/or improve the analysis and instrumentation of a MIR. -//! -//! To enable coverage, include the rustc command line option: -//! -//! * `-C instrument-coverage` -//! -//! MIR Dump Files, with additional `CoverageGraph` graphviz and `CoverageSpan` spanview -//! ------------------------------------------------------------------------------------ -//! -//! Additional debugging options include: -//! -//! * `-Z dump-mir=InstrumentCoverage` - Generate `.mir` files showing the state of the MIR, -//! before and after the `InstrumentCoverage` pass, for each compiled function. -//! -//! * `-Z dump-mir-graphviz` - If `-Z dump-mir` is also enabled for the current MIR node path, -//! each MIR dump is accompanied by a before-and-after graphical view of the MIR, in Graphviz -//! `.dot` file format (which can be visually rendered as a graph using any of a number of free -//! Graphviz viewers and IDE extensions). -//! -//! For the `InstrumentCoverage` pass, this option also enables generation of an additional -//! Graphviz `.dot` file for each function, rendering the `CoverageGraph`: the control flow -//! graph (CFG) of `BasicCoverageBlocks` (BCBs), as nodes, internally labeled to show the -//! `CoverageSpan`-based MIR elements each BCB represents (`BasicBlock`s, `Statement`s and -//! `Terminator`s), assigned coverage counters and/or expressions, and edge counters, as needed. -//! -//! (Note the additional option, `-Z graphviz-dark-mode`, can be added, to change the rendered -//! output from its default black-on-white background to a dark color theme, if desired.) -//! -//! * `-Z dump-mir-spanview` - If `-Z dump-mir` is also enabled for the current MIR node path, -//! each MIR dump is accompanied by a before-and-after `.html` document showing the function's -//! original source code, highlighted by it's MIR spans, at the `statement`-level (by default), -//! `terminator` only, or encompassing span for the `Terminator` plus all `Statement`s, in each -//! `block` (`BasicBlock`). -//! -//! For the `InstrumentCoverage` pass, this option also enables generation of an additional -//! spanview `.html` file for each function, showing the aggregated `CoverageSpan`s that will -//! require counters (or counter expressions) for accurate coverage analysis. -//! -//! Debug Logging -//! ------------- -//! -//! The `InstrumentCoverage` pass includes debug logging messages at various phases and decision -//! points, which can be enabled via environment variable: -//! -//! ```shell -//! RUSTC_LOG=rustc_mir_transform::coverage=debug -//! ``` -//! -//! Other module paths with coverage-related debug logs may also be of interest, particularly for -//! debugging the coverage map data, injected as global variables in the LLVM IR (during rustc's -//! code generation pass). For example: -//! -//! ```shell -//! RUSTC_LOG=rustc_mir_transform::coverage,rustc_codegen_llvm::coverageinfo=debug -//! ``` -//! -//! Coverage Debug Options -//! --------------------------------- -//! -//! Additional debugging options can be enabled using the environment variable: -//! -//! ```shell -//! RUSTC_COVERAGE_DEBUG_OPTIONS= -//! ``` -//! -//! These options are comma-separated, and specified in the format `option-name=value`. For example: -//! -//! ```shell -//! $ RUSTC_COVERAGE_DEBUG_OPTIONS=counter-format=id+operation,allow-unused-expressions=yes cargo build -//! ``` -//! -//! Coverage debug options include: -//! -//! * `allow-unused-expressions=yes` or `no` (default: `no`) -//! -//! The `InstrumentCoverage` algorithms _should_ only create and assign expressions to a -//! `BasicCoverageBlock`, or an incoming edge, if that expression is either (a) required to -//! count a `CoverageSpan`, or (b) a dependency of some other required counter expression. -//! -//! If an expression is generated that does not map to a `CoverageSpan` or dependency, this -//! probably indicates there was a bug in the algorithm that creates and assigns counters -//! and expressions. -//! -//! When this kind of bug is encountered, the rustc compiler will panic by default. Setting: -//! `allow-unused-expressions=yes` will log a warning message instead of panicking (effectively -//! ignoring the unused expressions), which may be helpful when debugging the root cause of -//! the problem. -//! -//! * `counter-format=`, where `` can be any plus-separated combination of `id`, -//! `block`, and/or `operation` (default: `block+operation`) -//! -//! This option effects both the `CoverageGraph` (graphviz `.dot` files) and debug logging, when -//! generating labels for counters and expressions. -//! -//! Depending on the values and combinations, counters can be labeled by: -//! -//! * `id` - counter or expression ID (ascending counter IDs, starting at 1, or descending -//! expression IDs, starting at `u32:MAX`) -//! * `block` - the `BasicCoverageBlock` label (for example, `bcb0`) or edge label (for -//! example `bcb0->bcb1`), for counters or expressions assigned to count a -//! `BasicCoverageBlock` or edge. Intermediate expressions (not directly associated with -//! a BCB or edge) will be labeled by their expression ID, unless `operation` is also -//! specified. -//! * `operation` - applied to expressions only, labels include the left-hand-side counter -//! or expression label (lhs operand), the operator (`+` or `-`), and the right-hand-side -//! counter or expression (rhs operand). Expression operand labels are generated -//! recursively, generating labels with nested operations, enclosed in parentheses -//! (for example: `bcb2 + (bcb0 - bcb1)`). - -use std::iter; -use std::ops::Deref; -use std::sync::OnceLock; - -use itertools::Itertools; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_middle::mir::coverage::*; -use rustc_middle::mir::create_dump_file; -use rustc_middle::mir::generic_graphviz::GraphvizWriter; -use rustc_middle::mir::spanview::{self, SpanViewable}; -use rustc_middle::mir::{self, BasicBlock}; -use rustc_middle::ty::TyCtxt; -use rustc_span::Span; - -use super::counters::{BcbCounter, CoverageCounters}; -use super::graph::{BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph}; -use super::spans::CoverageSpan; - -pub const NESTED_INDENT: &str = " "; - -const RUSTC_COVERAGE_DEBUG_OPTIONS: &str = "RUSTC_COVERAGE_DEBUG_OPTIONS"; - -pub(super) fn debug_options<'a>() -> &'a DebugOptions { - static DEBUG_OPTIONS: OnceLock = OnceLock::new(); - - &DEBUG_OPTIONS.get_or_init(DebugOptions::from_env) -} - -/// Parses and maintains coverage-specific debug options captured from the environment variable -/// "RUSTC_COVERAGE_DEBUG_OPTIONS", if set. -#[derive(Debug, Clone)] -pub(super) struct DebugOptions { - pub allow_unused_expressions: bool, - counter_format: ExpressionFormat, -} - -impl DebugOptions { - fn from_env() -> Self { - let mut allow_unused_expressions = true; - let mut counter_format = ExpressionFormat::default(); - - if let Ok(env_debug_options) = std::env::var(RUSTC_COVERAGE_DEBUG_OPTIONS) { - for setting_str in env_debug_options.replace(' ', "").replace('-', "_").split(',') { - let (option, value) = match setting_str.split_once('=') { - None => (setting_str, None), - Some((k, v)) => (k, Some(v)), - }; - match option { - "allow_unused_expressions" => { - allow_unused_expressions = bool_option_val(option, value); - debug!( - "{} env option `allow_unused_expressions` is set to {}", - RUSTC_COVERAGE_DEBUG_OPTIONS, allow_unused_expressions - ); - } - "counter_format" => { - match value { - None => { - bug!( - "`{}` option in environment variable {} requires one or more \ - plus-separated choices (a non-empty subset of \ - `id+block+operation`)", - option, - RUSTC_COVERAGE_DEBUG_OPTIONS - ); - } - Some(val) => { - counter_format = counter_format_option_val(val); - debug!( - "{} env option `counter_format` is set to {:?}", - RUSTC_COVERAGE_DEBUG_OPTIONS, counter_format - ); - } - }; - } - _ => bug!( - "Unsupported setting `{}` in environment variable {}", - option, - RUSTC_COVERAGE_DEBUG_OPTIONS - ), - }; - } - } - - Self { allow_unused_expressions, counter_format } - } -} - -fn bool_option_val(option: &str, some_strval: Option<&str>) -> bool { - if let Some(val) = some_strval { - if ["yes", "y", "on", "true"].contains(&val) { - true - } else if ["no", "n", "off", "false"].contains(&val) { - false - } else { - bug!( - "Unsupported value `{}` for option `{}` in environment variable {}", - option, - val, - RUSTC_COVERAGE_DEBUG_OPTIONS - ) - } - } else { - true - } -} - -fn counter_format_option_val(strval: &str) -> ExpressionFormat { - let mut counter_format = ExpressionFormat { id: false, block: false, operation: false }; - let components = strval.splitn(3, '+'); - for component in components { - match component { - "id" => counter_format.id = true, - "block" => counter_format.block = true, - "operation" => counter_format.operation = true, - _ => bug!( - "Unsupported counter_format choice `{}` in environment variable {}", - component, - RUSTC_COVERAGE_DEBUG_OPTIONS - ), - } - } - counter_format -} - -#[derive(Debug, Clone)] -struct ExpressionFormat { - id: bool, - block: bool, - operation: bool, -} - -impl Default for ExpressionFormat { - fn default() -> Self { - Self { id: false, block: true, operation: true } - } -} - -/// If enabled, this struct maintains a map from `BcbCounter` IDs (as `Operand`) to -/// the `BcbCounter` data and optional label (normally, the counter's associated -/// `BasicCoverageBlock` format string, if any). -/// -/// Use `format_counter` to convert one of these `BcbCounter` counters to a debug output string, -/// as directed by the `DebugOptions`. This allows the format of counter labels in logs and dump -/// files (including the `CoverageGraph` graphviz file) to be changed at runtime, via environment -/// variable. -/// -/// `DebugCounters` supports a recursive rendering of `Expression` counters, so they can be -/// presented as nested expressions such as `(bcb3 - (bcb0 + bcb1))`. -pub(super) struct DebugCounters { - state: Option, -} - -#[derive(Default)] -struct DebugCountersState { - counters: FxHashMap, -} - -impl DebugCounters { - pub fn new() -> Self { - Self { state: None } - } - - pub fn enable(&mut self) { - debug_assert!(!self.is_enabled()); - self.state = Some(DebugCountersState::default()); - } - - pub fn is_enabled(&self) -> bool { - self.state.is_some() - } - - pub fn add_counter(&mut self, counter_kind: &BcbCounter, some_block_label: Option) { - let Some(state) = &mut self.state else { return }; - - let id = counter_kind.as_operand(); - state - .counters - .try_insert(id, DebugCounter::new(counter_kind.clone(), some_block_label)) - .expect("attempt to add the same counter_kind to DebugCounters more than once"); - } - - pub fn some_block_label(&self, operand: Operand) -> Option<&String> { - let Some(state) = &self.state else { return None }; - - state.counters.get(&operand)?.some_block_label.as_ref() - } - - pub fn format_counter(&self, counter_kind: &BcbCounter) -> String { - match *counter_kind { - BcbCounter::Counter { .. } => { - format!("Counter({})", self.format_counter_kind(counter_kind)) - } - BcbCounter::Expression { .. } => { - format!("Expression({})", self.format_counter_kind(counter_kind)) - } - } - } - - fn format_counter_kind(&self, counter_kind: &BcbCounter) -> String { - let counter_format = &debug_options().counter_format; - if let BcbCounter::Expression { id, lhs, op, rhs } = *counter_kind { - if counter_format.operation { - return format!( - "{}{} {} {}", - if counter_format.id || !self.is_enabled() { - format!("#{} = ", id.index()) - } else { - String::new() - }, - self.format_operand(lhs), - match op { - Op::Add => "+", - Op::Subtract => "-", - }, - self.format_operand(rhs), - ); - } - } - - let id = counter_kind.as_operand(); - if let Some(state) = &self.state && (counter_format.block || !counter_format.id) { - if let Some(DebugCounter { some_block_label: Some(block_label), .. }) = - state.counters.get(&id) - { - return if counter_format.id { - format!("{}#{:?}", block_label, id) - } else { - block_label.to_string() - }; - } - } - format!("#{:?}", id) - } - - fn format_operand(&self, operand: Operand) -> String { - if matches!(operand, Operand::Zero) { - return String::from("0"); - } - if let Some(state) = &self.state { - if let Some(DebugCounter { counter_kind, some_block_label }) = - state.counters.get(&operand) - { - if let BcbCounter::Expression { .. } = counter_kind { - if let Some(label) = some_block_label && debug_options().counter_format.block { - return format!( - "{}:({})", - label, - self.format_counter_kind(counter_kind) - ); - } - return format!("({})", self.format_counter_kind(counter_kind)); - } - return self.format_counter_kind(counter_kind); - } - } - format!("#{:?}", operand) - } -} - -/// A non-public support class to `DebugCounters`. -#[derive(Debug)] -struct DebugCounter { - counter_kind: BcbCounter, - some_block_label: Option, -} - -impl DebugCounter { - fn new(counter_kind: BcbCounter, some_block_label: Option) -> Self { - Self { counter_kind, some_block_label } - } -} - -/// If enabled, this data structure captures additional debugging information used when generating -/// a Graphviz (.dot file) representation of the `CoverageGraph`, for debugging purposes. -pub(super) struct GraphvizData { - state: Option, -} - -#[derive(Default)] -struct GraphvizDataState { - bcb_to_coverage_spans_with_counters: - FxHashMap>, - bcb_to_dependency_counters: FxHashMap>, - edge_to_counter: FxHashMap<(BasicCoverageBlock, BasicBlock), BcbCounter>, -} - -impl GraphvizData { - pub fn new() -> Self { - Self { state: None } - } - - pub fn enable(&mut self) { - debug_assert!(!self.is_enabled()); - self.state = Some(GraphvizDataState::default()); - } - - pub fn is_enabled(&self) -> bool { - self.state.is_some() - } - - pub fn add_bcb_coverage_span_with_counter( - &mut self, - bcb: BasicCoverageBlock, - coverage_span: &CoverageSpan, - counter_kind: &BcbCounter, - ) { - let Some(state) = &mut self.state else { return }; - - state - .bcb_to_coverage_spans_with_counters - .entry(bcb) - .or_insert_with(Vec::new) - .push((coverage_span.clone(), counter_kind.clone())); - } - - pub fn get_bcb_coverage_spans_with_counters( - &self, - bcb: BasicCoverageBlock, - ) -> Option<&[(CoverageSpan, BcbCounter)]> { - let Some(state) = &self.state else { return None }; - - state.bcb_to_coverage_spans_with_counters.get(&bcb).map(Deref::deref) - } - - pub fn add_bcb_dependency_counter( - &mut self, - bcb: BasicCoverageBlock, - counter_kind: &BcbCounter, - ) { - let Some(state) = &mut self.state else { return }; - - state - .bcb_to_dependency_counters - .entry(bcb) - .or_insert_with(Vec::new) - .push(counter_kind.clone()); - } - - pub fn get_bcb_dependency_counters(&self, bcb: BasicCoverageBlock) -> Option<&[BcbCounter]> { - let Some(state) = &self.state else { return None }; - - state.bcb_to_dependency_counters.get(&bcb).map(Deref::deref) - } - - pub fn set_edge_counter( - &mut self, - from_bcb: BasicCoverageBlock, - to_bb: BasicBlock, - counter_kind: &BcbCounter, - ) { - let Some(state) = &mut self.state else { return }; - - state - .edge_to_counter - .try_insert((from_bcb, to_bb), counter_kind.clone()) - .expect("invalid attempt to insert more than one edge counter for the same edge"); - } - - pub fn get_edge_counter( - &self, - from_bcb: BasicCoverageBlock, - to_bb: BasicBlock, - ) -> Option<&BcbCounter> { - let Some(state) = &self.state else { return None }; - - state.edge_to_counter.get(&(from_bcb, to_bb)) - } -} - -/// If enabled, this struct captures additional data used to track whether expressions were used, -/// directly or indirectly, to compute the coverage counts for all `CoverageSpan`s, and any that are -/// _not_ used are retained in the `unused_expressions` Vec, to be included in debug output (logs -/// and/or a `CoverageGraph` graphviz output). -pub(super) struct UsedExpressions { - state: Option, -} - -#[derive(Default)] -struct UsedExpressionsState { - used_expression_operands: FxHashSet, - unused_expressions: Vec<(BcbCounter, Option, BasicCoverageBlock)>, -} - -impl UsedExpressions { - pub fn new() -> Self { - Self { state: None } - } - - pub fn enable(&mut self) { - debug_assert!(!self.is_enabled()); - self.state = Some(UsedExpressionsState::default()) - } - - pub fn is_enabled(&self) -> bool { - self.state.is_some() - } - - pub fn add_expression_operands(&mut self, expression: &BcbCounter) { - let Some(state) = &mut self.state else { return }; - - if let BcbCounter::Expression { lhs, rhs, .. } = *expression { - state.used_expression_operands.insert(lhs); - state.used_expression_operands.insert(rhs); - } - } - - pub fn expression_is_used(&self, expression: &BcbCounter) -> bool { - let Some(state) = &self.state else { return false }; - - state.used_expression_operands.contains(&expression.as_operand()) - } - - pub fn add_unused_expression_if_not_found( - &mut self, - expression: &BcbCounter, - edge_from_bcb: Option, - target_bcb: BasicCoverageBlock, - ) { - let Some(state) = &mut self.state else { return }; - - if !state.used_expression_operands.contains(&expression.as_operand()) { - state.unused_expressions.push((expression.clone(), edge_from_bcb, target_bcb)); - } - } - - /// Return the list of unused counters (if any) as a tuple with the counter (`BcbCounter`), - /// optional `from_bcb` (if it was an edge counter), and `target_bcb`. - pub fn get_unused_expressions( - &self, - ) -> Vec<(BcbCounter, Option, BasicCoverageBlock)> { - let Some(state) = &self.state else { return Vec::new() }; - - state.unused_expressions.clone() - } - - /// If enabled, validate that every BCB or edge counter not directly associated with a coverage - /// span is at least indirectly associated (it is a dependency of a BCB counter that _is_ - /// associated with a coverage span). - pub fn validate( - &mut self, - bcb_counters_without_direct_coverage_spans: &[( - Option, - BasicCoverageBlock, - BcbCounter, - )], - ) { - if !self.is_enabled() { - return; - } - - let mut not_validated = bcb_counters_without_direct_coverage_spans - .iter() - .map(|(_, _, counter_kind)| counter_kind) - .collect::>(); - let mut validating_count = 0; - while not_validated.len() != validating_count { - let to_validate = not_validated.split_off(0); - validating_count = to_validate.len(); - for counter_kind in to_validate { - if self.expression_is_used(counter_kind) { - self.add_expression_operands(counter_kind); - } else { - not_validated.push(counter_kind); - } - } - } - } - - pub fn alert_on_unused_expressions(&self, debug_counters: &DebugCounters) { - let Some(state) = &self.state else { return }; - - for (counter_kind, edge_from_bcb, target_bcb) in &state.unused_expressions { - let unused_counter_message = if let Some(from_bcb) = edge_from_bcb.as_ref() { - format!( - "non-coverage edge counter found without a dependent expression, in \ - {:?}->{:?}; counter={}", - from_bcb, - target_bcb, - debug_counters.format_counter(&counter_kind), - ) - } else { - format!( - "non-coverage counter found without a dependent expression, in {:?}; \ - counter={}", - target_bcb, - debug_counters.format_counter(&counter_kind), - ) - }; - - if debug_options().allow_unused_expressions { - debug!("WARNING: {}", unused_counter_message); - } else { - bug!("{}", unused_counter_message); - } - } - } -} - -/// Generates the MIR pass `CoverageSpan`-specific spanview dump file. -pub(super) fn dump_coverage_spanview<'tcx>( - tcx: TyCtxt<'tcx>, - mir_body: &mir::Body<'tcx>, - basic_coverage_blocks: &CoverageGraph, - pass_name: &str, - body_span: Span, - coverage_spans: &[CoverageSpan], -) { - let mir_source = mir_body.source; - let def_id = mir_source.def_id(); - - let span_viewables = span_viewables(tcx, mir_body, basic_coverage_blocks, &coverage_spans); - let mut file = create_dump_file(tcx, "html", false, pass_name, &0i32, mir_body) - .expect("Unexpected error creating MIR spanview HTML file"); - let crate_name = tcx.crate_name(def_id.krate); - let item_name = tcx.def_path(def_id).to_filename_friendly_no_crate(); - let title = format!("{crate_name}.{item_name} - Coverage Spans"); - spanview::write_document(tcx, body_span, span_viewables, &title, &mut file) - .expect("Unexpected IO error dumping coverage spans as HTML"); -} - -/// Converts the computed `BasicCoverageBlockData`s into `SpanViewable`s. -fn span_viewables<'tcx>( - tcx: TyCtxt<'tcx>, - mir_body: &mir::Body<'tcx>, - basic_coverage_blocks: &CoverageGraph, - coverage_spans: &[CoverageSpan], -) -> Vec { - let mut span_viewables = Vec::new(); - for coverage_span in coverage_spans { - let tooltip = coverage_span.format_coverage_statements(tcx, mir_body); - let CoverageSpan { span, bcb, .. } = coverage_span; - let bcb_data = &basic_coverage_blocks[*bcb]; - let id = bcb_data.id(); - let leader_bb = bcb_data.leader_bb(); - span_viewables.push(SpanViewable { bb: leader_bb, span: *span, id, tooltip }); - } - span_viewables -} - -/// Generates the MIR pass coverage-specific graphviz dump file. -pub(super) fn dump_coverage_graphviz<'tcx>( - tcx: TyCtxt<'tcx>, - mir_body: &mir::Body<'tcx>, - pass_name: &str, - basic_coverage_blocks: &CoverageGraph, - coverage_counters: &CoverageCounters, - graphviz_data: &GraphvizData, - intermediate_expressions: &[BcbCounter], - debug_used_expressions: &UsedExpressions, -) { - let debug_counters = &coverage_counters.debug_counters; - - let mir_source = mir_body.source; - let def_id = mir_source.def_id(); - let node_content = |bcb| { - bcb_to_string_sections( - tcx, - mir_body, - coverage_counters, - bcb, - &basic_coverage_blocks[bcb], - graphviz_data.get_bcb_coverage_spans_with_counters(bcb), - graphviz_data.get_bcb_dependency_counters(bcb), - // intermediate_expressions are injected into the mir::START_BLOCK, so - // include them in the first BCB. - if bcb.index() == 0 { Some(&intermediate_expressions) } else { None }, - ) - }; - let edge_labels = |from_bcb| { - let from_bcb_data = &basic_coverage_blocks[from_bcb]; - let from_terminator = from_bcb_data.terminator(mir_body); - let mut edge_labels = from_terminator.kind.fmt_successor_labels(); - edge_labels.retain(|label| label != "unreachable"); - let edge_counters = from_terminator - .successors() - .map(|successor_bb| graphviz_data.get_edge_counter(from_bcb, successor_bb)); - iter::zip(&edge_labels, edge_counters) - .map(|(label, some_counter)| { - if let Some(counter) = some_counter { - format!("{}\n{}", label, debug_counters.format_counter(counter)) - } else { - label.to_string() - } - }) - .collect::>() - }; - let graphviz_name = format!("Cov_{}_{}", def_id.krate.index(), def_id.index.index()); - let mut graphviz_writer = - GraphvizWriter::new(basic_coverage_blocks, &graphviz_name, node_content, edge_labels); - let unused_expressions = debug_used_expressions.get_unused_expressions(); - if unused_expressions.len() > 0 { - graphviz_writer.set_graph_label(&format!( - "Unused expressions:\n {}", - unused_expressions - .as_slice() - .iter() - .map(|(counter_kind, edge_from_bcb, target_bcb)| { - if let Some(from_bcb) = edge_from_bcb.as_ref() { - format!( - "{:?}->{:?}: {}", - from_bcb, - target_bcb, - debug_counters.format_counter(&counter_kind), - ) - } else { - format!( - "{:?}: {}", - target_bcb, - debug_counters.format_counter(&counter_kind), - ) - } - }) - .join("\n ") - )); - } - let mut file = create_dump_file(tcx, "dot", false, pass_name, &0i32, mir_body) - .expect("Unexpected error creating BasicCoverageBlock graphviz DOT file"); - graphviz_writer - .write_graphviz(tcx, &mut file) - .expect("Unexpected error writing BasicCoverageBlock graphviz DOT file"); -} - -fn bcb_to_string_sections<'tcx>( - tcx: TyCtxt<'tcx>, - mir_body: &mir::Body<'tcx>, - coverage_counters: &CoverageCounters, - bcb: BasicCoverageBlock, - bcb_data: &BasicCoverageBlockData, - some_coverage_spans_with_counters: Option<&[(CoverageSpan, BcbCounter)]>, - some_dependency_counters: Option<&[BcbCounter]>, - some_intermediate_expressions: Option<&[BcbCounter]>, -) -> Vec { - let debug_counters = &coverage_counters.debug_counters; - - let len = bcb_data.basic_blocks.len(); - let mut sections = Vec::new(); - if let Some(collect_intermediate_expressions) = some_intermediate_expressions { - sections.push( - collect_intermediate_expressions - .iter() - .map(|expression| { - format!("Intermediate {}", debug_counters.format_counter(expression)) - }) - .join("\n"), - ); - } - if let Some(coverage_spans_with_counters) = some_coverage_spans_with_counters { - sections.push( - coverage_spans_with_counters - .iter() - .map(|(covspan, counter)| { - format!( - "{} at {}", - debug_counters.format_counter(counter), - covspan.format(tcx, mir_body) - ) - }) - .join("\n"), - ); - } - if let Some(dependency_counters) = some_dependency_counters { - sections.push(format!( - "Non-coverage counters:\n {}", - dependency_counters - .iter() - .map(|counter| debug_counters.format_counter(counter)) - .join(" \n"), - )); - } - if let Some(counter_kind) = coverage_counters.bcb_counter(bcb) { - sections.push(format!("{counter_kind:?}")); - } - let non_term_blocks = bcb_data.basic_blocks[0..len - 1] - .iter() - .map(|&bb| format!("{:?}: {}", bb, mir_body[bb].terminator().kind.name())) - .collect::>(); - if non_term_blocks.len() > 0 { - sections.push(non_term_blocks.join("\n")); - } - sections.push(format!( - "{:?}: {}", - bcb_data.basic_blocks.last().unwrap(), - bcb_data.terminator(mir_body).kind.name(), - )); - sections -} diff --git a/compiler/rustc_mir_transform/src/coverage/graph.rs b/compiler/rustc_mir_transform/src/coverage/graph.rs index b6b0463614d0..ff2254d6941e 100644 --- a/compiler/rustc_mir_transform/src/coverage/graph.rs +++ b/compiler/rustc_mir_transform/src/coverage/graph.rs @@ -1,4 +1,3 @@ -use itertools::Itertools; use rustc_data_structures::graph::dominators::{self, Dominators}; use rustc_data_structures::graph::{self, GraphSuccessors, WithNumNodes, WithStartNode}; use rustc_index::bit_set::BitSet; @@ -8,8 +7,6 @@ use rustc_middle::mir::{self, BasicBlock, BasicBlockData, Terminator, Terminator use std::cmp::Ordering; use std::ops::{Index, IndexMut}; -const ID_SEPARATOR: &str = ","; - /// A coverage-specific simplification of the MIR control flow graph (CFG). The `CoverageGraph`s /// nodes are `BasicCoverageBlock`s, which encompass one or more MIR `BasicBlock`s. #[derive(Debug)] @@ -324,10 +321,6 @@ impl BasicCoverageBlockData { pub fn terminator<'a, 'tcx>(&self, mir_body: &'a mir::Body<'tcx>) -> &'a Terminator<'tcx> { &mir_body[self.last_bb()].terminator() } - - pub fn id(&self) -> String { - format!("@{}", self.basic_blocks.iter().map(|bb| bb.index().to_string()).join(ID_SEPARATOR)) - } } /// Represents a successor from a branching BasicCoverageBlock (such as the arms of a `SwitchInt`) diff --git a/compiler/rustc_mir_transform/src/coverage/mod.rs b/compiler/rustc_mir_transform/src/coverage/mod.rs index d0b28eb2f5d8..80c6d08fe0d0 100644 --- a/compiler/rustc_mir_transform/src/coverage/mod.rs +++ b/compiler/rustc_mir_transform/src/coverage/mod.rs @@ -1,7 +1,6 @@ pub mod query; mod counters; -mod debug; mod graph; mod spans; @@ -20,7 +19,6 @@ use rustc_index::IndexVec; use rustc_middle::hir; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::mir::coverage::*; -use rustc_middle::mir::dump_enabled; use rustc_middle::mir::{ self, BasicBlock, BasicBlockData, Coverage, SourceInfo, Statement, StatementKind, Terminator, TerminatorKind, @@ -94,13 +92,12 @@ impl<'tcx> MirPass<'tcx> for InstrumentCoverage { } trace!("InstrumentCoverage starting for {:?}", mir_source.def_id()); - Instrumentor::new(&self.name(), tcx, mir_body).inject_counters(); + Instrumentor::new(tcx, mir_body).inject_counters(); trace!("InstrumentCoverage done for {:?}", mir_source.def_id()); } } struct Instrumentor<'a, 'tcx> { - pass_name: &'a str, tcx: TyCtxt<'tcx>, mir_body: &'a mut mir::Body<'tcx>, source_file: Lrc, @@ -112,7 +109,7 @@ struct Instrumentor<'a, 'tcx> { } impl<'a, 'tcx> Instrumentor<'a, 'tcx> { - fn new(pass_name: &'a str, tcx: TyCtxt<'tcx>, mir_body: &'a mut mir::Body<'tcx>) -> Self { + fn new(tcx: TyCtxt<'tcx>, mir_body: &'a mut mir::Body<'tcx>) -> Self { let source_map = tcx.sess.source_map(); let def_id = mir_body.source.def_id(); let (some_fn_sig, hir_body) = fn_sig_and_body(tcx, def_id); @@ -141,7 +138,6 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { let coverage_counters = CoverageCounters::new(&basic_coverage_blocks); Self { - pass_name, tcx, mir_body, source_file, @@ -154,28 +150,9 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { } fn inject_counters(&'a mut self) { - let tcx = self.tcx; - let mir_source = self.mir_body.source; - let def_id = mir_source.def_id(); let fn_sig_span = self.fn_sig_span; let body_span = self.body_span; - let mut graphviz_data = debug::GraphvizData::new(); - let mut debug_used_expressions = debug::UsedExpressions::new(); - - let dump_mir = dump_enabled(tcx, self.pass_name, def_id); - let dump_graphviz = dump_mir && tcx.sess.opts.unstable_opts.dump_mir_graphviz; - let dump_spanview = dump_mir && tcx.sess.opts.unstable_opts.dump_mir_spanview.is_some(); - - if dump_graphviz { - graphviz_data.enable(); - self.coverage_counters.enable_debug(); - } - - if dump_graphviz || level_enabled!(tracing::Level::DEBUG) { - debug_used_expressions.enable(); - } - //////////////////////////////////////////////////// // Compute `CoverageSpan`s from the `CoverageGraph`. let coverage_spans = CoverageSpans::generate_coverage_spans( @@ -185,17 +162,6 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { &self.basic_coverage_blocks, ); - if dump_spanview { - debug::dump_coverage_spanview( - tcx, - self.mir_body, - &self.basic_coverage_blocks, - self.pass_name, - body_span, - &coverage_spans, - ); - } - //////////////////////////////////////////////////// // Create an optimized mix of `Counter`s and `Expression`s for the `CoverageGraph`. Ensure // every `CoverageSpan` has a `Counter` or `Expression` assigned to its `BasicCoverageBlock` @@ -209,14 +175,6 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { .make_bcb_counters(&mut self.basic_coverage_blocks, &coverage_spans); if let Ok(()) = result { - // If debugging, add any intermediate expressions (which are not associated with any - // BCB) to the `debug_used_expressions` map. - if debug_used_expressions.is_enabled() { - for intermediate_expression in &self.coverage_counters.intermediate_expressions { - debug_used_expressions.add_expression_operands(intermediate_expression); - } - } - //////////////////////////////////////////////////// // Remove the counter or edge counter from of each `CoverageSpan`s associated // `BasicCoverageBlock`, and inject a `Coverage` statement into the MIR. @@ -227,11 +185,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { // These `CoverageSpan`-associated counters are removed from their associated // `BasicCoverageBlock`s so that the only remaining counters in the `CoverageGraph` // are indirect counters (to be injected next, without associated code regions). - self.inject_coverage_span_counters( - coverage_spans, - &mut graphviz_data, - &mut debug_used_expressions, - ); + self.inject_coverage_span_counters(coverage_spans); //////////////////////////////////////////////////// // For any remaining `BasicCoverageBlock` counters (that were not associated with @@ -239,37 +193,17 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { // to ensure `BasicCoverageBlock` counters that other `Expression`s may depend on // are in fact counted, even though they don't directly contribute to counting // their own independent code region's coverage. - self.inject_indirect_counters(&mut graphviz_data, &mut debug_used_expressions); + self.inject_indirect_counters(); // Intermediate expressions will be injected as the final step, after generating // debug output, if any. //////////////////////////////////////////////////// }; - if graphviz_data.is_enabled() { - // Even if there was an error, a partial CoverageGraph can still generate a useful - // graphviz output. - debug::dump_coverage_graphviz( - tcx, - self.mir_body, - self.pass_name, - &self.basic_coverage_blocks, - &self.coverage_counters, - &graphviz_data, - &self.coverage_counters.intermediate_expressions, - &debug_used_expressions, - ); - } - if let Err(e) = result { bug!("Error processing: {:?}: {:?}", self.mir_body.source.def_id(), e.message) }; - // Depending on current `debug_options()`, `alert_on_unused_expressions()` could panic, so - // this check is performed as late as possible, to allow other debug output (logs and dump - // files), which might be helpful in analyzing unused expressions, to still be generated. - debug_used_expressions.alert_on_unused_expressions(&self.coverage_counters.debug_counters); - //////////////////////////////////////////////////// // Finally, inject the intermediate expressions collected along the way. for intermediate_expression in &self.coverage_counters.intermediate_expressions { @@ -285,15 +219,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { /// `bcb` to its `Counter`, when injected. Subsequent `CoverageSpan`s for a BCB that already has /// a `Counter` will inject an `Expression` instead, and compute its value by adding `ZERO` to /// the BCB `Counter` value. - /// - /// If debugging, add every BCB `Expression` associated with a `CoverageSpan`s to the - /// `used_expression_operands` map. - fn inject_coverage_span_counters( - &mut self, - coverage_spans: Vec, - graphviz_data: &mut debug::GraphvizData, - debug_used_expressions: &mut debug::UsedExpressions, - ) { + fn inject_coverage_span_counters(&mut self, coverage_spans: Vec) { let tcx = self.tcx; let source_map = tcx.sess.source_map(); let body_span = self.body_span; @@ -307,12 +233,10 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { self.coverage_counters.make_identity_counter(counter_operand) } else if let Some(counter_kind) = self.coverage_counters.take_bcb_counter(bcb) { bcb_counters[bcb] = Some(counter_kind.as_operand()); - debug_used_expressions.add_expression_operands(&counter_kind); counter_kind } else { bug!("Every BasicCoverageBlock should have a Counter or Expression"); }; - graphviz_data.add_bcb_coverage_span_with_counter(bcb, &covspan, &counter_kind); let code_region = make_code_region(source_map, file_name, span, body_span); @@ -333,11 +257,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { /// associated with a `CoverageSpan`, should only exist if the counter is an `Expression` /// dependency (one of the expression operands). Collect them, and inject the additional /// counters into the MIR, without a reportable coverage span. - fn inject_indirect_counters( - &mut self, - graphviz_data: &mut debug::GraphvizData, - debug_used_expressions: &mut debug::UsedExpressions, - ) { + fn inject_indirect_counters(&mut self) { let mut bcb_counters_without_direct_coverage_spans = Vec::new(); for (target_bcb, counter_kind) in self.coverage_counters.drain_bcb_counters() { bcb_counters_without_direct_coverage_spans.push((None, target_bcb, counter_kind)); @@ -352,19 +272,8 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { )); } - // If debug is enabled, validate that every BCB or edge counter not directly associated - // with a coverage span is at least indirectly associated (it is a dependency of a BCB - // counter that _is_ associated with a coverage span). - debug_used_expressions.validate(&bcb_counters_without_direct_coverage_spans); - for (edge_from_bcb, target_bcb, counter_kind) in bcb_counters_without_direct_coverage_spans { - debug_used_expressions.add_unused_expression_if_not_found( - &counter_kind, - edge_from_bcb, - target_bcb, - ); - match counter_kind { BcbCounter::Counter { .. } => { let inject_to_bb = if let Some(from_bcb) = edge_from_bcb { @@ -375,7 +284,6 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { let to_bb = self.bcb_leader_bb(target_bcb); let new_bb = inject_edge_counter_basic_block(self.mir_body, from_bb, to_bb); - graphviz_data.set_edge_counter(from_bcb, new_bb, &counter_kind); debug!( "Edge {:?} (last {:?}) -> {:?} (leader {:?}) requires a new MIR \ BasicBlock {:?}, for unclaimed edge counter {}", @@ -389,7 +297,6 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { new_bb } else { let target_bb = self.bcb_last_bb(target_bcb); - graphviz_data.add_bcb_dependency_counter(target_bcb, &counter_kind); debug!( "{:?} ({:?}) gets a new Coverage statement for unclaimed counter {}", target_bcb, @@ -431,7 +338,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { #[inline] fn format_counter(&self, counter_kind: &BcbCounter) -> String { - self.coverage_counters.debug_counters.format_counter(counter_kind) + format!("{counter_kind:?}") } fn make_mir_coverage_kind(&self, counter_kind: &BcbCounter) -> CoverageKind { diff --git a/compiler/rustc_mir_transform/src/coverage/spans.rs b/compiler/rustc_mir_transform/src/coverage/spans.rs index 32e8ca25d31f..5b24fa10beae 100644 --- a/compiler/rustc_mir_transform/src/coverage/spans.rs +++ b/compiler/rustc_mir_transform/src/coverage/spans.rs @@ -1,13 +1,10 @@ use super::graph::{BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph, START_BCB}; -use itertools::Itertools; use rustc_data_structures::graph::WithNumNodes; -use rustc_middle::mir::spanview::source_range_no_file; use rustc_middle::mir::{ self, AggregateKind, BasicBlock, FakeReadCause, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, }; -use rustc_middle::ty::TyCtxt; use rustc_span::source_map::original_sp; use rustc_span::{BytePos, ExpnKind, MacroKind, Span, Symbol}; @@ -20,31 +17,6 @@ pub(super) enum CoverageStatement { } impl CoverageStatement { - pub fn format<'tcx>(&self, tcx: TyCtxt<'tcx>, mir_body: &mir::Body<'tcx>) -> String { - match *self { - Self::Statement(bb, span, stmt_index) => { - let stmt = &mir_body[bb].statements[stmt_index]; - format!( - "{}: @{}[{}]: {:?}", - source_range_no_file(tcx, span), - bb.index(), - stmt_index, - stmt - ) - } - Self::Terminator(bb, span) => { - let term = mir_body[bb].terminator(); - format!( - "{}: @{}.{}: {:?}", - source_range_no_file(tcx, span), - bb.index(), - term.kind.name(), - term.kind - ) - } - } - } - pub fn span(&self) -> Span { match self { Self::Statement(_, span, _) | Self::Terminator(_, span) => *span, @@ -150,27 +122,6 @@ impl CoverageSpan { self.bcb == other.bcb } - pub fn format<'tcx>(&self, tcx: TyCtxt<'tcx>, mir_body: &mir::Body<'tcx>) -> String { - format!( - "{}\n {}", - source_range_no_file(tcx, self.span), - self.format_coverage_statements(tcx, mir_body).replace('\n', "\n "), - ) - } - - pub fn format_coverage_statements<'tcx>( - &self, - tcx: TyCtxt<'tcx>, - mir_body: &mir::Body<'tcx>, - ) -> String { - let mut sorted_coverage_statements = self.coverage_statements.clone(); - sorted_coverage_statements.sort_unstable_by_key(|covstmt| match *covstmt { - CoverageStatement::Statement(bb, _, index) => (bb, index), - CoverageStatement::Terminator(bb, _) => (bb, usize::MAX), - }); - sorted_coverage_statements.iter().map(|covstmt| covstmt.format(tcx, mir_body)).join("\n") - } - /// If the span is part of a macro, returns the macro name symbol. pub fn current_macro(&self) -> Option { self.current_macro_or_none diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index f2c8f0bc1989..5a6def1958bd 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -1478,15 +1478,12 @@ options! { dump_mir_exclude_pass_number: bool = (false, parse_bool, [UNTRACKED], "exclude the pass number when dumping MIR (used in tests) (default: no)"), dump_mir_graphviz: bool = (false, parse_bool, [UNTRACKED], - "in addition to `.mir` files, create graphviz `.dot` files (and with \ - `-Z instrument-coverage`, also create a `.dot` file for the MIR-derived \ - coverage graph) (default: no)"), + "in addition to `.mir` files, create graphviz `.dot` files (default: no)"), dump_mir_spanview: Option = (None, parse_mir_spanview, [UNTRACKED], "in addition to `.mir` files, create `.html` files to view spans for \ all `statement`s (including terminators), only `terminator` spans, or \ computed `block` spans (one span encompassing a block's terminator and \ - all statements). If `-Z instrument-coverage` is also enabled, create \ - an additional `.html` file showing the computed coverage spans."), + all statements)."), dump_mono_stats: SwitchWithOptPath = (SwitchWithOptPath::Disabled, parse_switch_with_opt_path, [UNTRACKED], "output statistics about monomorphization collection"), diff --git a/tests/mir-opt/coverage_graphviz.bar.InstrumentCoverage.0.dot b/tests/mir-opt/coverage_graphviz.bar.InstrumentCoverage.0.dot deleted file mode 100644 index 3b90aaeae423..000000000000 --- a/tests/mir-opt/coverage_graphviz.bar.InstrumentCoverage.0.dot +++ /dev/null @@ -1,6 +0,0 @@ -digraph Cov_0_4 { - graph [fontname="Courier, monospace"]; - node [fontname="Courier, monospace"]; - edge [fontname="Courier, monospace"]; - bcb0__Cov_0_4 [shape="none", label=<
bcb0
Counter(bcb0) at 18:1-20:2
19:5-19:9: @0[0]: Coverage::Counter(0) for $DIR/coverage_graphviz.rs:18:1 - 20:2
20:2-20:2: @0.Return: return
bb0: Return
>]; -} diff --git a/tests/mir-opt/coverage_graphviz.main.InstrumentCoverage.0.dot b/tests/mir-opt/coverage_graphviz.main.InstrumentCoverage.0.dot deleted file mode 100644 index 19c220e2e1d8..000000000000 --- a/tests/mir-opt/coverage_graphviz.main.InstrumentCoverage.0.dot +++ /dev/null @@ -1,13 +0,0 @@ -digraph Cov_0_3 { - graph [fontname="Courier, monospace"]; - node [fontname="Courier, monospace"]; - edge [fontname="Courier, monospace"]; - bcb3__Cov_0_3 [shape="none", label=<
bcb3
Counter(bcb3) at 13:10-13:10
13:10-13:10: @5[0]: Coverage::Counter(1) for $DIR/coverage_graphviz.rs:13:10 - 13:11
bb5: Goto
>]; - bcb2__Cov_0_3 [shape="none", label=<
bcb2
Expression(bcb1:(bcb0 + bcb3) - bcb3) at 12:13-12:18
12:13-12:18: @4[0]: Coverage::Expression(2) = Expression(1) + Zero for $DIR/coverage_graphviz.rs:15:1 - 15:2
Expression(bcb2:(bcb1:(bcb0 + bcb3) - bcb3) + 0) at 15:2-15:2
15:2-15:2: @4.Return: return
bb4: Return
>]; - bcb1__Cov_0_3 [shape="none", label=<
bcb1
Expression(bcb0 + bcb3) at 10:5-11:17
11:12-11:17: @2.Call: _2 = bar() -> [return: bb3, unwind: bb6]
bb1: FalseUnwind
bb2: Call
bb3: SwitchInt
>]; - bcb0__Cov_0_3 [shape="none", label=<
bcb0
Counter(bcb0) at 9:1-9:11
bb0: Goto
>]; - bcb3__Cov_0_3 -> bcb1__Cov_0_3 [label=<>]; - bcb1__Cov_0_3 -> bcb3__Cov_0_3 [label=<0>]; - bcb1__Cov_0_3 -> bcb2__Cov_0_3 [label=]; - bcb0__Cov_0_3 -> bcb1__Cov_0_3 [label=<>]; -} diff --git a/tests/mir-opt/coverage_graphviz.rs b/tests/mir-opt/coverage_graphviz.rs deleted file mode 100644 index 09403bb3a792..000000000000 --- a/tests/mir-opt/coverage_graphviz.rs +++ /dev/null @@ -1,20 +0,0 @@ -// Test that `-C instrument-coverage` with `-Z dump-mir-graphviz` generates a graphviz (.dot file) -// rendering of the `BasicCoverageBlock` coverage control flow graph, with counters and -// expressions. - -// needs-profiler-support -// compile-flags: -C instrument-coverage -Z dump-mir-graphviz -// EMIT_MIR coverage_graphviz.main.InstrumentCoverage.0.dot -// EMIT_MIR coverage_graphviz.bar.InstrumentCoverage.0.dot -fn main() { - loop { - if bar() { - break; - } - } -} - -#[inline(never)] -fn bar() -> bool { - true -} From bbd347409fdba70e1827ba0b07a4b84588387c62 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Tue, 19 Sep 2023 20:29:47 +1000 Subject: [PATCH 087/111] coverage: Remove vestigial `format_counter` methods --- .../src/coverage/counters.rs | 40 ++++++++----------- .../rustc_mir_transform/src/coverage/mod.rs | 20 ++-------- 2 files changed, 20 insertions(+), 40 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coverage/counters.rs b/compiler/rustc_mir_transform/src/coverage/counters.rs index b3a33ebae15b..972165f02d98 100644 --- a/compiler/rustc_mir_transform/src/coverage/counters.rs +++ b/compiler/rustc_mir_transform/src/coverage/counters.rs @@ -345,10 +345,7 @@ impl<'a> MakeBcbCounters<'a> { sumup_counter_operand, || None, ); - debug!( - " [new intermediate expression: {}]", - self.format_counter(&intermediate_expression) - ); + debug!(" [new intermediate expression: {:?}]", intermediate_expression); let intermediate_expression_operand = intermediate_expression.as_operand(); self.coverage_counters.intermediate_expressions.push(intermediate_expression); some_sumup_counter_operand.replace(intermediate_expression_operand); @@ -372,7 +369,7 @@ impl<'a> MakeBcbCounters<'a> { sumup_counter_operand, || Some(format!("{expression_branch:?}")), ); - debug!("{:?} gets an expression: {}", expression_branch, self.format_counter(&expression)); + debug!("{:?} gets an expression: {:?}", expression_branch, expression); let bcb = expression_branch.target_bcb; if expression_branch.is_only_path_to_target() { self.coverage_counters.set_bcb_counter(bcb, expression)?; @@ -394,10 +391,10 @@ impl<'a> MakeBcbCounters<'a> { // If the BCB already has a counter, return it. if let Some(counter_kind) = &self.coverage_counters.bcb_counters[bcb] { debug!( - "{}{:?} already has a counter: {}", + "{}{:?} already has a counter: {:?}", NESTED_INDENT.repeat(debug_indent_level), bcb, - self.format_counter(counter_kind), + counter_kind, ); return Ok(counter_kind.as_operand()); } @@ -410,19 +407,19 @@ impl<'a> MakeBcbCounters<'a> { let counter_kind = self.coverage_counters.make_counter(|| Some(format!("{bcb:?}"))); if one_path_to_target { debug!( - "{}{:?} gets a new counter: {}", + "{}{:?} gets a new counter: {:?}", NESTED_INDENT.repeat(debug_indent_level), bcb, - self.format_counter(&counter_kind), + counter_kind, ); } else { debug!( "{}{:?} has itself as its own predecessor. It can't be part of its own \ - Expression sum, so it will get its own new counter: {}. (Note, the compiled \ + Expression sum, so it will get its own new counter: {:?}. (Note, the compiled \ code will generate an infinite loop.)", NESTED_INDENT.repeat(debug_indent_level), bcb, - self.format_counter(&counter_kind), + counter_kind, ); } return self.coverage_counters.set_bcb_counter(bcb, counter_kind); @@ -460,9 +457,9 @@ impl<'a> MakeBcbCounters<'a> { || None, ); debug!( - "{}new intermediate expression: {}", + "{}new intermediate expression: {:?}", NESTED_INDENT.repeat(debug_indent_level), - self.format_counter(&intermediate_expression) + intermediate_expression ); let intermediate_expression_operand = intermediate_expression.as_operand(); self.coverage_counters.intermediate_expressions.push(intermediate_expression); @@ -476,10 +473,10 @@ impl<'a> MakeBcbCounters<'a> { || Some(format!("{bcb:?}")), ); debug!( - "{}{:?} gets a new counter (sum of predecessor counters): {}", + "{}{:?} gets a new counter (sum of predecessor counters): {:?}", NESTED_INDENT.repeat(debug_indent_level), bcb, - self.format_counter(&counter_kind) + counter_kind ); self.coverage_counters.set_bcb_counter(bcb, counter_kind) } @@ -510,11 +507,11 @@ impl<'a> MakeBcbCounters<'a> { self.coverage_counters.bcb_edge_counters.get(&(from_bcb, to_bcb)) { debug!( - "{}Edge {:?}->{:?} already has a counter: {}", + "{}Edge {:?}->{:?} already has a counter: {:?}", NESTED_INDENT.repeat(debug_indent_level), from_bcb, to_bcb, - self.format_counter(counter_kind) + counter_kind ); return Ok(counter_kind.as_operand()); } @@ -523,11 +520,11 @@ impl<'a> MakeBcbCounters<'a> { let counter_kind = self.coverage_counters.make_counter(|| Some(format!("{from_bcb:?}->{to_bcb:?}"))); debug!( - "{}Edge {:?}->{:?} gets a new counter: {}", + "{}Edge {:?}->{:?} gets a new counter: {:?}", NESTED_INDENT.repeat(debug_indent_level), from_bcb, to_bcb, - self.format_counter(&counter_kind) + counter_kind ); self.coverage_counters.set_bcb_edge_counter(from_bcb, to_bcb, counter_kind) } @@ -686,9 +683,4 @@ impl<'a> MakeBcbCounters<'a> { fn bcb_dominates(&self, dom: BasicCoverageBlock, node: BasicCoverageBlock) -> bool { self.basic_coverage_blocks.dominates(dom, node) } - - #[inline] - fn format_counter(&self, counter_kind: &BcbCounter) -> String { - format!("{counter_kind:?}") - } } diff --git a/compiler/rustc_mir_transform/src/coverage/mod.rs b/compiler/rustc_mir_transform/src/coverage/mod.rs index 80c6d08fe0d0..c75d33eeb315 100644 --- a/compiler/rustc_mir_transform/src/coverage/mod.rs +++ b/compiler/rustc_mir_transform/src/coverage/mod.rs @@ -286,22 +286,15 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { let new_bb = inject_edge_counter_basic_block(self.mir_body, from_bb, to_bb); debug!( "Edge {:?} (last {:?}) -> {:?} (leader {:?}) requires a new MIR \ - BasicBlock {:?}, for unclaimed edge counter {}", - edge_from_bcb, - from_bb, - target_bcb, - to_bb, - new_bb, - self.format_counter(&counter_kind), + BasicBlock {:?}, for unclaimed edge counter {:?}", + edge_from_bcb, from_bb, target_bcb, to_bb, new_bb, counter_kind, ); new_bb } else { let target_bb = self.bcb_last_bb(target_bcb); debug!( - "{:?} ({:?}) gets a new Coverage statement for unclaimed counter {}", - target_bcb, - target_bb, - self.format_counter(&counter_kind), + "{:?} ({:?}) gets a new Coverage statement for unclaimed counter {:?}", + target_bcb, target_bb, counter_kind, ); target_bb }; @@ -336,11 +329,6 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { &self.basic_coverage_blocks[bcb] } - #[inline] - fn format_counter(&self, counter_kind: &BcbCounter) -> String { - format!("{counter_kind:?}") - } - fn make_mir_coverage_kind(&self, counter_kind: &BcbCounter) -> CoverageKind { match *counter_kind { BcbCounter::Counter { id } => { From e015f5a3706a365d12ebd12c1e3b4a54631ddac8 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Tue, 19 Sep 2023 20:34:52 +1000 Subject: [PATCH 088/111] coverage: Remove vestigial counter/expression debug labels --- .../src/coverage/counters.rs | 27 ++++--------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coverage/counters.rs b/compiler/rustc_mir_transform/src/coverage/counters.rs index 972165f02d98..d56d4ad4f1e7 100644 --- a/compiler/rustc_mir_transform/src/coverage/counters.rs +++ b/compiler/rustc_mir_transform/src/coverage/counters.rs @@ -103,30 +103,18 @@ impl CoverageCounters { MakeBcbCounters::new(self, basic_coverage_blocks).make_bcb_counters(coverage_spans) } - fn make_counter(&mut self, _debug_block_label_fn: F) -> BcbCounter - where - F: Fn() -> Option, - { + fn make_counter(&mut self) -> BcbCounter { let id = self.next_counter(); BcbCounter::Counter { id } } - fn make_expression( - &mut self, - lhs: Operand, - op: Op, - rhs: Operand, - _debug_block_label_fn: F, - ) -> BcbCounter - where - F: Fn() -> Option, - { + fn make_expression(&mut self, lhs: Operand, op: Op, rhs: Operand) -> BcbCounter { let id = self.next_expression(); BcbCounter::Expression { id, lhs, op, rhs } } pub fn make_identity_counter(&mut self, counter_operand: Operand) -> BcbCounter { - self.make_expression(counter_operand, Op::Add, Operand::Zero, || unreachable!()) + self.make_expression(counter_operand, Op::Add, Operand::Zero) } /// Counter IDs start from one and go up. @@ -343,7 +331,6 @@ impl<'a> MakeBcbCounters<'a> { branch_counter_operand, Op::Add, sumup_counter_operand, - || None, ); debug!(" [new intermediate expression: {:?}]", intermediate_expression); let intermediate_expression_operand = intermediate_expression.as_operand(); @@ -367,7 +354,6 @@ impl<'a> MakeBcbCounters<'a> { branching_counter_operand, Op::Subtract, sumup_counter_operand, - || Some(format!("{expression_branch:?}")), ); debug!("{:?} gets an expression: {:?}", expression_branch, expression); let bcb = expression_branch.target_bcb; @@ -404,7 +390,7 @@ impl<'a> MakeBcbCounters<'a> { // program results in a tight infinite loop, but it should still compile. let one_path_to_target = self.bcb_has_one_path_to_target(bcb); if one_path_to_target || self.bcb_predecessors(bcb).contains(&bcb) { - let counter_kind = self.coverage_counters.make_counter(|| Some(format!("{bcb:?}"))); + let counter_kind = self.coverage_counters.make_counter(); if one_path_to_target { debug!( "{}{:?} gets a new counter: {:?}", @@ -454,7 +440,6 @@ impl<'a> MakeBcbCounters<'a> { sumup_edge_counter_operand, Op::Add, edge_counter_operand, - || None, ); debug!( "{}new intermediate expression: {:?}", @@ -470,7 +455,6 @@ impl<'a> MakeBcbCounters<'a> { first_edge_counter_operand, Op::Add, some_sumup_edge_counter_operand.unwrap(), - || Some(format!("{bcb:?}")), ); debug!( "{}{:?} gets a new counter (sum of predecessor counters): {:?}", @@ -517,8 +501,7 @@ impl<'a> MakeBcbCounters<'a> { } // Make a new counter to count this edge. - let counter_kind = - self.coverage_counters.make_counter(|| Some(format!("{from_bcb:?}->{to_bcb:?}"))); + let counter_kind = self.coverage_counters.make_counter(); debug!( "{}Edge {:?}->{:?} gets a new counter: {:?}", NESTED_INDENT.repeat(debug_indent_level), From 31e8981fa998a339fe091ec303a8a1414c5d1936 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Wed, 20 Sep 2023 11:21:39 +0200 Subject: [PATCH 089/111] RELEASES.md: Add missing patch releases This was copy-pasted from the current state of the `stable` branch --- RELEASES.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index ba11234b990a..d390f2b7f5ea 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -113,6 +113,16 @@ Compatibility Notes to a registry. [#12291](https://github.com/rust-lang/cargo/pull/12291) +Version 1.71.1 (2023-08-03) +=========================== + +- [Fix CVE-2023-38497: Cargo did not respect the umask when extracting dependencies](https://github.com/rust-lang/cargo/security/advisories/GHSA-j3xp-wfr4-hx87) +- [Fix bash completion for users of Rustup](https://github.com/rust-lang/rust/pull/113579) +- [Do not show `suspicious_double_ref_op` lint when calling `borrow()`](https://github.com/rust-lang/rust/pull/112517) +- [Fix ICE: substitute types before checking inlining compatibility](https://github.com/rust-lang/rust/pull/113802) +- [Fix ICE: don't use `can_eq` in `derive(..)` suggestion for missing method](https://github.com/rust-lang/rust/pull/111516) +- [Fix building Rust 1.71.0 from the source tarball](https://github.com/rust-lang/rust/issues/113678) + Version 1.71.0 (2023-07-13) ========================== From 66e0483a654a998cdc480b8e9a15d8d30aa4515b Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 20 Sep 2023 07:51:30 -0700 Subject: [PATCH 090/111] rustdoc: add comment about numeric spacing --- src/librustdoc/html/static/js/search.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 3dd60296e81e..2f0cae0a48e2 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -2335,6 +2335,10 @@ ${item.displayPath}${name}\ function makeTabHeader(tabNb, text, nbElems) { // https://blog.horizon-eda.org/misc/2020/02/19/ui.html + // + // CSS runs with `font-variant-numeric: tabular-nums` to ensure all + // digits are the same width. \u{2007} is a Unicode space character + // that is defined to be the same width as a digit. const fmtNbElems = nbElems < 10 ? `\u{2007}(${nbElems})\u{2007}\u{2007}` : nbElems < 100 ? `\u{2007}(${nbElems})\u{2007}` : From 76d9b3689c1425807e2c418159366b49a4fd819b Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Wed, 20 Sep 2023 19:24:52 +0200 Subject: [PATCH 091/111] Rename BoxMeUp to PanicPayload. --- library/core/src/panic.rs | 4 +-- library/panic_abort/src/android.rs | 4 +-- library/panic_abort/src/lib.rs | 4 +-- library/panic_unwind/src/lib.rs | 4 +-- library/std/src/panicking.rs | 42 +++++++++++++++--------------- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/library/core/src/panic.rs b/library/core/src/panic.rs index 20be60d35353..386f5fcbd6ab 100644 --- a/library/core/src/panic.rs +++ b/library/core/src/panic.rs @@ -99,7 +99,7 @@ pub macro unreachable_2021 { /// use. #[unstable(feature = "std_internals", issue = "none")] #[doc(hidden)] -pub unsafe trait BoxMeUp { +pub unsafe trait PanicPayload { /// Take full ownership of the contents. /// The return type is actually `Box`, but we cannot use `Box` in core. /// @@ -107,7 +107,7 @@ pub unsafe trait BoxMeUp { /// Calling this method twice, or calling `get` after calling this method, is an error. /// /// The argument is borrowed because the panic runtime (`__rust_start_panic`) only - /// gets a borrowed `dyn BoxMeUp`. + /// gets a borrowed `dyn PanicPayload`. fn take_box(&mut self) -> *mut (dyn Any + Send); /// Just borrow the contents. diff --git a/library/panic_abort/src/android.rs b/library/panic_abort/src/android.rs index 20b5b6b51468..47c22834597d 100644 --- a/library/panic_abort/src/android.rs +++ b/library/panic_abort/src/android.rs @@ -1,6 +1,6 @@ use alloc::string::String; use core::mem::transmute; -use core::panic::BoxMeUp; +use core::panic::PanicPayload; use core::ptr::copy_nonoverlapping; const ANDROID_SET_ABORT_MESSAGE: &[u8] = b"android_set_abort_message\0"; @@ -15,7 +15,7 @@ type SetAbortMessageType = unsafe extern "C" fn(*const libc::c_char) -> (); // // Weakly resolve the symbol for android_set_abort_message. This function is only available // for API >= 21. -pub(crate) unsafe fn android_set_abort_message(payload: &mut dyn BoxMeUp) { +pub(crate) unsafe fn android_set_abort_message(payload: &mut dyn PanicPayload) { let func_addr = libc::dlsym(libc::RTLD_DEFAULT, ANDROID_SET_ABORT_MESSAGE.as_ptr() as *const libc::c_char) as usize; diff --git a/library/panic_abort/src/lib.rs b/library/panic_abort/src/lib.rs index 0dc570b29db1..d675696f13f1 100644 --- a/library/panic_abort/src/lib.rs +++ b/library/panic_abort/src/lib.rs @@ -20,7 +20,7 @@ mod android; use core::any::Any; -use core::panic::BoxMeUp; +use core::panic::PanicPayload; #[rustc_std_internal_symbol] #[allow(improper_ctypes_definitions)] @@ -30,7 +30,7 @@ pub unsafe extern "C" fn __rust_panic_cleanup(_: *mut u8) -> *mut (dyn Any + Sen // "Leak" the payload and shim to the relevant abort on the platform in question. #[rustc_std_internal_symbol] -pub unsafe fn __rust_start_panic(_payload: &mut dyn BoxMeUp) -> u32 { +pub unsafe fn __rust_start_panic(_payload: &mut dyn PanicPayload) -> u32 { // Android has the ability to attach a message as part of the abort. #[cfg(target_os = "android")] android::android_set_abort_message(_payload); diff --git a/library/panic_unwind/src/lib.rs b/library/panic_unwind/src/lib.rs index e7d34daa0796..9363fde5de2e 100644 --- a/library/panic_unwind/src/lib.rs +++ b/library/panic_unwind/src/lib.rs @@ -29,7 +29,7 @@ use alloc::boxed::Box; use core::any::Any; -use core::panic::BoxMeUp; +use core::panic::PanicPayload; cfg_if::cfg_if! { if #[cfg(target_os = "emscripten")] { @@ -99,7 +99,7 @@ pub unsafe extern "C" fn __rust_panic_cleanup(payload: *mut u8) -> *mut (dyn Any // Entry point for raising an exception, just delegates to the platform-specific // implementation. #[rustc_std_internal_symbol] -pub unsafe fn __rust_start_panic(payload: &mut dyn BoxMeUp) -> u32 { +pub unsafe fn __rust_start_panic(payload: &mut dyn PanicPayload) -> u32 { let payload = Box::from_raw(payload.take_box()); imp::panic(payload) diff --git a/library/std/src/panicking.rs b/library/std/src/panicking.rs index b20e5464e6e0..8cf454493b4f 100644 --- a/library/std/src/panicking.rs +++ b/library/std/src/panicking.rs @@ -10,7 +10,7 @@ #![deny(unsafe_op_in_unsafe_fn)] use crate::panic::BacktraceStyle; -use core::panic::{BoxMeUp, Location, PanicInfo}; +use core::panic::{Location, PanicInfo, PanicPayload}; use crate::any::Any; use crate::fmt; @@ -47,9 +47,9 @@ extern "C" { } extern "Rust" { - /// `BoxMeUp` lazily performs allocation only when needed (this avoids + /// `PanicPayload` lazily performs allocation only when needed (this avoids /// allocations when using the "abort" panic runtime). - fn __rust_start_panic(payload: &mut dyn BoxMeUp) -> u32; + fn __rust_start_panic(payload: &mut dyn PanicPayload) -> u32; } /// This function is called by the panic runtime if FFI code catches a Rust @@ -565,14 +565,14 @@ pub fn panicking() -> bool { #[cfg(not(test))] #[panic_handler] pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! { - struct PanicPayload<'a> { + struct FormatStringPayload<'a> { inner: &'a fmt::Arguments<'a>, string: Option, } - impl<'a> PanicPayload<'a> { - fn new(inner: &'a fmt::Arguments<'a>) -> PanicPayload<'a> { - PanicPayload { inner, string: None } + impl<'a> FormatStringPayload<'a> { + fn new(inner: &'a fmt::Arguments<'a>) -> Self { + Self { inner, string: None } } fn fill(&mut self) -> &mut String { @@ -588,7 +588,7 @@ pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! { } } - unsafe impl<'a> BoxMeUp for PanicPayload<'a> { + unsafe impl<'a> PanicPayload for FormatStringPayload<'a> { fn take_box(&mut self) -> *mut (dyn Any + Send) { // We do two allocations here, unfortunately. But (a) they're required with the current // scheme, and (b) we don't handle panic + OOM properly anyway (see comment in @@ -602,9 +602,9 @@ pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! { } } - struct StrPanicPayload(&'static str); + struct StaticStrPayload(&'static str); - unsafe impl BoxMeUp for StrPanicPayload { + unsafe impl PanicPayload for StaticStrPayload { fn take_box(&mut self) -> *mut (dyn Any + Send) { Box::into_raw(Box::new(self.0)) } @@ -621,7 +621,7 @@ pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! { // `rust_panic_with_hook` construct a new `PanicInfo`? if let Some(msg) = msg.as_str() { rust_panic_with_hook( - &mut StrPanicPayload(msg), + &mut StaticStrPayload(msg), info.message(), loc, info.can_unwind(), @@ -629,7 +629,7 @@ pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! { ); } else { rust_panic_with_hook( - &mut PanicPayload::new(msg), + &mut FormatStringPayload::new(msg), info.message(), loc, info.can_unwind(), @@ -659,7 +659,7 @@ pub const fn begin_panic(msg: M) -> ! { let loc = Location::caller(); return crate::sys_common::backtrace::__rust_end_short_backtrace(move || { rust_panic_with_hook( - &mut PanicPayload::new(msg), + &mut Payload::new(msg), None, loc, /* can_unwind */ true, @@ -667,17 +667,17 @@ pub const fn begin_panic(msg: M) -> ! { ) }); - struct PanicPayload
{ + struct Payload { inner: Option, } - impl PanicPayload { - fn new(inner: A) -> PanicPayload { - PanicPayload { inner: Some(inner) } + impl Payload { + fn new(inner: A) -> Payload { + Payload { inner: Some(inner) } } } - unsafe impl BoxMeUp for PanicPayload { + unsafe impl PanicPayload for Payload { fn take_box(&mut self) -> *mut (dyn Any + Send) { // Note that this should be the only allocation performed in this code path. Currently // this means that panic!() on OOM will invoke this code path, but then again we're not @@ -706,7 +706,7 @@ pub const fn begin_panic(msg: M) -> ! { /// panics, panic hooks, and finally dispatching to the panic runtime to either /// abort or unwind. fn rust_panic_with_hook( - payload: &mut dyn BoxMeUp, + payload: &mut dyn PanicPayload, message: Option<&fmt::Arguments<'_>>, location: &Location<'_>, can_unwind: bool, @@ -782,7 +782,7 @@ pub fn rust_panic_without_hook(payload: Box) -> ! { struct RewrapBox(Box); - unsafe impl BoxMeUp for RewrapBox { + unsafe impl PanicPayload for RewrapBox { fn take_box(&mut self) -> *mut (dyn Any + Send) { Box::into_raw(mem::replace(&mut self.0, Box::new(()))) } @@ -799,7 +799,7 @@ pub fn rust_panic_without_hook(payload: Box) -> ! { /// yer breakpoints. #[inline(never)] #[cfg_attr(not(test), rustc_std_internal_symbol)] -fn rust_panic(msg: &mut dyn BoxMeUp) -> ! { +fn rust_panic(msg: &mut dyn PanicPayload) -> ! { let code = unsafe { __rust_start_panic(msg) }; rtabort!("failed to initiate panic, error {code}") } From 4ce5a002c9a3560a935ad8a740950c9fe138f6dc Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 20 Sep 2023 22:06:56 +0200 Subject: [PATCH 092/111] Update browser-ui-test version to 0.16.10 --- .../docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version b/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version index 0d67319168db..50b6386a009c 100644 --- a/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version +++ b/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version @@ -1 +1 @@ -0.16.9 \ No newline at end of file +0.16.10 \ No newline at end of file From 6cee6b0bdecc0cc18074fc1bb61b39bcc14573d8 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Mon, 11 Sep 2023 18:12:25 -0400 Subject: [PATCH 093/111] PR feedback --- .../src/dep_graph/serialized.rs | 5 +- compiler/rustc_serialize/src/opaque.rs | 86 ++++++++++++------- library/std/src/io/error.rs | 1 + 3 files changed, 56 insertions(+), 36 deletions(-) diff --git a/compiler/rustc_query_system/src/dep_graph/serialized.rs b/compiler/rustc_query_system/src/dep_graph/serialized.rs index 3fd83f79a489..7cdd02011eaa 100644 --- a/compiler/rustc_query_system/src/dep_graph/serialized.rs +++ b/compiler/rustc_query_system/src/dep_graph/serialized.rs @@ -394,10 +394,7 @@ struct NodeInfo { impl Encodable for NodeInfo { fn encode(&self, e: &mut FileEncoder) { let header = SerializedNodeHeader::new(self); - e.write_with(|dest| { - *dest = header.bytes; - header.bytes.len() - }); + e.write_array(header.bytes); if header.len().is_none() { e.emit_usize(self.edges.len()); diff --git a/compiler/rustc_serialize/src/opaque.rs b/compiler/rustc_serialize/src/opaque.rs index fcd35f2ea57b..44855ae629c1 100644 --- a/compiler/rustc_serialize/src/opaque.rs +++ b/compiler/rustc_serialize/src/opaque.rs @@ -22,8 +22,11 @@ const BUF_SIZE: usize = 8192; /// size of the buffer, rather than the full length of the encoded data, and /// because it doesn't need to reallocate memory along the way. pub struct FileEncoder { - /// The input buffer. For adequate performance, we need to be able to write - /// directly to the unwritten region of the buffer, without calling copy_from_slice. + // The input buffer. For adequate performance, we need to be able to write + // directly to the unwritten region of the buffer, without calling copy_from_slice. + // Note that our buffer is always initialized so that we can do that direct access + // without unsafe code. Users of this type write many more than BUF_SIZE bytes, so the + // initialization is approximately free. buf: Box<[u8; BUF_SIZE]>, buffered: usize, flushed: usize, @@ -54,13 +57,12 @@ impl FileEncoder { #[cold] #[inline(never)] - pub fn flush(&mut self) -> &mut [u8; BUF_SIZE] { + pub fn flush(&mut self) { if self.res.is_ok() { self.res = self.file.write_all(&self.buf[..self.buffered]); } self.flushed += self.buffered; self.buffered = 0; - &mut self.buf } pub fn file(&self) -> &File { @@ -76,7 +78,8 @@ impl FileEncoder { #[cold] #[inline(never)] fn write_all_cold_path(&mut self, buf: &[u8]) { - if let Some(dest) = self.flush().get_mut(..buf.len()) { + self.flush(); + if let Some(dest) = self.buf.get_mut(..buf.len()) { dest.copy_from_slice(buf); self.buffered += buf.len(); } else { @@ -99,13 +102,20 @@ impl FileEncoder { /// Write up to `N` bytes to this encoder. /// - /// Whenever possible, use this function to do writes whose length has a small and - /// compile-time constant upper bound. + /// This function can be used to avoid the overhead of calling memcpy for writes that + /// have runtime-variable length, but are small and have a small fixed upper bound. + /// + /// This can be used to do in-place encoding as is done for leb128 (without this function + /// we would need to write to a temporary buffer then memcpy into the encoder), and it can + /// also be used to implement the varint scheme we use for rmeta and dep graph encoding, + /// where we only want to encode the first few bytes of an integer. Copying in the whole + /// integer then only advancing the encoder state for the few bytes we care about is more + /// efficient than calling [`FileEncoder::write_all`], because variable-size copies are + /// always lowered to `memcpy`, which has overhead and contains a lot of logic we can bypass + /// with this function. Note that common architectures support fixed-size writes up to 8 bytes + /// with one instruction, so while this does in some sense do wasted work, we come out ahead. #[inline] - pub fn write_with(&mut self, mut visitor: V) - where - V: FnMut(&mut [u8; N]) -> usize, - { + pub fn write_with(&mut self, visitor: impl FnOnce(&mut [u8; N]) -> usize) { let flush_threshold = const { BUF_SIZE.checked_sub(N).unwrap() }; if std::intrinsics::unlikely(self.buffered > flush_threshold) { self.flush(); @@ -115,26 +125,50 @@ impl FileEncoder { // We produce a post-mono error if N > BUF_SIZE. let buf = unsafe { self.buffer_empty().first_chunk_mut::().unwrap_unchecked() }; let written = visitor(buf); - debug_assert!(written <= N); // We have to ensure that an errant visitor cannot cause self.buffered to exeed BUF_SIZE. - self.buffered += written.min(N); + if written > N { + Self::panic_invalid_write::(written); + } + self.buffered += written; + } + + #[cold] + #[inline(never)] + fn panic_invalid_write(written: usize) { + panic!("FileEncoder::write_with::<{N}> cannot be used to write {written} bytes"); + } + + /// Helper for calls where [`FileEncoder::write_with`] always writes the whole array. + #[inline] + pub fn write_array(&mut self, buf: [u8; N]) { + self.write_with(|dest| { + *dest = buf; + N + }) } pub fn finish(mut self) -> Result { self.flush(); - match self.res { + match std::mem::replace(&mut self.res, Ok(())) { Ok(()) => Ok(self.position()), Err(e) => Err(e), } } } +impl Drop for FileEncoder { + fn drop(&mut self) { + // Likely to be a no-op, because `finish` should have been called and + // it also flushes. But do it just in case. + self.flush(); + } +} + macro_rules! write_leb128 { ($this_fn:ident, $int_ty:ty, $write_leb_fn:ident) => { #[inline] fn $this_fn(&mut self, v: $int_ty) { - const MAX_ENCODED_LEN: usize = $crate::leb128::max_leb128_len::<$int_ty>(); - self.write_with::(|buf| leb128::$write_leb_fn(buf, v)) + self.write_with(|buf| leb128::$write_leb_fn(buf, v)) } }; } @@ -147,18 +181,12 @@ impl Encoder for FileEncoder { #[inline] fn emit_u16(&mut self, v: u16) { - self.write_with(|buf| { - *buf = v.to_le_bytes(); - 2 - }); + self.write_array(v.to_le_bytes()); } #[inline] fn emit_u8(&mut self, v: u8) { - self.write_with(|buf: &mut [u8; 1]| { - buf[0] = v; - 1 - }); + self.write_array([v]); } write_leb128!(emit_isize, isize, write_isize_leb128); @@ -168,10 +196,7 @@ impl Encoder for FileEncoder { #[inline] fn emit_i16(&mut self, v: i16) { - self.write_with(|buf| { - *buf = v.to_le_bytes(); - 2 - }); + self.write_array(v.to_le_bytes()); } #[inline] @@ -370,10 +395,7 @@ impl Encodable for IntEncodedWithFixedSize { #[inline] fn encode(&self, e: &mut FileEncoder) { let _start_pos = e.position(); - e.write_with(|buf| { - *buf = self.0.to_le_bytes(); - buf.len() - }); + e.write_array(self.0.to_le_bytes()); let _end_pos = e.position(); debug_assert_eq!((_end_pos - _start_pos), IntEncodedWithFixedSize::ENCODED_SIZE); } diff --git a/library/std/src/io/error.rs b/library/std/src/io/error.rs index 4eb0d92b38b0..f63142ff01fc 100644 --- a/library/std/src/io/error.rs +++ b/library/std/src/io/error.rs @@ -511,6 +511,7 @@ impl Error { /// let eof_error = Error::from(ErrorKind::UnexpectedEof); /// ``` #[stable(feature = "rust1", since = "1.0.0")] + #[inline(never)] pub fn new(kind: ErrorKind, error: E) -> Error where E: Into>, From 5f33647fb392c2d4f71af94a223bc1a9abb80643 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Wed, 20 Sep 2023 19:26:04 -0400 Subject: [PATCH 094/111] Add unit tests based on files that return odd sizes to stat --- compiler/rustc_span/src/source_map/tests.rs | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/compiler/rustc_span/src/source_map/tests.rs b/compiler/rustc_span/src/source_map/tests.rs index e393db020641..a12f50c87a21 100644 --- a/compiler/rustc_span/src/source_map/tests.rs +++ b/compiler/rustc_span/src/source_map/tests.rs @@ -567,3 +567,30 @@ fn test_next_point() { assert_eq!(span.hi().0, 6); assert!(sm.span_to_snippet(span).is_err()); } + +#[cfg(target_os = "linux")] +#[test] +fn read_binary_file_handles_lying_stat() { + // read_binary_file tries to read the contents of a file into an Lrc<[u8]> while + // never having two copies of the data in memory at once. This is an optimization + // to support include_bytes! with large files. But since Rust allocators are + // sensitive to alignment, our implementation can't be bootstrapped off calling + // std::fs::read. So we test that we have the same behavior even on files where + // fs::metadata lies. + + // stat always says that /proc/self/cmdline is length 0, but it isn't. + let cmdline = Path::new("/proc/self/cmdline"); + let len = std::fs::metadata(cmdline).unwrap().len() as usize; + let real = std::fs::read(cmdline).unwrap(); + assert!(len < real.len()); + let bin = RealFileLoader.read_binary_file(cmdline).unwrap(); + assert_eq!(&real[..], &bin[..]); + + // stat always says that /sys/devices/system/cpu/kernel_max is the size of a block. + let kernel_max = Path::new("/sys/devices/system/cpu/kernel_max"); + let len = std::fs::metadata(kernel_max).unwrap().len() as usize; + let real = std::fs::read(kernel_max).unwrap(); + assert!(len > real.len()); + let bin = RealFileLoader.read_binary_file(kernel_max).unwrap(); + assert_eq!(&real[..], &bin[..]); +} From bb6a46592f173bd1ee045f8d57ed66d9dbe5812d Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 20 Sep 2023 19:00:34 +0000 Subject: [PATCH 095/111] Fix ui-fulldeps --stage=1 with -Zignore-directory-in-diagnostics-source-blocks --- src/tools/compiletest/src/runtest.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 7b42d8e9b584..657d074b3803 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -2335,14 +2335,17 @@ impl<'test> TestCx<'test> { rustc.arg("-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX"); rustc.arg("-Ztranslate-remapped-path-to-local-path=no"); - // Hide Cargo dependency sources from ui tests to make sure the error message doesn't - // change depending on whether $CARGO_HOME is remapped or not. If this is not present, - // when $CARGO_HOME is remapped the source won't be shown, and when it's not remapped the - // source will be shown, causing a blessing hell. - rustc.arg("-Z").arg(format!( - "ignore-directory-in-diagnostics-source-blocks={}", - home::cargo_home().expect("failed to find cargo home").to_str().unwrap() - )); + // #[cfg(not(bootstrap))]: After beta bump, this should **always** run. + if !(self.config.stage_id.starts_with("stage1-") && self.config.suite == "ui-fulldeps") { + // Hide Cargo dependency sources from ui tests to make sure the error message doesn't + // change depending on whether $CARGO_HOME is remapped or not. If this is not present, + // when $CARGO_HOME is remapped the source won't be shown, and when it's not remapped the + // source will be shown, causing a blessing hell. + rustc.arg("-Z").arg(format!( + "ignore-directory-in-diagnostics-source-blocks={}", + home::cargo_home().expect("failed to find cargo home").to_str().unwrap() + )); + } // Optionally prevent default --sysroot if specified in test compile-flags. if !self.props.compile_flags.iter().any(|flag| flag.starts_with("--sysroot")) From 292588e3190d193cf2f8f923623cd9c5a758a084 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 20 Sep 2023 19:01:03 +0000 Subject: [PATCH 096/111] Make ui-fulldeps --stage=1 builds in CI --- .../host-x86_64/x86_64-gnu-llvm-15/script.sh | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-llvm-15/script.sh b/src/ci/docker/host-x86_64/x86_64-gnu-llvm-15/script.sh index 390304b6ad5a..918b19612ae4 100755 --- a/src/ci/docker/host-x86_64/x86_64-gnu-llvm-15/script.sh +++ b/src/ci/docker/host-x86_64/x86_64-gnu-llvm-15/script.sh @@ -4,15 +4,18 @@ set -ex # Only run the stage 1 tests on merges, not on PR CI jobs. if [[ -z "${PR_CI_JOB}" ]]; then -../x.py --stage 1 test --skip src/tools/tidy && \ - # Run the `mir-opt` tests again but this time for a 32-bit target. - # This enforces that tests using `// EMIT_MIR_FOR_EACH_BIT_WIDTH` have - # both 32-bit and 64-bit outputs updated by the PR author, before - # the PR is approved and tested for merging. - # It will also detect tests lacking `// EMIT_MIR_FOR_EACH_BIT_WIDTH`, - # despite having different output on 32-bit vs 64-bit targets. - ../x.py --stage 1 test tests/mir-opt \ - --host='' --target=i686-unknown-linux-gnu + ../x.py --stage 1 test --skip src/tools/tidy && \ + # Run the `mir-opt` tests again but this time for a 32-bit target. + # This enforces that tests using `// EMIT_MIR_FOR_EACH_BIT_WIDTH` have + # both 32-bit and 64-bit outputs updated by the PR author, before + # the PR is approved and tested for merging. + # It will also detect tests lacking `// EMIT_MIR_FOR_EACH_BIT_WIDTH`, + # despite having different output on 32-bit vs 64-bit targets. + ../x.py --stage 1 test tests/mir-opt \ + --host='' --target=i686-unknown-linux-gnu && \ + # Run `ui-fulldeps` in `--stage=1`, which actually uses the stage0 + # compiler, and is sensitive to the addition of new flags. + ../x.py --stage 1 test tests/ui-fulldeps fi # NOTE: intentionally uses all of `x.py`, `x`, and `x.ps1` to make sure they all work on Linux. From 4b865b7d1aa4ffe2e760adfdc9744d6a656ba96a Mon Sep 17 00:00:00 2001 From: The Miri Conjob Bot Date: Thu, 21 Sep 2023 05:28:08 +0000 Subject: [PATCH 097/111] Preparing for merge from rustc --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index a2030c1c3432..f6db58695fbc 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -19dd9535408db0f1ff3d16613619076aef524d19 +4fda889bf8735755573b27e6116ce025f3ded5f9 From de53877f8b52818458e12045d65d83df9970de10 Mon Sep 17 00:00:00 2001 From: lcnr Date: Wed, 20 Sep 2023 21:41:07 +0200 Subject: [PATCH 098/111] proof trees: use for `intercrate_ambiguity_causes` --- .../rustc_middle/src/traits/solve/inspect.rs | 66 +++- .../src/traits/solve/inspect/format.rs | 2 +- .../src/solve/eval_ctxt.rs | 9 +- .../src/solve/eval_ctxt/canonical.rs | 97 ++++- .../src/solve/inspect/analyse.rs | 226 +++++++++++ .../solve/{inspect.rs => inspect/build.rs} | 360 ++++++++++-------- .../src/solve/inspect/mod.rs | 7 + .../rustc_trait_selection/src/solve/mod.rs | 2 +- .../src/traits/coherence.rs | 331 +++++++++++----- 9 files changed, 818 insertions(+), 282 deletions(-) create mode 100644 compiler/rustc_trait_selection/src/solve/inspect/analyse.rs rename compiler/rustc_trait_selection/src/solve/{inspect.rs => inspect/build.rs} (76%) create mode 100644 compiler/rustc_trait_selection/src/solve/inspect/mod.rs diff --git a/compiler/rustc_middle/src/traits/solve/inspect.rs b/compiler/rustc_middle/src/traits/solve/inspect.rs index c3ed40867cf2..e7e40bee6b9d 100644 --- a/compiler/rustc_middle/src/traits/solve/inspect.rs +++ b/compiler/rustc_middle/src/traits/solve/inspect.rs @@ -1,30 +1,70 @@ +//! Data structure used to inspect trait solver behavior. +//! +//! During trait solving we optionally build "proof trees", the root of +//! which is a [GoalEvaluation] with [GoalEvaluationKind::Root]. These +//! trees are used to improve the debug experience and are also used by +//! the compiler itself to provide necessary context for error messages. +//! +//! Because each nested goal in the solver gets [canonicalized] separately +//! and we discard inference progress via "probes", we cannot mechanically +//! use proof trees without somehow "lifting up" data local to the current +//! `InferCtxt`. Any data used mechanically is therefore canonicalized and +//! stored as [CanonicalState]. As printing canonicalized data worsens the +//! debugging dumps, we do not simply canonicalize everything. +//! +//! This means proof trees contain inference variables and placeholders +//! local to a different `InferCtxt` which must not be used with the +//! current one. +//! +//! [canonicalized]: https://rustc-dev-guide.rust-lang.org/solve/canonicalization.html + use super::{ - CandidateSource, CanonicalInput, Certainty, Goal, IsNormalizesToHack, NoSolution, QueryInput, - QueryResult, + CandidateSource, Canonical, CanonicalInput, Certainty, Goal, IsNormalizesToHack, NoSolution, + QueryInput, QueryResult, }; -use crate::ty; +use crate::{infer::canonical::CanonicalVarValues, ty}; use format::ProofTreeFormatter; use std::fmt::{Debug, Write}; mod format; +/// Some `data` together with information about how they relate to the input +/// of the canonical query. +/// +/// This is only ever used as [CanonicalState]. Any type information in proof +/// trees used mechanically has to be canonicalized as we otherwise leak +/// inference variables from a nested `InferCtxt`. +#[derive(Debug, Clone, Copy, Eq, PartialEq, TypeFoldable, TypeVisitable)] +pub struct State<'tcx, T> { + pub var_values: CanonicalVarValues<'tcx>, + pub data: T, +} + +pub type CanonicalState<'tcx, T> = Canonical<'tcx, State<'tcx, T>>; + #[derive(Debug, Eq, PartialEq)] pub enum CacheHit { Provisional, Global, } +/// When evaluating the root goals we also store the +/// original values for the `CanonicalVarValues` of the +/// canonicalized goal. We use this to map any [CanonicalState] +/// from the local `InferCtxt` of the solver query to +/// the `InferCtxt` of the caller. #[derive(Eq, PartialEq)] -pub enum GoalEvaluationKind { - Root, +pub enum GoalEvaluationKind<'tcx> { + Root { orig_values: Vec> }, Nested { is_normalizes_to_hack: IsNormalizesToHack }, } #[derive(Eq, PartialEq)] pub struct GoalEvaluation<'tcx> { pub uncanonicalized_goal: Goal<'tcx, ty::Predicate<'tcx>>, - pub kind: GoalEvaluationKind, + pub kind: GoalEvaluationKind<'tcx>, pub evaluation: CanonicalGoalEvaluation<'tcx>, + /// The nested goals from instantiating the query response. pub returned_goals: Vec>>, } @@ -66,6 +106,7 @@ pub struct GoalEvaluationStep<'tcx> { /// of a goal. #[derive(Eq, PartialEq)] pub struct Probe<'tcx> { + /// What happened inside of this probe in chronological order. pub steps: Vec>, pub kind: ProbeKind<'tcx>, } @@ -78,12 +119,21 @@ impl Debug for Probe<'_> { #[derive(Eq, PartialEq)] pub enum ProbeStep<'tcx> { - AddGoal(Goal<'tcx, ty::Predicate<'tcx>>), + /// We added a goal to the `EvalCtxt` which will get proven + /// the next time `EvalCtxt::try_evaluate_added_goals` is called. + AddGoal(CanonicalState<'tcx, Goal<'tcx, ty::Predicate<'tcx>>>), + /// The inside of a `EvalCtxt::try_evaluate_added_goals` call. EvaluateGoals(AddedGoalsEvaluation<'tcx>), + /// A call to `probe` while proving the current goal. This is + /// used whenever there are multiple candidates to prove the + /// current goalby . NestedProbe(Probe<'tcx>), } -#[derive(Debug, PartialEq, Eq)] +/// What kind of probe we're in. In case the probe represents a candidate, or +/// the final result of the current goal - via [ProbeKind::Root] - we also +/// store the [QueryResult]. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum ProbeKind<'tcx> { /// The root inference context while proving a goal. Root { result: QueryResult<'tcx> }, diff --git a/compiler/rustc_middle/src/traits/solve/inspect/format.rs b/compiler/rustc_middle/src/traits/solve/inspect/format.rs index d33e83ae1edb..5733be00adfa 100644 --- a/compiler/rustc_middle/src/traits/solve/inspect/format.rs +++ b/compiler/rustc_middle/src/traits/solve/inspect/format.rs @@ -41,7 +41,7 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> { pub(super) fn format_goal_evaluation(&mut self, eval: &GoalEvaluation<'_>) -> std::fmt::Result { let goal_text = match eval.kind { - GoalEvaluationKind::Root => "ROOT GOAL", + GoalEvaluationKind::Root { orig_values: _ } => "ROOT GOAL", GoalEvaluationKind::Nested { is_normalizes_to_hack } => match is_normalizes_to_hack { IsNormalizesToHack::No => "GOAL", IsNormalizesToHack::Yes => "NORMALIZES-TO HACK GOAL", diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs index 7941f64873b6..066129d8e473 100644 --- a/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs +++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs @@ -344,7 +344,8 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { goal: Goal<'tcx, ty::Predicate<'tcx>>, ) -> Result<(bool, Certainty, Vec>>), NoSolution> { let (orig_values, canonical_goal) = self.canonicalize_goal(goal); - let mut goal_evaluation = self.inspect.new_goal_evaluation(goal, goal_evaluation_kind); + let mut goal_evaluation = + self.inspect.new_goal_evaluation(goal, &orig_values, goal_evaluation_kind); let encountered_overflow = self.search_graph.encountered_overflow(); let canonical_response = EvalCtxt::evaluate_canonical_goal( self.tcx(), @@ -568,7 +569,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { GoalEvaluationKind::Nested { is_normalizes_to_hack: IsNormalizesToHack::Yes }, unconstrained_goal, )?; - self.add_goals(instantiate_goals); + self.nested_goals.goals.extend(instantiate_goals); // Finally, equate the goal's RHS with the unconstrained var. // We put the nested goals from this into goals instead of @@ -605,7 +606,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { GoalEvaluationKind::Nested { is_normalizes_to_hack: IsNormalizesToHack::No }, goal, )?; - self.add_goals(instantiate_goals); + self.nested_goals.goals.extend(instantiate_goals); if has_changed { unchanged_certainty = None; } @@ -613,7 +614,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { match certainty { Certainty::Yes => {} Certainty::Maybe(_) => { - self.add_goal(goal); + self.nested_goals.goals.push(goal); unchanged_certainty = unchanged_certainty.map(|c| c.unify_with(certainty)); } } diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs index 790f9b840f2b..b3f9218d7619 100644 --- a/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs +++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs @@ -10,17 +10,21 @@ //! [c]: https://rustc-dev-guide.rust-lang.org/solve/canonicalization.html use super::{CanonicalInput, Certainty, EvalCtxt, Goal}; use crate::solve::canonicalize::{CanonicalizeMode, Canonicalizer}; -use crate::solve::{response_no_constraints_raw, CanonicalResponse, QueryResult, Response}; +use crate::solve::{ + inspect, response_no_constraints_raw, CanonicalResponse, QueryResult, Response, +}; use rustc_data_structures::fx::FxHashSet; use rustc_index::IndexVec; use rustc_infer::infer::canonical::query_response::make_query_region_constraints; use rustc_infer::infer::canonical::CanonicalVarValues; use rustc_infer::infer::canonical::{CanonicalExt, QueryRegionConstraints}; -use rustc_infer::infer::InferCtxt; +use rustc_infer::infer::{DefineOpaqueTypes, InferCtxt, InferOk}; +use rustc_middle::infer::canonical::Canonical; use rustc_middle::traits::query::NoSolution; use rustc_middle::traits::solve::{ ExternalConstraintsData, MaybeCause, PredefinedOpaquesData, QueryInput, }; +use rustc_middle::traits::ObligationCause; use rustc_middle::ty::{ self, BoundVar, GenericArgKind, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitableExt, @@ -29,6 +33,22 @@ use rustc_span::DUMMY_SP; use std::iter; use std::ops::Deref; +trait ResponseT<'tcx> { + fn var_values(&self) -> CanonicalVarValues<'tcx>; +} + +impl<'tcx> ResponseT<'tcx> for Response<'tcx> { + fn var_values(&self) -> CanonicalVarValues<'tcx> { + self.var_values + } +} + +impl<'tcx, T> ResponseT<'tcx> for inspect::State<'tcx, T> { + fn var_values(&self) -> CanonicalVarValues<'tcx> { + self.var_values + } +} + impl<'tcx> EvalCtxt<'_, 'tcx> { /// Canonicalizes the goal remembering the original values /// for each bound variable. @@ -188,12 +208,14 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { original_values: Vec>, response: CanonicalResponse<'tcx>, ) -> Result<(Certainty, Vec>>), NoSolution> { - let substitution = self.compute_query_response_substitution(&original_values, &response); + let substitution = + Self::compute_query_response_substitution(self.infcx, &original_values, &response); let Response { var_values, external_constraints, certainty } = response.substitute(self.tcx(), &substitution); - let nested_goals = self.unify_query_var_values(param_env, &original_values, var_values)?; + let nested_goals = + Self::unify_query_var_values(self.infcx, param_env, &original_values, var_values)?; let ExternalConstraintsData { region_constraints, opaque_types } = external_constraints.deref(); @@ -206,21 +228,21 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { /// This returns the substitutions to instantiate the bound variables of /// the canonical response. This depends on the `original_values` for the /// bound variables. - fn compute_query_response_substitution( - &self, + fn compute_query_response_substitution>( + infcx: &InferCtxt<'tcx>, original_values: &[ty::GenericArg<'tcx>], - response: &CanonicalResponse<'tcx>, + response: &Canonical<'tcx, T>, ) -> CanonicalVarValues<'tcx> { // FIXME: Longterm canonical queries should deal with all placeholders // created inside of the query directly instead of returning them to the // caller. - let prev_universe = self.infcx.universe(); + let prev_universe = infcx.universe(); let universes_created_in_query = response.max_universe.index(); for _ in 0..universes_created_in_query { - self.infcx.create_next_universe(); + infcx.create_next_universe(); } - let var_values = response.value.var_values; + let var_values = response.value.var_values(); assert_eq!(original_values.len(), var_values.len()); // If the query did not make progress with constraining inference variables, @@ -254,13 +276,13 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { } } - let var_values = self.tcx().mk_args_from_iter(response.variables.iter().enumerate().map( + let var_values = infcx.tcx.mk_args_from_iter(response.variables.iter().enumerate().map( |(index, info)| { if info.universe() != ty::UniverseIndex::ROOT { // A variable from inside a binder of the query. While ideally these shouldn't // exist at all (see the FIXME at the start of this method), we have to deal with // them for now. - self.infcx.instantiate_canonical_var(DUMMY_SP, info, |idx| { + infcx.instantiate_canonical_var(DUMMY_SP, info, |idx| { ty::UniverseIndex::from(prev_universe.index() + idx.index()) }) } else if info.is_existential() { @@ -274,7 +296,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { if let Some(v) = opt_values[BoundVar::from_usize(index)] { v } else { - self.infcx.instantiate_canonical_var(DUMMY_SP, info, |_| prev_universe) + infcx.instantiate_canonical_var(DUMMY_SP, info, |_| prev_universe) } } else { // For placeholders which were already part of the input, we simply map this @@ -287,9 +309,9 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { CanonicalVarValues { var_values } } - #[instrument(level = "debug", skip(self, param_env), ret)] + #[instrument(level = "debug", skip(infcx, param_env), ret)] fn unify_query_var_values( - &self, + infcx: &InferCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, original_values: &[ty::GenericArg<'tcx>], var_values: CanonicalVarValues<'tcx>, @@ -298,7 +320,18 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { let mut nested_goals = vec![]; for (&orig, response) in iter::zip(original_values, var_values.var_values) { - nested_goals.extend(self.eq_and_get_goals(param_env, orig, response)?); + nested_goals.extend( + infcx + .at(&ObligationCause::dummy(), param_env) + .eq(DefineOpaqueTypes::No, orig, response) + .map(|InferOk { value: (), obligations }| { + obligations.into_iter().map(|o| Goal::from(o)) + }) + .map_err(|e| { + debug!(?e, "failed to equate"); + NoSolution + })?, + ); } Ok(nested_goals) @@ -403,3 +436,35 @@ impl<'tcx> TypeFolder> for EagerResolver<'_, 'tcx> { } } } + +impl<'tcx> inspect::ProofTreeBuilder<'tcx> { + pub fn make_canonical_state>>( + ecx: &EvalCtxt<'_, 'tcx>, + data: T, + ) -> inspect::CanonicalState<'tcx, T> { + let state = inspect::State { var_values: ecx.var_values, data }; + let state = state.fold_with(&mut EagerResolver { infcx: ecx.infcx }); + Canonicalizer::canonicalize( + ecx.infcx, + CanonicalizeMode::Response { max_input_universe: ecx.max_input_universe }, + &mut vec![], + state, + ) + } + + pub fn instantiate_canonical_state>>( + infcx: &InferCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + original_values: &[ty::GenericArg<'tcx>], + state: inspect::CanonicalState<'tcx, T>, + ) -> Result<(Vec>>, T), NoSolution> { + let substitution = + EvalCtxt::compute_query_response_substitution(infcx, original_values, &state); + + let inspect::State { var_values, data } = state.substitute(infcx.tcx, &substitution); + + let nested_goals = + EvalCtxt::unify_query_var_values(infcx, param_env, original_values, var_values)?; + Ok((nested_goals, data)) + } +} diff --git a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs new file mode 100644 index 000000000000..ece0e801fe89 --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs @@ -0,0 +1,226 @@ +/// An infrastructure to mechanically analyse proof trees. +/// +/// It is unavoidable that this representation is somewhat +/// lossy as it should hide quite a few semantically relevant things, +/// e.g. canonicalization and the order of nested goals. +/// +/// @lcnr: However, a lot of the weirdness here is not strictly necessary +/// and could be improved in the future. This is mostly good enough for +/// coherence right now and was annoying to implement, so I am leaving it +/// as is until we start using it for something else. +use std::ops::ControlFlow; + +use rustc_infer::infer::InferCtxt; +use rustc_middle::traits::query::NoSolution; +use rustc_middle::traits::solve::{inspect, QueryResult}; +use rustc_middle::traits::solve::{Certainty, Goal}; +use rustc_middle::ty; + +use crate::solve::inspect::ProofTreeBuilder; +use crate::solve::{GenerateProofTree, InferCtxtEvalExt, UseGlobalCache}; + +pub struct InspectGoal<'a, 'tcx> { + infcx: &'a InferCtxt<'tcx>, + depth: usize, + orig_values: &'a [ty::GenericArg<'tcx>], + goal: Goal<'tcx, ty::Predicate<'tcx>>, + evaluation: &'a inspect::GoalEvaluation<'tcx>, +} + +pub struct InspectCandidate<'a, 'tcx> { + goal: &'a InspectGoal<'a, 'tcx>, + kind: inspect::ProbeKind<'tcx>, + nested_goals: Vec>>>, + result: QueryResult<'tcx>, +} + +impl<'a, 'tcx> InspectCandidate<'a, 'tcx> { + pub fn infcx(&self) -> &'a InferCtxt<'tcx> { + self.goal.infcx + } + + pub fn kind(&self) -> inspect::ProbeKind<'tcx> { + self.kind + } + + pub fn result(&self) -> Result { + self.result.map(|c| c.value.certainty) + } + + /// Visit the nested goals of this candidate. + /// + /// FIXME(@lcnr): we have to slightly adapt this API + /// to also use it to compute the most relevant goal + /// for fulfillment errors. Will do that once we actually + /// need it. + pub fn visit_nested>( + &self, + visitor: &mut V, + ) -> ControlFlow { + // HACK: An arbitrary cutoff to avoid dealing with overflow and cycles. + if self.goal.depth >= 10 { + let infcx = self.goal.infcx; + infcx.probe(|_| { + let mut instantiated_goals = vec![]; + for goal in &self.nested_goals { + let goal = match ProofTreeBuilder::instantiate_canonical_state( + infcx, + self.goal.goal.param_env, + self.goal.orig_values, + *goal, + ) { + Ok((_goals, goal)) => goal, + Err(NoSolution) => { + warn!( + "unexpected failure when instantiating {:?}: {:?}", + goal, self.nested_goals + ); + return ControlFlow::Continue(()); + } + }; + instantiated_goals.push(goal); + } + + for &goal in &instantiated_goals { + let (_, proof_tree) = + infcx.evaluate_root_goal(goal, GenerateProofTree::Yes(UseGlobalCache::No)); + let proof_tree = proof_tree.unwrap(); + visitor.visit_goal(&InspectGoal::new( + infcx, + self.goal.depth + 1, + &proof_tree, + ))?; + } + + ControlFlow::Continue(()) + })?; + } + ControlFlow::Continue(()) + } +} + +impl<'a, 'tcx> InspectGoal<'a, 'tcx> { + pub fn infcx(&self) -> &'a InferCtxt<'tcx> { + self.infcx + } + + pub fn goal(&self) -> Goal<'tcx, ty::Predicate<'tcx>> { + self.goal + } + + pub fn result(&self) -> Result { + self.evaluation.evaluation.result.map(|c| c.value.certainty) + } + + fn candidates_recur( + &'a self, + candidates: &mut Vec>, + nested_goals: &mut Vec>>>, + probe: &inspect::Probe<'tcx>, + ) { + for step in &probe.steps { + match step { + &inspect::ProbeStep::AddGoal(goal) => nested_goals.push(goal), + inspect::ProbeStep::EvaluateGoals(_) => (), + inspect::ProbeStep::NestedProbe(ref probe) => { + let num_goals = nested_goals.len(); + self.candidates_recur(candidates, nested_goals, probe); + nested_goals.truncate(num_goals); + } + } + } + + match probe.kind { + inspect::ProbeKind::Root { result: _ } + | inspect::ProbeKind::NormalizedSelfTyAssembly + | inspect::ProbeKind::UnsizeAssembly + | inspect::ProbeKind::UpcastProjectionCompatibility => (), + inspect::ProbeKind::MiscCandidate { name: _, result } + | inspect::ProbeKind::TraitCandidate { source: _, result } => { + candidates.push(InspectCandidate { + goal: self, + kind: probe.kind, + nested_goals: nested_goals.clone(), + result, + }); + } + } + } + + pub fn candidates(&'a self) -> Vec> { + let mut candidates = vec![]; + let last_eval_step = match self.evaluation.evaluation.kind { + inspect::CanonicalGoalEvaluationKind::Overflow + | inspect::CanonicalGoalEvaluationKind::CacheHit(_) => { + warn!("unexpected root evaluation: {:?}", self.evaluation); + return vec![]; + } + inspect::CanonicalGoalEvaluationKind::Uncached { ref revisions } => { + if let Some(last) = revisions.last() { + last + } else { + return vec![]; + } + } + }; + + let mut nested_goals = vec![]; + self.candidates_recur(&mut candidates, &mut nested_goals, &last_eval_step.evaluation); + + if candidates.is_empty() { + candidates.push(InspectCandidate { + goal: self, + kind: last_eval_step.evaluation.kind, + nested_goals, + result: self.evaluation.evaluation.result, + }); + } + + candidates + } + + fn new( + infcx: &'a InferCtxt<'tcx>, + depth: usize, + root: &'a inspect::GoalEvaluation<'tcx>, + ) -> Self { + match root.kind { + inspect::GoalEvaluationKind::Root { ref orig_values } => InspectGoal { + infcx, + depth, + orig_values, + goal: infcx.resolve_vars_if_possible(root.uncanonicalized_goal), + evaluation: root, + }, + inspect::GoalEvaluationKind::Nested { .. } => unreachable!(), + } + } +} + +/// The public API to interact with proof trees. +pub trait ProofTreeVisitor<'tcx> { + type BreakTy; + + fn visit_goal(&mut self, goal: &InspectGoal<'_, 'tcx>) -> ControlFlow; +} + +pub trait ProofTreeInferCtxtExt<'tcx> { + fn visit_proof_tree>( + &self, + goal: Goal<'tcx, ty::Predicate<'tcx>>, + visitor: &mut V, + ) -> ControlFlow; +} + +impl<'tcx> ProofTreeInferCtxtExt<'tcx> for InferCtxt<'tcx> { + fn visit_proof_tree>( + &self, + goal: Goal<'tcx, ty::Predicate<'tcx>>, + visitor: &mut V, + ) -> ControlFlow { + let (_, proof_tree) = + self.evaluate_root_goal(goal, GenerateProofTree::Yes(UseGlobalCache::No)); + let proof_tree = proof_tree.unwrap(); + visitor.visit_goal(&InspectGoal::new(self, 0, &proof_tree)) + } +} diff --git a/compiler/rustc_trait_selection/src/solve/inspect.rs b/compiler/rustc_trait_selection/src/solve/inspect/build.rs similarity index 76% rename from compiler/rustc_trait_selection/src/solve/inspect.rs rename to compiler/rustc_trait_selection/src/solve/inspect/build.rs index 749bba33c9b6..2eba98b02e90 100644 --- a/compiler/rustc_trait_selection/src/solve/inspect.rs +++ b/compiler/rustc_trait_selection/src/solve/inspect/build.rs @@ -1,153 +1,53 @@ +//! Building proof trees incrementally during trait solving. +//! +//! This code is *a bit* of a mess and can hopefully be +//! mostly ignored. For a general overview of how it works, +//! see the comment on [ProofTreeBuilder]. use rustc_middle::traits::query::NoSolution; -use rustc_middle::traits::solve::inspect::{self, CacheHit, ProbeKind}; use rustc_middle::traits::solve::{ CanonicalInput, Certainty, Goal, IsNormalizesToHack, QueryInput, QueryResult, }; use rustc_middle::ty::{self, TyCtxt}; use rustc_session::config::DumpSolverProofTree; -use super::eval_ctxt::UseGlobalCache; -use super::{GenerateProofTree, GoalEvaluationKind}; +use crate::solve::eval_ctxt::UseGlobalCache; +use crate::solve::{self, inspect, EvalCtxt, GenerateProofTree}; -#[derive(Eq, PartialEq, Debug)] -pub struct WipGoalEvaluation<'tcx> { - pub uncanonicalized_goal: Goal<'tcx, ty::Predicate<'tcx>>, - pub kind: WipGoalEvaluationKind, - pub evaluation: Option>, - pub returned_goals: Vec>>, +/// The core data structure when building proof trees. +/// +/// In case the current evaluation does not generate a proof +/// tree, `state` is simply `None` and we avoid any work. +/// +/// The possible states of the solver are represented via +/// variants of [DebugSolver]. For any nested computation we call +/// `ProofTreeBuilder::new_nested_computation_kind` which +/// creates a new `ProofTreeBuilder` to temporarily replace the +/// current one. Once that nested computation is done, +/// `ProofTreeBuilder::nested_computation_kind` is called +/// to add the finished nested evaluation to the parent. +/// +/// We provide additional information to the current state +/// by calling methods such as `ProofTreeBuilder::probe_kind`. +/// +/// The actual structure closely mirrors the finished proof +/// trees. At the end of trait solving `ProofTreeBuilder::finalize` +/// is called to recursively convert the whole structure to a +/// finished proof tree. +pub(in crate::solve) struct ProofTreeBuilder<'tcx> { + state: Option>>, } -impl<'tcx> WipGoalEvaluation<'tcx> { - pub fn finalize(self) -> inspect::GoalEvaluation<'tcx> { - inspect::GoalEvaluation { - uncanonicalized_goal: self.uncanonicalized_goal, - kind: match self.kind { - WipGoalEvaluationKind::Root => inspect::GoalEvaluationKind::Root, - WipGoalEvaluationKind::Nested { is_normalizes_to_hack } => { - inspect::GoalEvaluationKind::Nested { is_normalizes_to_hack } - } - }, - evaluation: self.evaluation.unwrap().finalize(), - returned_goals: self.returned_goals, - } - } -} - -#[derive(Eq, PartialEq, Debug)] -pub enum WipGoalEvaluationKind { - Root, - Nested { is_normalizes_to_hack: IsNormalizesToHack }, -} - -#[derive(Eq, PartialEq, Debug)] -pub enum WipCanonicalGoalEvaluationKind { - Overflow, - CacheHit(CacheHit), -} - -#[derive(Eq, PartialEq, Debug)] -pub struct WipCanonicalGoalEvaluation<'tcx> { - pub goal: CanonicalInput<'tcx>, - pub kind: Option, - pub revisions: Vec>, - pub result: Option>, -} - -impl<'tcx> WipCanonicalGoalEvaluation<'tcx> { - pub fn finalize(self) -> inspect::CanonicalGoalEvaluation<'tcx> { - let kind = match self.kind { - Some(WipCanonicalGoalEvaluationKind::Overflow) => { - inspect::CanonicalGoalEvaluationKind::Overflow - } - Some(WipCanonicalGoalEvaluationKind::CacheHit(hit)) => { - inspect::CanonicalGoalEvaluationKind::CacheHit(hit) - } - None => inspect::CanonicalGoalEvaluationKind::Uncached { - revisions: self - .revisions - .into_iter() - .map(WipGoalEvaluationStep::finalize) - .collect(), - }, - }; - - inspect::CanonicalGoalEvaluation { goal: self.goal, kind, result: self.result.unwrap() } - } -} - -#[derive(Eq, PartialEq, Debug)] -pub struct WipAddedGoalsEvaluation<'tcx> { - pub evaluations: Vec>>, - pub result: Option>, -} - -impl<'tcx> WipAddedGoalsEvaluation<'tcx> { - pub fn finalize(self) -> inspect::AddedGoalsEvaluation<'tcx> { - inspect::AddedGoalsEvaluation { - evaluations: self - .evaluations - .into_iter() - .map(|evaluations| { - evaluations.into_iter().map(WipGoalEvaluation::finalize).collect() - }) - .collect(), - result: self.result.unwrap(), - } - } -} - -#[derive(Eq, PartialEq, Debug)] -pub struct WipGoalEvaluationStep<'tcx> { - pub instantiated_goal: QueryInput<'tcx, ty::Predicate<'tcx>>, - - pub evaluation: WipProbe<'tcx>, -} - -impl<'tcx> WipGoalEvaluationStep<'tcx> { - pub fn finalize(self) -> inspect::GoalEvaluationStep<'tcx> { - let evaluation = self.evaluation.finalize(); - match evaluation.kind { - ProbeKind::Root { .. } => (), - _ => unreachable!("unexpected root evaluation: {evaluation:?}"), - } - inspect::GoalEvaluationStep { instantiated_goal: self.instantiated_goal, evaluation } - } -} - -#[derive(Eq, PartialEq, Debug)] -pub struct WipProbe<'tcx> { - pub steps: Vec>, - pub kind: Option>, -} - -impl<'tcx> WipProbe<'tcx> { - pub fn finalize(self) -> inspect::Probe<'tcx> { - inspect::Probe { - steps: self.steps.into_iter().map(WipProbeStep::finalize).collect(), - kind: self.kind.unwrap(), - } - } -} - -#[derive(Eq, PartialEq, Debug)] -pub enum WipProbeStep<'tcx> { - AddGoal(Goal<'tcx, ty::Predicate<'tcx>>), - EvaluateGoals(WipAddedGoalsEvaluation<'tcx>), - NestedProbe(WipProbe<'tcx>), -} - -impl<'tcx> WipProbeStep<'tcx> { - pub fn finalize(self) -> inspect::ProbeStep<'tcx> { - match self { - WipProbeStep::AddGoal(goal) => inspect::ProbeStep::AddGoal(goal), - WipProbeStep::EvaluateGoals(eval) => inspect::ProbeStep::EvaluateGoals(eval.finalize()), - WipProbeStep::NestedProbe(probe) => inspect::ProbeStep::NestedProbe(probe.finalize()), - } - } +struct BuilderData<'tcx> { + tree: DebugSolver<'tcx>, + use_global_cache: UseGlobalCache, } +/// The current state of the proof tree builder, at most places +/// in the code, only one or two variants are actually possible. +/// +/// We simply ICE in case that assumption is broken. #[derive(Debug)] -pub enum DebugSolver<'tcx> { +enum DebugSolver<'tcx> { Root, GoalEvaluation(WipGoalEvaluation<'tcx>), CanonicalGoalEvaluation(WipCanonicalGoalEvaluation<'tcx>), @@ -186,13 +86,143 @@ impl<'tcx> From> for DebugSolver<'tcx> { } } -pub struct ProofTreeBuilder<'tcx> { - state: Option>>, +#[derive(Eq, PartialEq, Debug)] +struct WipGoalEvaluation<'tcx> { + pub uncanonicalized_goal: Goal<'tcx, ty::Predicate<'tcx>>, + pub kind: WipGoalEvaluationKind<'tcx>, + pub evaluation: Option>, + pub returned_goals: Vec>>, } -struct BuilderData<'tcx> { - tree: DebugSolver<'tcx>, - use_global_cache: UseGlobalCache, +impl<'tcx> WipGoalEvaluation<'tcx> { + fn finalize(self) -> inspect::GoalEvaluation<'tcx> { + inspect::GoalEvaluation { + uncanonicalized_goal: self.uncanonicalized_goal, + kind: match self.kind { + WipGoalEvaluationKind::Root { orig_values } => { + inspect::GoalEvaluationKind::Root { orig_values } + } + WipGoalEvaluationKind::Nested { is_normalizes_to_hack } => { + inspect::GoalEvaluationKind::Nested { is_normalizes_to_hack } + } + }, + evaluation: self.evaluation.unwrap().finalize(), + returned_goals: self.returned_goals, + } + } +} + +#[derive(Eq, PartialEq, Debug)] +pub(in crate::solve) enum WipGoalEvaluationKind<'tcx> { + Root { orig_values: Vec> }, + Nested { is_normalizes_to_hack: IsNormalizesToHack }, +} + +#[derive(Eq, PartialEq, Debug)] +pub(in crate::solve) enum WipCanonicalGoalEvaluationKind { + Overflow, + CacheHit(inspect::CacheHit), +} + +#[derive(Eq, PartialEq, Debug)] +struct WipCanonicalGoalEvaluation<'tcx> { + goal: CanonicalInput<'tcx>, + kind: Option, + revisions: Vec>, + result: Option>, +} + +impl<'tcx> WipCanonicalGoalEvaluation<'tcx> { + fn finalize(self) -> inspect::CanonicalGoalEvaluation<'tcx> { + let kind = match self.kind { + Some(WipCanonicalGoalEvaluationKind::Overflow) => { + inspect::CanonicalGoalEvaluationKind::Overflow + } + Some(WipCanonicalGoalEvaluationKind::CacheHit(hit)) => { + inspect::CanonicalGoalEvaluationKind::CacheHit(hit) + } + None => inspect::CanonicalGoalEvaluationKind::Uncached { + revisions: self + .revisions + .into_iter() + .map(WipGoalEvaluationStep::finalize) + .collect(), + }, + }; + + inspect::CanonicalGoalEvaluation { goal: self.goal, kind, result: self.result.unwrap() } + } +} + +#[derive(Eq, PartialEq, Debug)] +struct WipAddedGoalsEvaluation<'tcx> { + evaluations: Vec>>, + result: Option>, +} + +impl<'tcx> WipAddedGoalsEvaluation<'tcx> { + fn finalize(self) -> inspect::AddedGoalsEvaluation<'tcx> { + inspect::AddedGoalsEvaluation { + evaluations: self + .evaluations + .into_iter() + .map(|evaluations| { + evaluations.into_iter().map(WipGoalEvaluation::finalize).collect() + }) + .collect(), + result: self.result.unwrap(), + } + } +} + +#[derive(Eq, PartialEq, Debug)] +struct WipGoalEvaluationStep<'tcx> { + instantiated_goal: QueryInput<'tcx, ty::Predicate<'tcx>>, + + evaluation: WipProbe<'tcx>, +} + +impl<'tcx> WipGoalEvaluationStep<'tcx> { + fn finalize(self) -> inspect::GoalEvaluationStep<'tcx> { + let evaluation = self.evaluation.finalize(); + match evaluation.kind { + inspect::ProbeKind::Root { .. } => (), + _ => unreachable!("unexpected root evaluation: {evaluation:?}"), + } + inspect::GoalEvaluationStep { instantiated_goal: self.instantiated_goal, evaluation } + } +} + +#[derive(Eq, PartialEq, Debug)] +struct WipProbe<'tcx> { + pub steps: Vec>, + pub kind: Option>, +} + +impl<'tcx> WipProbe<'tcx> { + fn finalize(self) -> inspect::Probe<'tcx> { + inspect::Probe { + steps: self.steps.into_iter().map(WipProbeStep::finalize).collect(), + kind: self.kind.unwrap(), + } + } +} + +#[derive(Eq, PartialEq, Debug)] +enum WipProbeStep<'tcx> { + AddGoal(inspect::CanonicalState<'tcx, Goal<'tcx, ty::Predicate<'tcx>>>), + EvaluateGoals(WipAddedGoalsEvaluation<'tcx>), + NestedProbe(WipProbe<'tcx>), +} + +impl<'tcx> WipProbeStep<'tcx> { + fn finalize(self) -> inspect::ProbeStep<'tcx> { + match self { + WipProbeStep::AddGoal(goal) => inspect::ProbeStep::AddGoal(goal), + WipProbeStep::EvaluateGoals(eval) => inspect::ProbeStep::EvaluateGoals(eval.finalize()), + WipProbeStep::NestedProbe(probe) => inspect::ProbeStep::NestedProbe(probe.finalize()), + } + } } impl<'tcx> ProofTreeBuilder<'tcx> { @@ -273,16 +303,19 @@ impl<'tcx> ProofTreeBuilder<'tcx> { self.state.is_none() } - pub(super) fn new_goal_evaluation( + pub(in crate::solve) fn new_goal_evaluation( &mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>, - kind: GoalEvaluationKind, + orig_values: &[ty::GenericArg<'tcx>], + kind: solve::GoalEvaluationKind, ) -> ProofTreeBuilder<'tcx> { self.nested(|| WipGoalEvaluation { uncanonicalized_goal: goal, kind: match kind { - GoalEvaluationKind::Root => WipGoalEvaluationKind::Root, - GoalEvaluationKind::Nested { is_normalizes_to_hack } => { + solve::GoalEvaluationKind::Root => { + WipGoalEvaluationKind::Root { orig_values: orig_values.to_vec() } + } + solve::GoalEvaluationKind::Nested { is_normalizes_to_hack } => { WipGoalEvaluationKind::Nested { is_normalizes_to_hack } } }, @@ -379,7 +412,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> { self.nested(|| WipProbe { steps: vec![], kind: None }) } - pub fn probe_kind(&mut self, probe_kind: ProbeKind<'tcx>) { + pub fn probe_kind(&mut self, probe_kind: inspect::ProbeKind<'tcx>) { if let Some(this) = self.as_mut() { match this { DebugSolver::Probe(this) => { @@ -390,18 +423,22 @@ impl<'tcx> ProofTreeBuilder<'tcx> { } } - pub fn add_goal(&mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>) { - if let Some(this) = self.as_mut() { - match this { - DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep { - evaluation: WipProbe { steps, .. }, - .. - }) - | DebugSolver::Probe(WipProbe { steps, .. }) => { - steps.push(WipProbeStep::AddGoal(goal)) - } - _ => unreachable!(), - } + pub fn add_goal(ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, ty::Predicate<'tcx>>) { + // Can't use `if let Some(this) = ecx.inspect.as_mut()` here because + // we have to immutably use the `EvalCtxt` for `make_canonical_state`. + if ecx.inspect.is_noop() { + return; + } + + let goal = Self::make_canonical_state(ecx, goal); + + match ecx.inspect.as_mut().unwrap() { + DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep { + evaluation: WipProbe { steps, .. }, + .. + }) + | DebugSolver::Probe(WipProbe { steps, .. }) => steps.push(WipProbeStep::AddGoal(goal)), + s => unreachable!("tried to add {goal:?} to {s:?}"), } } @@ -471,7 +508,10 @@ impl<'tcx> ProofTreeBuilder<'tcx> { } DebugSolver::GoalEvaluationStep(evaluation_step) => { assert_eq!( - evaluation_step.evaluation.kind.replace(ProbeKind::Root { result }), + evaluation_step + .evaluation + .kind + .replace(inspect::ProbeKind::Root { result }), None ); } diff --git a/compiler/rustc_trait_selection/src/solve/inspect/mod.rs b/compiler/rustc_trait_selection/src/solve/inspect/mod.rs new file mode 100644 index 000000000000..60d52305a6be --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/inspect/mod.rs @@ -0,0 +1,7 @@ +pub use rustc_middle::traits::solve::inspect::*; + +mod build; +pub(in crate::solve) use build::*; + +mod analyse; +pub use analyse::*; diff --git a/compiler/rustc_trait_selection/src/solve/mod.rs b/compiler/rustc_trait_selection/src/solve/mod.rs index bd612ce4778d..77a3b5e12845 100644 --- a/compiler/rustc_trait_selection/src/solve/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/mod.rs @@ -235,7 +235,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { #[instrument(level = "debug", skip(self))] fn add_goal(&mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>) { - self.inspect.add_goal(goal); + inspect::ProofTreeBuilder::add_goal(self, goal); self.nested_goals.goals.push(goal); } diff --git a/compiler/rustc_trait_selection/src/traits/coherence.rs b/compiler/rustc_trait_selection/src/traits/coherence.rs index e90723b0ee80..b4c0306ec600 100644 --- a/compiler/rustc_trait_selection/src/traits/coherence.rs +++ b/compiler/rustc_trait_selection/src/traits/coherence.rs @@ -6,9 +6,15 @@ use crate::infer::outlives::env::OutlivesEnvironment; use crate::infer::InferOk; +use crate::solve::inspect; +use crate::solve::inspect::{InspectGoal, ProofTreeInferCtxtExt, ProofTreeVisitor}; +use crate::traits::engine::TraitEngineExt; use crate::traits::outlives_bounds::InferCtxtExt as _; +use crate::traits::query::evaluate_obligation::InferCtxtExt; use crate::traits::select::{IntercrateAmbiguityCause, TreatInductiveCycleAs}; +use crate::traits::structural_normalize::StructurallyNormalizeExt; use crate::traits::util::impl_subject_and_oblig; +use crate::traits::NormalizeExt; use crate::traits::SkipLeakCheck; use crate::traits::{ self, Obligation, ObligationCause, ObligationCtxt, PredicateObligation, PredicateObligations, @@ -18,10 +24,13 @@ use rustc_data_structures::fx::FxIndexSet; use rustc_errors::Diagnostic; use rustc_hir::def_id::{DefId, CRATE_DEF_ID, LOCAL_CRATE}; use rustc_infer::infer::{DefineOpaqueTypes, InferCtxt, TyCtxtInferExt}; -use rustc_infer::traits::util; +use rustc_infer::traits::{util, TraitEngine}; +use rustc_middle::traits::query::NoSolution; +use rustc_middle::traits::solve::{Certainty, Goal}; use rustc_middle::traits::specialization_graph::OverlapMode; use rustc_middle::traits::DefiningAnchor; use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams}; +use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::visit::{TypeVisitable, TypeVisitableExt}; use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitor}; use rustc_session::lint::builtin::COINDUCTIVE_OVERLAP_IN_COHERENCE; @@ -31,9 +40,6 @@ use std::fmt::Debug; use std::iter; use std::ops::ControlFlow; -use super::query::evaluate_obligation::InferCtxtExt; -use super::NormalizeExt; - /// Whether we do the orphan check relative to this crate or /// to some remote crate. #[derive(Copy, Clone, Debug)] @@ -205,72 +211,79 @@ fn overlap<'tcx>( // Equate the headers to find their intersection (the general type, with infer vars, // that may apply both impls). - let equate_obligations = equate_impl_headers(selcx.infcx, &impl1_header, &impl2_header)?; + let mut obligations = equate_impl_headers(selcx.infcx, &impl1_header, &impl2_header)?; debug!("overlap: unification check succeeded"); - if overlap_mode.use_implicit_negative() { - for mode in [TreatInductiveCycleAs::Ambig, TreatInductiveCycleAs::Recur] { - if let Some(failing_obligation) = selcx.with_treat_inductive_cycle_as(mode, |selcx| { - impl_intersection_has_impossible_obligation( - selcx, - param_env, - &impl1_header, - &impl2_header, - &equate_obligations, - ) - }) { - if matches!(mode, TreatInductiveCycleAs::Recur) { - let first_local_impl = impl1_header - .impl_def_id - .as_local() - .or(impl2_header.impl_def_id.as_local()) - .expect("expected one of the impls to be local"); - infcx.tcx.struct_span_lint_hir( - COINDUCTIVE_OVERLAP_IN_COHERENCE, - infcx.tcx.local_def_id_to_hir_id(first_local_impl), - infcx.tcx.def_span(first_local_impl), - format!( - "implementations {} will conflict in the future", - match impl1_header.trait_ref { - Some(trait_ref) => { - let trait_ref = infcx.resolve_vars_if_possible(trait_ref); - format!( - "of `{}` for `{}`", - trait_ref.print_only_trait_path(), - trait_ref.self_ty() - ) - } - None => format!( - "for `{}`", - infcx.resolve_vars_if_possible(impl1_header.self_ty) - ), - }, - ), - |lint| { - lint.note( - "impls that are not considered to overlap may be considered to \ - overlap in the future", - ) - .span_label( - infcx.tcx.def_span(impl1_header.impl_def_id), - "the first impl is here", - ) - .span_label( - infcx.tcx.def_span(impl2_header.impl_def_id), - "the second impl is here", - ); - lint.note(format!( - "`{}` may be considered to hold in future releases, \ - causing the impls to overlap", - infcx.resolve_vars_if_possible(failing_obligation.predicate) - )); - lint - }, - ); - } + if !overlap_mode.use_implicit_negative() { + let impl_header = selcx.infcx.resolve_vars_if_possible(impl1_header); + return Some(OverlapResult { + impl_header, + intercrate_ambiguity_causes: Default::default(), + involves_placeholder: false, + }); + }; - return None; + obligations.extend( + [&impl1_header.predicates, &impl2_header.predicates].into_iter().flatten().map( + |&predicate| Obligation::new(infcx.tcx, ObligationCause::dummy(), param_env, predicate), + ), + ); + + for mode in [TreatInductiveCycleAs::Ambig, TreatInductiveCycleAs::Recur] { + if let Some(failing_obligation) = selcx.with_treat_inductive_cycle_as(mode, |selcx| { + impl_intersection_has_impossible_obligation(selcx, &obligations) + }) { + if matches!(mode, TreatInductiveCycleAs::Recur) { + let first_local_impl = impl1_header + .impl_def_id + .as_local() + .or(impl2_header.impl_def_id.as_local()) + .expect("expected one of the impls to be local"); + infcx.tcx.struct_span_lint_hir( + COINDUCTIVE_OVERLAP_IN_COHERENCE, + infcx.tcx.local_def_id_to_hir_id(first_local_impl), + infcx.tcx.def_span(first_local_impl), + format!( + "implementations {} will conflict in the future", + match impl1_header.trait_ref { + Some(trait_ref) => { + let trait_ref = infcx.resolve_vars_if_possible(trait_ref); + format!( + "of `{}` for `{}`", + trait_ref.print_only_trait_path(), + trait_ref.self_ty() + ) + } + None => format!( + "for `{}`", + infcx.resolve_vars_if_possible(impl1_header.self_ty) + ), + }, + ), + |lint| { + lint.note( + "impls that are not considered to overlap may be considered to \ + overlap in the future", + ) + .span_label( + infcx.tcx.def_span(impl1_header.impl_def_id), + "the first impl is here", + ) + .span_label( + infcx.tcx.def_span(impl2_header.impl_def_id), + "the second impl is here", + ); + lint.note(format!( + "`{}` may be considered to hold in future releases, \ + causing the impls to overlap", + infcx.resolve_vars_if_possible(failing_obligation.predicate) + )); + lint + }, + ); } + + return None; } } @@ -281,7 +294,12 @@ fn overlap<'tcx>( return None; } - let intercrate_ambiguity_causes = selcx.take_intercrate_ambiguity_causes(); + let intercrate_ambiguity_causes = if infcx.next_trait_solver() { + compute_intercrate_ambiguity_causes(&infcx, &obligations) + } else { + selcx.take_intercrate_ambiguity_causes() + }; + debug!("overlap: intercrate_ambiguity_causes={:#?}", intercrate_ambiguity_causes); let involves_placeholder = infcx .inner @@ -335,34 +353,24 @@ fn equate_impl_headers<'tcx>( /// of the two impls above to be empty. /// /// Importantly, this works even if there isn't a `impl !Error for MyLocalType`. -fn impl_intersection_has_impossible_obligation<'cx, 'tcx>( +fn impl_intersection_has_impossible_obligation<'a, 'cx, 'tcx>( selcx: &mut SelectionContext<'cx, 'tcx>, - param_env: ty::ParamEnv<'tcx>, - impl1_header: &ty::ImplHeader<'tcx>, - impl2_header: &ty::ImplHeader<'tcx>, - obligations: &PredicateObligations<'tcx>, -) -> Option> { + obligations: &'a [PredicateObligation<'tcx>], +) -> Option<&'a PredicateObligation<'tcx>> { let infcx = selcx.infcx; - [&impl1_header.predicates, &impl2_header.predicates] - .into_iter() - .flatten() - .map(|&predicate| { - Obligation::new(infcx.tcx, ObligationCause::dummy(), param_env, predicate) - }) - .chain(obligations.into_iter().cloned()) - .find(|obligation: &PredicateObligation<'tcx>| { - if infcx.next_trait_solver() { - infcx.evaluate_obligation(obligation).map_or(false, |result| !result.may_apply()) - } else { - // We use `evaluate_root_obligation` to correctly track intercrate - // ambiguity clauses. We cannot use this in the new solver. - selcx.evaluate_root_obligation(obligation).map_or( - false, // Overflow has occurred, and treat the obligation as possibly holding. - |result| !result.may_apply(), - ) - } - }) + obligations.iter().find(|obligation| { + if infcx.next_trait_solver() { + infcx.evaluate_obligation(obligation).map_or(false, |result| !result.may_apply()) + } else { + // We use `evaluate_root_obligation` to correctly track intercrate + // ambiguity clauses. We cannot use this in the new solver. + selcx.evaluate_root_obligation(obligation).map_or( + false, // Overflow has occurred, and treat the obligation as possibly holding. + |result| !result.may_apply(), + ) + } + }) } /// Check if both impls can be satisfied by a common type by considering whether @@ -882,3 +890,142 @@ where ControlFlow::Continue(()) } } + +/// Compute the `intercrate_ambiguity_causes` for the new solver using +/// "proof trees". +/// +/// This is a bit scuffed but seems to be good enough, at least +/// when looking at UI tests. Given that it is only used to improve +/// diagnostics this is good enough. We can always improve it once there +/// are test cases where it is currently not enough. +fn compute_intercrate_ambiguity_causes<'tcx>( + infcx: &InferCtxt<'tcx>, + obligations: &[PredicateObligation<'tcx>], +) -> FxIndexSet { + let mut causes: FxIndexSet = Default::default(); + + for obligation in obligations { + search_ambiguity_causes(infcx, obligation.clone().into(), &mut causes); + } + + causes +} + +struct AmbiguityCausesVisitor<'a> { + causes: &'a mut FxIndexSet, +} + +impl<'a, 'tcx> ProofTreeVisitor<'tcx> for AmbiguityCausesVisitor<'a> { + type BreakTy = !; + fn visit_goal(&mut self, goal: &InspectGoal<'_, 'tcx>) -> ControlFlow { + let infcx = goal.infcx(); + for cand in goal.candidates() { + cand.visit_nested(self)?; + } + // When searching for intercrate ambiguity causes, we only need to look + // at ambiguous goals, as for others the coherence unknowable candidate + // was irrelevant. + match goal.result() { + Ok(Certainty::Maybe(_)) => {} + Ok(Certainty::Yes) | Err(NoSolution) => return ControlFlow::Continue(()), + } + + let Goal { param_env, predicate } = goal.goal(); + + let trait_ref = match predicate.kind().no_bound_vars() { + Some(ty::PredicateKind::Clause(ty::ClauseKind::Trait(tr))) => tr.trait_ref, + Some(ty::PredicateKind::Clause(ty::ClauseKind::Projection(proj))) => { + proj.projection_ty.trait_ref(infcx.tcx) + } + _ => return ControlFlow::Continue(()), + }; + + let mut ambiguity_cause = None; + for cand in goal.candidates() { + match cand.result() { + Ok(Certainty::Maybe(_)) => {} + // We only add intercrate ambiguity causes if the goal would + // otherwise result in an error. + // + // FIXME: this isn't quite right. Changing a goal from YES with + // inference contraints to AMBIGUOUS can also cause a goal to not + // fail. + Ok(Certainty::Yes) => { + ambiguity_cause = None; + break; + } + Err(NoSolution) => continue, + } + + // FIXME: boiiii, using string comparisions here sure is scuffed. + if let inspect::ProbeKind::MiscCandidate { name: "coherence unknowable", result: _ } = + cand.kind() + { + let lazily_normalize_ty = |ty: Ty<'tcx>| { + let mut fulfill_cx = >::new(infcx); + if matches!(ty.kind(), ty::Alias(..)) { + // FIXME(-Ztrait-solver=next-coherence): we currently don't + // normalize opaque types here, resulting in diverging behavior + // for TAITs. + match infcx + .at(&ObligationCause::dummy(), param_env) + .structurally_normalize(ty, &mut *fulfill_cx) + { + Ok(ty) => Ok(ty), + Err(_errs) => Err(()), + } + } else { + Ok(ty) + } + }; + + infcx.probe(|_| { + match trait_ref_is_knowable(infcx.tcx, trait_ref, lazily_normalize_ty) { + Err(()) => {} + Ok(Ok(())) => warn!("expected an unknowable trait ref: {trait_ref:?}"), + Ok(Err(conflict)) => { + if !trait_ref.references_error() { + let self_ty = trait_ref.self_ty(); + let (trait_desc, self_desc) = with_no_trimmed_paths!({ + let trait_desc = trait_ref.print_only_trait_path().to_string(); + let self_desc = self_ty + .has_concrete_skeleton() + .then(|| self_ty.to_string()); + (trait_desc, self_desc) + }); + ambiguity_cause = Some(match conflict { + Conflict::Upstream => { + IntercrateAmbiguityCause::UpstreamCrateUpdate { + trait_desc, + self_desc, + } + } + Conflict::Downstream => { + IntercrateAmbiguityCause::DownstreamCrate { + trait_desc, + self_desc, + } + } + }); + } + } + } + }) + } + } + + if let Some(ambiguity_cause) = ambiguity_cause { + self.causes.insert(ambiguity_cause); + } + + ControlFlow::Continue(()) + } +} + +fn search_ambiguity_causes<'tcx>( + infcx: &InferCtxt<'tcx>, + goal: Goal<'tcx, ty::Predicate<'tcx>>, + causes: &mut FxIndexSet, +) { + infcx.visit_proof_tree(goal, &mut AmbiguityCausesVisitor { causes }); +} From 8eade3aa71fd3c08de4e8849a91ef9154e83a054 Mon Sep 17 00:00:00 2001 From: lcnr Date: Wed, 20 Sep 2023 21:41:19 +0200 Subject: [PATCH 099/111] update tests --- ...e-overlap-downstream-inherent.next.stderr} | 4 ++-- ...nce-overlap-downstream-inherent.old.stderr | 23 +++++++++++++++++++ .../coherence-overlap-downstream-inherent.rs | 3 +++ ... coherence-overlap-downstream.next.stderr} | 4 ++-- .../coherence-overlap-downstream.old.stderr | 21 +++++++++++++++++ .../coherence/coherence-overlap-downstream.rs | 3 +++ ...-overlap-issue-23516-inherent.next.stderr} | 2 +- ...ce-overlap-issue-23516-inherent.old.stderr | 14 +++++++++++ .../coherence-overlap-issue-23516-inherent.rs | 3 +++ ...coherence-overlap-issue-23516.next.stderr} | 2 +- .../coherence-overlap-issue-23516.old.stderr | 13 +++++++++++ .../coherence-overlap-issue-23516.rs | 3 +++ ...-crate-ambiguity-causes-notes.next.stderr} | 2 +- ...er-crate-ambiguity-causes-notes.old.stderr | 14 +++++++++++ .../inter-crate-ambiguity-causes-notes.rs | 3 +++ .../impl-trait/coherence-treats-tait-ambig.rs | 3 --- ...err => coherence-treats-tait-ambig.stderr} | 2 +- .../alias_eq_substs_eq_not_intercrate.stderr | 2 ++ ...trait_ref_is_knowable-norm-overflow.stderr | 6 +++++ 19 files changed, 116 insertions(+), 11 deletions(-) rename tests/ui/coherence/{coherence-overlap-downstream-inherent.stderr => coherence-overlap-downstream-inherent.next.stderr} (87%) create mode 100644 tests/ui/coherence/coherence-overlap-downstream-inherent.old.stderr rename tests/ui/coherence/{coherence-overlap-downstream.stderr => coherence-overlap-downstream.next.stderr} (88%) create mode 100644 tests/ui/coherence/coherence-overlap-downstream.old.stderr rename tests/ui/coherence/{coherence-overlap-issue-23516-inherent.stderr => coherence-overlap-issue-23516-inherent.next.stderr} (89%) create mode 100644 tests/ui/coherence/coherence-overlap-issue-23516-inherent.old.stderr rename tests/ui/coherence/{coherence-overlap-issue-23516.stderr => coherence-overlap-issue-23516.next.stderr} (90%) create mode 100644 tests/ui/coherence/coherence-overlap-issue-23516.old.stderr rename tests/ui/coherence/{inter-crate-ambiguity-causes-notes.stderr => inter-crate-ambiguity-causes-notes.next.stderr} (89%) create mode 100644 tests/ui/coherence/inter-crate-ambiguity-causes-notes.old.stderr rename tests/ui/impl-trait/{coherence-treats-tait-ambig.next.stderr => coherence-treats-tait-ambig.stderr} (88%) diff --git a/tests/ui/coherence/coherence-overlap-downstream-inherent.stderr b/tests/ui/coherence/coherence-overlap-downstream-inherent.next.stderr similarity index 87% rename from tests/ui/coherence/coherence-overlap-downstream-inherent.stderr rename to tests/ui/coherence/coherence-overlap-downstream-inherent.next.stderr index bbce4b530b4d..2938bc629b2c 100644 --- a/tests/ui/coherence/coherence-overlap-downstream-inherent.stderr +++ b/tests/ui/coherence/coherence-overlap-downstream-inherent.next.stderr @@ -1,5 +1,5 @@ error[E0592]: duplicate definitions with name `dummy` - --> $DIR/coherence-overlap-downstream-inherent.rs:7:26 + --> $DIR/coherence-overlap-downstream-inherent.rs:10:26 | LL | impl Sweet { fn dummy(&self) { } } | ^^^^^^^^^^^^^^^ duplicate definitions for `dummy` @@ -8,7 +8,7 @@ LL | impl Sweet { fn dummy(&self) { } } | --------------- other definition for `dummy` error[E0592]: duplicate definitions with name `f` - --> $DIR/coherence-overlap-downstream-inherent.rs:13:38 + --> $DIR/coherence-overlap-downstream-inherent.rs:16:38 | LL | impl A where T: Bar { fn f(&self) {} } | ^^^^^^^^^^^ duplicate definitions for `f` diff --git a/tests/ui/coherence/coherence-overlap-downstream-inherent.old.stderr b/tests/ui/coherence/coherence-overlap-downstream-inherent.old.stderr new file mode 100644 index 000000000000..2938bc629b2c --- /dev/null +++ b/tests/ui/coherence/coherence-overlap-downstream-inherent.old.stderr @@ -0,0 +1,23 @@ +error[E0592]: duplicate definitions with name `dummy` + --> $DIR/coherence-overlap-downstream-inherent.rs:10:26 + | +LL | impl Sweet { fn dummy(&self) { } } + | ^^^^^^^^^^^^^^^ duplicate definitions for `dummy` +LL | +LL | impl Sweet { fn dummy(&self) { } } + | --------------- other definition for `dummy` + +error[E0592]: duplicate definitions with name `f` + --> $DIR/coherence-overlap-downstream-inherent.rs:16:38 + | +LL | impl A where T: Bar { fn f(&self) {} } + | ^^^^^^^^^^^ duplicate definitions for `f` +LL | +LL | impl A { fn f(&self) {} } + | ----------- other definition for `f` + | + = note: downstream crates may implement trait `Bar<_>` for type `i32` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0592`. diff --git a/tests/ui/coherence/coherence-overlap-downstream-inherent.rs b/tests/ui/coherence/coherence-overlap-downstream-inherent.rs index 5dea33e330b6..2c3ef4fd3f71 100644 --- a/tests/ui/coherence/coherence-overlap-downstream-inherent.rs +++ b/tests/ui/coherence/coherence-overlap-downstream-inherent.rs @@ -1,3 +1,6 @@ +// revisions: old next +//[next] compile-flags: -Ztrait-solver=next + // Tests that we consider `T: Sugar + Fruit` to be ambiguous, even // though no impls are found. diff --git a/tests/ui/coherence/coherence-overlap-downstream.stderr b/tests/ui/coherence/coherence-overlap-downstream.next.stderr similarity index 88% rename from tests/ui/coherence/coherence-overlap-downstream.stderr rename to tests/ui/coherence/coherence-overlap-downstream.next.stderr index 7f373e595a35..9d62efbc315b 100644 --- a/tests/ui/coherence/coherence-overlap-downstream.stderr +++ b/tests/ui/coherence/coherence-overlap-downstream.next.stderr @@ -1,5 +1,5 @@ error[E0119]: conflicting implementations of trait `Sweet` - --> $DIR/coherence-overlap-downstream.rs:8:1 + --> $DIR/coherence-overlap-downstream.rs:11:1 | LL | impl Sweet for T { } | ------------------------- first implementation here @@ -7,7 +7,7 @@ LL | impl Sweet for T { } | ^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation error[E0119]: conflicting implementations of trait `Foo<_>` for type `i32` - --> $DIR/coherence-overlap-downstream.rs:14:1 + --> $DIR/coherence-overlap-downstream.rs:17:1 | LL | impl Foo for T where T: Bar {} | ----------------------- first implementation here diff --git a/tests/ui/coherence/coherence-overlap-downstream.old.stderr b/tests/ui/coherence/coherence-overlap-downstream.old.stderr new file mode 100644 index 000000000000..9d62efbc315b --- /dev/null +++ b/tests/ui/coherence/coherence-overlap-downstream.old.stderr @@ -0,0 +1,21 @@ +error[E0119]: conflicting implementations of trait `Sweet` + --> $DIR/coherence-overlap-downstream.rs:11:1 + | +LL | impl Sweet for T { } + | ------------------------- first implementation here +LL | impl Sweet for T { } + | ^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation + +error[E0119]: conflicting implementations of trait `Foo<_>` for type `i32` + --> $DIR/coherence-overlap-downstream.rs:17:1 + | +LL | impl Foo for T where T: Bar {} + | ----------------------- first implementation here +LL | impl Foo for i32 {} + | ^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `i32` + | + = note: downstream crates may implement trait `Bar<_>` for type `i32` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0119`. diff --git a/tests/ui/coherence/coherence-overlap-downstream.rs b/tests/ui/coherence/coherence-overlap-downstream.rs index 738ec0e3d455..a4e559604a00 100644 --- a/tests/ui/coherence/coherence-overlap-downstream.rs +++ b/tests/ui/coherence/coherence-overlap-downstream.rs @@ -1,3 +1,6 @@ +// revisions: old next +//[next] compile-flags: -Ztrait-solver=next + // Tests that we consider `T: Sugar + Fruit` to be ambiguous, even // though no impls are found. diff --git a/tests/ui/coherence/coherence-overlap-issue-23516-inherent.stderr b/tests/ui/coherence/coherence-overlap-issue-23516-inherent.next.stderr similarity index 89% rename from tests/ui/coherence/coherence-overlap-issue-23516-inherent.stderr rename to tests/ui/coherence/coherence-overlap-issue-23516-inherent.next.stderr index 3ad818cbc36d..c02a679c149c 100644 --- a/tests/ui/coherence/coherence-overlap-issue-23516-inherent.stderr +++ b/tests/ui/coherence/coherence-overlap-issue-23516-inherent.next.stderr @@ -1,5 +1,5 @@ error[E0592]: duplicate definitions with name `dummy` - --> $DIR/coherence-overlap-issue-23516-inherent.rs:9:25 + --> $DIR/coherence-overlap-issue-23516-inherent.rs:12:25 | LL | impl Cake { fn dummy(&self) { } } | ^^^^^^^^^^^^^^^ duplicate definitions for `dummy` diff --git a/tests/ui/coherence/coherence-overlap-issue-23516-inherent.old.stderr b/tests/ui/coherence/coherence-overlap-issue-23516-inherent.old.stderr new file mode 100644 index 000000000000..c02a679c149c --- /dev/null +++ b/tests/ui/coherence/coherence-overlap-issue-23516-inherent.old.stderr @@ -0,0 +1,14 @@ +error[E0592]: duplicate definitions with name `dummy` + --> $DIR/coherence-overlap-issue-23516-inherent.rs:12:25 + | +LL | impl Cake { fn dummy(&self) { } } + | ^^^^^^^^^^^^^^^ duplicate definitions for `dummy` +LL | +LL | impl Cake> { fn dummy(&self) { } } + | --------------- other definition for `dummy` + | + = note: downstream crates may implement trait `Sugar` for type `std::boxed::Box<_>` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0592`. diff --git a/tests/ui/coherence/coherence-overlap-issue-23516-inherent.rs b/tests/ui/coherence/coherence-overlap-issue-23516-inherent.rs index a272e620fcab..a7c90a6b8c8d 100644 --- a/tests/ui/coherence/coherence-overlap-issue-23516-inherent.rs +++ b/tests/ui/coherence/coherence-overlap-issue-23516-inherent.rs @@ -1,3 +1,6 @@ +// revisions: old next +//[next] compile-flags: -Ztrait-solver=next + // Tests that we consider `Box: !Sugar` to be ambiguous, even // though we see no impl of `Sugar` for `Box`. Therefore, an overlap // error is reported for the following pair of impls (#23516). diff --git a/tests/ui/coherence/coherence-overlap-issue-23516.stderr b/tests/ui/coherence/coherence-overlap-issue-23516.next.stderr similarity index 90% rename from tests/ui/coherence/coherence-overlap-issue-23516.stderr rename to tests/ui/coherence/coherence-overlap-issue-23516.next.stderr index cd398426704c..a4e87af8ac40 100644 --- a/tests/ui/coherence/coherence-overlap-issue-23516.stderr +++ b/tests/ui/coherence/coherence-overlap-issue-23516.next.stderr @@ -1,5 +1,5 @@ error[E0119]: conflicting implementations of trait `Sweet` for type `Box<_>` - --> $DIR/coherence-overlap-issue-23516.rs:8:1 + --> $DIR/coherence-overlap-issue-23516.rs:11:1 | LL | impl Sweet for T { } | ------------------------- first implementation here diff --git a/tests/ui/coherence/coherence-overlap-issue-23516.old.stderr b/tests/ui/coherence/coherence-overlap-issue-23516.old.stderr new file mode 100644 index 000000000000..a4e87af8ac40 --- /dev/null +++ b/tests/ui/coherence/coherence-overlap-issue-23516.old.stderr @@ -0,0 +1,13 @@ +error[E0119]: conflicting implementations of trait `Sweet` for type `Box<_>` + --> $DIR/coherence-overlap-issue-23516.rs:11:1 + | +LL | impl Sweet for T { } + | ------------------------- first implementation here +LL | impl Sweet for Box { } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Box<_>` + | + = note: downstream crates may implement trait `Sugar` for type `std::boxed::Box<_>` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0119`. diff --git a/tests/ui/coherence/coherence-overlap-issue-23516.rs b/tests/ui/coherence/coherence-overlap-issue-23516.rs index 63e42e8f412d..c846d39716bb 100644 --- a/tests/ui/coherence/coherence-overlap-issue-23516.rs +++ b/tests/ui/coherence/coherence-overlap-issue-23516.rs @@ -1,3 +1,6 @@ +// revisions: old next +//[next] compile-flags: -Ztrait-solver=next + // Tests that we consider `Box: !Sugar` to be ambiguous, even // though we see no impl of `Sugar` for `Box`. Therefore, an overlap // error is reported for the following pair of impls (#23516). diff --git a/tests/ui/coherence/inter-crate-ambiguity-causes-notes.stderr b/tests/ui/coherence/inter-crate-ambiguity-causes-notes.next.stderr similarity index 89% rename from tests/ui/coherence/inter-crate-ambiguity-causes-notes.stderr rename to tests/ui/coherence/inter-crate-ambiguity-causes-notes.next.stderr index 4ddd712b27c8..0dd28706e076 100644 --- a/tests/ui/coherence/inter-crate-ambiguity-causes-notes.stderr +++ b/tests/ui/coherence/inter-crate-ambiguity-causes-notes.next.stderr @@ -1,5 +1,5 @@ error[E0119]: conflicting implementations of trait `From<()>` for type `S` - --> $DIR/inter-crate-ambiguity-causes-notes.rs:9:1 + --> $DIR/inter-crate-ambiguity-causes-notes.rs:12:1 | LL | impl From<()> for S { | ------------------- first implementation here diff --git a/tests/ui/coherence/inter-crate-ambiguity-causes-notes.old.stderr b/tests/ui/coherence/inter-crate-ambiguity-causes-notes.old.stderr new file mode 100644 index 000000000000..0dd28706e076 --- /dev/null +++ b/tests/ui/coherence/inter-crate-ambiguity-causes-notes.old.stderr @@ -0,0 +1,14 @@ +error[E0119]: conflicting implementations of trait `From<()>` for type `S` + --> $DIR/inter-crate-ambiguity-causes-notes.rs:12:1 + | +LL | impl From<()> for S { + | ------------------- first implementation here +... +LL | impl From for S + | ^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `S` + | + = note: upstream crates may add a new impl of trait `std::iter::Iterator` for type `()` in future versions + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0119`. diff --git a/tests/ui/coherence/inter-crate-ambiguity-causes-notes.rs b/tests/ui/coherence/inter-crate-ambiguity-causes-notes.rs index 5b11c78ab260..743e80d3f181 100644 --- a/tests/ui/coherence/inter-crate-ambiguity-causes-notes.rs +++ b/tests/ui/coherence/inter-crate-ambiguity-causes-notes.rs @@ -1,3 +1,6 @@ +// revisions: old next +//[next] compile-flags: -Ztrait-solver=next + struct S; impl From<()> for S { diff --git a/tests/ui/impl-trait/coherence-treats-tait-ambig.rs b/tests/ui/impl-trait/coherence-treats-tait-ambig.rs index 156a7eb0e23d..df47208bf367 100644 --- a/tests/ui/impl-trait/coherence-treats-tait-ambig.rs +++ b/tests/ui/impl-trait/coherence-treats-tait-ambig.rs @@ -1,6 +1,3 @@ -// revisions: current next -//[next] compile-flags: -Ztrait-solver=next - #![feature(type_alias_impl_trait)] type T = impl Sized; diff --git a/tests/ui/impl-trait/coherence-treats-tait-ambig.next.stderr b/tests/ui/impl-trait/coherence-treats-tait-ambig.stderr similarity index 88% rename from tests/ui/impl-trait/coherence-treats-tait-ambig.next.stderr rename to tests/ui/impl-trait/coherence-treats-tait-ambig.stderr index 61fed16294b4..7c69c4bfe97b 100644 --- a/tests/ui/impl-trait/coherence-treats-tait-ambig.next.stderr +++ b/tests/ui/impl-trait/coherence-treats-tait-ambig.stderr @@ -1,5 +1,5 @@ error[E0119]: conflicting implementations of trait `Into` for type `Foo` - --> $DIR/coherence-treats-tait-ambig.rs:10:1 + --> $DIR/coherence-treats-tait-ambig.rs:7:1 | LL | impl Into for Foo { | ^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/traits/new-solver/alias_eq_substs_eq_not_intercrate.stderr b/tests/ui/traits/new-solver/alias_eq_substs_eq_not_intercrate.stderr index 8eda64e4490b..46677a583160 100644 --- a/tests/ui/traits/new-solver/alias_eq_substs_eq_not_intercrate.stderr +++ b/tests/ui/traits/new-solver/alias_eq_substs_eq_not_intercrate.stderr @@ -5,6 +5,8 @@ LL | impl Overlaps> for ::Assoc {} | --------------------------------------------------------- first implementation here LL | impl Overlaps for ::Assoc {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `<_ as TraitB>::Assoc` + | + = note: downstream crates may implement trait `TraitB` for type `std::boxed::Box<_>` error: aborting due to previous error diff --git a/tests/ui/traits/new-solver/coherence/trait_ref_is_knowable-norm-overflow.stderr b/tests/ui/traits/new-solver/coherence/trait_ref_is_knowable-norm-overflow.stderr index 5d5f325e4b47..73d46c4df960 100644 --- a/tests/ui/traits/new-solver/coherence/trait_ref_is_knowable-norm-overflow.stderr +++ b/tests/ui/traits/new-solver/coherence/trait_ref_is_knowable-norm-overflow.stderr @@ -1,3 +1,7 @@ +WARN rustc_trait_selection::traits::coherence expected an unknowable trait ref: <::Assoc as std::marker::Sized> +WARN rustc_trait_selection::traits::coherence expected an unknowable trait ref: <::Assoc as std::marker::Sized> +WARN rustc_trait_selection::traits::coherence expected an unknowable trait ref: <::Assoc as std::marker::Sized> +WARN rustc_trait_selection::traits::coherence expected an unknowable trait ref: <::Assoc as std::marker::Sized> error[E0119]: conflicting implementations of trait `Trait` for type `::Assoc` --> $DIR/trait_ref_is_knowable-norm-overflow.rs:17:1 | @@ -6,6 +10,8 @@ LL | impl Trait for T {} LL | struct LocalTy; LL | impl Trait for ::Assoc {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `::Assoc` + | + = note: upstream crates may add a new impl of trait `std::marker::Copy` for type `::Assoc` in future versions error: aborting due to previous error From 8024c69c2985274f8caf8bccdeec92a5632bd8c3 Mon Sep 17 00:00:00 2001 From: lcnr Date: Wed, 20 Sep 2023 21:41:29 +0200 Subject: [PATCH 100/111] HACK: avoid hang in structurally_normalize --- .../src/traits/structural_normalize.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/structural_normalize.rs b/compiler/rustc_trait_selection/src/traits/structural_normalize.rs index d3c4dc45923d..9d6be7689019 100644 --- a/compiler/rustc_trait_selection/src/traits/structural_normalize.rs +++ b/compiler/rustc_trait_selection/src/traits/structural_normalize.rs @@ -22,9 +22,14 @@ impl<'tcx> StructurallyNormalizeExt<'tcx> for At<'_, 'tcx> { assert!(!ty.is_ty_var(), "should have resolved vars before calling"); if self.infcx.next_trait_solver() { - while let ty::Alias(ty::Projection | ty::Inherent | ty::Weak, projection_ty) = - *ty.kind() - { + // FIXME(-Ztrait-solver=next): correctly handle + // overflow here. + for _ in 0..256 { + let ty::Alias(ty::Projection | ty::Inherent | ty::Weak, projection_ty) = *ty.kind() + else { + break; + }; + let new_infer_ty = self.infcx.next_ty_var(TypeVariableOrigin { kind: TypeVariableOriginKind::NormalizeProjectionType, span: self.cause.span, @@ -49,6 +54,7 @@ impl<'tcx> StructurallyNormalizeExt<'tcx> for At<'_, 'tcx> { break; } } + Ok(ty) } else { Ok(self.normalize(ty).into_value_registering_obligations(self.infcx, fulfill_cx)) From 8e139eefaf276108c56fda1ec0c7fb972ce8df0c Mon Sep 17 00:00:00 2001 From: lcnr Date: Thu, 21 Sep 2023 08:40:36 +0200 Subject: [PATCH 101/111] slight refactor, add comment --- .../src/solve/inspect/analyse.rs | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs index ece0e801fe89..e4fc8803e0f5 100644 --- a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs +++ b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs @@ -123,6 +123,9 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> { &inspect::ProbeStep::AddGoal(goal) => nested_goals.push(goal), inspect::ProbeStep::EvaluateGoals(_) => (), inspect::ProbeStep::NestedProbe(ref probe) => { + // Nested probes have to prove goals added in their parent + // but do not leak them, so we truncate the added goals + // afterwards. let num_goals = nested_goals.len(); self.candidates_recur(candidates, nested_goals, probe); nested_goals.truncate(num_goals); @@ -131,10 +134,25 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> { } match probe.kind { - inspect::ProbeKind::Root { result: _ } - | inspect::ProbeKind::NormalizedSelfTyAssembly + inspect::ProbeKind::NormalizedSelfTyAssembly | inspect::ProbeKind::UnsizeAssembly | inspect::ProbeKind::UpcastProjectionCompatibility => (), + // We add a candidate for the root evaluation if there + // is only one way to prove a given goal, e.g. for `WellFormed`. + // + // FIXME: This is currently wrong if we don't even try any + // candidates, e.g. for a trait goal, as in this case `candidates` is + // actually supposed to be empty. + inspect::ProbeKind::Root { result: _ } => { + if candidates.is_empty() { + candidates.push(InspectCandidate { + goal: self, + kind: probe.kind, + nested_goals: nested_goals.clone(), + result, + }); + } + } inspect::ProbeKind::MiscCandidate { name: _, result } | inspect::ProbeKind::TraitCandidate { source: _, result } => { candidates.push(InspectCandidate { @@ -167,15 +185,6 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> { let mut nested_goals = vec![]; self.candidates_recur(&mut candidates, &mut nested_goals, &last_eval_step.evaluation); - if candidates.is_empty() { - candidates.push(InspectCandidate { - goal: self, - kind: last_eval_step.evaluation.kind, - nested_goals, - result: self.evaluation.evaluation.result, - }); - } - candidates } From 8167a25e4e64920e3b02e6ed2342e5b6abc25e95 Mon Sep 17 00:00:00 2001 From: lcnr Date: Thu, 21 Sep 2023 08:44:12 +0200 Subject: [PATCH 102/111] w --- compiler/rustc_trait_selection/src/solve/inspect/analyse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs index e4fc8803e0f5..15c8d9e5bcb0 100644 --- a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs +++ b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs @@ -143,7 +143,7 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> { // FIXME: This is currently wrong if we don't even try any // candidates, e.g. for a trait goal, as in this case `candidates` is // actually supposed to be empty. - inspect::ProbeKind::Root { result: _ } => { + inspect::ProbeKind::Root { result } => { if candidates.is_empty() { candidates.push(InspectCandidate { goal: self, From 614760f612fa72ba9d6955c00624c592b884d28f Mon Sep 17 00:00:00 2001 From: lcnr Date: Thu, 21 Sep 2023 08:56:23 +0200 Subject: [PATCH 103/111] review --- .../src/traits/coherence.rs | 153 +++++++++--------- 1 file changed, 75 insertions(+), 78 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/coherence.rs b/compiler/rustc_trait_selection/src/traits/coherence.rs index b4c0306ec600..59f712721fb9 100644 --- a/compiler/rustc_trait_selection/src/traits/coherence.rs +++ b/compiler/rustc_trait_selection/src/traits/coherence.rs @@ -214,76 +214,69 @@ fn overlap<'tcx>( let mut obligations = equate_impl_headers(selcx.infcx, &impl1_header, &impl2_header)?; debug!("overlap: unification check succeeded"); - if !overlap_mode.use_implicit_negative() { - let impl_header = selcx.infcx.resolve_vars_if_possible(impl1_header); - return Some(OverlapResult { - impl_header, - intercrate_ambiguity_causes: Default::default(), - involves_placeholder: false, - }); - }; - obligations.extend( [&impl1_header.predicates, &impl2_header.predicates].into_iter().flatten().map( |&predicate| Obligation::new(infcx.tcx, ObligationCause::dummy(), param_env, predicate), ), ); - for mode in [TreatInductiveCycleAs::Ambig, TreatInductiveCycleAs::Recur] { - if let Some(failing_obligation) = selcx.with_treat_inductive_cycle_as(mode, |selcx| { - impl_intersection_has_impossible_obligation(selcx, &obligations) - }) { - if matches!(mode, TreatInductiveCycleAs::Recur) { - let first_local_impl = impl1_header - .impl_def_id - .as_local() - .or(impl2_header.impl_def_id.as_local()) - .expect("expected one of the impls to be local"); - infcx.tcx.struct_span_lint_hir( - COINDUCTIVE_OVERLAP_IN_COHERENCE, - infcx.tcx.local_def_id_to_hir_id(first_local_impl), - infcx.tcx.def_span(first_local_impl), - format!( - "implementations {} will conflict in the future", - match impl1_header.trait_ref { - Some(trait_ref) => { - let trait_ref = infcx.resolve_vars_if_possible(trait_ref); - format!( - "of `{}` for `{}`", - trait_ref.print_only_trait_path(), - trait_ref.self_ty() - ) - } - None => format!( - "for `{}`", - infcx.resolve_vars_if_possible(impl1_header.self_ty) - ), + if overlap_mode.use_implicit_negative() { + for mode in [TreatInductiveCycleAs::Ambig, TreatInductiveCycleAs::Recur] { + if let Some(failing_obligation) = selcx.with_treat_inductive_cycle_as(mode, |selcx| { + impl_intersection_has_impossible_obligation(selcx, &obligations) + }) { + if matches!(mode, TreatInductiveCycleAs::Recur) { + let first_local_impl = impl1_header + .impl_def_id + .as_local() + .or(impl2_header.impl_def_id.as_local()) + .expect("expected one of the impls to be local"); + infcx.tcx.struct_span_lint_hir( + COINDUCTIVE_OVERLAP_IN_COHERENCE, + infcx.tcx.local_def_id_to_hir_id(first_local_impl), + infcx.tcx.def_span(first_local_impl), + format!( + "implementations {} will conflict in the future", + match impl1_header.trait_ref { + Some(trait_ref) => { + let trait_ref = infcx.resolve_vars_if_possible(trait_ref); + format!( + "of `{}` for `{}`", + trait_ref.print_only_trait_path(), + trait_ref.self_ty() + ) + } + None => format!( + "for `{}`", + infcx.resolve_vars_if_possible(impl1_header.self_ty) + ), + }, + ), + |lint| { + lint.note( + "impls that are not considered to overlap may be considered to \ + overlap in the future", + ) + .span_label( + infcx.tcx.def_span(impl1_header.impl_def_id), + "the first impl is here", + ) + .span_label( + infcx.tcx.def_span(impl2_header.impl_def_id), + "the second impl is here", + ); + lint.note(format!( + "`{}` may be considered to hold in future releases, \ + causing the impls to overlap", + infcx.resolve_vars_if_possible(failing_obligation.predicate) + )); + lint }, - ), - |lint| { - lint.note( - "impls that are not considered to overlap may be considered to \ - overlap in the future", - ) - .span_label( - infcx.tcx.def_span(impl1_header.impl_def_id), - "the first impl is here", - ) - .span_label( - infcx.tcx.def_span(impl2_header.impl_def_id), - "the second impl is here", - ); - lint.note(format!( - "`{}` may be considered to hold in future releases, \ - causing the impls to overlap", - infcx.resolve_vars_if_possible(failing_obligation.predicate) - )); - lint - }, - ); - } + ); + } - return None; + return None; + } } } @@ -294,7 +287,9 @@ fn overlap<'tcx>( return None; } - let intercrate_ambiguity_causes = if infcx.next_trait_solver() { + let intercrate_ambiguity_causes = if !overlap_mode.use_implicit_negative() { + Default::default() + } else if infcx.next_trait_solver() { compute_intercrate_ambiguity_causes(&infcx, &obligations) } else { selcx.take_intercrate_ambiguity_causes() @@ -932,6 +927,8 @@ impl<'a, 'tcx> ProofTreeVisitor<'tcx> for AmbiguityCausesVisitor<'a> { let Goal { param_env, predicate } = goal.goal(); + // For bound predicates we simply call `infcx.replace_bound_vars_with_placeholders` + // and then prove the resulting predicate as a nested goal. let trait_ref = match predicate.kind().no_bound_vars() { Some(ty::PredicateKind::Clause(ty::ClauseKind::Trait(tr))) => tr.trait_ref, Some(ty::PredicateKind::Clause(ty::ClauseKind::Projection(proj))) => { @@ -942,21 +939,6 @@ impl<'a, 'tcx> ProofTreeVisitor<'tcx> for AmbiguityCausesVisitor<'a> { let mut ambiguity_cause = None; for cand in goal.candidates() { - match cand.result() { - Ok(Certainty::Maybe(_)) => {} - // We only add intercrate ambiguity causes if the goal would - // otherwise result in an error. - // - // FIXME: this isn't quite right. Changing a goal from YES with - // inference contraints to AMBIGUOUS can also cause a goal to not - // fail. - Ok(Certainty::Yes) => { - ambiguity_cause = None; - break; - } - Err(NoSolution) => continue, - } - // FIXME: boiiii, using string comparisions here sure is scuffed. if let inspect::ProbeKind::MiscCandidate { name: "coherence unknowable", result: _ } = cand.kind() @@ -1011,6 +993,21 @@ impl<'a, 'tcx> ProofTreeVisitor<'tcx> for AmbiguityCausesVisitor<'a> { } } }) + } else { + match cand.result() { + // We only add an ambiguity cause if the goal would otherwise + // result in an error. + // + // FIXME: While this matches the behavior of the + // old solver, it is not the only way in which the unknowable + // candidates *weaken* coherence, they can also force otherwise + // sucessful normalization to be ambiguous. + Ok(Certainty::Maybe(_) | Certainty::Yes) => { + ambiguity_cause = None; + break; + } + Err(NoSolution) => continue, + } } } From bc7bb3c8e7f62a490055cfd2aa43b6dc77c0f3f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mi=C4=85sko?= Date: Sat, 9 Sep 2023 00:00:00 +0000 Subject: [PATCH 104/111] compiletest: use builder pattern to construct Config in tests --- src/tools/compiletest/src/header/tests.rs | 234 +++++++++++++--------- 1 file changed, 141 insertions(+), 93 deletions(-) diff --git a/src/tools/compiletest/src/header/tests.rs b/src/tools/compiletest/src/header/tests.rs index 362fba11697b..2fd80b52ceee 100644 --- a/src/tools/compiletest/src/header/tests.rs +++ b/src/tools/compiletest/src/header/tests.rs @@ -53,47 +53,117 @@ fn test_parse_normalization_string() { assert_eq!(s, r#"normalize-stderr-16bit: something (16 bits) -> something ($WORD bits)."#); } -fn config() -> Config { - let args = &[ - "compiletest", - "--mode=ui", - "--suite=ui", - "--compile-lib-path=", - "--run-lib-path=", - "--python=", - "--jsondocck-path=", - "--src-base=", - "--build-base=", - "--sysroot-base=", - "--stage-id=stage2-x86_64-unknown-linux-gnu", - "--cc=c", - "--cxx=c++", - "--cflags=", - "--cxxflags=", - "--llvm-components=", - "--android-cross-path=", - "--target=x86_64-unknown-linux-gnu", - "--channel=nightly", - ]; - let mut args: Vec = args.iter().map(ToString::to_string).collect(); - args.push("--rustc-path".to_string()); - // This is a subtle/fragile thing. On rust-lang CI, there is no global - // `rustc`, and Cargo doesn't offer a convenient way to get the path to - // `rustc`. Fortunately bootstrap sets `RUSTC` for us, which is pointing - // to the stage0 compiler. - // - // Otherwise, if you are running compiletests's tests manually, you - // probably don't have `RUSTC` set, in which case this falls back to the - // global rustc. If your global rustc is too far out of sync with stage0, - // then this may cause confusing errors. Or if for some reason you don't - // have rustc in PATH, that would also fail. - args.push(std::env::var("RUSTC").unwrap_or_else(|_| { - eprintln!( - "warning: RUSTC not set, using global rustc (are you not running via bootstrap?)" - ); - "rustc".to_string() - })); - crate::parse_config(args) +#[derive(Default)] +struct ConfigBuilder { + channel: Option, + host: Option, + target: Option, + stage_id: Option, + llvm_version: Option, + git_hash: bool, + system_llvm: bool, +} + +impl ConfigBuilder { + fn channel(&mut self, s: &str) -> &mut Self { + self.channel = Some(s.to_owned()); + self + } + + fn host(&mut self, s: &str) -> &mut Self { + self.host = Some(s.to_owned()); + self + } + + fn target(&mut self, s: &str) -> &mut Self { + self.target = Some(s.to_owned()); + self + } + + fn stage_id(&mut self, s: &str) -> &mut Self { + self.stage_id = Some(s.to_owned()); + self + } + + fn llvm_version(&mut self, s: &str) -> &mut Self { + self.llvm_version = Some(s.to_owned()); + self + } + + fn git_hash(&mut self, b: bool) -> &mut Self { + self.git_hash = b; + self + } + + fn system_llvm(&mut self, s: bool) -> &mut Self { + self.system_llvm = s; + self + } + + fn build(&mut self) -> Config { + let args = &[ + "compiletest", + "--mode=ui", + "--suite=ui", + "--compile-lib-path=", + "--run-lib-path=", + "--python=", + "--jsondocck-path=", + "--src-base=", + "--build-base=", + "--sysroot-base=", + "--cc=c", + "--cxx=c++", + "--cflags=", + "--cxxflags=", + "--llvm-components=", + "--android-cross-path=", + "--stage-id", + self.stage_id.as_deref().unwrap_or("stage2-x86_64-unknown-linux-gnu"), + "--channel", + self.channel.as_deref().unwrap_or("nightly"), + "--host", + self.host.as_deref().unwrap_or("x86_64-unknown-linux-gnu"), + "--target", + self.target.as_deref().unwrap_or("x86_64-unknown-linux-gnu"), + ]; + let mut args: Vec = args.iter().map(ToString::to_string).collect(); + + if let Some(ref llvm_version) = self.llvm_version { + args.push("--llvm-version".to_owned()); + args.push(llvm_version.clone()); + } + + if self.git_hash { + args.push("--git-hash".to_owned()); + } + if self.system_llvm { + args.push("--system-llvm".to_owned()); + } + + args.push("--rustc-path".to_string()); + // This is a subtle/fragile thing. On rust-lang CI, there is no global + // `rustc`, and Cargo doesn't offer a convenient way to get the path to + // `rustc`. Fortunately bootstrap sets `RUSTC` for us, which is pointing + // to the stage0 compiler. + // + // Otherwise, if you are running compiletests's tests manually, you + // probably don't have `RUSTC` set, in which case this falls back to the + // global rustc. If your global rustc is too far out of sync with stage0, + // then this may cause confusing errors. Or if for some reason you don't + // have rustc in PATH, that would also fail. + args.push(std::env::var("RUSTC").unwrap_or_else(|_| { + eprintln!( + "warning: RUSTC not set, using global rustc (are you not running via bootstrap?)" + ); + "rustc".to_string() + })); + crate::parse_config(args) + } +} + +fn cfg() -> ConfigBuilder { + ConfigBuilder::default() } fn parse_rs(config: &Config, contents: &str) -> EarlyProps { @@ -115,7 +185,7 @@ fn parse_makefile(config: &Config, contents: &str) -> EarlyProps { #[test] fn should_fail() { - let config = config(); + let config: Config = cfg().build(); let tn = test::DynTestName(String::new()); let p = Path::new("a.rs"); @@ -127,7 +197,7 @@ fn should_fail() { #[test] fn revisions() { - let config = config(); + let config: Config = cfg().build(); assert_eq!(parse_rs(&config, "// revisions: a b c").revisions, vec!["a", "b", "c"],); assert_eq!( @@ -138,7 +208,7 @@ fn revisions() { #[test] fn aux_build() { - let config = config(); + let config: Config = cfg().build(); assert_eq!( parse_rs( @@ -155,36 +225,31 @@ fn aux_build() { #[test] fn no_system_llvm() { - let mut config = config(); - - config.system_llvm = false; + let config: Config = cfg().system_llvm(false).build(); assert!(!check_ignore(&config, "// no-system-llvm")); - config.system_llvm = true; + let config: Config = cfg().system_llvm(true).build(); assert!(check_ignore(&config, "// no-system-llvm")); } #[test] fn llvm_version() { - let mut config = config(); - - config.llvm_version = Some(80102); + let config: Config = cfg().llvm_version("8.1.2").build(); assert!(check_ignore(&config, "// min-llvm-version: 9.0")); - config.llvm_version = Some(90001); + let config: Config = cfg().llvm_version("9.0.1").build(); assert!(check_ignore(&config, "// min-llvm-version: 9.2")); - config.llvm_version = Some(90301); + let config: Config = cfg().llvm_version("9.3.1").build(); assert!(!check_ignore(&config, "// min-llvm-version: 9.2")); - config.llvm_version = Some(100000); + let config: Config = cfg().llvm_version("10.0.0").build(); assert!(!check_ignore(&config, "// min-llvm-version: 9.0")); } #[test] fn ignore_target() { - let mut config = config(); - config.target = "x86_64-unknown-linux-gnu".to_owned(); + let config: Config = cfg().target("x86_64-unknown-linux-gnu").build(); assert!(check_ignore(&config, "// ignore-x86_64-unknown-linux-gnu")); assert!(check_ignore(&config, "// ignore-x86_64")); @@ -200,8 +265,7 @@ fn ignore_target() { #[test] fn only_target() { - let mut config = config(); - config.target = "x86_64-pc-windows-gnu".to_owned(); + let config: Config = cfg().target("x86_64-pc-windows-gnu").build(); assert!(check_ignore(&config, "// only-x86")); assert!(check_ignore(&config, "// only-linux")); @@ -217,8 +281,7 @@ fn only_target() { #[test] fn stage() { - let mut config = config(); - config.stage_id = "stage1-x86_64-unknown-linux-gnu".to_owned(); + let config: Config = cfg().stage_id("stage1-x86_64-unknown-linux-gnu").build(); assert!(check_ignore(&config, "// ignore-stage1")); assert!(!check_ignore(&config, "// ignore-stage2")); @@ -226,18 +289,16 @@ fn stage() { #[test] fn cross_compile() { - let mut config = config(); - config.host = "x86_64-apple-darwin".to_owned(); - config.target = "wasm32-unknown-unknown".to_owned(); + let config: Config = cfg().host("x86_64-apple-darwin").target("wasm32-unknown-unknown").build(); assert!(check_ignore(&config, "// ignore-cross-compile")); - config.target = config.host.clone(); + let config: Config = cfg().host("x86_64-apple-darwin").target("x86_64-apple-darwin").build(); assert!(!check_ignore(&config, "// ignore-cross-compile")); } #[test] fn debugger() { - let mut config = config(); + let mut config = cfg().build(); config.debugger = None; assert!(!check_ignore(&config, "// ignore-cdb")); @@ -253,27 +314,24 @@ fn debugger() { #[test] fn git_hash() { - let mut config = config(); - config.git_hash = false; + let config: Config = cfg().git_hash(false).build(); assert!(check_ignore(&config, "// needs-git-hash")); - config.git_hash = true; + let config: Config = cfg().git_hash(true).build(); assert!(!check_ignore(&config, "// needs-git-hash")); } #[test] fn sanitizers() { - let mut config = config(); - // Target that supports all sanitizers: - config.target = "x86_64-unknown-linux-gnu".to_owned(); + let config: Config = cfg().target("x86_64-unknown-linux-gnu").build(); assert!(!check_ignore(&config, "// needs-sanitizer-address")); assert!(!check_ignore(&config, "// needs-sanitizer-leak")); assert!(!check_ignore(&config, "// needs-sanitizer-memory")); assert!(!check_ignore(&config, "// needs-sanitizer-thread")); // Target that doesn't support sanitizers: - config.target = "wasm32-unknown-emscripten".to_owned(); + let config: Config = cfg().target("wasm32-unknown-emscripten").build(); assert!(check_ignore(&config, "// needs-sanitizer-address")); assert!(check_ignore(&config, "// needs-sanitizer-leak")); assert!(check_ignore(&config, "// needs-sanitizer-memory")); @@ -291,8 +349,7 @@ fn asm_support() { ("i686-unknown-netbsd", true), ]; for (target, has_asm) in asms { - let mut config = config(); - config.target = target.to_string(); + let config = cfg().target(target).build(); assert_eq!(config.has_asm_support(), has_asm); assert_eq!(check_ignore(&config, "// needs-asm-support"), !has_asm) } @@ -300,8 +357,7 @@ fn asm_support() { #[test] fn channel() { - let mut config = config(); - config.channel = "beta".into(); + let config: Config = cfg().channel("beta").build(); assert!(check_ignore(&config, "// ignore-beta")); assert!(check_ignore(&config, "// only-nightly")); @@ -330,7 +386,7 @@ fn test_extract_version_range() { #[test] #[should_panic(expected = "Duplicate revision: `rpass1` in line ` rpass1 rpass1`")] fn test_duplicate_revisions() { - let config = config(); + let config: Config = cfg().build(); parse_rs(&config, "// revisions: rpass1 rpass1"); } @@ -345,8 +401,7 @@ fn ignore_arch() { ("thumbv7m-none-eabi", "thumb"), ]; for (target, arch) in archs { - let mut config = config(); - config.target = target.to_string(); + let config: Config = cfg().target(target).build(); assert!(config.matches_arch(arch), "{target} {arch}"); assert!(check_ignore(&config, &format!("// ignore-{arch}"))); } @@ -361,8 +416,7 @@ fn matches_os() { ("x86_64-unknown-none", "none"), ]; for (target, os) in oss { - let mut config = config(); - config.target = target.to_string(); + let config = cfg().target(target).build(); assert!(config.matches_os(os), "{target} {os}"); assert!(check_ignore(&config, &format!("// ignore-{os}"))); } @@ -376,8 +430,7 @@ fn matches_env() { ("arm-unknown-linux-musleabi", "musl"), ]; for (target, env) in envs { - let mut config = config(); - config.target = target.to_string(); + let config: Config = cfg().target(target).build(); assert!(config.matches_env(env), "{target} {env}"); assert!(check_ignore(&config, &format!("// ignore-{env}"))); } @@ -391,8 +444,7 @@ fn matches_abi() { ("arm-unknown-linux-gnueabi", "eabi"), ]; for (target, abi) in abis { - let mut config = config(); - config.target = target.to_string(); + let config: Config = cfg().target(target).build(); assert!(config.matches_abi(abi), "{target} {abi}"); assert!(check_ignore(&config, &format!("// ignore-{abi}"))); } @@ -408,8 +460,7 @@ fn is_big_endian() { ("powerpc64-unknown-linux-gnu", true), ]; for (target, is_big) in endians { - let mut config = config(); - config.target = target.to_string(); + let config = cfg().target(target).build(); assert_eq!(config.is_big_endian(), is_big, "{target} {is_big}"); assert_eq!(check_ignore(&config, "// ignore-endian-big"), is_big); } @@ -424,8 +475,7 @@ fn pointer_width() { ("msp430-none-elf", 16), ]; for (target, width) in widths { - let mut config = config(); - config.target = target.to_string(); + let config: Config = cfg().target(target).build(); assert_eq!(config.get_pointer_width(), width, "{target} {width}"); assert_eq!(check_ignore(&config, "// ignore-16bit"), width == 16); assert_eq!(check_ignore(&config, "// ignore-32bit"), width == 32); @@ -456,8 +506,7 @@ fn wasm_special() { ("wasm64-unknown-unknown", "wasm64", true), ]; for (target, pattern, ignore) in ignores { - let mut config = config(); - config.target = target.to_string(); + let config: Config = cfg().target(target).build(); assert_eq!( check_ignore(&config, &format!("// ignore-{pattern}")), ignore, @@ -476,8 +525,7 @@ fn families() { ("wasm32-unknown-emscripten", "unix"), ]; for (target, family) in families { - let mut config = config(); - config.target = target.to_string(); + let config: Config = cfg().target(target).build(); assert!(config.matches_family(family)); let other = if family == "windows" { "unix" } else { "windows" }; assert!(!config.matches_family(other)); From 64e27cb4d9eb41928ca959a616e06d2e9c4c3fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mi=C4=85sko?= Date: Sat, 9 Sep 2023 00:00:00 +0000 Subject: [PATCH 105/111] compiletest: load supported sanitizers from target spec --- src/tools/compiletest/src/common.rs | 18 +++++ src/tools/compiletest/src/header/needs.rs | 25 ++++--- src/tools/compiletest/src/util.rs | 89 ----------------------- 3 files changed, 31 insertions(+), 101 deletions(-) diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index b91d5a958bb6..ee17953f96f4 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -141,6 +141,22 @@ impl PanicStrategy { } } +#[derive(Clone, Debug, PartialEq, serde::Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum Sanitizer { + Address, + Cfi, + Kcfi, + KernelAddress, + Leak, + Memory, + Memtag, + Safestack, + ShadowCallStack, + Thread, + Hwaddress, +} + /// Configuration for compiletest #[derive(Debug, Default, Clone)] pub struct Config { @@ -560,6 +576,8 @@ pub struct TargetCfg { pub(crate) panic: PanicStrategy, #[serde(default)] pub(crate) dynamic_linking: bool, + #[serde(rename = "supported-sanitizers", default)] + pub(crate) sanitizers: Vec, } impl TargetCfg { diff --git a/src/tools/compiletest/src/header/needs.rs b/src/tools/compiletest/src/header/needs.rs index 62364ede47b3..cb04b6f756f5 100644 --- a/src/tools/compiletest/src/header/needs.rs +++ b/src/tools/compiletest/src/header/needs.rs @@ -1,4 +1,4 @@ -use crate::common::{Config, Debugger}; +use crate::common::{Config, Debugger, Sanitizer}; use crate::header::IgnoreDecision; use crate::util; @@ -220,19 +220,20 @@ impl CachedNeedsConditions { path.iter().any(|dir| dir.join("x86_64-w64-mingw32-dlltool").is_file()); let target = &&*config.target; + let sanitizers = &config.target_cfg().sanitizers; Self { sanitizer_support: std::env::var_os("RUSTC_SANITIZER_SUPPORT").is_some(), - sanitizer_address: util::ASAN_SUPPORTED_TARGETS.contains(target), - sanitizer_cfi: util::CFI_SUPPORTED_TARGETS.contains(target), - sanitizer_kcfi: util::KCFI_SUPPORTED_TARGETS.contains(target), - sanitizer_kasan: util::KASAN_SUPPORTED_TARGETS.contains(target), - sanitizer_leak: util::LSAN_SUPPORTED_TARGETS.contains(target), - sanitizer_memory: util::MSAN_SUPPORTED_TARGETS.contains(target), - sanitizer_thread: util::TSAN_SUPPORTED_TARGETS.contains(target), - sanitizer_hwaddress: util::HWASAN_SUPPORTED_TARGETS.contains(target), - sanitizer_memtag: util::MEMTAG_SUPPORTED_TARGETS.contains(target), - sanitizer_shadow_call_stack: util::SHADOWCALLSTACK_SUPPORTED_TARGETS.contains(target), - sanitizer_safestack: util::SAFESTACK_SUPPORTED_TARGETS.contains(target), + sanitizer_address: sanitizers.contains(&Sanitizer::Address), + sanitizer_cfi: sanitizers.contains(&Sanitizer::Cfi), + sanitizer_kcfi: sanitizers.contains(&Sanitizer::Kcfi), + sanitizer_kasan: sanitizers.contains(&Sanitizer::KernelAddress), + sanitizer_leak: sanitizers.contains(&Sanitizer::Leak), + sanitizer_memory: sanitizers.contains(&Sanitizer::Memory), + sanitizer_thread: sanitizers.contains(&Sanitizer::Thread), + sanitizer_hwaddress: sanitizers.contains(&Sanitizer::Hwaddress), + sanitizer_memtag: sanitizers.contains(&Sanitizer::Memtag), + sanitizer_shadow_call_stack: sanitizers.contains(&Sanitizer::ShadowCallStack), + sanitizer_safestack: sanitizers.contains(&Sanitizer::Safestack), profiler_support: std::env::var_os("RUSTC_PROFILER_SUPPORT").is_some(), xray: util::XRAY_SUPPORTED_TARGETS.contains(target), diff --git a/src/tools/compiletest/src/util.rs b/src/tools/compiletest/src/util.rs index 02648fe5c294..7a5cab4f68bb 100644 --- a/src/tools/compiletest/src/util.rs +++ b/src/tools/compiletest/src/util.rs @@ -9,93 +9,6 @@ use tracing::*; #[cfg(test)] mod tests; -pub const ASAN_SUPPORTED_TARGETS: &[&str] = &[ - "aarch64-apple-darwin", - "aarch64-apple-ios", - "aarch64-apple-ios-sim", - "aarch64-apple-ios-macabi", - "aarch64-unknown-fuchsia", - "aarch64-linux-android", - "aarch64-unknown-linux-gnu", - "arm-linux-androideabi", - "armv7-linux-androideabi", - "i686-linux-android", - "i686-unknown-linux-gnu", - "x86_64-apple-darwin", - "x86_64-apple-ios", - "x86_64-apple-ios-macabi", - "x86_64-unknown-fuchsia", - "x86_64-linux-android", - "x86_64-unknown-freebsd", - "x86_64-unknown-linux-gnu", - "s390x-unknown-linux-gnu", -]; - -// FIXME(rcvalle): More targets are likely supported. -pub const CFI_SUPPORTED_TARGETS: &[&str] = &[ - "aarch64-apple-darwin", - "aarch64-unknown-fuchsia", - "aarch64-linux-android", - "aarch64-unknown-freebsd", - "aarch64-unknown-linux-gnu", - "x86_64-apple-darwin", - "x86_64-unknown-fuchsia", - "x86_64-pc-solaris", - "x86_64-unknown-freebsd", - "x86_64-unknown-illumos", - "x86_64-unknown-linux-gnu", - "x86_64-unknown-linux-musl", - "x86_64-unknown-netbsd", -]; - -pub const KCFI_SUPPORTED_TARGETS: &[&str] = &["aarch64-linux-none", "x86_64-linux-none"]; - -pub const KASAN_SUPPORTED_TARGETS: &[&str] = &[ - "aarch64-unknown-none", - "riscv64gc-unknown-none-elf", - "riscv64imac-unknown-none-elf", - "x86_64-unknown-none", -]; - -pub const LSAN_SUPPORTED_TARGETS: &[&str] = &[ - // FIXME: currently broken, see #88132 - // "aarch64-apple-darwin", - "aarch64-unknown-linux-gnu", - "x86_64-apple-darwin", - "x86_64-apple-ios-macabi", - "x86_64-unknown-linux-gnu", - "s390x-unknown-linux-gnu", -]; - -pub const MSAN_SUPPORTED_TARGETS: &[&str] = &[ - "aarch64-unknown-linux-gnu", - "x86_64-unknown-freebsd", - "x86_64-unknown-linux-gnu", - "s390x-unknown-linux-gnu", -]; - -pub const TSAN_SUPPORTED_TARGETS: &[&str] = &[ - "aarch64-apple-darwin", - "aarch64-apple-ios", - "aarch64-apple-ios-sim", - "aarch64-apple-ios-macabi", - "aarch64-unknown-linux-gnu", - "x86_64-apple-darwin", - "x86_64-apple-ios", - "x86_64-apple-ios-macabi", - "x86_64-unknown-freebsd", - "x86_64-unknown-linux-gnu", - "s390x-unknown-linux-gnu", -]; - -pub const HWASAN_SUPPORTED_TARGETS: &[&str] = - &["aarch64-linux-android", "aarch64-unknown-linux-gnu"]; - -pub const MEMTAG_SUPPORTED_TARGETS: &[&str] = - &["aarch64-linux-android", "aarch64-unknown-linux-gnu"]; - -pub const SHADOWCALLSTACK_SUPPORTED_TARGETS: &[&str] = &["aarch64-linux-android"]; - pub const XRAY_SUPPORTED_TARGETS: &[&str] = &[ "aarch64-linux-android", "aarch64-unknown-linux-gnu", @@ -109,8 +22,6 @@ pub const XRAY_SUPPORTED_TARGETS: &[&str] = &[ "x86_64-unknown-openbsd", ]; -pub const SAFESTACK_SUPPORTED_TARGETS: &[&str] = &["x86_64-unknown-linux-gnu"]; - pub fn make_new_path(path: &str) -> String { assert!(cfg!(windows)); // Windows just uses PATH as the library search path, so we have to From 34c248d31f8351e33e597abcea2a3bffff653a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mi=C4=85sko?= Date: Sun, 10 Sep 2023 00:00:00 +0000 Subject: [PATCH 106/111] compiletest: load supports-xray from target spec --- src/tools/compiletest/src/common.rs | 2 ++ src/tools/compiletest/src/header/needs.rs | 3 +-- src/tools/compiletest/src/util.rs | 13 ------------- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index ee17953f96f4..ba273489eb8a 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -578,6 +578,8 @@ pub struct TargetCfg { pub(crate) dynamic_linking: bool, #[serde(rename = "supported-sanitizers", default)] pub(crate) sanitizers: Vec, + #[serde(rename = "supports-xray", default)] + pub(crate) xray: bool, } impl TargetCfg { diff --git a/src/tools/compiletest/src/header/needs.rs b/src/tools/compiletest/src/header/needs.rs index cb04b6f756f5..1113721fff61 100644 --- a/src/tools/compiletest/src/header/needs.rs +++ b/src/tools/compiletest/src/header/needs.rs @@ -1,6 +1,5 @@ use crate::common::{Config, Debugger, Sanitizer}; use crate::header::IgnoreDecision; -use crate::util; pub(super) fn handle_needs( cache: &CachedNeedsConditions, @@ -235,7 +234,7 @@ impl CachedNeedsConditions { sanitizer_shadow_call_stack: sanitizers.contains(&Sanitizer::ShadowCallStack), sanitizer_safestack: sanitizers.contains(&Sanitizer::Safestack), profiler_support: std::env::var_os("RUSTC_PROFILER_SUPPORT").is_some(), - xray: util::XRAY_SUPPORTED_TARGETS.contains(target), + xray: config.target_cfg().xray, // For tests using the `needs-rust-lld` directive (e.g. for `-Zgcc-ld=lld`), we need to find // whether `rust-lld` is present in the compiler under test. diff --git a/src/tools/compiletest/src/util.rs b/src/tools/compiletest/src/util.rs index 7a5cab4f68bb..8f9425eb0716 100644 --- a/src/tools/compiletest/src/util.rs +++ b/src/tools/compiletest/src/util.rs @@ -9,19 +9,6 @@ use tracing::*; #[cfg(test)] mod tests; -pub const XRAY_SUPPORTED_TARGETS: &[&str] = &[ - "aarch64-linux-android", - "aarch64-unknown-linux-gnu", - "aarch64-unknown-linux-musl", - "x86_64-linux-android", - "x86_64-unknown-freebsd", - "x86_64-unknown-linux-gnu", - "x86_64-unknown-linux-musl", - "x86_64-unknown-netbsd", - "x86_64-unknown-none-linuxkernel", - "x86_64-unknown-openbsd", -]; - pub fn make_new_path(path: &str) -> String { assert!(cfg!(windows)); // Windows just uses PATH as the library search path, so we have to From 9090ed8119871980a0fc9b9ea292f9eadf7a3c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mi=C4=85sko?= Date: Thu, 21 Sep 2023 00:00:00 +0000 Subject: [PATCH 107/111] Fix test on targets with crt-static default --- .../sanitizer/address-sanitizer-globals-tracking.rs | 5 +++-- tests/codegen/sanitizer/memory-track-origins.rs | 11 ++++++----- tests/codegen/sanitizer/no-sanitize-inlining.rs | 5 +++-- tests/codegen/sanitizer/no-sanitize.rs | 2 +- tests/codegen/sanitizer/sanitizer-recover.rs | 1 + tests/ui/sanitize/cfg.rs | 3 ++- 6 files changed, 16 insertions(+), 11 deletions(-) diff --git a/tests/codegen/sanitizer/address-sanitizer-globals-tracking.rs b/tests/codegen/sanitizer/address-sanitizer-globals-tracking.rs index a70ef7751b66..e9dd04e1927b 100644 --- a/tests/codegen/sanitizer/address-sanitizer-globals-tracking.rs +++ b/tests/codegen/sanitizer/address-sanitizer-globals-tracking.rs @@ -19,8 +19,9 @@ // only-linux // // revisions:ASAN ASAN-FAT-LTO -//[ASAN] compile-flags: -Zsanitizer=address -//[ASAN-FAT-LTO] compile-flags: -Zsanitizer=address -Cprefer-dynamic=false -Clto=fat +// compile-flags: -Zsanitizer=address -Ctarget-feature=-crt-static +//[ASAN] compile-flags: +//[ASAN-FAT-LTO] compile-flags: -Cprefer-dynamic=false -Clto=fat #![crate_type="staticlib"] diff --git a/tests/codegen/sanitizer/memory-track-origins.rs b/tests/codegen/sanitizer/memory-track-origins.rs index 4bd50508d152..e15a3b2274ee 100644 --- a/tests/codegen/sanitizer/memory-track-origins.rs +++ b/tests/codegen/sanitizer/memory-track-origins.rs @@ -4,11 +4,12 @@ // needs-sanitizer-memory // revisions:MSAN-0 MSAN-1 MSAN-2 MSAN-1-LTO MSAN-2-LTO // -//[MSAN-0] compile-flags: -Zsanitizer=memory -//[MSAN-1] compile-flags: -Zsanitizer=memory -Zsanitizer-memory-track-origins=1 -//[MSAN-2] compile-flags: -Zsanitizer=memory -Zsanitizer-memory-track-origins -//[MSAN-1-LTO] compile-flags: -Zsanitizer=memory -Zsanitizer-memory-track-origins=1 -C lto=fat -//[MSAN-2-LTO] compile-flags: -Zsanitizer=memory -Zsanitizer-memory-track-origins -C lto=fat +// compile-flags: -Zsanitizer=memory -Ctarget-feature=-crt-static +//[MSAN-0] compile-flags: +//[MSAN-1] compile-flags: -Zsanitizer-memory-track-origins=1 +//[MSAN-2] compile-flags: -Zsanitizer-memory-track-origins +//[MSAN-1-LTO] compile-flags: -Zsanitizer-memory-track-origins=1 -C lto=fat +//[MSAN-2-LTO] compile-flags: -Zsanitizer-memory-track-origins -C lto=fat #![crate_type="lib"] diff --git a/tests/codegen/sanitizer/no-sanitize-inlining.rs b/tests/codegen/sanitizer/no-sanitize-inlining.rs index f4af60baefe9..e371b19eb366 100644 --- a/tests/codegen/sanitizer/no-sanitize-inlining.rs +++ b/tests/codegen/sanitizer/no-sanitize-inlining.rs @@ -4,8 +4,9 @@ // needs-sanitizer-address // needs-sanitizer-leak // revisions: ASAN LSAN -//[ASAN] compile-flags: -Zsanitizer=address -C opt-level=3 -Z mir-opt-level=4 -//[LSAN] compile-flags: -Zsanitizer=leak -C opt-level=3 -Z mir-opt-level=4 +// compile-flags: -Copt-level=3 -Zmir-opt-level=4 -Ctarget-feature=-crt-static +//[ASAN] compile-flags: -Zsanitizer=address +//[LSAN] compile-flags: -Zsanitizer=leak #![crate_type="lib"] #![feature(no_sanitize)] diff --git a/tests/codegen/sanitizer/no-sanitize.rs b/tests/codegen/sanitizer/no-sanitize.rs index 783b568e2792..d0b692434537 100644 --- a/tests/codegen/sanitizer/no-sanitize.rs +++ b/tests/codegen/sanitizer/no-sanitize.rs @@ -2,7 +2,7 @@ // selectively disable sanitizer instrumentation. // // needs-sanitizer-address -// compile-flags: -Zsanitizer=address +// compile-flags: -Zsanitizer=address -Ctarget-feature=-crt-static #![crate_type="lib"] #![feature(no_sanitize)] diff --git a/tests/codegen/sanitizer/sanitizer-recover.rs b/tests/codegen/sanitizer/sanitizer-recover.rs index 7b00fcf8e1bd..59b1fdd6494a 100644 --- a/tests/codegen/sanitizer/sanitizer-recover.rs +++ b/tests/codegen/sanitizer/sanitizer-recover.rs @@ -6,6 +6,7 @@ // revisions:ASAN ASAN-RECOVER MSAN MSAN-RECOVER MSAN-RECOVER-LTO // no-prefer-dynamic // +// compile-flags: -Ctarget-feature=-crt-static //[ASAN] compile-flags: -Zsanitizer=address -Copt-level=0 //[ASAN-RECOVER] compile-flags: -Zsanitizer=address -Zsanitizer-recover=address -Copt-level=0 //[MSAN] compile-flags: -Zsanitizer=memory diff --git a/tests/ui/sanitize/cfg.rs b/tests/ui/sanitize/cfg.rs index f8ccf3b042db..761c646ec884 100644 --- a/tests/ui/sanitize/cfg.rs +++ b/tests/ui/sanitize/cfg.rs @@ -3,11 +3,12 @@ // check-pass // revisions: address cfi kcfi leak memory thread +//compile-flags: -Ctarget-feature=-crt-static //[address]needs-sanitizer-address //[address]compile-flags: -Zsanitizer=address --cfg address //[cfi]needs-sanitizer-cfi //[cfi]compile-flags: -Zsanitizer=cfi --cfg cfi -//[cfi]compile-flags: -Clto -Ccodegen-units=1 -Ctarget-feature=-crt-static +//[cfi]compile-flags: -Clto -Ccodegen-units=1 //[kcfi]needs-llvm-components: x86 //[kcfi]compile-flags: -Zsanitizer=kcfi --cfg kcfi --target x86_64-unknown-none //[leak]needs-sanitizer-leak From e6cd29d06e48186562eefe6007854ca9776e6e8f Mon Sep 17 00:00:00 2001 From: The Miri Conjob Bot Date: Thu, 21 Sep 2023 05:40:21 +0000 Subject: [PATCH 108/111] fmt --- src/tools/miri/src/shims/backtrace.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/tools/miri/src/shims/backtrace.rs b/src/tools/miri/src/shims/backtrace.rs index 836cab3ac9da..ee2edd462d19 100644 --- a/src/tools/miri/src/shims/backtrace.rs +++ b/src/tools/miri/src/shims/backtrace.rs @@ -88,10 +88,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_pointer(ptr, &place)?; } - this.write_immediate( - Immediate::new_slice(alloc.ptr(), len, this), - dest, - )?; + this.write_immediate(Immediate::new_slice(alloc.ptr(), len, this), dest)?; } // storage for pointers is allocated by the caller 1 => { From 96a85d67fd0e986ba8f9ee2207f3c916f46b4bb1 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 21 Sep 2023 07:46:41 +0200 Subject: [PATCH 109/111] update lockfile --- src/tools/miri/Cargo.lock | 58 +++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock index ca5b6d225154..8cd2df8b5c58 100644 --- a/src/tools/miri/Cargo.lock +++ b/src/tools/miri/Cargo.lock @@ -70,6 +70,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + [[package]] name = "bstr" version = "1.4.0" @@ -201,12 +207,12 @@ checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "env_logger" -version = "0.9.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" dependencies = [ - "atty", "humantime", + "is-terminal", "log", "regex", "termcolor", @@ -316,6 +322,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi 0.3.1", + "rustix 0.38.14", + "windows-sys 0.48.0", +] + [[package]] name = "itoa" version = "1.0.6" @@ -330,9 +347,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.142" +version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] name = "libffi" @@ -369,6 +386,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" +[[package]] +name = "linux-raw-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" + [[package]] name = "lock_api" version = "0.4.9" @@ -454,7 +477,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", "static_assertions", @@ -581,7 +604,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -590,7 +613,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -655,11 +678,24 @@ version = "0.37.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", - "linux-raw-sys", + "linux-raw-sys 0.3.7", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys 0.4.7", "windows-sys 0.48.0", ] @@ -756,7 +792,7 @@ dependencies = [ "cfg-if", "fastrand", "redox_syscall 0.3.5", - "rustix", + "rustix 0.37.19", "windows-sys 0.45.0", ] From e018f49d4597cc5575703e3ff1d53a8cba11757a Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 21 Sep 2023 08:02:58 +0200 Subject: [PATCH 110/111] remove atty dependency by updating colored --- src/tools/miri/Cargo.lock | 32 ++++++-------------------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock index 8cd2df8b5c58..f253d71e50d3 100644 --- a/src/tools/miri/Cargo.lock +++ b/src/tools/miri/Cargo.lock @@ -32,17 +32,6 @@ version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -161,13 +150,13 @@ dependencies = [ [[package]] name = "colored" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" dependencies = [ - "atty", + "is-terminal", "lazy_static", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -275,15 +264,6 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.3.1" @@ -317,7 +297,7 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" dependencies = [ - "hermit-abi 0.3.1", + "hermit-abi", "libc", "windows-sys 0.48.0", ] @@ -328,7 +308,7 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.1", + "hermit-abi", "rustix 0.38.14", "windows-sys 0.48.0", ] From ed8fbcb05910f4439ec572bd163cc99a3603e378 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 21 Sep 2023 08:03:45 +0200 Subject: [PATCH 111/111] fix clippy lints --- src/tools/miri/src/shims/x86/sse2.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/tools/miri/src/shims/x86/sse2.rs b/src/tools/miri/src/shims/x86/sse2.rs index b68690a835c2..d3bfb53afd26 100644 --- a/src/tools/miri/src/shims/x86/sse2.rs +++ b/src/tools/miri/src/shims/x86/sse2.rs @@ -426,8 +426,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let right_res = i8::try_from(right).unwrap_or(if right < 0 { i8::MIN } else { i8::MAX }); - this.write_scalar(Scalar::from_i8(left_res.try_into().unwrap()), &left_dest)?; - this.write_scalar(Scalar::from_i8(right_res.try_into().unwrap()), &right_dest)?; + this.write_scalar(Scalar::from_i8(left_res), &left_dest)?; + this.write_scalar(Scalar::from_i8(right_res), &right_dest)?; } } // Used to implement the _mm_packus_epi16 function. @@ -487,11 +487,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let right_res = i16::try_from(right).unwrap_or(if right < 0 { i16::MIN } else { i16::MAX }); - this.write_scalar(Scalar::from_i16(left_res.try_into().unwrap()), &left_dest)?; - this.write_scalar( - Scalar::from_i16(right_res.try_into().unwrap()), - &right_dest, - )?; + this.write_scalar(Scalar::from_i16(left_res), &left_dest)?; + this.write_scalar(Scalar::from_i16(right_res), &right_dest)?; } } // Used to implement _mm_min_sd and _mm_max_sd functions.