From 2a870404fd3e2292955478875336217a27939202 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Thu, 12 Mar 2026 12:50:16 +1100 Subject: [PATCH 1/6] Add a suite of ChunkedBitSet union/subtract/intersect test scenarios --- compiler/rustc_index/src/bit_set/tests.rs | 110 ++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/compiler/rustc_index/src/bit_set/tests.rs b/compiler/rustc_index/src/bit_set/tests.rs index 341e0622df75e..08704863ec3d2 100644 --- a/compiler/rustc_index/src/bit_set/tests.rs +++ b/compiler/rustc_index/src/bit_set/tests.rs @@ -298,6 +298,116 @@ fn chunked_bitset() { b10000b.assert_valid(); } +/// Additional helper methods for testing. +impl ChunkedBitSet { + /// Creates a new `ChunkedBitSet` containing all `i` for which `fill_fn(i)` is true. + fn fill_with(domain_size: usize, fill_fn: impl Fn(usize) -> bool) -> Self { + let mut this = ChunkedBitSet::new_empty(domain_size); + for i in 0..domain_size { + if fill_fn(i) { + this.insert(i); + } + } + this + } + + /// Asserts that for each `i` in `0..self.domain_size()`, `self.contains(i) == expected_fn(i)`. + #[track_caller] + fn assert_filled_with(&self, expected_fn: impl Fn(usize) -> bool) { + for i in 0..self.domain_size() { + let expected = expected_fn(i); + assert_eq!(self.contains(i), expected, "i = {i}"); + } + } +} + +#[test] +fn chunked_bulk_ops() { + struct ChunkedBulkOp { + name: &'static str, + op_fn: fn(&mut ChunkedBitSet, &ChunkedBitSet) -> bool, + spec_fn: fn(fn(usize) -> bool, fn(usize) -> bool, usize) -> bool, + } + let ops = &[ + ChunkedBulkOp { + name: "union", + op_fn: ChunkedBitSet::union, + spec_fn: |fizz, buzz, i| fizz(i) || buzz(i), + }, + ChunkedBulkOp { + name: "subtract", + op_fn: ChunkedBitSet::subtract, + spec_fn: |fizz, buzz, i| fizz(i) && !buzz(i), + }, + ChunkedBulkOp { + name: "intersect", + op_fn: ChunkedBitSet::intersect, + spec_fn: |fizz, buzz, i| fizz(i) && buzz(i), + }, + ]; + + let domain_sizes = [ + CHUNK_BITS / 7, // Smaller than a full chunk. + CHUNK_BITS, + (CHUNK_BITS + CHUNK_BITS / 7), // Larger than a full chunk. + ]; + + for ChunkedBulkOp { name, op_fn, spec_fn } in ops { + for domain_size in domain_sizes { + // If false, use different values for LHS and RHS, to test "fizz op buzz". + // If true, use identical values, to test "fizz op fizz". + for identical in [false, true] { + // If false, make a clone of LHS before doing the op. + // This covers optimizations that depend on whether chunk words are shared or not. + for unique in [false, true] { + // Print the current test case, so that we can see which one failed. + println!( + "Testing op={name}, domain_size={domain_size}, identical={identical}, unique={unique} ..." + ); + + let fizz_fn = |i| i % 3 == 0; + let buzz_fn = if identical { fizz_fn } else { |i| i % 5 == 0 }; + + // Check that `fizz op buzz` gives the expected results. + chunked_bulk_ops_test_inner( + domain_size, + unique, + fizz_fn, + buzz_fn, + op_fn, + |i| spec_fn(fizz_fn, buzz_fn, i), + ); + } + } + } + } +} + +fn chunked_bulk_ops_test_inner( + domain_size: usize, + unique: bool, + fizz_fn: impl Fn(usize) -> bool + Copy, + buzz_fn: impl Fn(usize) -> bool + Copy, + op_fn: impl Fn(&mut ChunkedBitSet, &ChunkedBitSet) -> bool, + expected_fn: impl Fn(usize) -> bool + Copy, +) { + // Create two bitsets, "fizz" (LHS) and "buzz" (RHS). + let mut fizz = ChunkedBitSet::fill_with(domain_size, fizz_fn); + let buzz = ChunkedBitSet::fill_with(domain_size, buzz_fn); + + // If requested, clone `fizz` so that its word Rcs are not uniquely-owned. + let _cloned = (!unique).then(|| fizz.clone()); + + // Perform the op (e.g. union/subtract/intersect), and verify that the + // mutated LHS contains exactly the expected values. + let changed = op_fn(&mut fizz, &buzz); + fizz.assert_filled_with(expected_fn); + + // Verify that the "changed" return value is correct. + let should_change = (0..domain_size).any(|i| fizz_fn(i) != expected_fn(i)); + assert_eq!(changed, should_change); +} + fn with_elements_chunked(elements: &[usize], domain_size: usize) -> ChunkedBitSet { let mut s = ChunkedBitSet::new_empty(domain_size); for &e in elements { From 8aafed8a1773c5739f6413ce54a243c65c13c723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Tue, 5 May 2026 17:10:34 +0200 Subject: [PATCH 2/6] Add `sync` option to `-Z threads` to force synchronization on one thread --- compiler/rustc_codegen_ssa/src/base.rs | 4 +-- compiler/rustc_interface/src/interface.rs | 6 ++-- compiler/rustc_interface/src/tests.rs | 2 +- compiler/rustc_metadata/src/rmeta/encoder.rs | 2 +- compiler/rustc_middle/src/dep_graph/graph.rs | 2 +- compiler/rustc_query_impl/src/execution.rs | 2 +- compiler/rustc_session/src/config.rs | 6 +--- compiler/rustc_session/src/options.rs | 33 ++++++++++---------- compiler/rustc_session/src/session.rs | 8 +++-- 9 files changed, 33 insertions(+), 32 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 4e2884c8cb63f..ad3e636083e06 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -780,14 +780,14 @@ pub fn codegen_crate( // This likely is a temporary measure. Once we don't have to support the // non-parallel compiler anymore, we can compile CGUs end-to-end in // parallel and get rid of the complicated scheduling logic. - let mut pre_compiled_cgus = if tcx.sess.threads() > 1 { + let mut pre_compiled_cgus = if let Some(threads) = tcx.sess.threads() { tcx.sess.time("compile_first_CGU_batch", || { // Try to find one CGU to compile per thread. let cgus: Vec<_> = cgu_reuse .iter() .enumerate() .filter(|&(_, reuse)| reuse == &CguReuse::No) - .take(tcx.sess.threads()) + .take(threads) .collect(); // Compile the found CGUs in parallel. diff --git a/compiler/rustc_interface/src/interface.rs b/compiler/rustc_interface/src/interface.rs index ab8bc1c7f1b34..875ed4ae5d307 100644 --- a/compiler/rustc_interface/src/interface.rs +++ b/compiler/rustc_interface/src/interface.rs @@ -385,7 +385,9 @@ pub fn run_compiler(config: Config, f: impl FnOnce(&Compiler) -> R + Se trace!("run_compiler"); // Set parallel mode before thread pool creation, which will create `Lock`s. - rustc_data_structures::sync::set_dyn_thread_safe_mode(config.opts.unstable_opts.threads > 1); + rustc_data_structures::sync::set_dyn_thread_safe_mode( + config.opts.unstable_opts.threads.is_some(), + ); // Check jobserver before run_in_thread_pool_with_globals, which call jobserver::acquire_thread let early_dcx = EarlyDiagCtxt::new(config.opts.error_format); @@ -407,7 +409,7 @@ pub fn run_compiler(config: Config, f: impl FnOnce(&Compiler) -> R + Se util::run_in_thread_pool_with_globals( &early_dcx, config.opts.edition, - config.opts.unstable_opts.threads, + config.opts.unstable_opts.threads.unwrap_or(1), &config.extra_symbols, SourceMapInputs { file_loader, path_mapping, hash_kind, checksum_hash_kind }, |current_gcx, jobserver_proxy| { diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index a7e0dd2ac39c0..83930bf1249ac 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -734,7 +734,7 @@ fn test_unstable_options_tracking_hash() { untracked!(span_debug, true); untracked!(span_free_formats, true); untracked!(temps_dir, Some(String::from("abc"))); - untracked!(threads, 99); + untracked!(threads, Some(99)); untracked!(time_llvm_passes, true); untracked!(time_passes, true); untracked!(time_passes_format, TimePassesFormat::Json); diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 729a0dda7cf3b..a4103f1757f6a 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -2465,7 +2465,7 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { return; }; - if tcx.sess.threads() != 1 { + if tcx.sess.threads().is_some() { // Prefetch some queries used by metadata encoding. // This is not necessary for correctness, but is only done for performance reasons. // It can be removed if it turns out to cause trouble or be detrimental to performance. diff --git a/compiler/rustc_middle/src/dep_graph/graph.rs b/compiler/rustc_middle/src/dep_graph/graph.rs index a219809541cc1..cc007d35c2195 100644 --- a/compiler/rustc_middle/src/dep_graph/graph.rs +++ b/compiler/rustc_middle/src/dep_graph/graph.rs @@ -629,7 +629,7 @@ impl DepGraphData { let ok = match color { DepNodeColor::Unknown => true, DepNodeColor::Red => false, - DepNodeColor::Green(..) => sess.threads() > 1, // Other threads may mark this green + DepNodeColor::Green(..) => sess.threads().is_some(), // Other threads may mark this green }; if !ok { panic!("{}", msg()) diff --git a/compiler/rustc_query_impl/src/execution.rs b/compiler/rustc_query_impl/src/execution.rs index ed9ad8c7a0a68..b614bc14b4539 100644 --- a/compiler/rustc_query_impl/src/execution.rs +++ b/compiler/rustc_query_impl/src/execution.rs @@ -294,7 +294,7 @@ fn try_execute_query<'tcx, C: QueryCache, const INCR: bool>( // re-executing the query since `try_start` only checks that the query is not currently // executing, but another thread may have already completed the query and stores it result // in the query cache. - if tcx.sess.threads() > 1 { + if tcx.sess.threads().is_some() { if let Some((value, index)) = query.cache.lookup(&key) { tcx.prof.query_cache_hit(index.into()); return (value, Some(index)); diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index f8da18632a997..40cf5b6e10798 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -2494,11 +2494,7 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M cg.codegen_units, ); - if unstable_opts.threads == 0 { - early_dcx.early_fatal("value for threads must be a positive non-zero integer"); - } - - if unstable_opts.threads == parse::MAX_THREADS_CAP { + if unstable_opts.threads == Some(parse::MAX_THREADS_CAP) { early_dcx.early_warn(format!("number of threads was capped at {}", parse::MAX_THREADS_CAP)); } diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index f7a3387e5238a..c484ce58b0cac 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -759,7 +759,7 @@ mod desc { pub(crate) const parse_number: &str = "a number"; pub(crate) const parse_opt_number: &str = parse_number; pub(crate) const parse_frame_pointer: &str = "one of `true`/`yes`/`on`, `false`/`no`/`off`, or (with -Zunstable-options) `non-leaf` or `always`"; - pub(crate) const parse_threads: &str = parse_number; + pub(crate) const parse_threads: &str = "a number or `sync`"; pub(crate) const parse_time_passes_format: &str = "`text` (default) or `json`"; pub(crate) const parse_passes: &str = "a space-separated list of passes, or `all`"; pub(crate) const parse_panic_strategy: &str = "either `unwind`, `abort`, or `immediate-abort`"; @@ -1067,22 +1067,23 @@ pub mod parse { } } - pub(crate) fn parse_threads(slot: &mut usize, v: Option<&str>) -> bool { - let ret = match v.and_then(|s| s.parse().ok()) { - Some(0) => { - *slot = std::thread::available_parallelism().map_or(1, NonZero::::get); - true - } - Some(i) => { - *slot = i; - true - } - None => false, + pub(crate) fn parse_threads(slot: &mut Option, v: Option<&str>) -> bool { + let Some(s) = v else { return false }; + if s == "sync" { + // Enable synchronization despite only using one thread. + *slot = Some(1); + return true; + } + let n = match s.parse().ok() { + Some(0) => std::thread::available_parallelism().map_or(1, NonZero::::get), + Some(i) => i, + None => return false, }; // We want to cap the number of threads here to avoid large numbers like 999999 and compiler panics. // This solution was suggested here https://github.com/rust-lang/rust/issues/117638#issuecomment-1800925067 - *slot = slot.clone().min(MAX_THREADS_CAP); - ret + let n = n.min(MAX_THREADS_CAP); + *slot = (n > 1).then_some(n); // Enable synchronization if we're using more than one thread. + true } /// Use this for any numeric option that has a static default. @@ -2670,12 +2671,12 @@ written to standard error output)"), #[rustc_lint_opt_deny_field_access("use `Session::lto` instead of this field")] thinlto: Option = (None, parse_opt_bool, [TRACKED], "enable ThinLTO when possible"), - /// We default to 1 here since we want to behave like + /// We default to None here since we want to behave like /// a sequential compiler for now. This'll likely be adjusted /// in the future. Note that -Zthreads=0 is the way to get /// the num_cpus behavior. #[rustc_lint_opt_deny_field_access("use `Session::threads` instead of this field")] - threads: usize = (1, parse_threads, [UNTRACKED], + threads: Option = (None, parse_threads, [UNTRACKED], "use a thread pool with N threads"), time_llvm_passes: bool = (false, parse_bool, [UNTRACKED], "measure time of each LLVM pass (default: no)"), diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 3b2fc53381a93..c71f897fba31a 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -813,10 +813,12 @@ impl Session { .unwrap_or(self.panic_strategy().unwinds() || self.target.default_uwtable) } - /// Returns the number of query threads that should be used for this - /// compilation + /// Returns the number of threads used for the thread pool. + /// + /// `None` means thread pool is not used and synchronization is disabled. + /// `Some(n)` means synchronization is enabled with `n` worker threads. #[inline] - pub fn threads(&self) -> usize { + pub fn threads(&self) -> Option { self.opts.unstable_opts.threads } From e8873630a0f68fe2f46a38db2478b37c8aa22f36 Mon Sep 17 00:00:00 2001 From: joboet Date: Tue, 5 May 2026 18:22:35 +0200 Subject: [PATCH 3/6] core: drop unmapped ZSTs in array `map` --- ...sroot_tests-128bit-atomic-operations.patch | 2 +- library/core/src/array/drain.rs | 69 +++++++++++++------ library/coretests/tests/array.rs | 34 +++++++-- library/coretests/tests/lib.rs | 1 + 4 files changed, 77 insertions(+), 29 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/patches/0027-sysroot_tests-128bit-atomic-operations.patch b/compiler/rustc_codegen_cranelift/patches/0027-sysroot_tests-128bit-atomic-operations.patch index 7ba4475e31454..7194d8144ca69 100644 --- a/compiler/rustc_codegen_cranelift/patches/0027-sysroot_tests-128bit-atomic-operations.patch +++ b/compiler/rustc_codegen_cranelift/patches/0027-sysroot_tests-128bit-atomic-operations.patch @@ -16,6 +16,7 @@ index 1e336bf..35e6f54 100644 +++ b/coretests/tests/lib.rs @@ -2,4 +2,3 @@ // tidy-alphabetical-start + #![cfg_attr(not(panic = "abort"), feature(reentrant_lock))] -#![cfg_attr(target_has_atomic = "128", feature(integer_atomics))] #![feature(array_ptr_get)] #![feature(array_try_from_fn)] @@ -36,4 +37,3 @@ index b735957..ea728b6 100644 #[cfg(target_has_atomic = "ptr")] -- 2.26.2.7.g19db9cfb68 - diff --git a/library/core/src/array/drain.rs b/library/core/src/array/drain.rs index 17792dca583d2..b2ff54bdfa21c 100644 --- a/library/core/src/array/drain.rs +++ b/library/core/src/array/drain.rs @@ -1,8 +1,8 @@ use crate::marker::{Destruct, PhantomData}; -use crate::mem::{ManuallyDrop, SizedTypeProperties, conjure_zst}; -use crate::ptr::{NonNull, drop_in_place, from_raw_parts_mut, null_mut}; +use crate::mem::{ManuallyDrop, SizedTypeProperties, conjure_zst, transmute}; +use crate::ptr::{NonNull, drop_in_place, from_raw_parts_mut, without_provenance_mut}; -impl<'l, 'f, T, U, const N: usize, F: FnMut(T) -> U> Drain<'l, 'f, T, N, F> { +impl<'l, 'f, T, U, F: FnMut(T) -> U> Drain<'l, 'f, T, F> { /// This function returns a function that lets you index the given array in const. /// As implemented it can optimize better than iterators, and can be constified. /// It acts like a sort of guard (owns the array) and iterator combined, which can be implemented @@ -14,9 +14,11 @@ impl<'l, 'f, T, U, const N: usize, F: FnMut(T) -> U> Drain<'l, 'f, T, N, F> { /// This will also not actually store the array. /// /// SAFETY: must only be called `N` times. Thou shalt not drop the array either. - // FIXME(const-hack): this is a hack for `let guard = Guard(array); |i| f(guard[i])`. #[rustc_const_unstable(feature = "array_try_map", issue = "79711")] - pub(super) const unsafe fn new(array: &'l mut ManuallyDrop<[T; N]>, f: &'f mut F) -> Self { + pub(super) const unsafe fn new( + array: &'l mut ManuallyDrop<[T; N]>, + f: &'f mut F, + ) -> Self { // dont drop the array, transfers "ownership" to Self let ptr: NonNull = NonNull::from_mut(array).cast(); // SAFETY: @@ -24,16 +26,17 @@ impl<'l, 'f, T, U, const N: usize, F: FnMut(T) -> U> Drain<'l, 'f, T, N, F> { // at the end of `slice`. `end` will never be dereferenced, only checked // for direct pointer equality with `ptr` to check if the drainer is done. unsafe { - let end = if T::IS_ZST { null_mut() } else { ptr.as_ptr().add(N) }; - Self { ptr, end, f, l: PhantomData } + let end_or_len = + if T::IS_ZST { without_provenance_mut(N) } else { ptr.as_ptr().add(N) }; + Self { ptr, end_or_len, f, l: PhantomData } } } } /// See [`Drain::new`]; this is our fake iterator. #[unstable(feature = "array_try_map", issue = "79711")] -pub(super) struct Drain<'l, 'f, T, const N: usize, F> { - // FIXME(const-hack): This is essentially a slice::IterMut<'static>, replace when possible. +pub(super) struct Drain<'l, 'f, T, F> { + // FIXME(const-hack): This is a slice::IterMut<'l>, replace when possible. /// The pointer to the next element to return, or the past-the-end location /// if the drainer is empty. /// @@ -41,16 +44,16 @@ pub(super) struct Drain<'l, 'f, T, const N: usize, F> { /// As we "own" this array, we dont need to store any lifetime. ptr: NonNull, /// For non-ZSTs, the non-null pointer to the past-the-end element. - /// For ZSTs, this is null. - end: *mut T, + /// For ZSTs, this is the number of unprocessed items. + end_or_len: *mut T, f: &'f mut F, - l: PhantomData<&'l mut [T; N]>, + l: PhantomData<&'l mut [T]>, } #[rustc_const_unstable(feature = "array_try_map", issue = "79711")] #[unstable(feature = "array_try_map", issue = "79711")] -impl const FnOnce<(usize,)> for &mut Drain<'_, '_, T, N, F> +impl const FnOnce<(usize,)> for &mut Drain<'_, '_, T, F> where F: [const] FnMut(T) -> U, { @@ -63,7 +66,7 @@ where } #[rustc_const_unstable(feature = "array_try_map", issue = "79711")] #[unstable(feature = "array_try_map", issue = "79711")] -impl const FnMut<(usize,)> for &mut Drain<'_, '_, T, N, F> +impl const FnMut<(usize,)> for &mut Drain<'_, '_, T, F> where F: [const] FnMut(T) -> U, { @@ -73,6 +76,16 @@ where (_ /* ignore argument */,): (usize,), ) -> Self::Output { if T::IS_ZST { + #[expect(ptr_to_integer_transmute_in_consts)] + // SAFETY: + // This is equivalent to `self.end_or_len.addr`, but that's not + // available in `const`. `self.end_or_len` doesn't have provenance, + // so transmuting is fine. + let len = unsafe { transmute::<*mut T, usize>(self.end_or_len) }; + // SAFETY: + // The caller guarantees that this is never called more than N times + // (see `Drain::new`), hence this cannot underflow. + self.end_or_len = without_provenance_mut(unsafe { len.unchecked_sub(1) }); // its UB to call this more than N times, so returning more ZSTs is valid. // SAFETY: its a ZST? we conjur. (self.f)(unsafe { conjure_zst::() }) @@ -88,20 +101,32 @@ where } #[rustc_const_unstable(feature = "array_try_map", issue = "79711")] #[unstable(feature = "array_try_map", issue = "79711")] -impl const Drop for Drain<'_, '_, T, N, F> { +impl const Drop for Drain<'_, '_, T, F> { fn drop(&mut self) { - if !T::IS_ZST { + let slice = if T::IS_ZST { + from_raw_parts_mut::<[T]>( + self.ptr.as_ptr(), + #[expect(ptr_to_integer_transmute_in_consts)] + // SAFETY: + // This is equivalent to `self.end_or_len.addr`, but that's not + // available in `const`. `self.end_or_len` doesn't have provenance, + // so transmuting is fine. + unsafe { + transmute::<*mut T, usize>(self.end_or_len) + }, + ) + } else { // SAFETY: we cant read more than N elements - let slice = unsafe { + unsafe { from_raw_parts_mut::<[T]>( self.ptr.as_ptr(), // SAFETY: `start <= end` - self.end.offset_from_unsigned(self.ptr.as_ptr()), + self.end_or_len.offset_from_unsigned(self.ptr.as_ptr()), ) - }; + } + }; - // SAFETY: By the type invariant, we're allowed to drop all these. (we own it, after all) - unsafe { drop_in_place(slice) } - } + // SAFETY: By the type invariant, we're allowed to drop all these. (we own it, after all) + unsafe { drop_in_place(slice) } } } diff --git a/library/coretests/tests/array.rs b/library/coretests/tests/array.rs index 43fed944e9280..a3b0e59278f79 100644 --- a/library/coretests/tests/array.rs +++ b/library/coretests/tests/array.rs @@ -1,3 +1,4 @@ +use core::cell::Cell; use core::num::NonZero; use core::sync::atomic::{AtomicUsize, Ordering}; use core::{array, assert_eq}; @@ -168,8 +169,6 @@ fn iterator_debug() { #[test] fn iterator_drops() { - use core::cell::Cell; - // This test makes sure the correct number of elements are dropped. The `R` // type is just a reference to a `Cell` that is incremented when an `R` is // dropped. @@ -337,8 +336,6 @@ fn array_map_drop_safety() { #[test] fn cell_allows_array_cycle() { - use core::cell::Cell; - #[derive(Debug)] struct B<'a> { a: [Cell>>; 2], @@ -513,7 +510,6 @@ fn array_rsplit_array_mut_out_of_bounds() { #[test] fn array_intoiter_advance_by() { - use std::cell::Cell; struct DropCounter<'a>(usize, &'a Cell); impl Drop for DropCounter<'_> { fn drop(&mut self) { @@ -566,7 +562,6 @@ fn array_intoiter_advance_by() { #[test] fn array_intoiter_advance_back_by() { - use std::cell::Cell; struct DropCounter<'a>(usize, &'a Cell); impl Drop for DropCounter<'_> { fn drop(&mut self) { @@ -718,6 +713,33 @@ fn array_map_drops_unmapped_elements_on_panic() { } } +#[cfg(not(panic = "abort"))] +#[test] +fn array_map_drops_unmapped_zst_elements_on_panic() { + use std::sync::ReentrantLock; + + static DROPPED: ReentrantLock> = ReentrantLock::new(Cell::new(0)); + + struct ZstDrop; + impl Drop for ZstDrop { + fn drop(&mut self) { + DROPPED.lock().update(|x| x + 1); + } + } + + let dropped = DROPPED.lock(); + dropped.set(0); + let array = [const { ZstDrop }; 5]; + let success = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let _ = array.map(|x| { + drop(x); + assert_eq!(dropped.get(), 1); + }); + })); + assert!(success.is_err()); + assert_eq!(dropped.get(), 5); +} + // This covers the `PartialEq::<[T]>::eq` impl for `[T; N]` when it returns false. #[test] fn array_eq() { diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index c4292c2a421b1..12b81fea9d27c 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -1,4 +1,5 @@ // tidy-alphabetical-start +#![cfg_attr(not(panic = "abort"), feature(reentrant_lock))] #![cfg_attr(target_has_atomic = "128", feature(integer_atomics))] #![feature(array_ptr_get)] #![feature(array_try_from_fn)] From ddddb4d10975302345d43cdc9a9d5cc9e4cf234e Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Thu, 7 May 2026 23:33:00 +0900 Subject: [PATCH 4/6] add regression test for closure return ICE --- .../closures/return-in-closure-with-fn-bound.rs | 8 ++++++++ .../return-in-closure-with-fn-bound.stderr | 15 +++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 tests/ui/closures/return-in-closure-with-fn-bound.rs create mode 100644 tests/ui/closures/return-in-closure-with-fn-bound.stderr diff --git a/tests/ui/closures/return-in-closure-with-fn-bound.rs b/tests/ui/closures/return-in-closure-with-fn-bound.rs new file mode 100644 index 0000000000000..5e05cec98ca16 --- /dev/null +++ b/tests/ui/closures/return-in-closure-with-fn-bound.rs @@ -0,0 +1,8 @@ +// Regression test for https://github.com/rust-lang/rust/issues/155893. + +fn func(_f: impl Fn()) { + func(|| return 2) + //~^ ERROR mismatched types +} + +fn main() {} diff --git a/tests/ui/closures/return-in-closure-with-fn-bound.stderr b/tests/ui/closures/return-in-closure-with-fn-bound.stderr new file mode 100644 index 0000000000000..c51e6faca42b6 --- /dev/null +++ b/tests/ui/closures/return-in-closure-with-fn-bound.stderr @@ -0,0 +1,15 @@ +error[E0308]: mismatched types + --> $DIR/return-in-closure-with-fn-bound.rs:4:20 + | +LL | func(|| return 2) + | ^ expected `()`, found integer + | +note: return type inferred to be `()` here + --> $DIR/return-in-closure-with-fn-bound.rs:4:20 + | +LL | func(|| return 2) + | ^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. From 2d88ee87ddaa584a19e438528f60f825f735076a Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Thu, 26 Feb 2026 23:08:22 +0200 Subject: [PATCH 5/6] Reborrow traits --- compiler/rustc_borrowck/src/borrow_set.rs | 47 ++- compiler/rustc_borrowck/src/dataflow.rs | 2 +- compiler/rustc_borrowck/src/lib.rs | 31 ++ .../src/polonius/legacy/loan_invalidations.rs | 15 + compiler/rustc_borrowck/src/type_check/mod.rs | 120 ++++++ compiler/rustc_codegen_cranelift/src/base.rs | 5 + compiler/rustc_codegen_ssa/src/mir/rvalue.rs | 8 + .../src/check_consts/check.rs | 4 + .../src/check_consts/qualifs.rs | 2 + .../src/check_consts/resolver.rs | 13 + .../rustc_const_eval/src/interpret/step.rs | 5 + compiler/rustc_hir/src/lang_items.rs | 2 +- .../src/coherence/builtin.rs | 354 +++++++++++++++++- compiler/rustc_hir_analysis/src/errors.rs | 18 + compiler/rustc_hir_typeck/src/coercion.rs | 99 ++++- .../rustc_hir_typeck/src/expr_use_visitor.rs | 12 +- .../rustc_hir_typeck/src/fn_ctxt/_impl.rs | 3 + compiler/rustc_lint/src/autorefs.rs | 1 + compiler/rustc_middle/src/mir/pretty.rs | 8 + compiler/rustc_middle/src/mir/statement.rs | 8 + compiler/rustc_middle/src/mir/syntax.rs | 19 + compiler/rustc_middle/src/mir/visit.rs | 14 + compiler/rustc_middle/src/thir.rs | 12 + compiler/rustc_middle/src/thir/visit.rs | 1 + compiler/rustc_middle/src/ty/adjustment.rs | 8 + .../src/builder/expr/as_place.rs | 6 + .../src/builder/expr/as_rvalue.rs | 4 + .../src/builder/expr/category.rs | 3 +- .../rustc_mir_build/src/builder/expr/into.rs | 10 + .../rustc_mir_build/src/check_unsafety.rs | 3 +- compiler/rustc_mir_build/src/thir/cx/expr.rs | 7 + .../src/thir/pattern/check_match.rs | 6 + compiler/rustc_mir_build/src/thir/print.rs | 7 + .../src/impls/borrowed_locals.rs | 3 +- .../src/move_paths/builder.rs | 5 +- .../src/add_subtyping_projections.rs | 3 + .../src/dataflow_const_prop.rs | 2 +- compiler/rustc_mir_transform/src/gvn.rs | 15 + .../src/known_panics_lint.rs | 6 +- compiler/rustc_mir_transform/src/lint.rs | 1 + .../rustc_mir_transform/src/promote_consts.rs | 6 + compiler/rustc_mir_transform/src/validate.rs | 2 +- compiler/rustc_public/src/mir/body.rs | 8 + compiler/rustc_public/src/mir/pretty.rs | 7 + compiler/rustc_public/src/mir/visit.rs | 5 + .../src/unstable/convert/stable/mir.rs | 5 + compiler/rustc_span/src/symbol.rs | 2 + compiler/rustc_ty_utils/src/consts.rs | 6 + library/core/src/marker.rs | 16 + library/core/src/ops/mod.rs | 3 - library/core/src/ops/reborrow.rs | 16 - .../clippy_utils/src/qualify_min_const_fn.rs | 2 +- .../feature-gate-reborrow-coerce-shared.rs | 2 +- ...feature-gate-reborrow-coerce-shared.stderr | 4 +- .../ui/feature-gates/feature-gate-reborrow.rs | 2 +- .../feature-gate-reborrow.stderr | 4 +- tests/ui/reborrow/custom_marker.rs | 17 + .../ui/reborrow/custom_marker_assign_deref.rs | 26 ++ .../reborrow/custom_marker_coerce_shared.rs | 22 ++ .../custom_marker_coerce_shared_copy.rs | 22 ++ .../custom_marker_coerce_shared_move.rs | 21 ++ .../custom_marker_coerce_shared_move.stderr | 16 + tests/ui/reborrow/custom_marker_deref.rs | 17 + tests/ui/reborrow/custom_marker_mut_a_b.rs | 17 + .../ui/reborrow/custom_marker_mut_a_b.stderr | 14 + tests/ui/reborrow/custom_marker_mut_self.rs | 15 + .../ui/reborrow/custom_marker_mut_self.stderr | 15 + tests/ui/reborrow/custom_marker_mut_self_a.rs | 18 + .../reborrow/custom_marker_mut_self_a.stderr | 28 ++ tests/ui/reborrow/custom_marker_mut_self_b.rs | 17 + .../reborrow/custom_marker_mut_self_b.stderr | 16 + .../reborrow/custom_marker_two_lifetimes.rs | 8 + .../custom_marker_two_lifetimes.stderr | 8 + tests/ui/reborrow/custom_mut.rs | 9 +- tests/ui/reborrow/custom_mut.stderr | 29 -- tests/ui/reborrow/custom_mut_coerce_shared.rs | 15 +- .../reborrow/custom_mut_coerce_shared.stderr | 19 - 77 files changed, 1247 insertions(+), 104 deletions(-) delete mode 100644 library/core/src/ops/reborrow.rs create mode 100644 tests/ui/reborrow/custom_marker.rs create mode 100644 tests/ui/reborrow/custom_marker_assign_deref.rs create mode 100644 tests/ui/reborrow/custom_marker_coerce_shared.rs create mode 100644 tests/ui/reborrow/custom_marker_coerce_shared_copy.rs create mode 100644 tests/ui/reborrow/custom_marker_coerce_shared_move.rs create mode 100644 tests/ui/reborrow/custom_marker_coerce_shared_move.stderr create mode 100644 tests/ui/reborrow/custom_marker_deref.rs create mode 100644 tests/ui/reborrow/custom_marker_mut_a_b.rs create mode 100644 tests/ui/reborrow/custom_marker_mut_a_b.stderr create mode 100644 tests/ui/reborrow/custom_marker_mut_self.rs create mode 100644 tests/ui/reborrow/custom_marker_mut_self.stderr create mode 100644 tests/ui/reborrow/custom_marker_mut_self_a.rs create mode 100644 tests/ui/reborrow/custom_marker_mut_self_a.stderr create mode 100644 tests/ui/reborrow/custom_marker_mut_self_b.rs create mode 100644 tests/ui/reborrow/custom_marker_mut_self_b.stderr create mode 100644 tests/ui/reborrow/custom_marker_two_lifetimes.rs create mode 100644 tests/ui/reborrow/custom_marker_two_lifetimes.stderr delete mode 100644 tests/ui/reborrow/custom_mut.stderr delete mode 100644 tests/ui/reborrow/custom_mut_coerce_shared.stderr diff --git a/compiler/rustc_borrowck/src/borrow_set.rs b/compiler/rustc_borrowck/src/borrow_set.rs index 4644c210137fe..6d68703642314 100644 --- a/compiler/rustc_borrowck/src/borrow_set.rs +++ b/compiler/rustc_borrowck/src/borrow_set.rs @@ -2,11 +2,12 @@ use std::fmt; use std::ops::Index; use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; +use rustc_hir::Mutability; use rustc_index::bit_set::DenseBitSet; use rustc_middle::mir::visit::{MutatingUseContext, NonUseContext, PlaceContext, Visitor}; use rustc_middle::mir::{self, Body, Local, Location, traversal}; -use rustc_middle::span_bug; use rustc_middle::ty::{RegionVid, TyCtxt}; +use rustc_middle::{bug, span_bug, ty}; use rustc_mir_dataflow::move_paths::MoveData; use tracing::debug; @@ -300,6 +301,50 @@ impl<'a, 'tcx> Visitor<'tcx> for GatherBorrows<'a, 'tcx> { idx }; + self.local_map.entry(borrowed_place.local).or_default().insert(idx); + } else if let &mir::Rvalue::Reborrow(target, mutability, borrowed_place) = rvalue { + let borrowed_place_ty = borrowed_place.ty(self.body, self.tcx).ty; + let &ty::Adt(reborrowed_adt, _reborrowed_args) = borrowed_place_ty.kind() else { + unreachable!() + }; + let &ty::Adt(target_adt, assigned_args) = target.kind() else { unreachable!() }; + let Some(ty::GenericArgKind::Lifetime(region)) = assigned_args.get(0).map(|r| r.kind()) + else { + bug!( + "hir-typeck passed but {} does not have a lifetime argument", + if mutability == Mutability::Mut { "Reborrow" } else { "CoerceShared" } + ); + }; + let region = region.as_var(); + let kind = if mutability == Mutability::Mut { + // Reborrow + if target_adt.did() != reborrowed_adt.did() { + bug!( + "hir-typeck passed but Reborrow involves mismatching types at {location:?}" + ) + } + + mir::BorrowKind::Mut { kind: mir::MutBorrowKind::Default } + } else { + // CoerceShared + if target_adt.did() == reborrowed_adt.did() { + bug!( + "hir-typeck passed but CoerceShared involves matching types at {location:?}" + ) + } + mir::BorrowKind::Shared + }; + let borrow = BorrowData { + kind, + region, + reserve_location: location, + activation_location: TwoPhaseActivation::NotTwoPhase, + borrowed_place, + assigned_place: *assigned_place, + }; + let (idx, _) = self.location_map.insert_full(location, borrow); + let idx = BorrowIndex::from(idx); + self.local_map.entry(borrowed_place.local).or_default().insert(idx); } diff --git a/compiler/rustc_borrowck/src/dataflow.rs b/compiler/rustc_borrowck/src/dataflow.rs index 8ddfa4b61edde..ef167644f0a9d 100644 --- a/compiler/rustc_borrowck/src/dataflow.rs +++ b/compiler/rustc_borrowck/src/dataflow.rs @@ -549,7 +549,7 @@ impl<'tcx> rustc_mir_dataflow::Analysis<'tcx> for Borrows<'_, 'tcx> { ) { match &stmt.kind { mir::StatementKind::Assign(box (lhs, rhs)) => { - if let mir::Rvalue::Ref(_, _, place) = rhs { + if let mir::Rvalue::Ref(_, _, place) | mir::Rvalue::Reborrow(_, _, place) = rhs { if place.ignore_borrow( self.tcx, self.body, diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index acdeea91a189f..25bcce8fdbf41 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -1265,6 +1265,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> { let mut error_reported = false; let borrows_in_scope = self.borrows_in_scope(location, state); + debug!(?borrows_in_scope, ?location); each_borrow_involving_path( self, @@ -1507,6 +1508,36 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> { ); } + &Rvalue::Reborrow(_target, mutability, place) => { + let access_kind = ( + Deep, + if mutability == Mutability::Mut { + Write(WriteKind::MutableBorrow(BorrowKind::Mut { + kind: MutBorrowKind::Default, + })) + } else { + Read(ReadKind::Borrow(BorrowKind::Shared)) + }, + ); + + self.access_place( + location, + (place, span), + access_kind, + LocalMutationIsAllowed::Yes, + state, + ); + + let action = InitializationRequiringAction::Borrow; + + self.check_if_path_or_subpath_is_moved( + location, + action, + (place.as_ref(), span), + state, + ); + } + &Rvalue::RawPtr(kind, place) => { let access_kind = match kind { RawPtrKind::Mut => ( diff --git a/compiler/rustc_borrowck/src/polonius/legacy/loan_invalidations.rs b/compiler/rustc_borrowck/src/polonius/legacy/loan_invalidations.rs index 136719a323ce1..da3fc0763a430 100644 --- a/compiler/rustc_borrowck/src/polonius/legacy/loan_invalidations.rs +++ b/compiler/rustc_borrowck/src/polonius/legacy/loan_invalidations.rs @@ -274,6 +274,21 @@ impl<'a, 'tcx> LoanInvalidationsGenerator<'a, 'tcx> { self.access_place(location, place, access_kind, LocalMutationIsAllowed::No); } + &Rvalue::Reborrow(_target, mutability, place) => { + let access_kind = ( + Deep, + if mutability == Mutability::Mut { + Reservation(WriteKind::MutableBorrow(BorrowKind::Mut { + kind: MutBorrowKind::TwoPhaseBorrow, + })) + } else { + Read(ReadKind::Borrow(BorrowKind::Shared)) + }, + ); + + self.access_place(location, place, access_kind, LocalMutationIsAllowed::No); + } + &Rvalue::RawPtr(kind, place) => { let access_kind = match kind { RawPtrKind::Mut => ( diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 8998ced10bf9e..48389d9354998 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -1580,6 +1580,15 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { self.add_reborrow_constraint(location, *region, borrowed_place); } + Rvalue::Reborrow(target, mutability, borrowed_place) => { + self.add_generic_reborrow_constraint( + *mutability, + location, + borrowed_place, + *target, + ); + } + Rvalue::BinaryOp( BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge, box (left, right), @@ -2218,6 +2227,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { | Rvalue::ThreadLocalRef(..) | Rvalue::Repeat(..) | Rvalue::Ref(..) + | Rvalue::Reborrow(..) | Rvalue::RawPtr(..) | Rvalue::Cast(..) | Rvalue::BinaryOp(..) @@ -2422,6 +2432,116 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { } } + fn add_generic_reborrow_constraint( + &mut self, + mutability: Mutability, + location: Location, + borrowed_place: &Place<'tcx>, + dest_ty: Ty<'tcx>, + ) { + let Self { borrow_set, location_table, polonius_facts, constraints, infcx, body, .. } = + self; + + debug!( + "add_generic_reborrow_constraint({:?}, {:?}, {:?}, {:?})", + mutability, location, borrowed_place, dest_ty + ); + + let tcx = infcx.tcx; + let def = body.source.def_id().expect_local(); + let upvars = tcx.closure_captures(def); + let field = + path_utils::is_upvar_field_projection(tcx, upvars, borrowed_place.as_ref(), body); + let category = if let Some(field) = field { + ConstraintCategory::ClosureUpvar(field) + } else { + ConstraintCategory::Boring + }; + + let borrowed_ty = borrowed_place.ty(self.body, tcx).ty; + + let ty::Adt(dest_adt, dest_args) = dest_ty.kind() else { bug!() }; + let [dest_arg, ..] = ***dest_args else { bug!() }; + let ty::GenericArgKind::Lifetime(dest_region) = dest_arg.kind() else { bug!() }; + constraints.liveness_constraints.add_location(dest_region.as_var(), location); + + // In Polonius mode, we also push a `loan_issued_at` fact + // linking the loan to the region. + if let Some(polonius_facts) = polonius_facts { + let _prof_timer = infcx.tcx.prof.generic_activity("polonius_fact_generation"); + if let Some(borrow_index) = borrow_set.get_index_of(&location) { + let region_vid = dest_region.as_var(); + polonius_facts.loan_issued_at.push(( + region_vid.into(), + borrow_index, + location_table.mid_index(location), + )); + } + } + + if mutability.is_not() { + // FIXME(reborrow): for CoerceShared we need to relate the types manually, field by + // field. We cannot just attempt to relate `T` and `::Target` by + // calling relate_types as they are (generally) two unrelated user-defined ADTs, such as + // `CustomMut<'a>` and `CustomRef<'a>`, or `CustomMut<'a, T>` and `CustomRef<'a, T>`. + // Field-by-field relate_types is expected to work based on the wf-checks that the + // CoerceShared trait performs. + let ty::Adt(borrowed_adt, borrowed_args) = borrowed_ty.kind() else { unreachable!() }; + let borrowed_fields = borrowed_adt.all_fields().collect::>(); + for dest_field in dest_adt.all_fields() { + let Some(borrowed_field) = + borrowed_fields.iter().find(|f| f.name == dest_field.name) + else { + continue; + }; + let dest_ty = dest_field.ty(tcx, dest_args); + let borrowed_ty = borrowed_field.ty(tcx, borrowed_args); + if let ( + ty::Ref(borrow_region, _, Mutability::Mut), + ty::Ref(ref_region, _, Mutability::Not), + ) = (borrowed_ty.kind(), dest_ty.kind()) + { + self.relate_types( + borrowed_ty.peel_refs(), + ty::Variance::Covariant, + dest_ty.peel_refs(), + location.to_locations(), + category, + ) + .unwrap(); + self.constraints.outlives_constraints.push(OutlivesConstraint { + sup: ref_region.as_var(), + sub: borrow_region.as_var(), + locations: location.to_locations(), + span: location.to_locations().span(self.body), + category, + variance_info: ty::VarianceDiagInfo::default(), + from_closure: false, + }); + } else { + self.relate_types( + borrowed_ty, + ty::Variance::Covariant, + dest_ty, + location.to_locations(), + category, + ) + .unwrap(); + } + } + } else { + // Exclusive reborrow + self.relate_types( + borrowed_ty, + ty::Variance::Covariant, + dest_ty, + location.to_locations(), + category, + ) + .unwrap(); + } + } + fn prove_aggregate_predicates( &mut self, aggregate_kind: &AggregateKind<'tcx>, diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index 3d15ad819310d..1d90c8e0dadcf 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -629,6 +629,11 @@ fn codegen_stmt<'tcx>(fx: &mut FunctionCx<'_, '_, 'tcx>, cur_block: Block, stmt: let ref_ = place.place_ref(fx, lval.layout()); lval.write_cvalue(fx, ref_); } + Rvalue::Reborrow(_, _, place) => { + let cplace = codegen_place(fx, place); + let val = cplace.to_cvalue(fx); + lval.write_cvalue(fx, val) + } Rvalue::ThreadLocalRef(def_id) => { let val = crate::constant::codegen_tls_ref(fx, def_id, lval.layout()); lval.write_cvalue(fx, val); diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index 9a491c9c3eb1f..45c6621ba0131 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -518,6 +518,14 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { self.codegen_place_to_pointer(bx, place, mk_ref) } + // Note: Exclusive reborrowing is always equal to a memcpy, as the types do not change. + // Generic shared reborrowing is not (necessarily) a simple memcpy, but currently the + // coherence check places such restrictions on the CoerceShared trait as to guarantee + // that it is. + mir::Rvalue::Reborrow(_, _, place) => { + self.codegen_operand(bx, &mir::Operand::Copy(place)) + } + mir::Rvalue::RawPtr(kind, place) => { let mk_ptr = move |tcx: TyCtxt<'tcx>, ty: Ty<'tcx>| { Ty::new_ptr(tcx, ty, kind.to_mutbl_lossy()) diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index b66d69b924076..d7b18518aba91 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -610,6 +610,10 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { } } + Rvalue::Reborrow(..) => { + // FIXME(reborrow): figure out if this is relevant at all. + } + Rvalue::RawPtr(RawPtrKind::FakeForPtrMetadata, place) => { // These are only inserted for slice length, so the place must already be indirect. // This implies we do not have to worry about whether the borrow escapes. diff --git a/compiler/rustc_const_eval/src/check_consts/qualifs.rs b/compiler/rustc_const_eval/src/check_consts/qualifs.rs index 6f7fccdaf2665..e1cab9af046be 100644 --- a/compiler/rustc_const_eval/src/check_consts/qualifs.rs +++ b/compiler/rustc_const_eval/src/check_consts/qualifs.rs @@ -252,6 +252,8 @@ where in_place::(cx, in_local, place.as_ref()) } + Rvalue::Reborrow(_, _, place) => in_place::(cx, in_local, place.as_ref()), + Rvalue::WrapUnsafeBinder(op, _) => in_operand::(cx, in_local, op), Rvalue::Aggregate(kind, operands) => { diff --git a/compiler/rustc_const_eval/src/check_consts/resolver.rs b/compiler/rustc_const_eval/src/check_consts/resolver.rs index 044b8b091b8de..a230f797b56fd 100644 --- a/compiler/rustc_const_eval/src/check_consts/resolver.rs +++ b/compiler/rustc_const_eval/src/check_consts/resolver.rs @@ -191,6 +191,19 @@ where } } + mir::Rvalue::Reborrow(target, mutability, borrowed_place) => { + // A Reborrow allows mutation if it is Reborrow or if the CoerceShared target isn't + // Freeze. + if !borrowed_place.is_indirect() + && (mutability.is_mut() || !target.is_freeze(self.ccx.tcx, self.ccx.typing_env)) + { + if Q::in_any_value_of_ty(self.ccx, *target) { + self.state.qualif.insert(borrowed_place.local); + self.state.borrow.insert(borrowed_place.local); + } + } + } + mir::Rvalue::Cast(..) | mir::Rvalue::Use(..) | mir::Rvalue::CopyForDeref(..) diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs index 6aa2971c900cf..8376d0499990e 100644 --- a/compiler/rustc_const_eval/src/interpret/step.rs +++ b/compiler/rustc_const_eval/src/interpret/step.rs @@ -230,6 +230,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { })?; } + Reborrow(_, _, place) => { + let op = self.eval_place_to_op(place, Some(dest.layout))?; + self.copy_op(&op, &dest)?; + } + RawPtr(kind, place) => { // Figure out whether this is an addr_of of an already raw place. let place_base_raw = if place.is_indirect_first_projection() { diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index 133efb77317b4..36cf398dedb10 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -437,7 +437,7 @@ language_item_table! { // Reborrowing related lang-items Reborrow, sym::reborrow, reborrow, Target::Trait, GenericRequirement::Exact(0); - CoerceShared, sym::coerce_shared, coerce_shared, Target::Trait, GenericRequirement::Exact(0); + CoerceShared, sym::coerce_shared, coerce_shared, Target::Trait, GenericRequirement::Exact(1); // Field representing types. FieldRepresentingType, sym::field_representing_type, field_representing_type, Target::Struct, GenericRequirement::Exact(3); diff --git a/compiler/rustc_hir_analysis/src/coherence/builtin.rs b/compiler/rustc_hir_analysis/src/coherence/builtin.rs index ba22ee6d1aa8a..0fb565b67d046 100644 --- a/compiler/rustc_hir_analysis/src/coherence/builtin.rs +++ b/compiler/rustc_hir_analysis/src/coherence/builtin.rs @@ -9,12 +9,13 @@ use rustc_hir as hir; use rustc_hir::ItemKind; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::lang_items::LangItem; -use rustc_infer::infer::{self, RegionResolutionError, SubregionOrigin, TyCtxtInferExt}; -use rustc_infer::traits::Obligation; +use rustc_infer::infer::{self, InferCtxt, RegionResolutionError, SubregionOrigin, TyCtxtInferExt}; +use rustc_infer::traits::{Obligation, PredicateObligations}; use rustc_middle::ty::adjustment::CoerceUnsizedInfo; use rustc_middle::ty::print::PrintTraitRefExt as _; +use rustc_middle::ty::relate::solver_relating::RelateExt; use rustc_middle::ty::{ - self, Ty, TyCtxt, TypeVisitableExt, TypingMode, suggest_constraining_type_params, + self, Ty, TyCtxt, TypeVisitableExt, TypingMode, Unnormalized, suggest_constraining_type_params, }; use rustc_span::{DUMMY_SP, Span, sym}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; @@ -22,7 +23,7 @@ use rustc_trait_selection::traits::misc::{ ConstParamTyImplementationError, CopyImplementationError, InfringingFieldsReason, type_allowed_to_implement_const_param_ty, type_allowed_to_implement_copy, }; -use rustc_trait_selection::traits::{self, ObligationCause, ObligationCtxt}; +use rustc_trait_selection::traits::{self, FulfillmentError, ObligationCause, ObligationCtxt}; use tracing::debug; use crate::errors; @@ -43,6 +44,8 @@ pub(super) fn check_trait<'tcx>( visit_implementation_of_const_param_ty(checker) })?; checker.check(lang_items.coerce_unsized_trait(), visit_implementation_of_coerce_unsized)?; + checker.check(lang_items.reborrow(), visit_implementation_of_reborrow)?; + checker.check(lang_items.coerce_shared(), visit_implementation_of_coerce_shared)?; checker .check(lang_items.dispatch_from_dyn_trait(), visit_implementation_of_dispatch_from_dyn)?; checker.check( @@ -259,6 +262,28 @@ fn visit_implementation_of_coerce_unsized(checker: &Checker<'_>) -> Result<(), E tcx.ensure_result().coerce_unsized_info(impl_did) } +fn visit_implementation_of_reborrow(checker: &Checker<'_>) -> Result<(), ErrorGuaranteed> { + let tcx = checker.tcx; + let impl_did = checker.impl_def_id; + debug!("visit_implementation_of_reborrow: impl_did={:?}", impl_did); + + // Just compute this for the side-effects, in particular reporting + // errors; other parts of the code may demand it for the info of + // course. + reborrow_info(tcx, impl_did) +} + +fn visit_implementation_of_coerce_shared(checker: &Checker<'_>) -> Result<(), ErrorGuaranteed> { + let tcx = checker.tcx; + let impl_did = checker.impl_def_id; + debug!("visit_implementation_of_coerce_shared: impl_did={:?}", impl_did); + + // Just compute this for the side-effects, in particular reporting + // errors; other parts of the code may demand it for the info of + // course. + coerce_shared_info(tcx, impl_did) +} + fn is_from_coerce_pointee_derive(tcx: TyCtxt<'_>, span: Span) -> bool { span.ctxt() .outer_expn_data() @@ -444,6 +469,327 @@ fn visit_implementation_of_dispatch_from_dyn(checker: &Checker<'_>) -> Result<() } } +fn structurally_normalize_ty<'tcx>( + tcx: TyCtxt<'tcx>, + infcx: &InferCtxt<'tcx>, + impl_did: LocalDefId, + span: Span, + ty: Unnormalized<'tcx, Ty<'tcx>>, +) -> Option<(Ty<'tcx>, PredicateObligations<'tcx>)> { + let ocx = ObligationCtxt::new(infcx); + let Ok(normalized_ty) = ocx.structurally_normalize_ty( + &traits::ObligationCause::misc(span, impl_did), + tcx.param_env(impl_did), + ty, + ) else { + // We shouldn't have errors here in the old solver, except for + // evaluate/fulfill mismatches, but that's not a reason for an ICE. + return None; + }; + let errors = ocx.try_evaluate_obligations(); + if !errors.is_empty() { + if infcx.next_trait_solver() { + unreachable!(); + } + // We shouldn't have errors here in the old solver, except for + // evaluate/fulfill mismatches, but that's not a reason for an ICE. + debug!(?errors, "encountered errors while fulfilling"); + return None; + } + + Some((normalized_ty, ocx.into_pending_obligations())) +} + +pub(crate) fn reborrow_info<'tcx>( + tcx: TyCtxt<'tcx>, + impl_did: LocalDefId, +) -> Result<(), ErrorGuaranteed> { + debug!("compute_reborrow_info(impl_did={:?})", impl_did); + let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis()); + let span = tcx.def_span(impl_did); + let trait_name = "Reborrow"; + + let reborrow_trait = tcx.require_lang_item(LangItem::Reborrow, span); + + let source = tcx.type_of(impl_did).instantiate_identity().skip_norm_wip(); + let trait_ref = tcx.impl_trait_ref(impl_did).instantiate_identity().skip_norm_wip(); + + if trait_impl_lifetime_params_count(tcx, impl_did) != 1 { + return Err(tcx + .dcx() + .emit_err(errors::CoerceSharedNotSingleLifetimeParam { span, trait_name })); + } + + assert_eq!(trait_ref.def_id, reborrow_trait); + let param_env = tcx.param_env(impl_did); + assert!(!source.has_escaping_bound_vars()); + + let (def, args) = match source.kind() { + &ty::Adt(def, args) if def.is_struct() => (def, args), + _ => { + // Note: reusing error here as it takes trait_name as argument. + return Err(tcx.dcx().emit_err(errors::CoerceUnsizedNonStruct { span, trait_name })); + } + }; + + let lifetimes_count = generic_lifetime_params_count(args); + let data_fields = collect_struct_data_fields(tcx, def, args); + + if lifetimes_count != 1 { + let item = tcx.hir_expect_item(impl_did); + let _span = if let ItemKind::Impl(hir::Impl { of_trait: Some(of_trait), .. }) = &item.kind { + of_trait.trait_ref.path.span + } else { + tcx.def_span(impl_did) + }; + + return Err(tcx.dcx().emit_err(errors::CoerceSharedMulti { span, trait_name })); + } + + if data_fields.is_empty() { + return Ok(()); + } + + // We've found some data fields. They must all be either be Copy or Reborrow. + for (field, span) in data_fields { + if assert_field_type_is_reborrow( + tcx, + &infcx, + reborrow_trait, + impl_did, + param_env, + field, + span, + ) + .is_ok() + { + // Field implements Reborrow. + return Ok(()); + } + + // Field does not implement Reborrow: it must be Copy. + assert_field_type_is_copy(tcx, &infcx, impl_did, param_env, field, span)?; + } + + Ok(()) +} + +fn assert_field_type_is_reborrow<'tcx>( + tcx: TyCtxt<'tcx>, + infcx: &InferCtxt<'tcx>, + reborrow_trait: DefId, + impl_did: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + ty: Ty<'tcx>, + span: Span, +) -> Result<(), Vec>> { + if ty.ref_mutability() == Some(ty::Mutability::Mut) { + // Mutable references are Reborrow but not really. + return Ok(()); + } + let ocx = ObligationCtxt::new_with_diagnostics(infcx); + let cause = traits::ObligationCause::misc(span, impl_did); + let obligation = + Obligation::new(tcx, cause, param_env, ty::TraitRef::new(tcx, reborrow_trait, [ty])); + ocx.register_obligation(obligation); + let errors = ocx.evaluate_obligations_error_on_ambiguity(); + + if !errors.is_empty() { Err(errors) } else { Ok(()) } +} + +pub(crate) fn coerce_shared_info<'tcx>( + tcx: TyCtxt<'tcx>, + impl_did: LocalDefId, +) -> Result<(), ErrorGuaranteed> { + debug!("compute_coerce_shared_info(impl_did={:?})", impl_did); + let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis()); + let span = tcx.def_span(impl_did); + let trait_name = "CoerceShared"; + + let coerce_shared_trait = tcx.require_lang_item(LangItem::CoerceShared, span); + + let source = tcx.type_of(impl_did).instantiate_identity().skip_norm_wip(); + let trait_ref = tcx.impl_trait_ref(impl_did).instantiate_identity().skip_norm_wip(); + + if trait_impl_lifetime_params_count(tcx, impl_did) != 1 { + return Err(tcx + .dcx() + .emit_err(errors::CoerceSharedNotSingleLifetimeParam { span, trait_name })); + } + + assert_eq!(trait_ref.def_id, coerce_shared_trait); + let Some((target, _obligations)) = structurally_normalize_ty( + tcx, + &infcx, + impl_did, + span, + Unnormalized::new_wip(trait_ref.args.type_at(1)), + ) else { + todo!("something went wrong with structurally_normalize_ty"); + }; + + let param_env = tcx.param_env(impl_did); + assert!(!source.has_escaping_bound_vars()); + + let data = match (source.kind(), target.kind()) { + (&ty::Adt(def_a, args_a), &ty::Adt(def_b, args_b)) + if def_a.is_struct() && def_b.is_struct() => + { + // Check that both A and B have exactly one lifetime argument, and that they have the + // same number of data fields that is not more than 1. The eventual intention is to + // support multiple lifetime arguments (with the reborrowed lifetimes inferred from + // usage one way or another) and multiple data fields with B allowed to leave out fields + // from A. The current state is just the simplest choice. + let a_lifetimes_count = generic_lifetime_params_count(args_a); + let a_data_fields = collect_struct_data_fields(tcx, def_a, args_a); + let b_lifetimes_count = generic_lifetime_params_count(args_b); + let b_data_fields = collect_struct_data_fields(tcx, def_b, args_b); + + if a_lifetimes_count != 1 + || b_lifetimes_count != 1 + || a_data_fields.len() > 1 + || b_data_fields.len() > 1 + || a_data_fields.len() != b_data_fields.len() + { + let item = tcx.hir_expect_item(impl_did); + let span = if let ItemKind::Impl(hir::Impl { of_trait: Some(of_trait), .. }) = + &item.kind + { + of_trait.trait_ref.path.span + } else { + tcx.def_span(impl_did) + }; + + return Err(tcx.dcx().emit_err(errors::CoerceSharedMulti { span, trait_name })); + } + + if a_data_fields.len() == 1 { + // We found one data field for both: we'll attempt to perform CoerceShared between + // them below. + let (a, span_a) = a_data_fields[0]; + let (b, span_b) = b_data_fields[0]; + + Some((a, b, coerce_shared_trait, span_a, span_b)) + } else { + // We found no data fields in either: this is a reborrowable marker type being + // coerced into a shared marker. That is fine too. + None + } + } + + _ => { + // Note: reusing CoerceUnsizedNonStruct error as it takes trait_name as argument. + return Err(tcx.dcx().emit_err(errors::CoerceUnsizedNonStruct { span, trait_name })); + } + }; + + // We've proven that we have two types with one lifetime each and 0 or 1 data fields each. + if let Some((source, target, trait_def_id, source_field_span, _target_field_span)) = data { + // struct Source(SourceData); + // struct Target(TargetData); + // + // 1 data field each; they must be the same type and Copy, or relate to one another using + // CoerceShared. + if source.ref_mutability() == Some(ty::Mutability::Mut) + && target.ref_mutability() == Some(ty::Mutability::Not) + && infcx + .eq_structurally_relating_aliases( + param_env, + source.peel_refs(), + target.peel_refs(), + source_field_span, + ) + .is_ok() + { + // &mut T implements CoerceShared to &T, except not really. + return Ok(()); + } + if infcx + .eq_structurally_relating_aliases(param_env, source, target, source_field_span) + .is_err() + { + // The two data fields don't agree on a common type; this means + // that they must be `A: CoerceShared`. Register an obligation + // for that. + let ocx = ObligationCtxt::new_with_diagnostics(&infcx); + let cause = traits::ObligationCause::misc(span, impl_did); + let obligation = Obligation::new( + tcx, + cause, + param_env, + ty::TraitRef::new(tcx, trait_def_id, [source, target]), + ); + ocx.register_obligation(obligation); + let errors = ocx.evaluate_obligations_error_on_ambiguity(); + + if !errors.is_empty() { + return Err(infcx.err_ctxt().report_fulfillment_errors(errors)); + } + // Finally, resolve all regions. + ocx.resolve_regions_and_report_errors(impl_did, param_env, [])?; + } else { + // Types match: check that it is Copy. + assert_field_type_is_copy(tcx, &infcx, impl_did, param_env, source, source_field_span)?; + } + } + + Ok(()) +} + +fn trait_impl_lifetime_params_count(tcx: TyCtxt<'_>, did: LocalDefId) -> usize { + tcx.generics_of(did) + .own_params + .iter() + .filter(|p| matches!(p.kind, ty::GenericParamDefKind::Lifetime)) + .count() +} + +fn generic_lifetime_params_count(args: &[ty::GenericArg<'_>]) -> usize { + args.iter().filter(|arg| arg.as_region().is_some()).count() +} + +fn collect_struct_data_fields<'tcx>( + tcx: TyCtxt<'tcx>, + def: ty::AdtDef<'tcx>, + args: ty::GenericArgsRef<'tcx>, +) -> Vec<(Ty<'tcx>, Span)> { + def.non_enum_variant() + .fields + .iter() + .filter_map(|f| { + // Ignore PhantomData fields + let ty = f.ty(tcx, args); + if ty.is_phantom_data() { + return None; + } + Some((ty, tcx.def_span(f.did))) + }) + .collect() +} + +fn assert_field_type_is_copy<'tcx>( + tcx: TyCtxt<'tcx>, + infcx: &InferCtxt<'tcx>, + impl_did: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + ty: Ty<'tcx>, + span: Span, +) -> Result<(), ErrorGuaranteed> { + let copy_trait = tcx.require_lang_item(LangItem::Copy, span); + let ocx = ObligationCtxt::new_with_diagnostics(infcx); + let cause = traits::ObligationCause::misc(span, impl_did); + let obligation = + Obligation::new(tcx, cause, param_env, ty::TraitRef::new(tcx, copy_trait, [ty])); + ocx.register_obligation(obligation); + let errors = ocx.evaluate_obligations_error_on_ambiguity(); + + if !errors.is_empty() { + Err(infcx.err_ctxt().report_fulfillment_errors(errors)) + } else { + Ok(()) + } +} + pub(crate) fn coerce_unsized_info<'tcx>( tcx: TyCtxt<'tcx>, impl_did: LocalDefId, diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs index 1bb54f27dcbdd..e0528a3e3b49c 100644 --- a/compiler/rustc_hir_analysis/src/errors.rs +++ b/compiler/rustc_hir_analysis/src/errors.rs @@ -1358,6 +1358,24 @@ pub(crate) struct CoerceMulti { pub fields: MultiSpan, } +#[derive(Diagnostic)] +#[diag( + "implementing `{$trait_name}` requires that a single lifetime parameter is passed between source and target" +)] +pub(crate) struct CoerceSharedNotSingleLifetimeParam { + #[primary_span] + pub span: Span, + pub trait_name: &'static str, +} + +#[derive(Diagnostic)] +#[diag("implementing `{$trait_name}` does not allow multiple lifetimes or fields to be coerced")] +pub(crate) struct CoerceSharedMulti { + #[primary_span] + pub span: Span, + pub trait_name: &'static str, +} + #[derive(Diagnostic)] #[diag("the trait `{$trait_name}` may only be implemented for a coercion between structures", code = E0377)] pub(crate) struct CoerceUnsizedNonStruct { diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index abd5f38f0ed08..9d9a42c132343 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -283,7 +283,8 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { } // Examine the target type and consider type-specific coercions, such - // as auto-borrowing, coercing pointer mutability, or pin-ergonomics. + // as auto-borrowing, coercing pointer mutability, pin-ergonomics, or + // generic reborrow. match *b.kind() { ty::RawPtr(_, b_mutbl) => { return self.coerce_to_raw_ptr(a, b, b_mutbl); @@ -297,6 +298,26 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { _ if let Some(to_pin_ref) = self.maybe_to_pin_ref(a, b) => { return self.coerce_to_pin_ref(to_pin_ref); } + ty::Adt(_, _) + if self.tcx.features().reborrow() + && self + .fcx + .infcx + .type_implements_trait( + self.tcx + .lang_items() + .reborrow() + .expect("Unexpectedly using core/std without reborrow"), + [b], + self.fcx.param_env, + ) + .must_apply_modulo_regions() => + { + let reborrow_coerce = self.commit_if_ok(|_| self.coerce_reborrow(a, b)); + if reborrow_coerce.is_ok() { + return reborrow_coerce; + } + } _ => {} } @@ -320,6 +341,14 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { // It cannot convert closures that require unsafe. self.coerce_closure_to_fn(a, b) } + ty::Adt(_, _) if self.tcx.features().reborrow() => { + let reborrow_coerce = self.commit_if_ok(|_| self.coerce_shared_reborrow(a, b)); + if reborrow_coerce.is_ok() { + reborrow_coerce + } else { + self.unify(a, b, ForceLeakCheck::No) + } + } _ => { // Otherwise, just use unification rules. self.unify(a, b, ForceLeakCheck::No) @@ -934,6 +963,74 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { Ok(coerce) } + /// Applies generic exclusive reborrowing on type implementing `Reborrow`. + #[instrument(skip(self), level = "trace")] + fn coerce_reborrow(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { + debug_assert!(self.shallow_resolve(a) == a); + debug_assert!(self.shallow_resolve(b) == b); + + // We need to make sure the two types are compatible for reborrow. + let (ty::Adt(a_def, _), ty::Adt(b_def, _)) = (a.kind(), b.kind()) else { + return Err(TypeError::Mismatch); + }; + if a_def.did() == b_def.did() { + // Reborrow is applicable here + self.unify_and( + a, + b, + [], + Adjust::GenericReborrow(ty::Mutability::Mut), + ForceLeakCheck::No, + ) + } else { + // FIXME: CoerceShared check goes here, error for now + Err(TypeError::Mismatch) + } + } + + /// Applies generic exclusive reborrowing on type implementing `Reborrow`. + #[instrument(skip(self), level = "trace")] + fn coerce_shared_reborrow(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { + debug_assert!(self.shallow_resolve(a) == a); + debug_assert!(self.shallow_resolve(b) == b); + + // We need to make sure the two types are compatible for reborrow. + let (ty::Adt(a_def, _), ty::Adt(b_def, _)) = (a.kind(), b.kind()) else { + return Err(TypeError::Mismatch); + }; + if a_def.did() == b_def.did() { + // CoerceShared cannot be T -> T. + return Err(TypeError::Mismatch); + } + let Some(coerce_shared_trait_did) = self.tcx.lang_items().coerce_shared() else { + return Err(TypeError::Mismatch); + }; + let coerce_shared_trait_ref = ty::TraitRef::new(self.tcx, coerce_shared_trait_did, [a, b]); + let obligation = traits::Obligation::new( + self.tcx, + ObligationCause::dummy(), + self.param_env, + ty::Binder::dummy(coerce_shared_trait_ref), + ); + let ocx = ObligationCtxt::new(&self.infcx); + ocx.register_obligation(obligation); + let errs = ocx.evaluate_obligations_error_on_ambiguity(); + if errs.is_empty() { + Ok(InferOk { + value: ( + vec![Adjustment { + kind: Adjust::GenericReborrow(ty::Mutability::Not), + target: b, + }], + b, + ), + obligations: ocx.into_pending_obligations(), + }) + } else { + Err(TypeError::Mismatch) + } + } + fn coerce_from_fn_pointer( &self, a: Ty<'tcx>, diff --git a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs index a6129d97a328a..010f96f85a22b 100644 --- a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs +++ b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs @@ -751,6 +751,15 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx adjustment::Adjust::Borrow(ref autoref) => { self.walk_autoref(expr, &place_with_id, autoref); } + + adjustment::Adjust::GenericReborrow(_reborrow) => { + // To build an expression as a place expression, it needs to be a field + // projection or deref at the outmost layer. So it is field projection or deref + // on an adjusted value. But this means that adjustment is applied on a + // subexpression that is not the final operand/rvalue for function call or + // assignment. This is a contradiction. + unreachable!("Reborrow trait usage during adjustment walk"); + } } place_with_id = self.cat_expr_adjusted(expr, place_with_id, adjustment)?; } @@ -1282,7 +1291,8 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx adjustment::Adjust::NeverToAny | adjustment::Adjust::Pointer(_) - | adjustment::Adjust::Borrow(_) => { + | adjustment::Adjust::Borrow(_) + | adjustment::Adjust::GenericReborrow(..) => { // Result is an rvalue. Ok(self.cat_rvalue(expr.hir_id, target)) } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index eae0cb5cb4e2c..6dd6741b48ea5 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -343,6 +343,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Adjust::Pointer(_pointer_coercion) => { // FIXME(const_trait_impl): We should probably enforce these. } + Adjust::GenericReborrow(_) => { + // FIXME(reborrow): figure out if we have effects to enforce here. + } Adjust::Borrow(_) => { // No effects to enforce here. } diff --git a/compiler/rustc_lint/src/autorefs.rs b/compiler/rustc_lint/src/autorefs.rs index 9a374488ab6fa..24759f3ee4a01 100644 --- a/compiler/rustc_lint/src/autorefs.rs +++ b/compiler/rustc_lint/src/autorefs.rs @@ -173,6 +173,7 @@ fn has_implicit_borrow(Adjustment { kind, .. }: &Adjustment<'_>) -> Option<(Muta Adjust::NeverToAny | Adjust::Pointer(..) | Adjust::Deref(DerefAdjustKind::Builtin | DerefAdjustKind::Pin) + | Adjust::GenericReborrow(..) | Adjust::Borrow(AutoBorrow::RawPtr(..) | AutoBorrow::Pin(..)) => None, } } diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index 380ba959c25fa..e1bc29d5c3684 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -1128,6 +1128,14 @@ impl<'tcx> Debug for Rvalue<'tcx> { write!(fmt, "&{region}{kind_str}{place:?}") } + Reborrow(target, mutability, ref place) => { + write!( + fmt, + "{target:?}({} {place:?})", + if mutability.is_mut() { "reborrow" } else { "coerce shared" } + ) + } + CopyForDeref(ref place) => write!(fmt, "deref_copy {place:#?}"), RawPtr(mutability, ref place) => { diff --git a/compiler/rustc_middle/src/mir/statement.rs b/compiler/rustc_middle/src/mir/statement.rs index c5945848baad4..c1f1b398ac147 100644 --- a/compiler/rustc_middle/src/mir/statement.rs +++ b/compiler/rustc_middle/src/mir/statement.rs @@ -766,6 +766,7 @@ impl<'tcx> Rvalue<'tcx> { | Rvalue::CopyForDeref(_) | Rvalue::Repeat(_, _) | Rvalue::Ref(_, _, _) + | Rvalue::Reborrow(_, _, _) | Rvalue::ThreadLocalRef(_) | Rvalue::RawPtr(_, _) | Rvalue::Cast( @@ -790,6 +791,12 @@ impl<'tcx> Rvalue<'tcx> { } } + /// Returns true if rvalue is a generic Reborrow coercion (usage of Reborrow or CoerceShared + /// trait). + pub fn is_generic_reborrow(&self) -> bool { + matches!(self, Self::Reborrow(..)) + } + pub fn ty(&self, local_decls: &D, tcx: TyCtxt<'tcx>) -> Ty<'tcx> where D: ?Sized + HasLocalDecls<'tcx>, @@ -804,6 +811,7 @@ impl<'tcx> Rvalue<'tcx> { let place_ty = place.ty(local_decls, tcx).ty; Ty::new_ref(tcx, reg, place_ty, bk.to_mutbl_lossy()) } + Rvalue::Reborrow(target, _, _) => target, Rvalue::RawPtr(kind, ref place) => { let place_ty = place.ty(local_decls, tcx).ty; Ty::new_ptr(tcx, place_ty, kind.to_mutbl_lossy()) diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 0eeefd4060be5..8b015e6cecaae 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -1461,6 +1461,25 @@ pub enum Rvalue<'tcx> { /// Wraps a value in an unsafe binder. WrapUnsafeBinder(Operand<'tcx>, Ty<'tcx>), + + /// Creates a bitwise copy of the indicated place with the same type (if Mut) or its + /// CoerceShared target type (if Not). The type is known to be an ADT with exactly one lifetime + /// parameter, and it is known to implement the Reborrow trait (for Mut), and the CoerceShared + /// trait (only if Not). The CoerceShared target type is known to also have exactly one lifetime + /// parameter, implement Copy and (currently) have the same memory layout as the source type. + /// + /// The borrow checker uses the single lifetime in the source and target types to create a + /// Covariant outlives-bound between the source and target with the Mutability of the Reborrow. + /// This makes accessing the source value for writes (and reads if Mut) for the lifetime of the + /// target value a borrow check error, imitating `&mut T` and `&T`'s reborrowing on user ADTs. + /// + /// Future work may add support for multiple lifetimes and changing memory layout as part of + /// CoerceShared. These may be end up implemented as multiple MIR operations. + /// + /// This is produced by the [`ExprKind::Reborrow`]. + /// + /// [`ExprKind::Reborrow`]: crate::thir::ExprKind::Reborrow + Reborrow(Ty<'tcx>, Mutability, Place<'tcx>), } #[derive(Clone, Copy, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, StableHash)] diff --git a/compiler/rustc_middle/src/mir/visit.rs b/compiler/rustc_middle/src/mir/visit.rs index c660c77cd84a7..d73eac59dfb5d 100644 --- a/compiler/rustc_middle/src/mir/visit.rs +++ b/compiler/rustc_middle/src/mir/visit.rs @@ -718,6 +718,18 @@ macro_rules! make_mir_visitor { self.visit_place(path, ctx, location); } + Rvalue::Reborrow(target, mutability, place) => { + self.visit_ty($(& $mutability)? *target, TyContext::Location(location)); + self.visit_place( + place, + match mutability { + Mutability::Not => PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow), + Mutability::Mut => PlaceContext::MutatingUse(MutatingUseContext::Borrow), + }, + location + ); + } + Rvalue::CopyForDeref(place) => { self.visit_place( place, @@ -802,6 +814,8 @@ macro_rules! make_mir_visitor { self.visit_operand(op, location); self.visit_ty($(& $mutability)? *ty, TyContext::Location(location)); } + + } } diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index 887eb3d888900..0cc57e5021f86 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -546,6 +546,18 @@ pub enum ExprKind<'tcx> { Yield { value: ExprId, }, + /// Use of an ADT that implements the Reborrow (for Mut) or CoerceShared traits (for Not). This + /// expression is produced by the [`Adjust::GenericReborrow`] in places where normally the ADT + /// would be moved or assigned over. Instead, this produces an [`Rvalue::Reborrow`] which + /// produces a bitwise copy of the source ADT and disables the source for the copy's lifetime. + /// + /// [`Adjust::GenericReborrow`]: crate::ty::adjustment::Adjust::GenericReborrow + /// [`Rvalue::Reborrow`]: mir::Rvalue::Reborrow + Reborrow { + source: ExprId, + mutability: Mutability, + target: Ty<'tcx>, + }, } /// Represents the association of a field identifier and an expression. diff --git a/compiler/rustc_middle/src/thir/visit.rs b/compiler/rustc_middle/src/thir/visit.rs index aa1b6b1663bfd..3361e43b15dd9 100644 --- a/compiler/rustc_middle/src/thir/visit.rs +++ b/compiler/rustc_middle/src/thir/visit.rs @@ -187,6 +187,7 @@ pub fn walk_expr<'thir, 'tcx: 'thir, V: Visitor<'thir, 'tcx>>( } ThreadLocalRef(_) => {} Yield { value } => visitor.visit_expr(&visitor.thir()[value]), + Reborrow { .. } => {} } } diff --git a/compiler/rustc_middle/src/ty/adjustment.rs b/compiler/rustc_middle/src/ty/adjustment.rs index 7d4f56d89c7aa..7174427e517dd 100644 --- a/compiler/rustc_middle/src/ty/adjustment.rs +++ b/compiler/rustc_middle/src/ty/adjustment.rs @@ -103,6 +103,14 @@ pub enum Adjust { Borrow(AutoBorrow), Pointer(PointerCoercion), + + /// Take a user-type T implementing the Reborrow trait (for Mut) or the CoerceShared trait (for + /// Not) and reborrow as `T` or `CoreceShared`. + /// + /// This produces an [`ExprKind::Reborrow`]. + /// + /// [`ExprKind::Reborrow`]: crate::thir::ExprKind::Reborrow + GenericReborrow(hir::Mutability), } #[derive(Copy, Clone, Debug, TyEncodable, TyDecodable, StableHash, TypeFoldable, TypeVisitable)] diff --git a/compiler/rustc_mir_build/src/builder/expr/as_place.rs b/compiler/rustc_mir_build/src/builder/expr/as_place.rs index b95b565322f18..ff7e518f91a81 100644 --- a/compiler/rustc_mir_build/src/builder/expr/as_place.rs +++ b/compiler/rustc_mir_build/src/builder/expr/as_place.rs @@ -590,6 +590,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let temp = unpack!(block = this.as_temp(block, temp_lifetime, expr_id, mutability)); block.and(PlaceBuilder::from(temp)) } + ExprKind::Reborrow { .. } => { + // FIXME(reborrow): it should currently be impossible to end up evaluating a + // Reborrow expression as a place. That might not in the future, but what this then + // evaluates to requires further thought. + unreachable!(); + } } } diff --git a/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs b/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs index 3cf4e43160e5d..903ff07a88936 100644 --- a/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs +++ b/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs @@ -434,6 +434,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ); block.and(Rvalue::Use(operand, WithRetag::Yes)) } + ExprKind::Reborrow { source, mutability, target } => { + let temp = unpack!(block = this.as_temp(block, scope, source, mutability)); + block.and(Rvalue::Reborrow(target, mutability, temp.into())) + } } } diff --git a/compiler/rustc_mir_build/src/builder/expr/category.rs b/compiler/rustc_mir_build/src/builder/expr/category.rs index 5404d9800c3f5..eb6a0754358d1 100644 --- a/compiler/rustc_mir_build/src/builder/expr/category.rs +++ b/compiler/rustc_mir_build/src/builder/expr/category.rs @@ -43,7 +43,8 @@ impl Category { | ExprKind::PlaceTypeAscription { .. } | ExprKind::ValueTypeAscription { .. } | ExprKind::PlaceUnwrapUnsafeBinder { .. } - | ExprKind::ValueUnwrapUnsafeBinder { .. } => Some(Category::Place), + | ExprKind::ValueUnwrapUnsafeBinder { .. } + | ExprKind::Reborrow { .. } => Some(Category::Place), ExprKind::LogicalOp { .. } | ExprKind::Match { .. } diff --git a/compiler/rustc_mir_build/src/builder/expr/into.rs b/compiler/rustc_mir_build/src/builder/expr/into.rs index 08379760d8c1b..7413ab9bd238f 100644 --- a/compiler/rustc_mir_build/src/builder/expr/into.rs +++ b/compiler/rustc_mir_build/src/builder/expr/into.rs @@ -908,6 +908,16 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { this.cfg.push_assign(block, source_info, destination, rvalue); block.unit() } + ExprKind::Reborrow { source, mutability, target } => { + let place = unpack!(block = this.as_place(block, source)); + this.cfg.push_assign( + block, + source_info, + destination, + Rvalue::Reborrow(target, mutability, place.into()), + ); + block.unit() + } }; if !expr_is_block_or_scope { diff --git a/compiler/rustc_mir_build/src/check_unsafety.rs b/compiler/rustc_mir_build/src/check_unsafety.rs index aceddcc54de95..055932b5c30da 100644 --- a/compiler/rustc_mir_build/src/check_unsafety.rs +++ b/compiler/rustc_mir_build/src/check_unsafety.rs @@ -397,7 +397,8 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { | ExprKind::If { .. } | ExprKind::InlineAsm { .. } | ExprKind::LogicalOp { .. } - | ExprKind::Use { .. } => { + | ExprKind::Use { .. } + | ExprKind::Reborrow { .. } => { // We don't need to save the old value and restore it // because all the place expressions can't have more // than one child. diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 69260792a95d2..b32d7dce4f4d3 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -221,6 +221,13 @@ impl<'tcx> ThirBuildCx<'tcx> { debug!(?kind); kind } + Adjust::GenericReborrow(mutability) => { + let expr = self.thir.exprs.push(expr); + let kind = + ExprKind::Reborrow { source: expr, mutability, target: adjustment.target }; + + kind + } }; Expr { temp_scope_id, ty: adjustment.target, span, kind } diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index 4dc3e02ace71a..5b786f5a710f8 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -364,6 +364,12 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> { | VarRef { .. } | ZstLiteral { .. } | Yield { .. } => true, + ExprKind::Reborrow { .. } => { + // FIXME(reborrow): matching on a Reborrow expression should be impossible + // currently. Whether this remains to be true, and if the reborrow result then is a + // known valid scrutinee requires further thought. + unreachable!("Reborrow expression in match") + } } } diff --git a/compiler/rustc_mir_build/src/thir/print.rs b/compiler/rustc_mir_build/src/thir/print.rs index ea34e5f4d97df..5330e3397db8e 100644 --- a/compiler/rustc_mir_build/src/thir/print.rs +++ b/compiler/rustc_mir_build/src/thir/print.rs @@ -600,6 +600,13 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { self.print_expr(*value, depth_lvl + 2); print_indented!(self, "}", depth_lvl); } + ExprKind::Reborrow { source: _, mutability: _, target: _ } => { + print_indented!(self, "Reborrow {", depth_lvl); + print_indented!(self, "source:", depth_lvl + 1); + print_indented!(self, "mutability:", depth_lvl + 1); + print_indented!(self, "ty:", depth_lvl + 1); + print_indented!(self, "}", depth_lvl); + } } } diff --git a/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs b/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs index d5548266aa017..9ec68f5260c05 100644 --- a/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs +++ b/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs @@ -78,7 +78,8 @@ where // We ignore fake borrows as these get removed after analysis and shouldn't effect // the layout of generators. Rvalue::RawPtr(_, borrowed_place) - | Rvalue::Ref(_, BorrowKind::Mut { .. } | BorrowKind::Shared, borrowed_place) => { + | Rvalue::Ref(_, BorrowKind::Mut { .. } | BorrowKind::Shared, borrowed_place) + | Rvalue::Reborrow(_, _, borrowed_place) => { if !borrowed_place.is_indirect() { self.trans.gen_(borrowed_place.local); } diff --git a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs index ab53baae43276..905f36a3edb06 100644 --- a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs +++ b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs @@ -438,7 +438,10 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> { } } Rvalue::CopyForDeref(..) => unreachable!(), - Rvalue::Ref(..) | Rvalue::RawPtr(..) | Rvalue::Discriminant(..) => {} + Rvalue::Ref(..) + | Rvalue::Reborrow(..) + | Rvalue::RawPtr(..) + | Rvalue::Discriminant(..) => {} } } diff --git a/compiler/rustc_mir_transform/src/add_subtyping_projections.rs b/compiler/rustc_mir_transform/src/add_subtyping_projections.rs index a6a60fddf9097..fc31d502087fd 100644 --- a/compiler/rustc_mir_transform/src/add_subtyping_projections.rs +++ b/compiler/rustc_mir_transform/src/add_subtyping_projections.rs @@ -23,6 +23,9 @@ impl<'a, 'tcx> MutVisitor<'tcx> for SubTypeChecker<'a, 'tcx> { rvalue: &mut Rvalue<'tcx>, location: Location, ) { + if rvalue.is_generic_reborrow() { + return; + } // We don't need to do anything for deref temps as they are // not part of the source code, but used for desugaring purposes. if self.local_decls[place.local].is_deref_temp() { diff --git a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs index c28147ff7eb1b..1c465977ec6ca 100644 --- a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs +++ b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs @@ -469,7 +469,7 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { Rvalue::Discriminant(place) => state.get_discr(place.as_ref(), &self.map), Rvalue::Use(operand, _) => return self.handle_operand(operand, state), Rvalue::CopyForDeref(_) => bug!("`CopyForDeref` in runtime MIR"), - Rvalue::Ref(..) | Rvalue::RawPtr(..) => { + Rvalue::Ref(..) | Rvalue::Reborrow(..) | Rvalue::RawPtr(..) => { // We don't track such places. return ValueOrPlace::TOP; } diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index a7376fa7e65aa..77f068ab7a664 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -1073,6 +1073,21 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> { self.simplify_place_projection(place, location); return self.new_pointer(*place, AddressKind::Ref(borrow_kind)); } + Rvalue::Reborrow(_, mutbl, place) => { + if mutbl == Mutability::Mut { + // Note: this is adapted from simplify_aggregate. + let mut operand = Operand::Copy(place); + let val = self.simplify_operand(&mut operand, location); + // FIXME(reborrow): Is it correct to make these retagging assignments? + *rvalue = Rvalue::Use(Operand::Copy(place), WithRetag::Yes); + return val; + } else { + // FIXME(reborrow): CoerceShared should perform effectively a copy followed by a + // transmute, or possibly something more complicated in the future. For now we + // leave this unoptimised. + return None; + } + } Rvalue::RawPtr(mutbl, ref mut place) => { self.simplify_place_projection(place, location); return self.new_pointer(*place, AddressKind::Address(mutbl)); diff --git a/compiler/rustc_mir_transform/src/known_panics_lint.rs b/compiler/rustc_mir_transform/src/known_panics_lint.rs index 45c8bae295d96..6c2ca9166b101 100644 --- a/compiler/rustc_mir_transform/src/known_panics_lint.rs +++ b/compiler/rustc_mir_transform/src/known_panics_lint.rs @@ -421,8 +421,8 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { } // Do not try creating references (#67862) - Rvalue::RawPtr(_, place) | Rvalue::Ref(_, _, place) => { - trace!("skipping RawPtr | Ref for {:?}", place); + Rvalue::RawPtr(_, place) | Rvalue::Ref(_, _, place) | Rvalue::Reborrow(_, _, place) => { + trace!("skipping RawPtr | Ref | Reborrow for {:?}", place); // This may be creating mutable references or immutable references to cells. // If that happens, the pointed to value could be mutated via that reference. @@ -553,7 +553,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { self.eval_operand(operand)?.into() } - CopyForDeref(place) => self.eval_place(place)?.into(), + CopyForDeref(place) | Reborrow(_, _, place) => self.eval_place(place)?.into(), BinaryOp(bin_op, box (ref left, ref right)) => { let left = self.eval_operand(left)?; diff --git a/compiler/rustc_mir_transform/src/lint.rs b/compiler/rustc_mir_transform/src/lint.rs index e450e6754da13..e0b6b2a1104ea 100644 --- a/compiler/rustc_mir_transform/src/lint.rs +++ b/compiler/rustc_mir_transform/src/lint.rs @@ -96,6 +96,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Lint<'a, 'tcx> { | Rvalue::UnaryOp(..) | Rvalue::BinaryOp(..) | Rvalue::Ref(..) + | Rvalue::Reborrow(..) | Rvalue::RawPtr(..) | Rvalue::Discriminant(..) => false, }; diff --git a/compiler/rustc_mir_transform/src/promote_consts.rs b/compiler/rustc_mir_transform/src/promote_consts.rs index 041ff45c11d7e..340975c8fa70c 100644 --- a/compiler/rustc_mir_transform/src/promote_consts.rs +++ b/compiler/rustc_mir_transform/src/promote_consts.rs @@ -580,6 +580,12 @@ impl<'tcx> Validator<'_, 'tcx> { self.validate_ref(*kind, place)?; } + Rvalue::Reborrow(_, _, place) => { + // FIXME(reborrow): should probably have a place_simplified like above. + let op = &Operand::Copy(*place); + self.validate_operand(op)? + } + Rvalue::Aggregate(_, operands) => { for o in operands { self.validate_operand(o)?; diff --git a/compiler/rustc_mir_transform/src/validate.rs b/compiler/rustc_mir_transform/src/validate.rs index a6cbd93a6a734..746662e6a302f 100644 --- a/compiler/rustc_mir_transform/src/validate.rs +++ b/compiler/rustc_mir_transform/src/validate.rs @@ -1152,7 +1152,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { ); } } - Rvalue::Ref(..) => {} + Rvalue::Ref(..) | Rvalue::Reborrow(..) => {} Rvalue::BinaryOp(op, vals) => { use BinOp::*; let a = vals.0.ty(&self.body.local_decls, self.tcx); diff --git a/compiler/rustc_public/src/mir/body.rs b/compiler/rustc_public/src/mir/body.rs index 6aeed20b1f481..f9b5f9af951e5 100644 --- a/compiler/rustc_public/src/mir/body.rs +++ b/compiler/rustc_public/src/mir/body.rs @@ -586,6 +586,13 @@ pub enum Rvalue { /// Yields the operand unchanged, except for possibly a retag. Use(Operand, WithRetag), + + /// Creates a bitwise copy of the source type, producing either a value of the same type (when + /// Mutability::Mut) or a different type with a guaranteed equal memory layout defined by the + /// CoerceShared trait. See [`Rvalue::Reborrow`] for a more detailed explanation. + /// + /// [`Rvalue::Reborrow`]: rustc_middle::mir::Rvalue::Reborrow + Reborrow(Ty, Mutability, Place), } impl Rvalue { @@ -600,6 +607,7 @@ impl Rvalue { let place_ty = place.ty(locals)?; Ok(Ty::new_ref(reg.clone(), place_ty, bk.to_mutable_lossy())) } + Rvalue::Reborrow(target, _, _) => Ok(*target), Rvalue::AddressOf(mutability, place) => { let place_ty = place.ty(locals)?; Ok(Ty::new_ptr(place_ty, mutability.to_mutable_lossy())) diff --git a/compiler/rustc_public/src/mir/pretty.rs b/compiler/rustc_public/src/mir/pretty.rs index dac39c636e31e..dec4044fd260b 100644 --- a/compiler/rustc_public/src/mir/pretty.rs +++ b/compiler/rustc_public/src/mir/pretty.rs @@ -379,6 +379,13 @@ fn pretty_rvalue(writer: &mut W, rval: &Rvalue) -> io::Result<()> { }; write!(writer, "{kind}{place:?}") } + Rvalue::Reborrow(target, mutability, place) => { + let kind = match mutability { + Mutability::Not => "Reborrow", + Mutability::Mut => "CoerceShared", + }; + write!(writer, "${kind}({place:?}) as {target}") + } Rvalue::Repeat(op, cnst) => { write!(writer, "[{}; {}]", pretty_operand(op), pretty_ty_const(cnst)) } diff --git a/compiler/rustc_public/src/mir/visit.rs b/compiler/rustc_public/src/mir/visit.rs index cf6540cc6c772..5a3afc9937351 100644 --- a/compiler/rustc_public/src/mir/visit.rs +++ b/compiler/rustc_public/src/mir/visit.rs @@ -272,6 +272,11 @@ macro_rules! make_mir_visitor { let pcx = PlaceContext { is_mut: matches!(kind, BorrowKind::Mut { .. }) }; self.visit_place(place, pcx, location); } + Rvalue::Reborrow(target, mutability, place) => { + self.visit_ty(target, location); + let pcx = PlaceContext { is_mut: matches!(mutability, Mutability::Mut) }; + self.visit_place(place, pcx, location); + } Rvalue::Repeat(op, constant) => { self.visit_operand(op, location); self.visit_ty_const(constant, location); diff --git a/compiler/rustc_public/src/unstable/convert/stable/mir.rs b/compiler/rustc_public/src/unstable/convert/stable/mir.rs index 43c81e3dc02a8..7e76d5a91ac66 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/mir.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/mir.rs @@ -204,6 +204,11 @@ impl<'tcx> Stable<'tcx> for mir::Rvalue<'tcx> { kind.stable(tables, cx), place.stable(tables, cx), ), + Reborrow(target, kind, place) => crate::mir::Rvalue::Reborrow( + target.stable(tables, cx), + kind.stable(tables, cx), + place.stable(tables, cx), + ), ThreadLocalRef(def_id) => { crate::mir::Rvalue::ThreadLocalRef(tables.crate_item(*def_id)) } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index ed86375807756..90f6bf669a2bc 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -286,6 +286,7 @@ symbols! { Rc, RcWeak, Ready, + Reborrow, RefCell, Reference, Relaxed, @@ -624,6 +625,7 @@ symbols! { cmse_nonsecure_entry, coerce_pointee_validated, coerce_shared, + coerce_shared_target, coerce_unsized, coff, cold, diff --git a/compiler/rustc_ty_utils/src/consts.rs b/compiler/rustc_ty_utils/src/consts.rs index 49e0bdde37870..8ed32df656454 100644 --- a/compiler/rustc_ty_utils/src/consts.rs +++ b/compiler/rustc_ty_utils/src/consts.rs @@ -208,6 +208,9 @@ fn recurse_build<'tcx>( | ExprKind::ThreadLocalRef(_) => { error(GenericConstantTooComplexSub::OperationNotSupported(node.span))? } + ExprKind::Reborrow { .. } => { + todo!(); + } }) } @@ -305,6 +308,9 @@ impl<'a, 'tcx> IsThirPolymorphic<'a, 'tcx> { | thir::ExprKind::InlineAsm(_) | thir::ExprKind::ThreadLocalRef(_) | thir::ExprKind::Yield { .. } => false, + thir::ExprKind::Reborrow { .. } => { + todo!(); + } } } fn pat_is_poly(&mut self, pat: &thir::Pat<'tcx>) -> bool { diff --git a/library/core/src/marker.rs b/library/core/src/marker.rs index f56a4d7308e90..9e6b02f009837 100644 --- a/library/core/src/marker.rs +++ b/library/core/src/marker.rs @@ -1347,3 +1347,19 @@ pub macro CoercePointee($item:item) { pub trait CoercePointeeValidated { /* compiler built-in */ } + +/// Allows value to be reborrowed as exclusive, creating a copy of the value +/// that disables the source for reads and writes for the lifetime of the copy. +#[lang = "reborrow"] +#[unstable(feature = "reborrow", issue = "145612")] +pub trait Reborrow { + /* compiler built-in */ +} + +/// Allows reborrowable value to be reborrowed as shared, creating a copy +/// that disables the source for writes for the lifetime of the copy. +#[lang = "coerce_shared"] +#[unstable(feature = "reborrow", issue = "145612")] +pub trait CoerceShared: Reborrow { + /* compiler built-in */ +} diff --git a/library/core/src/ops/mod.rs b/library/core/src/ops/mod.rs index ab1ad407ee282..87dd873fdb57d 100644 --- a/library/core/src/ops/mod.rs +++ b/library/core/src/ops/mod.rs @@ -149,7 +149,6 @@ mod function; mod index; mod index_range; mod range; -mod reborrow; mod try_trait; mod unsize; @@ -190,8 +189,6 @@ pub use self::range::{Bound, RangeBounds, RangeInclusive, RangeToInclusive}; pub use self::range::{OneSidedRange, OneSidedRangeBound}; #[stable(feature = "rust1", since = "1.0.0")] pub use self::range::{Range, RangeFrom, RangeFull, RangeTo}; -#[unstable(feature = "reborrow", issue = "145612")] -pub use self::reborrow::{CoerceShared, Reborrow}; #[unstable(feature = "try_trait_v2_residual", issue = "91285")] pub use self::try_trait::Residual; #[unstable(feature = "try_trait_v2_yeet", issue = "96374")] diff --git a/library/core/src/ops/reborrow.rs b/library/core/src/ops/reborrow.rs deleted file mode 100644 index f83f4233a4de5..0000000000000 --- a/library/core/src/ops/reborrow.rs +++ /dev/null @@ -1,16 +0,0 @@ -/// Allows value to be reborrowed as exclusive, creating a copy of the value -/// that disables the source for reads and writes for the lifetime of the copy. -#[lang = "reborrow"] -#[unstable(feature = "reborrow", issue = "145612")] -pub trait Reborrow { - // Empty. -} - -/// Allows reborrowable value to be reborrowed as shared, creating a copy -/// that disables the source for writes for the lifetime of the copy. -#[lang = "coerce_shared"] -#[unstable(feature = "reborrow", issue = "145612")] -pub trait CoerceShared: Reborrow { - /// The type of this value when reborrowed as shared. - type Target: Copy; -} diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs index f4ef9f78b35ca..0f5e021788c67 100644 --- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs +++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs @@ -134,7 +134,7 @@ fn check_rvalue<'tcx>( ) -> McfResult { match rvalue { Rvalue::ThreadLocalRef(_) => Err((span, "cannot access thread local storage in const fn".into())), - Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) | Rvalue::RawPtr(_, place) => { + Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) | Rvalue::Reborrow(_, _, place) | Rvalue::RawPtr(_, place) => { check_place(cx, *place, span, body, msrv) }, Rvalue::CopyForDeref(place) => check_place(cx, *place, span, body, msrv), diff --git a/tests/ui/feature-gates/feature-gate-reborrow-coerce-shared.rs b/tests/ui/feature-gates/feature-gate-reborrow-coerce-shared.rs index c8ca453708912..48a14959d8d64 100644 --- a/tests/ui/feature-gates/feature-gate-reborrow-coerce-shared.rs +++ b/tests/ui/feature-gates/feature-gate-reborrow-coerce-shared.rs @@ -1,3 +1,3 @@ -use std::ops::CoerceShared; //~ ERROR use of unstable library feature `reborrow` +use std::marker::CoerceShared; //~ ERROR use of unstable library feature `reborrow` fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-reborrow-coerce-shared.stderr b/tests/ui/feature-gates/feature-gate-reborrow-coerce-shared.stderr index dbbbcdf2fd57d..c4c5e06778af3 100644 --- a/tests/ui/feature-gates/feature-gate-reborrow-coerce-shared.stderr +++ b/tests/ui/feature-gates/feature-gate-reborrow-coerce-shared.stderr @@ -1,8 +1,8 @@ error[E0658]: use of unstable library feature `reborrow` --> $DIR/feature-gate-reborrow-coerce-shared.rs:1:5 | -LL | use std::ops::CoerceShared; - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | use std::marker::CoerceShared; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: see issue #145612 for more information = help: add `#![feature(reborrow)]` to the crate attributes to enable diff --git a/tests/ui/feature-gates/feature-gate-reborrow.rs b/tests/ui/feature-gates/feature-gate-reborrow.rs index 96eecfb28a106..f016f6c6bfa59 100644 --- a/tests/ui/feature-gates/feature-gate-reborrow.rs +++ b/tests/ui/feature-gates/feature-gate-reborrow.rs @@ -1,3 +1,3 @@ -use std::ops::Reborrow; //~ ERROR use of unstable library feature `reborrow` +use std::marker::Reborrow; //~ ERROR use of unstable library feature `reborrow` fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-reborrow.stderr b/tests/ui/feature-gates/feature-gate-reborrow.stderr index 1224909f564bc..5e3033f3bf1fe 100644 --- a/tests/ui/feature-gates/feature-gate-reborrow.stderr +++ b/tests/ui/feature-gates/feature-gate-reborrow.stderr @@ -1,8 +1,8 @@ error[E0658]: use of unstable library feature `reborrow` --> $DIR/feature-gate-reborrow.rs:1:5 | -LL | use std::ops::Reborrow; - | ^^^^^^^^^^^^^^^^^^ +LL | use std::marker::Reborrow; + | ^^^^^^^^^^^^^^^^^^^^^ | = note: see issue #145612 for more information = help: add `#![feature(reborrow)]` to the crate attributes to enable diff --git a/tests/ui/reborrow/custom_marker.rs b/tests/ui/reborrow/custom_marker.rs new file mode 100644 index 0000000000000..80689d81d0cc1 --- /dev/null +++ b/tests/ui/reborrow/custom_marker.rs @@ -0,0 +1,17 @@ +//@ run-pass + +#![feature(reborrow)] +use std::marker::{Reborrow, PhantomData}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); +impl<'a> Reborrow for CustomMarker<'a> {} + +fn method<'a>(_a: CustomMarker<'a>) -> &'a () { + &() +} + +fn main() { + let a = CustomMarker(PhantomData); + let _ = method(a); + let _ = method(a); +} diff --git a/tests/ui/reborrow/custom_marker_assign_deref.rs b/tests/ui/reborrow/custom_marker_assign_deref.rs new file mode 100644 index 0000000000000..79ea2a35acdaf --- /dev/null +++ b/tests/ui/reborrow/custom_marker_assign_deref.rs @@ -0,0 +1,26 @@ +//@ run-pass + +#![feature(reborrow)] +use std::marker::{Reborrow, PhantomData}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); +impl<'a> Reborrow for CustomMarker<'a> {} + +impl<'a> std::ops::Deref for CustomMarker<'a> { + type Target = CustomMarker<'a>; + fn deref(&self) -> &Self::Target { + self + } +} + +impl<'a> std::ops::DerefMut for CustomMarker<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + self + } +} + +fn main() { + let mut a = CustomMarker(PhantomData); + + *a = CustomMarker(PhantomData); +} diff --git a/tests/ui/reborrow/custom_marker_coerce_shared.rs b/tests/ui/reborrow/custom_marker_coerce_shared.rs new file mode 100644 index 0000000000000..17c7bac98d17a --- /dev/null +++ b/tests/ui/reborrow/custom_marker_coerce_shared.rs @@ -0,0 +1,22 @@ +//@ run-pass + +#![feature(reborrow)] +use std::marker::{CoerceShared, PhantomData, Reborrow}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); +impl<'a> Reborrow for CustomMarker<'a> {} +#[derive(Debug, Clone, Copy)] +struct CustomMarkerRef<'a>(PhantomData<&'a ()>); +impl<'a> CoerceShared> for CustomMarker<'a> {} + + +fn method<'a>(_a: CustomMarkerRef<'a>) -> &'a () { + &() +} + +fn main() { + let a = CustomMarker(PhantomData); + let b = method(a); + let c = method(a); + let _ = (b, c); +} diff --git a/tests/ui/reborrow/custom_marker_coerce_shared_copy.rs b/tests/ui/reborrow/custom_marker_coerce_shared_copy.rs new file mode 100644 index 0000000000000..56bc1f896da0f --- /dev/null +++ b/tests/ui/reborrow/custom_marker_coerce_shared_copy.rs @@ -0,0 +1,22 @@ +//@ run-pass + +#![feature(reborrow)] +use std::marker::{CoerceShared, PhantomData, Reborrow}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); +impl<'a> Reborrow for CustomMarker<'a> {} +#[derive(Debug, Clone, Copy)] +struct CustomMarkerRef<'a>(PhantomData<&'a ()>); +impl<'a> CoerceShared> for CustomMarker<'a> {} + + +fn method<'a>(_a: CustomMarkerRef<'a>) -> &'a () { + &() +} + +fn main() { + let a = CustomMarker(PhantomData); + let b = method(a); + let c = method(a); + let _ = (&a, b, c); +} diff --git a/tests/ui/reborrow/custom_marker_coerce_shared_move.rs b/tests/ui/reborrow/custom_marker_coerce_shared_move.rs new file mode 100644 index 0000000000000..532d13da258c8 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_coerce_shared_move.rs @@ -0,0 +1,21 @@ +#![feature(reborrow)] +use std::marker::{CoerceShared, PhantomData, Reborrow}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); +impl<'a> Reborrow for CustomMarker<'a> {} +#[derive(Clone, Copy)] +struct CustomMarkerRef<'a>(PhantomData<&'a ()>); +impl<'a> CoerceShared> for CustomMarker<'a> {} + + +fn method<'a>(_a: CustomMarkerRef<'a>) -> &'a () { + &() +} + +fn main() { + let a = CustomMarker(PhantomData); + let b = method(a); + let c = method(a); + let _ = (a, b, c); + //~^ ERROR: cannot move out of `a` because it is borrowed +} diff --git a/tests/ui/reborrow/custom_marker_coerce_shared_move.stderr b/tests/ui/reborrow/custom_marker_coerce_shared_move.stderr new file mode 100644 index 0000000000000..90382af3ce30e --- /dev/null +++ b/tests/ui/reborrow/custom_marker_coerce_shared_move.stderr @@ -0,0 +1,16 @@ +error[E0505]: cannot move out of `a` because it is borrowed + --> $DIR/custom_marker_coerce_shared_move.rs:19:14 + | +LL | let a = CustomMarker(PhantomData); + | - binding `a` declared here +LL | let b = method(a); + | - borrow of `a` occurs here +LL | let c = method(a); +LL | let _ = (a, b, c); + | ^ - borrow later used here + | | + | move out of `a` occurs here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0505`. diff --git a/tests/ui/reborrow/custom_marker_deref.rs b/tests/ui/reborrow/custom_marker_deref.rs new file mode 100644 index 0000000000000..74b9bac22ed0e --- /dev/null +++ b/tests/ui/reborrow/custom_marker_deref.rs @@ -0,0 +1,17 @@ +//@ run-pass + +#![feature(reborrow)] +use std::marker::{Reborrow, PhantomData}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); +impl<'a> Reborrow for CustomMarker<'a> {} + +fn method<'a>(_a: CustomMarker<'a>) -> &'a () { + &() +} + +fn main() { + let mut a = CustomMarker(PhantomData); + let b = &mut a; + let _ = method(*b); +} diff --git a/tests/ui/reborrow/custom_marker_mut_a_b.rs b/tests/ui/reborrow/custom_marker_mut_a_b.rs new file mode 100644 index 0000000000000..3baf320b583b7 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_mut_a_b.rs @@ -0,0 +1,17 @@ +#![feature(reborrow)] +use std::marker::{Reborrow, PhantomData}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); +impl<'a> Reborrow for CustomMarker<'a> {} + +fn method<'a>(_a: CustomMarker<'a>) -> &'a () { + &() +} + +fn main() { + let a = CustomMarker(PhantomData); + let b = method(a); + let c = method(a); + //~^ ERROR: cannot borrow `a` as mutable more than once at a time + let _ = (b, c); +} diff --git a/tests/ui/reborrow/custom_marker_mut_a_b.stderr b/tests/ui/reborrow/custom_marker_mut_a_b.stderr new file mode 100644 index 0000000000000..36e3bc2918032 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_mut_a_b.stderr @@ -0,0 +1,14 @@ +error[E0499]: cannot borrow `a` as mutable more than once at a time + --> $DIR/custom_marker_mut_a_b.rs:14:20 + | +LL | let b = method(a); + | - first mutable borrow occurs here +LL | let c = method(a); + | ^ second mutable borrow occurs here +LL | +LL | let _ = (b, c); + | - first borrow later used here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0499`. diff --git a/tests/ui/reborrow/custom_marker_mut_self.rs b/tests/ui/reborrow/custom_marker_mut_self.rs new file mode 100644 index 0000000000000..a688f503517d0 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_mut_self.rs @@ -0,0 +1,15 @@ +#![feature(reborrow)] +use std::marker::{Reborrow, PhantomData}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); +impl<'a> Reborrow for CustomMarker<'a> {} + +fn method<'a>(_a: CustomMarker<'a>) -> &'a () { + &() +} + +fn main() { + let a = CustomMarker(PhantomData); + let b = method(a); + let _ = (a, b); //~ERROR cannot move out of `a` because it is borrowed +} diff --git a/tests/ui/reborrow/custom_marker_mut_self.stderr b/tests/ui/reborrow/custom_marker_mut_self.stderr new file mode 100644 index 0000000000000..77262eed339db --- /dev/null +++ b/tests/ui/reborrow/custom_marker_mut_self.stderr @@ -0,0 +1,15 @@ +error[E0505]: cannot move out of `a` because it is borrowed + --> $DIR/custom_marker_mut_self.rs:14:14 + | +LL | let a = CustomMarker(PhantomData); + | - binding `a` declared here +LL | let b = method(a); + | - borrow of `a` occurs here +LL | let _ = (a, b); + | ^ - borrow later used here + | | + | move out of `a` occurs here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0505`. diff --git a/tests/ui/reborrow/custom_marker_mut_self_a.rs b/tests/ui/reborrow/custom_marker_mut_self_a.rs new file mode 100644 index 0000000000000..f4cc8defb05e6 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_mut_self_a.rs @@ -0,0 +1,18 @@ +#![feature(reborrow)] +use std::marker::{Reborrow, PhantomData}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); +impl<'a> Reborrow for CustomMarker<'a> {} + +fn method<'a>(_a: CustomMarker<'a>) -> &'a () { + &() +} + +fn main() { + let a = CustomMarker(PhantomData); + let b = method(a); + let _ = method(a); + //~^ ERROR: cannot borrow `a` as mutable more than once at a time + let _ = (a, b); + //~^ ERROR: cannot move out of `a` because it is borrowed +} diff --git a/tests/ui/reborrow/custom_marker_mut_self_a.stderr b/tests/ui/reborrow/custom_marker_mut_self_a.stderr new file mode 100644 index 0000000000000..4241b6ec15a20 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_mut_self_a.stderr @@ -0,0 +1,28 @@ +error[E0499]: cannot borrow `a` as mutable more than once at a time + --> $DIR/custom_marker_mut_self_a.rs:14:20 + | +LL | let b = method(a); + | - first mutable borrow occurs here +LL | let _ = method(a); + | ^ second mutable borrow occurs here +LL | +LL | let _ = (a, b); + | - first borrow later used here + +error[E0505]: cannot move out of `a` because it is borrowed + --> $DIR/custom_marker_mut_self_a.rs:16:14 + | +LL | let a = CustomMarker(PhantomData); + | - binding `a` declared here +LL | let b = method(a); + | - borrow of `a` occurs here +... +LL | let _ = (a, b); + | ^ - borrow later used here + | | + | move out of `a` occurs here + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0499, E0505. +For more information about an error, try `rustc --explain E0499`. diff --git a/tests/ui/reborrow/custom_marker_mut_self_b.rs b/tests/ui/reborrow/custom_marker_mut_self_b.rs new file mode 100644 index 0000000000000..16356954908b0 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_mut_self_b.rs @@ -0,0 +1,17 @@ +#![feature(reborrow)] +use std::marker::{Reborrow, PhantomData}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); +impl<'a> Reborrow for CustomMarker<'a> {} + +fn method<'a>(_a: CustomMarker<'a>) -> &'a () { + &() +} + +fn main() { + let a = CustomMarker(PhantomData); + let _ = method(a); + let b = method(a); + let _ = (a, b); + //~^ ERROR: cannot move out of `a` because it is borrowed +} diff --git a/tests/ui/reborrow/custom_marker_mut_self_b.stderr b/tests/ui/reborrow/custom_marker_mut_self_b.stderr new file mode 100644 index 0000000000000..adca4331f1bbf --- /dev/null +++ b/tests/ui/reborrow/custom_marker_mut_self_b.stderr @@ -0,0 +1,16 @@ +error[E0505]: cannot move out of `a` because it is borrowed + --> $DIR/custom_marker_mut_self_b.rs:15:14 + | +LL | let a = CustomMarker(PhantomData); + | - binding `a` declared here +LL | let _ = method(a); +LL | let b = method(a); + | - borrow of `a` occurs here +LL | let _ = (a, b); + | ^ - borrow later used here + | | + | move out of `a` occurs here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0505`. diff --git a/tests/ui/reborrow/custom_marker_two_lifetimes.rs b/tests/ui/reborrow/custom_marker_two_lifetimes.rs new file mode 100644 index 0000000000000..d03282145054d --- /dev/null +++ b/tests/ui/reborrow/custom_marker_two_lifetimes.rs @@ -0,0 +1,8 @@ +#![feature(reborrow)] +use std::marker::{Reborrow, PhantomData}; + +struct CustomMarker<'a, 'b>(PhantomData<&'a mut ()>, PhantomData<&'b ()>); +impl<'a, 'b> Reborrow for CustomMarker<'a, 'b> {} +//~^ ERROR: implementing `Reborrow` requires that a single lifetime parameter is passed between source and target + +fn main() {} diff --git a/tests/ui/reborrow/custom_marker_two_lifetimes.stderr b/tests/ui/reborrow/custom_marker_two_lifetimes.stderr new file mode 100644 index 0000000000000..ce5c4d09aeb79 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_two_lifetimes.stderr @@ -0,0 +1,8 @@ +error: implementing `Reborrow` requires that a single lifetime parameter is passed between source and target + --> $DIR/custom_marker_two_lifetimes.rs:5:1 + | +LL | impl<'a, 'b> Reborrow for CustomMarker<'a, 'b> {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/reborrow/custom_mut.rs b/tests/ui/reborrow/custom_mut.rs index 1e7c469323822..39b5ed4906102 100644 --- a/tests/ui/reborrow/custom_mut.rs +++ b/tests/ui/reborrow/custom_mut.rs @@ -1,13 +1,16 @@ +//@ run-pass + #![feature(reborrow)] -use std::ops::Reborrow; +use std::marker::Reborrow; +#[allow(unused)] struct CustomMut<'a, T>(&'a mut T); impl<'a, T> Reborrow for CustomMut<'a, T> {} -fn method(a: CustomMut<'_, ()>) {} +fn method(_: CustomMut<'_, ()>) {} fn main() { let a = CustomMut(&mut ()); let _ = method(a); - let _ = method(a); //~ERROR use of moved value: `a` + let _ = method(a); } diff --git a/tests/ui/reborrow/custom_mut.stderr b/tests/ui/reborrow/custom_mut.stderr deleted file mode 100644 index 3b3f47b62d6fa..0000000000000 --- a/tests/ui/reborrow/custom_mut.stderr +++ /dev/null @@ -1,29 +0,0 @@ -error[E0382]: use of moved value: `a` - --> $DIR/custom_mut.rs:12:20 - | -LL | let a = CustomMut(&mut ()); - | - move occurs because `a` has type `CustomMut<'_, ()>`, which does not implement the `Copy` trait -LL | let _ = method(a); - | - value moved here -LL | let _ = method(a); - | ^ value used here after move - | -note: consider changing this parameter type in function `method` to borrow instead if owning the value isn't necessary - --> $DIR/custom_mut.rs:7:14 - | -LL | fn method(a: CustomMut<'_, ()>) {} - | ------ ^^^^^^^^^^^^^^^^^ this parameter takes ownership of the value - | | - | in this function -note: if `CustomMut<'_, ()>` implemented `Clone`, you could clone the value - --> $DIR/custom_mut.rs:4:1 - | -LL | struct CustomMut<'a, T>(&'a mut T); - | ^^^^^^^^^^^^^^^^^^^^^^^ consider implementing `Clone` for this type -... -LL | let _ = method(a); - | - you could clone this value - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0382`. diff --git a/tests/ui/reborrow/custom_mut_coerce_shared.rs b/tests/ui/reborrow/custom_mut_coerce_shared.rs index e2d25835c093a..dcc02db5802bd 100644 --- a/tests/ui/reborrow/custom_mut_coerce_shared.rs +++ b/tests/ui/reborrow/custom_mut_coerce_shared.rs @@ -1,11 +1,12 @@ +//@ run-pass + #![feature(reborrow)] -use std::ops::{CoerceShared, Reborrow}; +use std::marker::{CoerceShared, Reborrow}; +#[allow(unused)] struct CustomMut<'a, T>(&'a mut T); impl<'a, T> Reborrow for CustomMut<'a, T> {} -impl<'a, T> CoerceShared for CustomMut<'a, T> { - type Target = CustomRef<'a, T>; -} +impl<'a, T> CoerceShared> for CustomMut<'a, T> {} struct CustomRef<'a, T>(&'a T); @@ -16,13 +17,9 @@ impl<'a, T> Clone for CustomRef<'a, T> { } impl<'a, T> Copy for CustomRef<'a, T> {} -fn method(a: CustomRef<'_, ()>) {} //~NOTE function defined here +fn method(_a: CustomRef<'_, ()>) {} fn main() { let a = CustomMut(&mut ()); method(a); - //~^ ERROR mismatched types - //~| NOTE expected `CustomRef<'_, ()>`, found `CustomMut<'_, ()>` - //~| NOTE arguments to this function are incorrect - //~| NOTE expected struct `CustomRef<'_, ()>` } diff --git a/tests/ui/reborrow/custom_mut_coerce_shared.stderr b/tests/ui/reborrow/custom_mut_coerce_shared.stderr deleted file mode 100644 index 508651badc0a4..0000000000000 --- a/tests/ui/reborrow/custom_mut_coerce_shared.stderr +++ /dev/null @@ -1,19 +0,0 @@ -error[E0308]: mismatched types - --> $DIR/custom_mut_coerce_shared.rs:23:12 - | -LL | method(a); - | ------ ^ expected `CustomRef<'_, ()>`, found `CustomMut<'_, ()>` - | | - | arguments to this function are incorrect - | - = note: expected struct `CustomRef<'_, ()>` - found struct `CustomMut<'_, ()>` -note: function defined here - --> $DIR/custom_mut_coerce_shared.rs:19:4 - | -LL | fn method(a: CustomRef<'_, ()>) {} - | ^^^^^^ -------------------- - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0308`. From 506693e7d632ab9fe267536608a3770ed71a09b8 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 1 May 2026 11:05:26 -0700 Subject: [PATCH 6/6] Support `-Cpanic=unwind` on WASI targets This commit is some minor updates/restructuring in a few locations with the end result being supporting `-Cpanic=unwind` on WASI targets. This continues to be off-by-default insofar as WASI targets default to `-Cpanic=abort`, meaning that actually using anything in this commit requires `-Zbuild-std`. Specifically the changes made here are: * The self-contained sysroot for WASI targets now contains a copy of `libunwind.a` from wasi-sdk, first shipped with wasi-sdk-33 (also updated here). * The `unwind` crate here in this repository uses the `libunwind` module instead of the custom bare-metal wasm implementation of exceptions. This means that Rust uses the `_Unwind_*` symbols which allows it to interoperate with C/C++/etc. * Wasm targets are all updated to pass the LLVM argument `-wasm-use-legacy-eh=false` to differ from LLVM's/clang's default of using the legacy exception handling proposal for WebAssembly. This has no effect by default because `panic=abort` is used on most targets. Emscripten is exempted from this as the Emscripten target is explicitly intended to follow LLVM's/clang's defaults. * There's a single test in the test suite that links to the `panic_unwind` crate which ended up requiring `-Wexceptions` from Wasmtime, so the test parts were updated and Wasmtime was updated in CI, too. The net result of all of this is that this should not actually affect any WebAssembly target's default behavior. Optionally, though, WASI programs can be built with exception handling via: RUSTFLAGS='-Cpanic=unwind' cargo +nightly run -Z build-std --target wasm32-wasip2 Effectively `-Zbuild-std` and `-Cpanic=unwind` is all that's necessary to enable this support on wasm targets. Finally, this ends up closing 154593 as well. The WASI targets are now defined to use `-lunwind` to implement unwinding. This means that the in-tree definition of `__cpp_exception` is no longer of concern and the definition is always sourced externally. If Rust is linked with other C/C++ code using WASI then these idioms are compatible with wasi-sdk, for example, to use that as a linker. The main caveat is that when using an external linker the `-fwasm-exceptions` argument needs to be passed to `clang` for it to be able to find the `libunwind.a` library to link against. Closes 154593 --- compiler/rustc_target/src/spec/base/wasm.rs | 8 ++++++++ .../spec/targets/wasm32_unknown_emscripten.rs | 5 +++++ library/unwind/src/lib.rs | 1 + library/unwind/src/libunwind.rs | 9 ++++++++- src/bootstrap/src/core/build_steps/compile.rs | 10 ++++++++++ src/bootstrap/src/lib.rs | 2 +- .../docker/host-x86_64/dist-various-2/Dockerfile | 4 ++-- src/ci/docker/host-x86_64/pr-check-2/Dockerfile | 4 ++-- .../docker/host-x86_64/test-various/Dockerfile | 8 ++++---- tests/assembly-llvm/wasm_exceptions.rs | 16 ++++++++-------- 10 files changed, 49 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_target/src/spec/base/wasm.rs b/compiler/rustc_target/src/spec/base/wasm.rs index 1f0f564a77908..587eeac14beb7 100644 --- a/compiler/rustc_target/src/spec/base/wasm.rs +++ b/compiler/rustc_target/src/spec/base/wasm.rs @@ -110,6 +110,14 @@ pub(crate) fn options() -> TargetOptions { // representation, so this is disabled. generate_arange_section: false, + // Differ from LLVM's default to use the legacy exception-handling + // proposal instructions and use the standard exception-handling + // instructions. Note that this is only applicable when unwinding is + // actually turned on, which it's not by default on this target. For + // `-Zbuild-std` builds, however, this affects when rebuilding libstd + // with unwinding. + llvm_args: cvs!["-wasm-use-legacy-eh=false"], + ..Default::default() } } diff --git a/compiler/rustc_target/src/spec/targets/wasm32_unknown_emscripten.rs b/compiler/rustc_target/src/spec/targets/wasm32_unknown_emscripten.rs index 4b6f5b655760b..e640e31164596 100644 --- a/compiler/rustc_target/src/spec/targets/wasm32_unknown_emscripten.rs +++ b/compiler/rustc_target/src/spec/targets/wasm32_unknown_emscripten.rs @@ -31,6 +31,11 @@ pub(crate) fn target() -> Target { panic_strategy: PanicStrategy::Unwind, no_default_libraries: false, families: cvs!["unix", "wasm"], + // Explicitly override the `base::wasm`'s `llvm_args` back to empty. The + // base is to force using the most standard exception-handling + // instructions, when enabled, but this target is intended to follow + // Emscripten, which is whatever LLVM defaults to. + llvm_args: cvs![], ..base::wasm::options() }; Target { diff --git a/library/unwind/src/lib.rs b/library/unwind/src/lib.rs index cce6ca748cccd..22568d5f6f1f9 100644 --- a/library/unwind/src/lib.rs +++ b/library/unwind/src/lib.rs @@ -33,6 +33,7 @@ cfg_select! { target_os = "psp", target_os = "solid_asp3", all(target_vendor = "fortanix", target_env = "sgx"), + all(target_os = "wasi", panic = "unwind"), ) => { mod libunwind; pub use libunwind::*; diff --git a/library/unwind/src/libunwind.rs b/library/unwind/src/libunwind.rs index 091efa9c51292..ead6e0827501d 100644 --- a/library/unwind/src/libunwind.rs +++ b/library/unwind/src/libunwind.rs @@ -75,7 +75,7 @@ pub const unwinder_private_data_size: usize = 2; #[cfg(all(target_arch = "wasm32", target_os = "emscripten"))] pub const unwinder_private_data_size: usize = 20; -#[cfg(all(target_arch = "wasm32", target_os = "linux"))] +#[cfg(all(target_arch = "wasm32", any(target_os = "linux", target_os = "wasi")))] pub const unwinder_private_data_size: usize = 2; #[cfg(target_arch = "hexagon")] @@ -111,6 +111,13 @@ pub type _Unwind_Exception_Cleanup_Fn = ), link(name = "unwind", kind = "static", modifiers = "-bundle") )] +// Explicitly link the `unwind` library on WASI targets. +// +// This is provided in the self-contained sysroot for WASI targets by default. +// Note that Rust defaults to `-Cpanic=abort` on WASI targets meaning that this +// doesn't end up getting used by default, but this does mean that with +// `-Zbuild-std` this'll automatically link it in. +#[cfg_attr(target_os = "wasi", link(name = "unwind"))] unsafe extern "C-unwind" { pub fn _Unwind_Resume(exception: *mut _Unwind_Exception) -> !; } diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 46d05b9d5d2f7..68a4f928464f1 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -457,6 +457,16 @@ fn copy_self_contained_objects( DependencyType::TargetSelfContained, ); } + if srcdir.join("eh").exists() { + copy_and_stamp( + builder, + &libdir_self_contained, + &srcdir.join("eh"), + "libunwind.a", + &mut target_deps, + DependencyType::TargetSelfContained, + ); + } } else if target.is_windows_gnu() || target.is_windows_gnullvm() { for obj in ["crt2.o", "dllcrt2.o"].iter() { let src = compiler_file(builder, &builder.cc(target), target, CLang::C, obj); diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index fd3d129c231d7..6518224576c9d 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -1500,7 +1500,7 @@ impl Build { if let Some(path) = finder.maybe_have("wasmtime") && let Ok(mut path) = path.into_os_string().into_string() { - path.push_str(" run -C cache=n --dir ."); + path.push_str(" run -Wexceptions -C cache=n --dir ."); // Make sure that tests have access to RUSTC_BOOTSTRAP. This (for example) is // required for libtest to work on beta/stable channels. // diff --git a/src/ci/docker/host-x86_64/dist-various-2/Dockerfile b/src/ci/docker/host-x86_64/dist-various-2/Dockerfile index 71ff3ea3a76a9..0414933801294 100644 --- a/src/ci/docker/host-x86_64/dist-various-2/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-various-2/Dockerfile @@ -87,9 +87,9 @@ RUN /tmp/build-fuchsia-toolchain.sh COPY host-x86_64/dist-various-2/build-x86_64-fortanix-unknown-sgx-toolchain.sh /tmp/ RUN /tmp/build-x86_64-fortanix-unknown-sgx-toolchain.sh -RUN curl -L https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-32/wasi-sdk-32.0-x86_64-linux.tar.gz | \ +RUN curl -L https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-33/wasi-sdk-33.0-x86_64-linux.tar.gz | \ tar -xz -ENV WASI_SDK_PATH=/tmp/wasi-sdk-32.0-x86_64-linux +ENV WASI_SDK_PATH=/tmp/wasi-sdk-33.0-x86_64-linux COPY scripts/freebsd-toolchain.sh /tmp/ RUN /tmp/freebsd-toolchain.sh i686 diff --git a/src/ci/docker/host-x86_64/pr-check-2/Dockerfile b/src/ci/docker/host-x86_64/pr-check-2/Dockerfile index 23976199b8a9b..acbd99db6c4f8 100644 --- a/src/ci/docker/host-x86_64/pr-check-2/Dockerfile +++ b/src/ci/docker/host-x86_64/pr-check-2/Dockerfile @@ -21,9 +21,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ mingw-w64 \ && rm -rf /var/lib/apt/lists/* -RUN curl -L https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-32/wasi-sdk-32.0-x86_64-linux.tar.gz | \ +RUN curl -L https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-33/wasi-sdk-33.0-x86_64-linux.tar.gz | \ tar -xz -ENV WASI_SDK_PATH=/wasi-sdk-32.0-x86_64-linux +ENV WASI_SDK_PATH=/wasi-sdk-33.0-x86_64-linux ENV RUST_CONFIGURE_ARGS="--set rust.validate-mir-opts=3" diff --git a/src/ci/docker/host-x86_64/test-various/Dockerfile b/src/ci/docker/host-x86_64/test-various/Dockerfile index 104a40403d3e9..26e77bc5da200 100644 --- a/src/ci/docker/host-x86_64/test-various/Dockerfile +++ b/src/ci/docker/host-x86_64/test-various/Dockerfile @@ -43,9 +43,9 @@ WORKDIR / COPY scripts/sccache.sh /scripts/ RUN sh /scripts/sccache.sh -RUN curl -L https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-32/wasi-sdk-32.0-x86_64-linux.tar.gz | \ +RUN curl -L https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-33/wasi-sdk-33.0-x86_64-linux.tar.gz | \ tar -xz -ENV WASI_SDK_PATH=/wasi-sdk-32.0-x86_64-linux +ENV WASI_SDK_PATH=/wasi-sdk-33.0-x86_64-linux ENV RUST_CONFIGURE_ARGS="--musl-root-x86_64=/usr/local/x86_64-linux-musl \ --set rust.lld" @@ -57,9 +57,9 @@ ENV RUST_CONFIGURE_ARGS="--musl-root-x86_64=/usr/local/x86_64-linux-musl \ ENV NO_DEBUG_ASSERTIONS=1 ENV NO_OVERFLOW_CHECKS=1 -RUN curl -L https://github.com/bytecodealliance/wasmtime/releases/download/v38.0.4/wasmtime-v38.0.4-x86_64-linux.tar.xz | \ +RUN curl -L https://github.com/bytecodealliance/wasmtime/releases/download/v44.0.1/wasmtime-v44.0.1-x86_64-linux.tar.xz | \ tar -xJ -ENV PATH="$PATH:/wasmtime-v38.0.4-x86_64-linux" +ENV PATH="$PATH:/wasmtime-v44.0.1-x86_64-linux" ENV WASM_WASIP_TARGET=wasm32-wasip1 ENV WASM_WASIP_SCRIPT="python3 /checkout/x.py --stage 2 test --host= --target $WASM_WASIP_TARGET \ diff --git a/tests/assembly-llvm/wasm_exceptions.rs b/tests/assembly-llvm/wasm_exceptions.rs index 704e8026f3f48..69127462a0199 100644 --- a/tests/assembly-llvm/wasm_exceptions.rs +++ b/tests/assembly-llvm/wasm_exceptions.rs @@ -33,11 +33,11 @@ pub fn test_cleanup() { } // CHECK-NOT: call - // CHECK: try + // CHECK: try_table (catch_all_ref 0) // CHECK: call may_panic - // CHECK: catch_all - // CHECK: rethrow - // CHECK: end_try + // CHECK: end_try_table + // CHECK: call log_number + // CHECK: throw_ref } // CHECK-LABEL: test_rtry: @@ -57,11 +57,11 @@ pub fn test_rtry() { } // CHECK-NOT: call - // CHECK: try + // CHECK: try_table (catch __cpp_exception 0) // CHECK: call may_panic - // CHECK: catch + // CHECK: end_try_table // CHECK: call log_number // CHECK: call log_number - // CHECK-NOT: rethrow - // CHECK: end_try + // CHECK-NOT: throw_ref + // CHECK: end_function }