From 2a870404fd3e2292955478875336217a27939202 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Thu, 12 Mar 2026 12:50:16 +1100 Subject: [PATCH 1/4] 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/4] 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/4] 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 8e1f263f663f8001a7f2bc7d117a73c2aead1ffd Mon Sep 17 00:00:00 2001 From: yukang Date: Sat, 29 Nov 2025 21:18:27 +0800 Subject: [PATCH 4/4] Skipping borrowck because of trivial const --- compiler/rustc_borrowck/src/lib.rs | 5 +++ .../rustc_mir_transform/src/trivial_const.rs | 4 ++ .../const-traits/trivial-const-ice-149278.rs | 11 ++++++ .../trivial-const-ice-149278.stderr | 37 +++++++++++++++++++ .../opaques/trivial-const-defines-opaque.rs | 15 ++++++++ 5 files changed, 72 insertions(+) create mode 100644 tests/ui/traits/const-traits/trivial-const-ice-149278.rs create mode 100644 tests/ui/traits/const-traits/trivial-const-ice-149278.stderr create mode 100644 tests/ui/traits/next-solver/opaques/trivial-const-defines-opaque.rs diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index acdeea91a189f..168157cf29207 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -115,6 +115,11 @@ fn mir_borrowck( def: LocalDefId, ) -> Result<&FxIndexMap>, ErrorGuaranteed> { assert!(!tcx.is_typeck_child(def.to_def_id())); + if tcx.is_trivial_const(def) { + debug!("Skipping borrowck because of trivial const"); + let opaque_types = Default::default(); + return Ok(tcx.arena.alloc(opaque_types)); + } let (input_body, _) = tcx.mir_promoted(def); debug!("run query mir_borrowck: {}", tcx.def_path_str(def)); diff --git a/compiler/rustc_mir_transform/src/trivial_const.rs b/compiler/rustc_mir_transform/src/trivial_const.rs index 027e9ec631666..a2ab6ff2cd802 100644 --- a/compiler/rustc_mir_transform/src/trivial_const.rs +++ b/compiler/rustc_mir_transform/src/trivial_const.rs @@ -59,6 +59,10 @@ where return None; } + if !tcx.opaque_types_defined_by(def).is_empty() { + return None; + } + let body = body_provider(); if body.has_opaque_types() { diff --git a/tests/ui/traits/const-traits/trivial-const-ice-149278.rs b/tests/ui/traits/const-traits/trivial-const-ice-149278.rs new file mode 100644 index 0000000000000..bfc49ebe02090 --- /dev/null +++ b/tests/ui/traits/const-traits/trivial-const-ice-149278.rs @@ -0,0 +1,11 @@ +trait Trait2: Sized {} + +impl Trait2 for () { + const FOO: () = { + //~^ ERROR const `FOO` is not a member of trait `Trait2` + //~^^ ERROR item does not constrain `Assoc::{opaque#0}` + type Assoc = impl Copy; //~ ERROR `impl Trait` in type aliases is unstable + }; +} + +fn main() {} diff --git a/tests/ui/traits/const-traits/trivial-const-ice-149278.stderr b/tests/ui/traits/const-traits/trivial-const-ice-149278.stderr new file mode 100644 index 0000000000000..9b328bd59d24c --- /dev/null +++ b/tests/ui/traits/const-traits/trivial-const-ice-149278.stderr @@ -0,0 +1,37 @@ +error[E0438]: const `FOO` is not a member of trait `Trait2` + --> $DIR/trivial-const-ice-149278.rs:4:5 + | +LL | / const FOO: () = { +LL | | +LL | | +LL | | type Assoc = impl Copy; +LL | | }; + | |______^ not a member of trait `Trait2` + +error[E0658]: `impl Trait` in type aliases is unstable + --> $DIR/trivial-const-ice-149278.rs:7:22 + | +LL | type Assoc = impl Copy; + | ^^^^^^^^^ + | + = note: see issue #63063 for more information + = help: add `#![feature(type_alias_impl_trait)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: item does not constrain `Assoc::{opaque#0}` + --> $DIR/trivial-const-ice-149278.rs:4:11 + | +LL | const FOO: () = { + | ^^^ + | + = note: consider removing `#[define_opaque]` or adding an empty `#[define_opaque()]` +note: this opaque type is supposed to be constrained + --> $DIR/trivial-const-ice-149278.rs:7:22 + | +LL | type Assoc = impl Copy; + | ^^^^^^^^^ + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0438, E0658. +For more information about an error, try `rustc --explain E0438`. diff --git a/tests/ui/traits/next-solver/opaques/trivial-const-defines-opaque.rs b/tests/ui/traits/next-solver/opaques/trivial-const-defines-opaque.rs new file mode 100644 index 0000000000000..addaba90d48ac --- /dev/null +++ b/tests/ui/traits/next-solver/opaques/trivial-const-defines-opaque.rs @@ -0,0 +1,15 @@ +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver +//@ check-pass + +#![feature(type_alias_impl_trait)] + +type Tait = impl Sized; + +#[define_opaque(Tait)] +const FOO: Tait = 1; + +fn main() { + let _: Tait = FOO; +}