From 8ca25b8e49ca3442a56029f59677dfaab5b6eaf5 Mon Sep 17 00:00:00 2001 From: Markus Everling Date: Thu, 29 Dec 2022 23:43:34 +0100 Subject: [PATCH 01/14] Fix `vec_deque::Drain` FIXME --- .../alloc/src/collections/vec_deque/drain.rs | 28 +++++-------------- .../alloc/src/collections/vec_deque/mod.rs | 18 ++++++------ 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/library/alloc/src/collections/vec_deque/drain.rs b/library/alloc/src/collections/vec_deque/drain.rs index 89feb361ddc..a102aaad452 100644 --- a/library/alloc/src/collections/vec_deque/drain.rs +++ b/library/alloc/src/collections/vec_deque/drain.rs @@ -57,31 +57,17 @@ pub(super) unsafe fn new( unsafe fn as_slices(&self) -> (*mut [T], *mut [T]) { unsafe { let deque = self.deque.as_ref(); - // FIXME: This is doing almost exactly the same thing as the else branch in `VecDeque::slice_ranges`. - // Unfortunately, we can't just call `slice_ranges` here, as the deque's `len` is currently - // just `drain_start`, so the range check would (almost) always panic. Between temporarily - // adjusting the deques `len` to call `slice_ranges`, and just copy pasting the `slice_ranges` - // implementation, this seemed like the less hacky solution, though it might be good to - // find a better one in the future. - // because `self.remaining != 0`, we know that `self.idx < deque.original_len`, so it's a valid - // logical index. - let wrapped_start = deque.to_physical_idx(self.idx); + let start = self.idx; + // We know that `self.idx + self.remaining <= deque.len <= usize::MAX`, so this won't overflow. + let end = start + self.remaining; - let head_len = deque.capacity() - wrapped_start; - - let (a_range, b_range) = if head_len >= self.remaining { - (wrapped_start..wrapped_start + self.remaining, 0..0) - } else { - let tail_len = self.remaining - head_len; - (wrapped_start..deque.capacity(), 0..tail_len) - }; - - // SAFETY: the range `self.idx..self.idx+self.remaining` lies strictly inside - // the range `0..deque.original_len`. because of this, and because of the fact - // that we acquire `a_range` and `b_range` exactly like `slice_ranges` would, + // SAFETY: the range `start..end` lies strictly inside + // the range `0..deque.original_len`. Because of this, and because + // we haven't touched the elements inside this range yet, // it's guaranteed that `a_range` and `b_range` represent valid ranges into // the deques buffer. + let (a_range, b_range) = deque.slice_ranges(start..end, end); (deque.buffer_range(a_range), deque.buffer_range(b_range)) } } diff --git a/library/alloc/src/collections/vec_deque/mod.rs b/library/alloc/src/collections/vec_deque/mod.rs index c955db46d29..6d3e784c8b7 100644 --- a/library/alloc/src/collections/vec_deque/mod.rs +++ b/library/alloc/src/collections/vec_deque/mod.rs @@ -1147,7 +1147,7 @@ pub fn iter_mut(&mut self) -> IterMut<'_, T> { #[inline] #[stable(feature = "deque_extras_15", since = "1.5.0")] pub fn as_slices(&self) -> (&[T], &[T]) { - let (a_range, b_range) = self.slice_ranges(..); + let (a_range, b_range) = self.slice_ranges(.., self.len); // SAFETY: `slice_ranges` always returns valid ranges into // the physical buffer. unsafe { (&*self.buffer_range(a_range), &*self.buffer_range(b_range)) } @@ -1181,7 +1181,7 @@ pub fn as_slices(&self) -> (&[T], &[T]) { #[inline] #[stable(feature = "deque_extras_15", since = "1.5.0")] pub fn as_mut_slices(&mut self) -> (&mut [T], &mut [T]) { - let (a_range, b_range) = self.slice_ranges(..); + let (a_range, b_range) = self.slice_ranges(.., self.len); // SAFETY: `slice_ranges` always returns valid ranges into // the physical buffer. unsafe { (&mut *self.buffer_range(a_range), &mut *self.buffer_range(b_range)) } @@ -1223,19 +1223,21 @@ pub fn is_empty(&self) -> bool { /// Given a range into the logical buffer of the deque, this function /// return two ranges into the physical buffer that correspond to - /// the given range. - fn slice_ranges(&self, range: R) -> (Range, Range) + /// the given range. The `len` parameter should usually just be `self.len`; + /// the reason it's passed explicitly is that if the deque is wrapped in + /// a `Drain`, then `self.len` is not actually the length of the deque. + fn slice_ranges(&self, range: R, len: usize) -> (Range, Range) where R: RangeBounds, { - let Range { start, end } = slice::range(range, ..self.len); + let Range { start, end } = slice::range(range, ..len); let len = end - start; if len == 0 { (0..0, 0..0) } else { // `slice::range` guarantees that `start <= end <= self.len`. - // because `len != 0`, we know that `start < end`, so `start < self.len` + // because `len != 0`, we know that `start < end`, so `start < len` // and the indexing is valid. let wrapped_start = self.to_physical_idx(start); @@ -1281,7 +1283,7 @@ pub fn range(&self, range: R) -> Iter<'_, T> where R: RangeBounds, { - let (a_range, b_range) = self.slice_ranges(range); + let (a_range, b_range) = self.slice_ranges(range, self.len); // SAFETY: The ranges returned by `slice_ranges` // are valid ranges into the physical buffer, so // it's ok to pass them to `buffer_range` and @@ -1321,7 +1323,7 @@ pub fn range_mut(&mut self, range: R) -> IterMut<'_, T> where R: RangeBounds, { - let (a_range, b_range) = self.slice_ranges(range); + let (a_range, b_range) = self.slice_ranges(range, self.len); // SAFETY: The ranges returned by `slice_ranges` // are valid ranges into the physical buffer, so // it's ok to pass them to `buffer_range` and From 1e114a88bde098d1c057161aa252fa75d5739592 Mon Sep 17 00:00:00 2001 From: Markus Everling Date: Sun, 5 Feb 2023 02:16:43 +0100 Subject: [PATCH 02/14] Add `slice_ranges` safety comment --- library/alloc/src/collections/vec_deque/drain.rs | 9 ++++----- library/alloc/src/collections/vec_deque/mod.rs | 8 ++++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/library/alloc/src/collections/vec_deque/drain.rs b/library/alloc/src/collections/vec_deque/drain.rs index a102aaad452..99bd7902e69 100644 --- a/library/alloc/src/collections/vec_deque/drain.rs +++ b/library/alloc/src/collections/vec_deque/drain.rs @@ -62,11 +62,10 @@ unsafe fn as_slices(&self) -> (*mut [T], *mut [T]) { // We know that `self.idx + self.remaining <= deque.len <= usize::MAX`, so this won't overflow. let end = start + self.remaining; - // SAFETY: the range `start..end` lies strictly inside - // the range `0..deque.original_len`. Because of this, and because - // we haven't touched the elements inside this range yet, - // it's guaranteed that `a_range` and `b_range` represent valid ranges into - // the deques buffer. + // SAFETY: `start..end` represents the range of elements that + // haven't been drained yet, so they're all initialized, + // and `slice::range(start..end, end) == start..end`, + // so the preconditions for `slice_ranges` are met. let (a_range, b_range) = deque.slice_ranges(start..end, end); (deque.buffer_range(a_range), deque.buffer_range(b_range)) } diff --git a/library/alloc/src/collections/vec_deque/mod.rs b/library/alloc/src/collections/vec_deque/mod.rs index 6d3e784c8b7..813430ae615 100644 --- a/library/alloc/src/collections/vec_deque/mod.rs +++ b/library/alloc/src/collections/vec_deque/mod.rs @@ -1226,6 +1226,14 @@ pub fn is_empty(&self) -> bool { /// the given range. The `len` parameter should usually just be `self.len`; /// the reason it's passed explicitly is that if the deque is wrapped in /// a `Drain`, then `self.len` is not actually the length of the deque. + /// + /// # Safety + /// + /// This function is always safe to call. For the resulting ranges to be valid + /// ranges into the physical buffer, the caller must ensure that for all possible + /// values of `range` and `len`, the result of calling `slice::range(range, ..len)` + /// represents a valid range into the logical buffer, and that all elements + /// in that range are initialized. fn slice_ranges(&self, range: R, len: usize) -> (Range, Range) where R: RangeBounds, From acc876e42fce0dc3d6596f754dcfbc0ce07cabd6 Mon Sep 17 00:00:00 2001 From: Markus Everling Date: Mon, 20 Feb 2023 23:25:26 +0100 Subject: [PATCH 03/14] Changes according to review --- library/alloc/src/collections/vec_deque/drain.rs | 13 +++++++------ library/alloc/src/collections/vec_deque/mod.rs | 9 ++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/library/alloc/src/collections/vec_deque/drain.rs b/library/alloc/src/collections/vec_deque/drain.rs index 99bd7902e69..0be274a3822 100644 --- a/library/alloc/src/collections/vec_deque/drain.rs +++ b/library/alloc/src/collections/vec_deque/drain.rs @@ -52,21 +52,22 @@ pub(super) unsafe fn new( } } - // Only returns pointers to the slices, as that's - // all we need to drop them. May only be called if `self.remaining != 0`. + // Only returns pointers to the slices, as that's all we need + // to drop them. May only be called if `self.remaining != 0`. unsafe fn as_slices(&self) -> (*mut [T], *mut [T]) { unsafe { let deque = self.deque.as_ref(); - let start = self.idx; // We know that `self.idx + self.remaining <= deque.len <= usize::MAX`, so this won't overflow. - let end = start + self.remaining; + let logical_remaining_range = self.idx..self.idx + self.remaining; - // SAFETY: `start..end` represents the range of elements that + // SAFETY: `logical_remaining_range` represents the + // range into the logical buffer of elements that // haven't been drained yet, so they're all initialized, // and `slice::range(start..end, end) == start..end`, // so the preconditions for `slice_ranges` are met. - let (a_range, b_range) = deque.slice_ranges(start..end, end); + let (a_range, b_range) = + deque.slice_ranges(logical_remaining_range.clone(), logical_remaining_range.end); (deque.buffer_range(a_range), deque.buffer_range(b_range)) } } diff --git a/library/alloc/src/collections/vec_deque/mod.rs b/library/alloc/src/collections/vec_deque/mod.rs index 813430ae615..40f31f1a194 100644 --- a/library/alloc/src/collections/vec_deque/mod.rs +++ b/library/alloc/src/collections/vec_deque/mod.rs @@ -1230,10 +1230,9 @@ pub fn is_empty(&self) -> bool { /// # Safety /// /// This function is always safe to call. For the resulting ranges to be valid - /// ranges into the physical buffer, the caller must ensure that for all possible - /// values of `range` and `len`, the result of calling `slice::range(range, ..len)` - /// represents a valid range into the logical buffer, and that all elements - /// in that range are initialized. + /// ranges into the physical buffer, the caller must ensure that the result of + /// calling `slice::range(range, ..len)` represents a valid range into the + /// logical buffer, and that all elements in that range are initialized. fn slice_ranges(&self, range: R, len: usize) -> (Range, Range) where R: RangeBounds, @@ -1244,7 +1243,7 @@ fn slice_ranges(&self, range: R, len: usize) -> (Range, Range) if len == 0 { (0..0, 0..0) } else { - // `slice::range` guarantees that `start <= end <= self.len`. + // `slice::range` guarantees that `start <= end <= len`. // because `len != 0`, we know that `start < end`, so `start < len` // and the indexing is valid. let wrapped_start = self.to_physical_idx(start); From 3455d66041eb1d766814b57608d7ae8f20c8b04e Mon Sep 17 00:00:00 2001 From: Urgau Date: Thu, 9 Mar 2023 21:10:08 +0100 Subject: [PATCH 04/14] Honor current target when checking conditional compilation values This is fixed by simply using the currently registered target in the current session. We need to use it because of target json that are not by design included in the rustc list of targets. --- compiler/rustc_interface/src/util.rs | 2 +- compiler/rustc_session/src/config.rs | 7 ++++--- tests/ui/check-cfg/my-awesome-platform.json | 12 +++++++++++ tests/ui/check-cfg/values-target-json.rs | 21 ++++++++++++++++++++ tests/ui/check-cfg/values-target-json.stderr | 13 ++++++++++++ 5 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 tests/ui/check-cfg/my-awesome-platform.json create mode 100644 tests/ui/check-cfg/values-target-json.rs create mode 100644 tests/ui/check-cfg/values-target-json.stderr diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index e5d2fb2ea28..043892410ce 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -110,7 +110,7 @@ pub fn create_session( add_configuration(&mut cfg, &mut sess, &*codegen_backend); let mut check_cfg = config::to_crate_check_config(check_cfg); - check_cfg.fill_well_known(); + check_cfg.fill_well_known(&sess.target); sess.parse_sess.config = cfg; sess.parse_sess.check_config = check_cfg; diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index d4e4ace889b..485c3f55462 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -1137,7 +1137,7 @@ fn fill_well_known_names(&mut self) { } /// Fills a `CrateCheckConfig` with well-known configuration values. - fn fill_well_known_values(&mut self) { + fn fill_well_known_values(&mut self, current_target: &Target) { if !self.well_known_values { return; } @@ -1229,6 +1229,7 @@ fn fill_well_known_values(&mut self) { for target in TARGETS .iter() .map(|target| Target::expect_builtin(&TargetTriple::from_triple(target))) + .chain(iter::once(current_target.clone())) { values_target_os.insert(Symbol::intern(&target.options.os)); values_target_family @@ -1243,9 +1244,9 @@ fn fill_well_known_values(&mut self) { } } - pub fn fill_well_known(&mut self) { + pub fn fill_well_known(&mut self, current_target: &Target) { self.fill_well_known_names(); - self.fill_well_known_values(); + self.fill_well_known_values(current_target); } } diff --git a/tests/ui/check-cfg/my-awesome-platform.json b/tests/ui/check-cfg/my-awesome-platform.json new file mode 100644 index 00000000000..5e9ab8f1a2d --- /dev/null +++ b/tests/ui/check-cfg/my-awesome-platform.json @@ -0,0 +1,12 @@ +{ + "llvm-target": "x86_64-unknown-none-gnu", + "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", + "arch": "x86_64", + "target-endian": "little", + "target-pointer-width": "64", + "target-c-int-width": "32", + "os": "ericos", + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "executables": true +} diff --git a/tests/ui/check-cfg/values-target-json.rs b/tests/ui/check-cfg/values-target-json.rs new file mode 100644 index 00000000000..2ef5a44592b --- /dev/null +++ b/tests/ui/check-cfg/values-target-json.rs @@ -0,0 +1,21 @@ +// This test checks that we don't lint values defined by a custom target (target json) +// +// check-pass +// needs-llvm-components: x86 +// compile-flags: --crate-type=lib --check-cfg=values() --target={{src-base}}/check-cfg/my-awesome-platform.json -Z unstable-options + +#![feature(lang_items, no_core, auto_traits)] +#![no_core] + +#[lang = "sized"] +trait Sized {} + +#[cfg(target_os = "linuz")] +//~^ WARNING unexpected `cfg` condition value +fn target_os_linux_misspell() {} + +#[cfg(target_os = "linux")] +fn target_os_linux() {} + +#[cfg(target_os = "ericos")] +fn target_os_ericos() {} diff --git a/tests/ui/check-cfg/values-target-json.stderr b/tests/ui/check-cfg/values-target-json.stderr new file mode 100644 index 00000000000..b58d2970773 --- /dev/null +++ b/tests/ui/check-cfg/values-target-json.stderr @@ -0,0 +1,13 @@ +warning: unexpected `cfg` condition value + --> $DIR/values-target-json.rs:13:7 + | +LL | #[cfg(target_os = "linuz")] + | ^^^^^^^^^^^^------- + | | + | help: did you mean: `"linux"` + | + = note: expected values for `target_os` are: aix, android, cuda, dragonfly, emscripten, ericos, espidf, freebsd, fuchsia, haiku, hermit, horizon, illumos, ios, l4re, linux, macos, netbsd, none, nto, openbsd, psp, redox, solaris, solid_asp3, tvos, uefi, unknown, vita, vxworks, wasi, watchos, windows, xous + = note: `#[warn(unexpected_cfgs)]` on by default + +warning: 1 warning emitted + From ffa901913487e999832c050f7c42da17ad7982ca Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Thu, 9 Mar 2023 00:49:41 +0530 Subject: [PATCH 05/14] Move __thread_local_inner to sys Move __thread_local_inner macro in crate::thread::local to crate::sys. Currently, the tidy check does not fail for `library/std/src/thread/local.rs` even though it contains platform specific code. This is beacause target_family did not exist at the time the tidy checks were written [1]. [1]: https://github.com/rust-lang/rust/pull/105861#discussion_r1125841678 Signed-off-by: Ayush Singh --- library/std/src/sys/mod.rs | 194 ++++++++++++++++++++++++++++++++ library/std/src/thread/local.rs | 194 -------------------------------- 2 files changed, 194 insertions(+), 194 deletions(-) diff --git a/library/std/src/sys/mod.rs b/library/std/src/sys/mod.rs index c080c176a2a..5bbe39610cc 100644 --- a/library/std/src/sys/mod.rs +++ b/library/std/src/sys/mod.rs @@ -76,3 +76,197 @@ pub mod c; } } + +#[doc(hidden)] +#[unstable(feature = "thread_local_internals", reason = "should not be necessary", issue = "none")] +#[macro_export] +#[allow_internal_unstable(thread_local_internals, cfg_target_thread_local, thread_local)] +#[allow_internal_unsafe] +macro_rules! __thread_local_inner { + // used to generate the `LocalKey` value for const-initialized thread locals + (@key $t:ty, const $init:expr) => {{ + #[cfg_attr(not(windows), inline)] // see comments below + #[deny(unsafe_op_in_unsafe_fn)] + unsafe fn __getit( + _init: $crate::option::Option<&mut $crate::option::Option<$t>>, + ) -> $crate::option::Option<&'static $t> { + const INIT_EXPR: $t = $init; + + // wasm without atomics maps directly to `static mut`, and dtors + // aren't implemented because thread dtors aren't really a thing + // on wasm right now + // + // FIXME(#84224) this should come after the `target_thread_local` + // block. + #[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] + { + static mut VAL: $t = INIT_EXPR; + unsafe { $crate::option::Option::Some(&VAL) } + } + + // If the platform has support for `#[thread_local]`, use it. + #[cfg(all( + target_thread_local, + not(all(target_family = "wasm", not(target_feature = "atomics"))), + ))] + { + #[thread_local] + static mut VAL: $t = INIT_EXPR; + + // If a dtor isn't needed we can do something "very raw" and + // just get going. + if !$crate::mem::needs_drop::<$t>() { + unsafe { + return $crate::option::Option::Some(&VAL) + } + } + + // 0 == dtor not registered + // 1 == dtor registered, dtor not run + // 2 == dtor registered and is running or has run + #[thread_local] + static mut STATE: $crate::primitive::u8 = 0; + + unsafe extern "C" fn destroy(ptr: *mut $crate::primitive::u8) { + let ptr = ptr as *mut $t; + + unsafe { + $crate::debug_assert_eq!(STATE, 1); + STATE = 2; + $crate::ptr::drop_in_place(ptr); + } + } + + unsafe { + match STATE { + // 0 == we haven't registered a destructor, so do + // so now. + 0 => { + $crate::thread::__FastLocalKeyInner::<$t>::register_dtor( + $crate::ptr::addr_of_mut!(VAL) as *mut $crate::primitive::u8, + destroy, + ); + STATE = 1; + $crate::option::Option::Some(&VAL) + } + // 1 == the destructor is registered and the value + // is valid, so return the pointer. + 1 => $crate::option::Option::Some(&VAL), + // otherwise the destructor has already run, so we + // can't give access. + _ => $crate::option::Option::None, + } + } + } + + // On platforms without `#[thread_local]` we fall back to the + // same implementation as below for os thread locals. + #[cfg(all( + not(target_thread_local), + not(all(target_family = "wasm", not(target_feature = "atomics"))), + ))] + { + #[inline] + const fn __init() -> $t { INIT_EXPR } + static __KEY: $crate::thread::__OsLocalKeyInner<$t> = + $crate::thread::__OsLocalKeyInner::new(); + #[allow(unused_unsafe)] + unsafe { + __KEY.get(move || { + if let $crate::option::Option::Some(init) = _init { + if let $crate::option::Option::Some(value) = init.take() { + return value; + } else if $crate::cfg!(debug_assertions) { + $crate::unreachable!("missing initial value"); + } + } + __init() + }) + } + } + } + + unsafe { + $crate::thread::LocalKey::new(__getit) + } + }}; + + // used to generate the `LocalKey` value for `thread_local!` + (@key $t:ty, $init:expr) => { + { + #[inline] + fn __init() -> $t { $init } + + // When reading this function you might ask "why is this inlined + // everywhere other than Windows?", and that's a very reasonable + // question to ask. The short story is that it segfaults rustc if + // this function is inlined. The longer story is that Windows looks + // to not support `extern` references to thread locals across DLL + // boundaries. This appears to at least not be supported in the ABI + // that LLVM implements. + // + // Because of this we never inline on Windows, but we do inline on + // other platforms (where external references to thread locals + // across DLLs are supported). A better fix for this would be to + // inline this function on Windows, but only for "statically linked" + // components. For example if two separately compiled rlibs end up + // getting linked into a DLL then it's fine to inline this function + // across that boundary. It's only not fine to inline this function + // across a DLL boundary. Unfortunately rustc doesn't currently + // have this sort of logic available in an attribute, and it's not + // clear that rustc is even equipped to answer this (it's more of a + // Cargo question kinda). This means that, unfortunately, Windows + // gets the pessimistic path for now where it's never inlined. + // + // The issue of "should enable on Windows sometimes" is #84933 + #[cfg_attr(not(windows), inline)] + unsafe fn __getit( + init: $crate::option::Option<&mut $crate::option::Option<$t>>, + ) -> $crate::option::Option<&'static $t> { + #[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] + static __KEY: $crate::thread::__StaticLocalKeyInner<$t> = + $crate::thread::__StaticLocalKeyInner::new(); + + #[thread_local] + #[cfg(all( + target_thread_local, + not(all(target_family = "wasm", not(target_feature = "atomics"))), + ))] + static __KEY: $crate::thread::__FastLocalKeyInner<$t> = + $crate::thread::__FastLocalKeyInner::new(); + + #[cfg(all( + not(target_thread_local), + not(all(target_family = "wasm", not(target_feature = "atomics"))), + ))] + static __KEY: $crate::thread::__OsLocalKeyInner<$t> = + $crate::thread::__OsLocalKeyInner::new(); + + // FIXME: remove the #[allow(...)] marker when macros don't + // raise warning for missing/extraneous unsafe blocks anymore. + // See https://github.com/rust-lang/rust/issues/74838. + #[allow(unused_unsafe)] + unsafe { + __KEY.get(move || { + if let $crate::option::Option::Some(init) = init { + if let $crate::option::Option::Some(value) = init.take() { + return value; + } else if $crate::cfg!(debug_assertions) { + $crate::unreachable!("missing default value"); + } + } + __init() + }) + } + } + + unsafe { + $crate::thread::LocalKey::new(__getit) + } + } + }; + ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => { + $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> = + $crate::__thread_local_inner!(@key $t, $($init)*); + } +} diff --git a/library/std/src/thread/local.rs b/library/std/src/thread/local.rs index cf7c2e05a2e..c82b5c2284f 100644 --- a/library/std/src/thread/local.rs +++ b/library/std/src/thread/local.rs @@ -173,200 +173,6 @@ macro_rules! thread_local { ); } -#[doc(hidden)] -#[unstable(feature = "thread_local_internals", reason = "should not be necessary", issue = "none")] -#[macro_export] -#[allow_internal_unstable(thread_local_internals, cfg_target_thread_local, thread_local)] -#[allow_internal_unsafe] -macro_rules! __thread_local_inner { - // used to generate the `LocalKey` value for const-initialized thread locals - (@key $t:ty, const $init:expr) => {{ - #[cfg_attr(not(windows), inline)] // see comments below - #[deny(unsafe_op_in_unsafe_fn)] - unsafe fn __getit( - _init: $crate::option::Option<&mut $crate::option::Option<$t>>, - ) -> $crate::option::Option<&'static $t> { - const INIT_EXPR: $t = $init; - - // wasm without atomics maps directly to `static mut`, and dtors - // aren't implemented because thread dtors aren't really a thing - // on wasm right now - // - // FIXME(#84224) this should come after the `target_thread_local` - // block. - #[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] - { - static mut VAL: $t = INIT_EXPR; - unsafe { $crate::option::Option::Some(&VAL) } - } - - // If the platform has support for `#[thread_local]`, use it. - #[cfg(all( - target_thread_local, - not(all(target_family = "wasm", not(target_feature = "atomics"))), - ))] - { - #[thread_local] - static mut VAL: $t = INIT_EXPR; - - // If a dtor isn't needed we can do something "very raw" and - // just get going. - if !$crate::mem::needs_drop::<$t>() { - unsafe { - return $crate::option::Option::Some(&VAL) - } - } - - // 0 == dtor not registered - // 1 == dtor registered, dtor not run - // 2 == dtor registered and is running or has run - #[thread_local] - static mut STATE: $crate::primitive::u8 = 0; - - unsafe extern "C" fn destroy(ptr: *mut $crate::primitive::u8) { - let ptr = ptr as *mut $t; - - unsafe { - $crate::debug_assert_eq!(STATE, 1); - STATE = 2; - $crate::ptr::drop_in_place(ptr); - } - } - - unsafe { - match STATE { - // 0 == we haven't registered a destructor, so do - // so now. - 0 => { - $crate::thread::__FastLocalKeyInner::<$t>::register_dtor( - $crate::ptr::addr_of_mut!(VAL) as *mut $crate::primitive::u8, - destroy, - ); - STATE = 1; - $crate::option::Option::Some(&VAL) - } - // 1 == the destructor is registered and the value - // is valid, so return the pointer. - 1 => $crate::option::Option::Some(&VAL), - // otherwise the destructor has already run, so we - // can't give access. - _ => $crate::option::Option::None, - } - } - } - - // On platforms without `#[thread_local]` we fall back to the - // same implementation as below for os thread locals. - #[cfg(all( - not(target_thread_local), - not(all(target_family = "wasm", not(target_feature = "atomics"))), - ))] - { - #[inline] - const fn __init() -> $t { INIT_EXPR } - static __KEY: $crate::thread::__OsLocalKeyInner<$t> = - $crate::thread::__OsLocalKeyInner::new(); - #[allow(unused_unsafe)] - unsafe { - __KEY.get(move || { - if let $crate::option::Option::Some(init) = _init { - if let $crate::option::Option::Some(value) = init.take() { - return value; - } else if $crate::cfg!(debug_assertions) { - $crate::unreachable!("missing initial value"); - } - } - __init() - }) - } - } - } - - unsafe { - $crate::thread::LocalKey::new(__getit) - } - }}; - - // used to generate the `LocalKey` value for `thread_local!` - (@key $t:ty, $init:expr) => { - { - #[inline] - fn __init() -> $t { $init } - - // When reading this function you might ask "why is this inlined - // everywhere other than Windows?", and that's a very reasonable - // question to ask. The short story is that it segfaults rustc if - // this function is inlined. The longer story is that Windows looks - // to not support `extern` references to thread locals across DLL - // boundaries. This appears to at least not be supported in the ABI - // that LLVM implements. - // - // Because of this we never inline on Windows, but we do inline on - // other platforms (where external references to thread locals - // across DLLs are supported). A better fix for this would be to - // inline this function on Windows, but only for "statically linked" - // components. For example if two separately compiled rlibs end up - // getting linked into a DLL then it's fine to inline this function - // across that boundary. It's only not fine to inline this function - // across a DLL boundary. Unfortunately rustc doesn't currently - // have this sort of logic available in an attribute, and it's not - // clear that rustc is even equipped to answer this (it's more of a - // Cargo question kinda). This means that, unfortunately, Windows - // gets the pessimistic path for now where it's never inlined. - // - // The issue of "should enable on Windows sometimes" is #84933 - #[cfg_attr(not(windows), inline)] - unsafe fn __getit( - init: $crate::option::Option<&mut $crate::option::Option<$t>>, - ) -> $crate::option::Option<&'static $t> { - #[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] - static __KEY: $crate::thread::__StaticLocalKeyInner<$t> = - $crate::thread::__StaticLocalKeyInner::new(); - - #[thread_local] - #[cfg(all( - target_thread_local, - not(all(target_family = "wasm", not(target_feature = "atomics"))), - ))] - static __KEY: $crate::thread::__FastLocalKeyInner<$t> = - $crate::thread::__FastLocalKeyInner::new(); - - #[cfg(all( - not(target_thread_local), - not(all(target_family = "wasm", not(target_feature = "atomics"))), - ))] - static __KEY: $crate::thread::__OsLocalKeyInner<$t> = - $crate::thread::__OsLocalKeyInner::new(); - - // FIXME: remove the #[allow(...)] marker when macros don't - // raise warning for missing/extraneous unsafe blocks anymore. - // See https://github.com/rust-lang/rust/issues/74838. - #[allow(unused_unsafe)] - unsafe { - __KEY.get(move || { - if let $crate::option::Option::Some(init) = init { - if let $crate::option::Option::Some(value) = init.take() { - return value; - } else if $crate::cfg!(debug_assertions) { - $crate::unreachable!("missing default value"); - } - } - __init() - }) - } - } - - unsafe { - $crate::thread::LocalKey::new(__getit) - } - } - }; - ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => { - $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> = - $crate::__thread_local_inner!(@key $t, $($init)*); - } -} - /// An error returned by [`LocalKey::try_with`](struct.LocalKey.html#method.try_with). #[stable(feature = "thread_local_try_with", since = "1.26.0")] #[non_exhaustive] From 45d50216a9fbf2943a370939e959de5a60d00c56 Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Thu, 9 Mar 2023 21:55:29 +0530 Subject: [PATCH 06/14] Split __thread_local_inner macro Split the __thread_local_inner macro to make it more readable. Also move everything to crate::sys::common::thread_local. Signed-off-by: Ayush Singh --- library/std/src/sys/common/mod.rs | 1 + library/std/src/sys/common/thread_local.rs | 315 +++++++++++++++++++++ library/std/src/sys/mod.rs | 194 ------------- 3 files changed, 316 insertions(+), 194 deletions(-) create mode 100644 library/std/src/sys/common/thread_local.rs diff --git a/library/std/src/sys/common/mod.rs b/library/std/src/sys/common/mod.rs index 29fc0835d76..2b8782ddf44 100644 --- a/library/std/src/sys/common/mod.rs +++ b/library/std/src/sys/common/mod.rs @@ -12,6 +12,7 @@ pub mod alloc; pub mod small_c_string; +pub mod thread_local; #[cfg(test)] mod tests; diff --git a/library/std/src/sys/common/thread_local.rs b/library/std/src/sys/common/thread_local.rs new file mode 100644 index 00000000000..cddf0989348 --- /dev/null +++ b/library/std/src/sys/common/thread_local.rs @@ -0,0 +1,315 @@ +#[doc(hidden)] +#[unstable(feature = "thread_local_internals", reason = "should not be necessary", issue = "none")] +#[macro_export] +#[allow_internal_unstable(thread_local_internals, cfg_target_thread_local, thread_local)] +#[allow_internal_unsafe] +#[cfg(all( + not(target_thread_local), + not(all(target_family = "wasm", not(target_feature = "atomics"))) +))] +macro_rules! __thread_local_inner { + // used to generate the `LocalKey` value for const-initialized thread locals + (@key $t:ty, const $init:expr) => {{ + #[cfg_attr(not(windows), inline)] // see comments below + #[deny(unsafe_op_in_unsafe_fn)] + unsafe fn __getit( + _init: $crate::option::Option<&mut $crate::option::Option<$t>>, + ) -> $crate::option::Option<&'static $t> { + const INIT_EXPR: $t = $init; + + // On platforms without `#[thread_local]` we fall back to the + // same implementation as below for os thread locals. + #[inline] + const fn __init() -> $t { INIT_EXPR } + static __KEY: $crate::thread::__OsLocalKeyInner<$t> = + $crate::thread::__OsLocalKeyInner::new(); + #[allow(unused_unsafe)] + unsafe { + __KEY.get(move || { + if let $crate::option::Option::Some(init) = _init { + if let $crate::option::Option::Some(value) = init.take() { + return value; + } else if $crate::cfg!(debug_assertions) { + $crate::unreachable!("missing initial value"); + } + } + __init() + }) + } + } + + unsafe { + $crate::thread::LocalKey::new(__getit) + } + }}; + + // used to generate the `LocalKey` value for `thread_local!` + (@key $t:ty, $init:expr) => { + { + #[inline] + fn __init() -> $t { $init } + + // When reading this function you might ask "why is this inlined + // everywhere other than Windows?", and that's a very reasonable + // question to ask. The short story is that it segfaults rustc if + // this function is inlined. The longer story is that Windows looks + // to not support `extern` references to thread locals across DLL + // boundaries. This appears to at least not be supported in the ABI + // that LLVM implements. + // + // Because of this we never inline on Windows, but we do inline on + // other platforms (where external references to thread locals + // across DLLs are supported). A better fix for this would be to + // inline this function on Windows, but only for "statically linked" + // components. For example if two separately compiled rlibs end up + // getting linked into a DLL then it's fine to inline this function + // across that boundary. It's only not fine to inline this function + // across a DLL boundary. Unfortunately rustc doesn't currently + // have this sort of logic available in an attribute, and it's not + // clear that rustc is even equipped to answer this (it's more of a + // Cargo question kinda). This means that, unfortunately, Windows + // gets the pessimistic path for now where it's never inlined. + // + // The issue of "should enable on Windows sometimes" is #84933 + #[cfg_attr(not(windows), inline)] + unsafe fn __getit( + init: $crate::option::Option<&mut $crate::option::Option<$t>>, + ) -> $crate::option::Option<&'static $t> { + static __KEY: $crate::thread::__OsLocalKeyInner<$t> = + $crate::thread::__OsLocalKeyInner::new(); + + // FIXME: remove the #[allow(...)] marker when macros don't + // raise warning for missing/extraneous unsafe blocks anymore. + // See https://github.com/rust-lang/rust/issues/74838. + #[allow(unused_unsafe)] + unsafe { + __KEY.get(move || { + if let $crate::option::Option::Some(init) = init { + if let $crate::option::Option::Some(value) = init.take() { + return value; + } else if $crate::cfg!(debug_assertions) { + $crate::unreachable!("missing default value"); + } + } + __init() + }) + } + } + + unsafe { + $crate::thread::LocalKey::new(__getit) + } + } + }; + ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => { + $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> = + $crate::__thread_local_inner!(@key $t, $($init)*); + } +} + +#[doc(hidden)] +#[unstable(feature = "thread_local_internals", reason = "should not be necessary", issue = "none")] +#[macro_export] +#[allow_internal_unstable(thread_local_internals, cfg_target_thread_local, thread_local)] +#[allow_internal_unsafe] +#[cfg(all(target_thread_local, not(all(target_family = "wasm", not(target_feature = "atomics")))))] +macro_rules! __thread_local_inner { + // used to generate the `LocalKey` value for const-initialized thread locals + (@key $t:ty, const $init:expr) => {{ + #[cfg_attr(not(windows), inline)] // see comments below + #[deny(unsafe_op_in_unsafe_fn)] + unsafe fn __getit( + _init: $crate::option::Option<&mut $crate::option::Option<$t>>, + ) -> $crate::option::Option<&'static $t> { + const INIT_EXPR: $t = $init; + // If the platform has support for `#[thread_local]`, use it. + #[thread_local] + static mut VAL: $t = INIT_EXPR; + + // If a dtor isn't needed we can do something "very raw" and + // just get going. + if !$crate::mem::needs_drop::<$t>() { + unsafe { + return $crate::option::Option::Some(&VAL) + } + } + + // 0 == dtor not registered + // 1 == dtor registered, dtor not run + // 2 == dtor registered and is running or has run + #[thread_local] + static mut STATE: $crate::primitive::u8 = 0; + + unsafe extern "C" fn destroy(ptr: *mut $crate::primitive::u8) { + let ptr = ptr as *mut $t; + + unsafe { + $crate::debug_assert_eq!(STATE, 1); + STATE = 2; + $crate::ptr::drop_in_place(ptr); + } + } + + unsafe { + match STATE { + // 0 == we haven't registered a destructor, so do + // so now. + 0 => { + $crate::thread::__FastLocalKeyInner::<$t>::register_dtor( + $crate::ptr::addr_of_mut!(VAL) as *mut $crate::primitive::u8, + destroy, + ); + STATE = 1; + $crate::option::Option::Some(&VAL) + } + // 1 == the destructor is registered and the value + // is valid, so return the pointer. + 1 => $crate::option::Option::Some(&VAL), + // otherwise the destructor has already run, so we + // can't give access. + _ => $crate::option::Option::None, + } + } + } + + unsafe { + $crate::thread::LocalKey::new(__getit) + } + }}; + + // used to generate the `LocalKey` value for `thread_local!` + (@key $t:ty, $init:expr) => { + { + #[inline] + fn __init() -> $t { $init } + + // When reading this function you might ask "why is this inlined + // everywhere other than Windows?", and that's a very reasonable + // question to ask. The short story is that it segfaults rustc if + // this function is inlined. The longer story is that Windows looks + // to not support `extern` references to thread locals across DLL + // boundaries. This appears to at least not be supported in the ABI + // that LLVM implements. + // + // Because of this we never inline on Windows, but we do inline on + // other platforms (where external references to thread locals + // across DLLs are supported). A better fix for this would be to + // inline this function on Windows, but only for "statically linked" + // components. For example if two separately compiled rlibs end up + // getting linked into a DLL then it's fine to inline this function + // across that boundary. It's only not fine to inline this function + // across a DLL boundary. Unfortunately rustc doesn't currently + // have this sort of logic available in an attribute, and it's not + // clear that rustc is even equipped to answer this (it's more of a + // Cargo question kinda). This means that, unfortunately, Windows + // gets the pessimistic path for now where it's never inlined. + // + // The issue of "should enable on Windows sometimes" is #84933 + #[cfg_attr(not(windows), inline)] + unsafe fn __getit( + init: $crate::option::Option<&mut $crate::option::Option<$t>>, + ) -> $crate::option::Option<&'static $t> { + #[thread_local] + static __KEY: $crate::thread::__FastLocalKeyInner<$t> = + $crate::thread::__FastLocalKeyInner::new(); + + // FIXME: remove the #[allow(...)] marker when macros don't + // raise warning for missing/extraneous unsafe blocks anymore. + // See https://github.com/rust-lang/rust/issues/74838. + #[allow(unused_unsafe)] + unsafe { + __KEY.get(move || { + if let $crate::option::Option::Some(init) = init { + if let $crate::option::Option::Some(value) = init.take() { + return value; + } else if $crate::cfg!(debug_assertions) { + $crate::unreachable!("missing default value"); + } + } + __init() + }) + } + } + + unsafe { + $crate::thread::LocalKey::new(__getit) + } + } + }; + ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => { + $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> = + $crate::__thread_local_inner!(@key $t, $($init)*); + } +} + +#[doc(hidden)] +#[unstable(feature = "thread_local_internals", reason = "should not be necessary", issue = "none")] +#[macro_export] +#[allow_internal_unstable(thread_local_internals, cfg_target_thread_local, thread_local)] +#[allow_internal_unsafe] +#[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] +macro_rules! __thread_local_inner { + // used to generate the `LocalKey` value for const-initialized thread locals + (@key $t:ty, const $init:expr) => {{ + #[inline] // see comments below + #[deny(unsafe_op_in_unsafe_fn)] + unsafe fn __getit( + _init: $crate::option::Option<&mut $crate::option::Option<$t>>, + ) -> $crate::option::Option<&'static $t> { + const INIT_EXPR: $t = $init; + + // wasm without atomics maps directly to `static mut`, and dtors + // aren't implemented because thread dtors aren't really a thing + // on wasm right now + // + // FIXME(#84224) this should come after the `target_thread_local` + // block. + static mut VAL: $t = INIT_EXPR; + unsafe { $crate::option::Option::Some(&VAL) } + } + + unsafe { + $crate::thread::LocalKey::new(__getit) + } + }}; + + // used to generate the `LocalKey` value for `thread_local!` + (@key $t:ty, $init:expr) => { + { + #[inline] + fn __init() -> $t { $init } + #[inline] + unsafe fn __getit( + init: $crate::option::Option<&mut $crate::option::Option<$t>>, + ) -> $crate::option::Option<&'static $t> { + static __KEY: $crate::thread::__StaticLocalKeyInner<$t> = + $crate::thread::__StaticLocalKeyInner::new(); + + // FIXME: remove the #[allow(...)] marker when macros don't + // raise warning for missing/extraneous unsafe blocks anymore. + // See https://github.com/rust-lang/rust/issues/74838. + #[allow(unused_unsafe)] + unsafe { + __KEY.get(move || { + if let $crate::option::Option::Some(init) = init { + if let $crate::option::Option::Some(value) = init.take() { + return value; + } else if $crate::cfg!(debug_assertions) { + $crate::unreachable!("missing default value"); + } + } + __init() + }) + } + } + + unsafe { + $crate::thread::LocalKey::new(__getit) + } + } + }; + ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => { + $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> = + $crate::__thread_local_inner!(@key $t, $($init)*); + } +} diff --git a/library/std/src/sys/mod.rs b/library/std/src/sys/mod.rs index 5bbe39610cc..c080c176a2a 100644 --- a/library/std/src/sys/mod.rs +++ b/library/std/src/sys/mod.rs @@ -76,197 +76,3 @@ pub mod c; } } - -#[doc(hidden)] -#[unstable(feature = "thread_local_internals", reason = "should not be necessary", issue = "none")] -#[macro_export] -#[allow_internal_unstable(thread_local_internals, cfg_target_thread_local, thread_local)] -#[allow_internal_unsafe] -macro_rules! __thread_local_inner { - // used to generate the `LocalKey` value for const-initialized thread locals - (@key $t:ty, const $init:expr) => {{ - #[cfg_attr(not(windows), inline)] // see comments below - #[deny(unsafe_op_in_unsafe_fn)] - unsafe fn __getit( - _init: $crate::option::Option<&mut $crate::option::Option<$t>>, - ) -> $crate::option::Option<&'static $t> { - const INIT_EXPR: $t = $init; - - // wasm without atomics maps directly to `static mut`, and dtors - // aren't implemented because thread dtors aren't really a thing - // on wasm right now - // - // FIXME(#84224) this should come after the `target_thread_local` - // block. - #[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] - { - static mut VAL: $t = INIT_EXPR; - unsafe { $crate::option::Option::Some(&VAL) } - } - - // If the platform has support for `#[thread_local]`, use it. - #[cfg(all( - target_thread_local, - not(all(target_family = "wasm", not(target_feature = "atomics"))), - ))] - { - #[thread_local] - static mut VAL: $t = INIT_EXPR; - - // If a dtor isn't needed we can do something "very raw" and - // just get going. - if !$crate::mem::needs_drop::<$t>() { - unsafe { - return $crate::option::Option::Some(&VAL) - } - } - - // 0 == dtor not registered - // 1 == dtor registered, dtor not run - // 2 == dtor registered and is running or has run - #[thread_local] - static mut STATE: $crate::primitive::u8 = 0; - - unsafe extern "C" fn destroy(ptr: *mut $crate::primitive::u8) { - let ptr = ptr as *mut $t; - - unsafe { - $crate::debug_assert_eq!(STATE, 1); - STATE = 2; - $crate::ptr::drop_in_place(ptr); - } - } - - unsafe { - match STATE { - // 0 == we haven't registered a destructor, so do - // so now. - 0 => { - $crate::thread::__FastLocalKeyInner::<$t>::register_dtor( - $crate::ptr::addr_of_mut!(VAL) as *mut $crate::primitive::u8, - destroy, - ); - STATE = 1; - $crate::option::Option::Some(&VAL) - } - // 1 == the destructor is registered and the value - // is valid, so return the pointer. - 1 => $crate::option::Option::Some(&VAL), - // otherwise the destructor has already run, so we - // can't give access. - _ => $crate::option::Option::None, - } - } - } - - // On platforms without `#[thread_local]` we fall back to the - // same implementation as below for os thread locals. - #[cfg(all( - not(target_thread_local), - not(all(target_family = "wasm", not(target_feature = "atomics"))), - ))] - { - #[inline] - const fn __init() -> $t { INIT_EXPR } - static __KEY: $crate::thread::__OsLocalKeyInner<$t> = - $crate::thread::__OsLocalKeyInner::new(); - #[allow(unused_unsafe)] - unsafe { - __KEY.get(move || { - if let $crate::option::Option::Some(init) = _init { - if let $crate::option::Option::Some(value) = init.take() { - return value; - } else if $crate::cfg!(debug_assertions) { - $crate::unreachable!("missing initial value"); - } - } - __init() - }) - } - } - } - - unsafe { - $crate::thread::LocalKey::new(__getit) - } - }}; - - // used to generate the `LocalKey` value for `thread_local!` - (@key $t:ty, $init:expr) => { - { - #[inline] - fn __init() -> $t { $init } - - // When reading this function you might ask "why is this inlined - // everywhere other than Windows?", and that's a very reasonable - // question to ask. The short story is that it segfaults rustc if - // this function is inlined. The longer story is that Windows looks - // to not support `extern` references to thread locals across DLL - // boundaries. This appears to at least not be supported in the ABI - // that LLVM implements. - // - // Because of this we never inline on Windows, but we do inline on - // other platforms (where external references to thread locals - // across DLLs are supported). A better fix for this would be to - // inline this function on Windows, but only for "statically linked" - // components. For example if two separately compiled rlibs end up - // getting linked into a DLL then it's fine to inline this function - // across that boundary. It's only not fine to inline this function - // across a DLL boundary. Unfortunately rustc doesn't currently - // have this sort of logic available in an attribute, and it's not - // clear that rustc is even equipped to answer this (it's more of a - // Cargo question kinda). This means that, unfortunately, Windows - // gets the pessimistic path for now where it's never inlined. - // - // The issue of "should enable on Windows sometimes" is #84933 - #[cfg_attr(not(windows), inline)] - unsafe fn __getit( - init: $crate::option::Option<&mut $crate::option::Option<$t>>, - ) -> $crate::option::Option<&'static $t> { - #[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] - static __KEY: $crate::thread::__StaticLocalKeyInner<$t> = - $crate::thread::__StaticLocalKeyInner::new(); - - #[thread_local] - #[cfg(all( - target_thread_local, - not(all(target_family = "wasm", not(target_feature = "atomics"))), - ))] - static __KEY: $crate::thread::__FastLocalKeyInner<$t> = - $crate::thread::__FastLocalKeyInner::new(); - - #[cfg(all( - not(target_thread_local), - not(all(target_family = "wasm", not(target_feature = "atomics"))), - ))] - static __KEY: $crate::thread::__OsLocalKeyInner<$t> = - $crate::thread::__OsLocalKeyInner::new(); - - // FIXME: remove the #[allow(...)] marker when macros don't - // raise warning for missing/extraneous unsafe blocks anymore. - // See https://github.com/rust-lang/rust/issues/74838. - #[allow(unused_unsafe)] - unsafe { - __KEY.get(move || { - if let $crate::option::Option::Some(init) = init { - if let $crate::option::Option::Some(value) = init.take() { - return value; - } else if $crate::cfg!(debug_assertions) { - $crate::unreachable!("missing default value"); - } - } - __init() - }) - } - } - - unsafe { - $crate::thread::LocalKey::new(__getit) - } - } - }; - ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => { - $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> = - $crate::__thread_local_inner!(@key $t, $($init)*); - } -} From c84c5e6439199d58ac62a144590c335b182c12d1 Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Fri, 10 Mar 2023 16:09:30 +0100 Subject: [PATCH 07/14] rustdoc: Don't crash on `crate` references in blocks This is a regression from #94857. --- compiler/rustc_resolve/src/lib.rs | 12 ++---------- tests/rustdoc-ui/crate-reference-in-block-module.rs | 5 +++++ .../crate-reference-in-block-module.stderr | 0 3 files changed, 7 insertions(+), 10 deletions(-) create mode 100644 tests/rustdoc-ui/crate-reference-in-block-module.rs create mode 100644 tests/rustdoc-ui/crate-reference-in-block-module.stderr diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 5eba208e3ed..f6888e55ad4 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -1849,20 +1849,12 @@ fn resolve_rustdoc_path( &mut self, path_str: &str, ns: Namespace, - mut parent_scope: ParentScope<'a>, + parent_scope: ParentScope<'a>, ) -> Option { let mut segments = Vec::from_iter(path_str.split("::").map(Ident::from_str).map(Segment::from_ident)); if let Some(segment) = segments.first_mut() { - if segment.ident.name == kw::Crate { - // FIXME: `resolve_path` always resolves `crate` to the current crate root, but - // rustdoc wants it to resolve to the `parent_scope`'s crate root. This trick of - // replacing `crate` with `self` and changing the current module should achieve - // the same effect. - segment.ident.name = kw::SelfLower; - parent_scope.module = - self.expect_module(parent_scope.module.def_id().krate.as_def_id()); - } else if segment.ident.name == kw::Empty { + if segment.ident.name == kw::Empty { segment.ident.name = kw::PathRoot; } } diff --git a/tests/rustdoc-ui/crate-reference-in-block-module.rs b/tests/rustdoc-ui/crate-reference-in-block-module.rs new file mode 100644 index 00000000000..aede030e072 --- /dev/null +++ b/tests/rustdoc-ui/crate-reference-in-block-module.rs @@ -0,0 +1,5 @@ +// check-pass +fn main() { + /// [](crate) + struct X; +} diff --git a/tests/rustdoc-ui/crate-reference-in-block-module.stderr b/tests/rustdoc-ui/crate-reference-in-block-module.stderr new file mode 100644 index 00000000000..e69de29bb2d From 3dee4630ba1fe9de0aa03e920dfe993a6d357945 Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Fri, 3 Mar 2023 20:37:51 +0000 Subject: [PATCH 08/14] Add note when matching token with nonterminal The current error message is _really_ confusing. --- compiler/rustc_expand/src/mbe/diagnostics.rs | 12 +++++++++--- tests/ui/macros/nonterminal-matching.stderr | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index f469b2daef5..b1d9cea2773 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -1,12 +1,10 @@ -use std::borrow::Cow; - use crate::base::{DummyResult, ExtCtxt, MacResult}; use crate::expand::{parse_ast_fragment, AstFragmentKind}; use crate::mbe::{ macro_parser::{MatcherLoc, NamedParseResult, ParseResult::*, TtParser}, macro_rules::{try_match_macro, Tracker}, }; -use rustc_ast::token::{self, Token}; +use rustc_ast::token::{self, Token, TokenKind}; use rustc_ast::tokenstream::TokenStream; use rustc_ast_pretty::pprust; use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, DiagnosticMessage}; @@ -14,6 +12,7 @@ use rustc_span::source_map::SourceMap; use rustc_span::symbol::Ident; use rustc_span::Span; +use std::borrow::Cow; use super::macro_rules::{parser_from_cx, NoopTracker}; @@ -63,6 +62,13 @@ pub(super) fn failed_to_match_macro<'cx>( err.note(format!("while trying to match {remaining_matcher}")); } + if let MatcherLoc::Token { token: expected_token } = &remaining_matcher + && (matches!(expected_token.kind, TokenKind::Interpolated(_)) + || matches!(token.kind, TokenKind::Interpolated(_))) + { + err.note("captured metavariables except for `$tt`, `$ident` and `$lifetime` cannot be compared to other tokens"); + } + // Check whether there's a missing comma in this macro call, like `println!("{}" a);` if let Some((arg, comma_span)) = arg.add_comma() { for lhs in lhses { diff --git a/tests/ui/macros/nonterminal-matching.stderr b/tests/ui/macros/nonterminal-matching.stderr index 5bbd5439098..762ecc3207f 100644 --- a/tests/ui/macros/nonterminal-matching.stderr +++ b/tests/ui/macros/nonterminal-matching.stderr @@ -18,6 +18,7 @@ LL | macro n(a $nt_item b) { ... LL | complex_nonterminal!(enum E {}); | ------------------------------- in this macro invocation + = note: captured metavariables except for `$tt`, `$ident` and `$lifetime` cannot be compared to other tokens = note: this error originates in the macro `complex_nonterminal` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to previous error From 5828910ff4ea90eb2092074dd641d36f0146a734 Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Fri, 10 Mar 2023 21:05:28 +0530 Subject: [PATCH 09/14] Moved thread_local implementation to sys::common This allows removing all the platform-dependent code from `library/std/src/thread/local.rs` and `library/std/src/thread/mod.rs` Signed-off-by: Ayush Singh --- library/std/src/sys/common/thread_local.rs | 315 --------------- .../src/sys/common/thread_local/fast_local.rs | 276 +++++++++++++ .../std/src/sys/common/thread_local/mod.rs | 109 +++++ .../src/sys/common/thread_local/os_local.rs | 217 ++++++++++ .../sys/common/thread_local/static_local.rs | 115 ++++++ library/std/src/thread/local.rs | 373 ------------------ library/std/src/thread/mod.rs | 37 +- src/tools/tidy/src/pal.rs | 2 - tests/ui/threads-sendsync/issue-43733-2.rs | 2 +- tests/ui/threads-sendsync/issue-43733.rs | 8 +- 10 files changed, 723 insertions(+), 731 deletions(-) delete mode 100644 library/std/src/sys/common/thread_local.rs create mode 100644 library/std/src/sys/common/thread_local/fast_local.rs create mode 100644 library/std/src/sys/common/thread_local/mod.rs create mode 100644 library/std/src/sys/common/thread_local/os_local.rs create mode 100644 library/std/src/sys/common/thread_local/static_local.rs diff --git a/library/std/src/sys/common/thread_local.rs b/library/std/src/sys/common/thread_local.rs deleted file mode 100644 index cddf0989348..00000000000 --- a/library/std/src/sys/common/thread_local.rs +++ /dev/null @@ -1,315 +0,0 @@ -#[doc(hidden)] -#[unstable(feature = "thread_local_internals", reason = "should not be necessary", issue = "none")] -#[macro_export] -#[allow_internal_unstable(thread_local_internals, cfg_target_thread_local, thread_local)] -#[allow_internal_unsafe] -#[cfg(all( - not(target_thread_local), - not(all(target_family = "wasm", not(target_feature = "atomics"))) -))] -macro_rules! __thread_local_inner { - // used to generate the `LocalKey` value for const-initialized thread locals - (@key $t:ty, const $init:expr) => {{ - #[cfg_attr(not(windows), inline)] // see comments below - #[deny(unsafe_op_in_unsafe_fn)] - unsafe fn __getit( - _init: $crate::option::Option<&mut $crate::option::Option<$t>>, - ) -> $crate::option::Option<&'static $t> { - const INIT_EXPR: $t = $init; - - // On platforms without `#[thread_local]` we fall back to the - // same implementation as below for os thread locals. - #[inline] - const fn __init() -> $t { INIT_EXPR } - static __KEY: $crate::thread::__OsLocalKeyInner<$t> = - $crate::thread::__OsLocalKeyInner::new(); - #[allow(unused_unsafe)] - unsafe { - __KEY.get(move || { - if let $crate::option::Option::Some(init) = _init { - if let $crate::option::Option::Some(value) = init.take() { - return value; - } else if $crate::cfg!(debug_assertions) { - $crate::unreachable!("missing initial value"); - } - } - __init() - }) - } - } - - unsafe { - $crate::thread::LocalKey::new(__getit) - } - }}; - - // used to generate the `LocalKey` value for `thread_local!` - (@key $t:ty, $init:expr) => { - { - #[inline] - fn __init() -> $t { $init } - - // When reading this function you might ask "why is this inlined - // everywhere other than Windows?", and that's a very reasonable - // question to ask. The short story is that it segfaults rustc if - // this function is inlined. The longer story is that Windows looks - // to not support `extern` references to thread locals across DLL - // boundaries. This appears to at least not be supported in the ABI - // that LLVM implements. - // - // Because of this we never inline on Windows, but we do inline on - // other platforms (where external references to thread locals - // across DLLs are supported). A better fix for this would be to - // inline this function on Windows, but only for "statically linked" - // components. For example if two separately compiled rlibs end up - // getting linked into a DLL then it's fine to inline this function - // across that boundary. It's only not fine to inline this function - // across a DLL boundary. Unfortunately rustc doesn't currently - // have this sort of logic available in an attribute, and it's not - // clear that rustc is even equipped to answer this (it's more of a - // Cargo question kinda). This means that, unfortunately, Windows - // gets the pessimistic path for now where it's never inlined. - // - // The issue of "should enable on Windows sometimes" is #84933 - #[cfg_attr(not(windows), inline)] - unsafe fn __getit( - init: $crate::option::Option<&mut $crate::option::Option<$t>>, - ) -> $crate::option::Option<&'static $t> { - static __KEY: $crate::thread::__OsLocalKeyInner<$t> = - $crate::thread::__OsLocalKeyInner::new(); - - // FIXME: remove the #[allow(...)] marker when macros don't - // raise warning for missing/extraneous unsafe blocks anymore. - // See https://github.com/rust-lang/rust/issues/74838. - #[allow(unused_unsafe)] - unsafe { - __KEY.get(move || { - if let $crate::option::Option::Some(init) = init { - if let $crate::option::Option::Some(value) = init.take() { - return value; - } else if $crate::cfg!(debug_assertions) { - $crate::unreachable!("missing default value"); - } - } - __init() - }) - } - } - - unsafe { - $crate::thread::LocalKey::new(__getit) - } - } - }; - ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => { - $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> = - $crate::__thread_local_inner!(@key $t, $($init)*); - } -} - -#[doc(hidden)] -#[unstable(feature = "thread_local_internals", reason = "should not be necessary", issue = "none")] -#[macro_export] -#[allow_internal_unstable(thread_local_internals, cfg_target_thread_local, thread_local)] -#[allow_internal_unsafe] -#[cfg(all(target_thread_local, not(all(target_family = "wasm", not(target_feature = "atomics")))))] -macro_rules! __thread_local_inner { - // used to generate the `LocalKey` value for const-initialized thread locals - (@key $t:ty, const $init:expr) => {{ - #[cfg_attr(not(windows), inline)] // see comments below - #[deny(unsafe_op_in_unsafe_fn)] - unsafe fn __getit( - _init: $crate::option::Option<&mut $crate::option::Option<$t>>, - ) -> $crate::option::Option<&'static $t> { - const INIT_EXPR: $t = $init; - // If the platform has support for `#[thread_local]`, use it. - #[thread_local] - static mut VAL: $t = INIT_EXPR; - - // If a dtor isn't needed we can do something "very raw" and - // just get going. - if !$crate::mem::needs_drop::<$t>() { - unsafe { - return $crate::option::Option::Some(&VAL) - } - } - - // 0 == dtor not registered - // 1 == dtor registered, dtor not run - // 2 == dtor registered and is running or has run - #[thread_local] - static mut STATE: $crate::primitive::u8 = 0; - - unsafe extern "C" fn destroy(ptr: *mut $crate::primitive::u8) { - let ptr = ptr as *mut $t; - - unsafe { - $crate::debug_assert_eq!(STATE, 1); - STATE = 2; - $crate::ptr::drop_in_place(ptr); - } - } - - unsafe { - match STATE { - // 0 == we haven't registered a destructor, so do - // so now. - 0 => { - $crate::thread::__FastLocalKeyInner::<$t>::register_dtor( - $crate::ptr::addr_of_mut!(VAL) as *mut $crate::primitive::u8, - destroy, - ); - STATE = 1; - $crate::option::Option::Some(&VAL) - } - // 1 == the destructor is registered and the value - // is valid, so return the pointer. - 1 => $crate::option::Option::Some(&VAL), - // otherwise the destructor has already run, so we - // can't give access. - _ => $crate::option::Option::None, - } - } - } - - unsafe { - $crate::thread::LocalKey::new(__getit) - } - }}; - - // used to generate the `LocalKey` value for `thread_local!` - (@key $t:ty, $init:expr) => { - { - #[inline] - fn __init() -> $t { $init } - - // When reading this function you might ask "why is this inlined - // everywhere other than Windows?", and that's a very reasonable - // question to ask. The short story is that it segfaults rustc if - // this function is inlined. The longer story is that Windows looks - // to not support `extern` references to thread locals across DLL - // boundaries. This appears to at least not be supported in the ABI - // that LLVM implements. - // - // Because of this we never inline on Windows, but we do inline on - // other platforms (where external references to thread locals - // across DLLs are supported). A better fix for this would be to - // inline this function on Windows, but only for "statically linked" - // components. For example if two separately compiled rlibs end up - // getting linked into a DLL then it's fine to inline this function - // across that boundary. It's only not fine to inline this function - // across a DLL boundary. Unfortunately rustc doesn't currently - // have this sort of logic available in an attribute, and it's not - // clear that rustc is even equipped to answer this (it's more of a - // Cargo question kinda). This means that, unfortunately, Windows - // gets the pessimistic path for now where it's never inlined. - // - // The issue of "should enable on Windows sometimes" is #84933 - #[cfg_attr(not(windows), inline)] - unsafe fn __getit( - init: $crate::option::Option<&mut $crate::option::Option<$t>>, - ) -> $crate::option::Option<&'static $t> { - #[thread_local] - static __KEY: $crate::thread::__FastLocalKeyInner<$t> = - $crate::thread::__FastLocalKeyInner::new(); - - // FIXME: remove the #[allow(...)] marker when macros don't - // raise warning for missing/extraneous unsafe blocks anymore. - // See https://github.com/rust-lang/rust/issues/74838. - #[allow(unused_unsafe)] - unsafe { - __KEY.get(move || { - if let $crate::option::Option::Some(init) = init { - if let $crate::option::Option::Some(value) = init.take() { - return value; - } else if $crate::cfg!(debug_assertions) { - $crate::unreachable!("missing default value"); - } - } - __init() - }) - } - } - - unsafe { - $crate::thread::LocalKey::new(__getit) - } - } - }; - ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => { - $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> = - $crate::__thread_local_inner!(@key $t, $($init)*); - } -} - -#[doc(hidden)] -#[unstable(feature = "thread_local_internals", reason = "should not be necessary", issue = "none")] -#[macro_export] -#[allow_internal_unstable(thread_local_internals, cfg_target_thread_local, thread_local)] -#[allow_internal_unsafe] -#[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] -macro_rules! __thread_local_inner { - // used to generate the `LocalKey` value for const-initialized thread locals - (@key $t:ty, const $init:expr) => {{ - #[inline] // see comments below - #[deny(unsafe_op_in_unsafe_fn)] - unsafe fn __getit( - _init: $crate::option::Option<&mut $crate::option::Option<$t>>, - ) -> $crate::option::Option<&'static $t> { - const INIT_EXPR: $t = $init; - - // wasm without atomics maps directly to `static mut`, and dtors - // aren't implemented because thread dtors aren't really a thing - // on wasm right now - // - // FIXME(#84224) this should come after the `target_thread_local` - // block. - static mut VAL: $t = INIT_EXPR; - unsafe { $crate::option::Option::Some(&VAL) } - } - - unsafe { - $crate::thread::LocalKey::new(__getit) - } - }}; - - // used to generate the `LocalKey` value for `thread_local!` - (@key $t:ty, $init:expr) => { - { - #[inline] - fn __init() -> $t { $init } - #[inline] - unsafe fn __getit( - init: $crate::option::Option<&mut $crate::option::Option<$t>>, - ) -> $crate::option::Option<&'static $t> { - static __KEY: $crate::thread::__StaticLocalKeyInner<$t> = - $crate::thread::__StaticLocalKeyInner::new(); - - // FIXME: remove the #[allow(...)] marker when macros don't - // raise warning for missing/extraneous unsafe blocks anymore. - // See https://github.com/rust-lang/rust/issues/74838. - #[allow(unused_unsafe)] - unsafe { - __KEY.get(move || { - if let $crate::option::Option::Some(init) = init { - if let $crate::option::Option::Some(value) = init.take() { - return value; - } else if $crate::cfg!(debug_assertions) { - $crate::unreachable!("missing default value"); - } - } - __init() - }) - } - } - - unsafe { - $crate::thread::LocalKey::new(__getit) - } - } - }; - ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => { - $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> = - $crate::__thread_local_inner!(@key $t, $($init)*); - } -} diff --git a/library/std/src/sys/common/thread_local/fast_local.rs b/library/std/src/sys/common/thread_local/fast_local.rs new file mode 100644 index 00000000000..2addcc4a759 --- /dev/null +++ b/library/std/src/sys/common/thread_local/fast_local.rs @@ -0,0 +1,276 @@ +#[doc(hidden)] +#[macro_export] +#[allow_internal_unstable( + thread_local_internals, + cfg_target_thread_local, + thread_local, + libstd_thread_internals +)] +#[allow_internal_unsafe] +macro_rules! __thread_local_inner { + // used to generate the `LocalKey` value for const-initialized thread locals + (@key $t:ty, const $init:expr) => {{ + #[cfg_attr(not(windows), inline)] // see comments below + #[deny(unsafe_op_in_unsafe_fn)] + unsafe fn __getit( + _init: $crate::option::Option<&mut $crate::option::Option<$t>>, + ) -> $crate::option::Option<&'static $t> { + const INIT_EXPR: $t = $init; + // If the platform has support for `#[thread_local]`, use it. + #[thread_local] + static mut VAL: $t = INIT_EXPR; + + // If a dtor isn't needed we can do something "very raw" and + // just get going. + if !$crate::mem::needs_drop::<$t>() { + unsafe { + return $crate::option::Option::Some(&VAL) + } + } + + // 0 == dtor not registered + // 1 == dtor registered, dtor not run + // 2 == dtor registered and is running or has run + #[thread_local] + static mut STATE: $crate::primitive::u8 = 0; + + unsafe extern "C" fn destroy(ptr: *mut $crate::primitive::u8) { + let ptr = ptr as *mut $t; + + unsafe { + $crate::debug_assert_eq!(STATE, 1); + STATE = 2; + $crate::ptr::drop_in_place(ptr); + } + } + + unsafe { + match STATE { + // 0 == we haven't registered a destructor, so do + // so now. + 0 => { + $crate::thread::__LocalKeyInner::<$t>::register_dtor( + $crate::ptr::addr_of_mut!(VAL) as *mut $crate::primitive::u8, + destroy, + ); + STATE = 1; + $crate::option::Option::Some(&VAL) + } + // 1 == the destructor is registered and the value + // is valid, so return the pointer. + 1 => $crate::option::Option::Some(&VAL), + // otherwise the destructor has already run, so we + // can't give access. + _ => $crate::option::Option::None, + } + } + } + + unsafe { + $crate::thread::LocalKey::new(__getit) + } + }}; + + // used to generate the `LocalKey` value for `thread_local!` + (@key $t:ty, $init:expr) => { + { + #[inline] + fn __init() -> $t { $init } + + // When reading this function you might ask "why is this inlined + // everywhere other than Windows?", and that's a very reasonable + // question to ask. The short story is that it segfaults rustc if + // this function is inlined. The longer story is that Windows looks + // to not support `extern` references to thread locals across DLL + // boundaries. This appears to at least not be supported in the ABI + // that LLVM implements. + // + // Because of this we never inline on Windows, but we do inline on + // other platforms (where external references to thread locals + // across DLLs are supported). A better fix for this would be to + // inline this function on Windows, but only for "statically linked" + // components. For example if two separately compiled rlibs end up + // getting linked into a DLL then it's fine to inline this function + // across that boundary. It's only not fine to inline this function + // across a DLL boundary. Unfortunately rustc doesn't currently + // have this sort of logic available in an attribute, and it's not + // clear that rustc is even equipped to answer this (it's more of a + // Cargo question kinda). This means that, unfortunately, Windows + // gets the pessimistic path for now where it's never inlined. + // + // The issue of "should enable on Windows sometimes" is #84933 + #[cfg_attr(not(windows), inline)] + unsafe fn __getit( + init: $crate::option::Option<&mut $crate::option::Option<$t>>, + ) -> $crate::option::Option<&'static $t> { + #[thread_local] + static __KEY: $crate::thread::__LocalKeyInner<$t> = + $crate::thread::__LocalKeyInner::<$t>::new(); + + // FIXME: remove the #[allow(...)] marker when macros don't + // raise warning for missing/extraneous unsafe blocks anymore. + // See https://github.com/rust-lang/rust/issues/74838. + #[allow(unused_unsafe)] + unsafe { + __KEY.get(move || { + if let $crate::option::Option::Some(init) = init { + if let $crate::option::Option::Some(value) = init.take() { + return value; + } else if $crate::cfg!(debug_assertions) { + $crate::unreachable!("missing default value"); + } + } + __init() + }) + } + } + + unsafe { + $crate::thread::LocalKey::new(__getit) + } + } + }; + ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => { + $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> = + $crate::__thread_local_inner!(@key $t, $($init)*); + } +} + +#[doc(hidden)] +pub mod fast { + use super::super::lazy::LazyKeyInner; + use crate::cell::Cell; + use crate::sys::thread_local_dtor::register_dtor; + use crate::{fmt, mem, panic}; + + #[derive(Copy, Clone)] + enum DtorState { + Unregistered, + Registered, + RunningOrHasRun, + } + + // This data structure has been carefully constructed so that the fast path + // only contains one branch on x86. That optimization is necessary to avoid + // duplicated tls lookups on OSX. + // + // LLVM issue: https://bugs.llvm.org/show_bug.cgi?id=41722 + pub struct Key { + // If `LazyKeyInner::get` returns `None`, that indicates either: + // * The value has never been initialized + // * The value is being recursively initialized + // * The value has already been destroyed or is being destroyed + // To determine which kind of `None`, check `dtor_state`. + // + // This is very optimizer friendly for the fast path - initialized but + // not yet dropped. + inner: LazyKeyInner, + + // Metadata to keep track of the state of the destructor. Remember that + // this variable is thread-local, not global. + dtor_state: Cell, + } + + impl fmt::Debug for Key { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Key").finish_non_exhaustive() + } + } + + impl Key { + pub const fn new() -> Key { + Key { inner: LazyKeyInner::new(), dtor_state: Cell::new(DtorState::Unregistered) } + } + + // note that this is just a publicly-callable function only for the + // const-initialized form of thread locals, basically a way to call the + // free `register_dtor` function defined elsewhere in std. + pub unsafe fn register_dtor(a: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) { + unsafe { + register_dtor(a, dtor); + } + } + + pub unsafe fn get T>(&self, init: F) -> Option<&'static T> { + // SAFETY: See the definitions of `LazyKeyInner::get` and + // `try_initialize` for more information. + // + // The caller must ensure no mutable references are ever active to + // the inner cell or the inner T when this is called. + // The `try_initialize` is dependant on the passed `init` function + // for this. + unsafe { + match self.inner.get() { + Some(val) => Some(val), + None => self.try_initialize(init), + } + } + } + + // `try_initialize` is only called once per fast thread local variable, + // except in corner cases where thread_local dtors reference other + // thread_local's, or it is being recursively initialized. + // + // Macos: Inlining this function can cause two `tlv_get_addr` calls to + // be performed for every call to `Key::get`. + // LLVM issue: https://bugs.llvm.org/show_bug.cgi?id=41722 + #[inline(never)] + unsafe fn try_initialize T>(&self, init: F) -> Option<&'static T> { + // SAFETY: See comment above (this function doc). + if !mem::needs_drop::() || unsafe { self.try_register_dtor() } { + // SAFETY: See comment above (this function doc). + Some(unsafe { self.inner.initialize(init) }) + } else { + None + } + } + + // `try_register_dtor` is only called once per fast thread local + // variable, except in corner cases where thread_local dtors reference + // other thread_local's, or it is being recursively initialized. + unsafe fn try_register_dtor(&self) -> bool { + match self.dtor_state.get() { + DtorState::Unregistered => { + // SAFETY: dtor registration happens before initialization. + // Passing `self` as a pointer while using `destroy_value` + // is safe because the function will build a pointer to a + // Key, which is the type of self and so find the correct + // size. + unsafe { register_dtor(self as *const _ as *mut u8, destroy_value::) }; + self.dtor_state.set(DtorState::Registered); + true + } + DtorState::Registered => { + // recursively initialized + true + } + DtorState::RunningOrHasRun => false, + } + } + } + + unsafe extern "C" fn destroy_value(ptr: *mut u8) { + let ptr = ptr as *mut Key; + + // SAFETY: + // + // The pointer `ptr` has been built just above and comes from + // `try_register_dtor` where it is originally a Key coming from `self`, + // making it non-NUL and of the correct type. + // + // Right before we run the user destructor be sure to set the + // `Option` to `None`, and `dtor_state` to `RunningOrHasRun`. This + // causes future calls to `get` to run `try_initialize_drop` again, + // which will now fail, and return `None`. + // + // Wrap the call in a catch to ensure unwinding is caught in the event + // a panic takes place in a destructor. + if let Err(_) = panic::catch_unwind(panic::AssertUnwindSafe(|| unsafe { + let value = (*ptr).inner.take(); + (*ptr).dtor_state.set(DtorState::RunningOrHasRun); + drop(value); + })) { + rtabort!("thread local panicked on drop"); + } + } +} diff --git a/library/std/src/sys/common/thread_local/mod.rs b/library/std/src/sys/common/thread_local/mod.rs new file mode 100644 index 00000000000..1fee84a0434 --- /dev/null +++ b/library/std/src/sys/common/thread_local/mod.rs @@ -0,0 +1,109 @@ +//! The following module declarations are outside cfg_if because the internal +//! `__thread_local_internal` macro does not seem to be exported properly when using cfg_if +#![unstable(feature = "thread_local_internals", reason = "should not be necessary", issue = "none")] + +#[cfg(all(target_thread_local, not(all(target_family = "wasm", not(target_feature = "atomics")))))] +mod fast_local; +#[cfg(all( + not(target_thread_local), + not(all(target_family = "wasm", not(target_feature = "atomics"))) +))] +mod os_local; +#[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] +mod static_local; + +#[cfg(not(test))] +cfg_if::cfg_if! { + if #[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] { + #[doc(hidden)] + pub use static_local::statik::Key; + } else if #[cfg(all(target_thread_local, not(all(target_family = "wasm", not(target_feature = "atomics")))))] { + #[doc(hidden)] + pub use fast_local::fast::Key; + } else if #[cfg(all(not(target_thread_local), not(all(target_family = "wasm", not(target_feature = "atomics")))))] { + #[doc(hidden)] + pub use os_local::os::Key; + } +} + +#[doc(hidden)] +#[cfg(test)] +pub use realstd::thread::__LocalKeyInner as Key; + +mod lazy { + use crate::cell::UnsafeCell; + use crate::hint; + use crate::mem; + + pub struct LazyKeyInner { + inner: UnsafeCell>, + } + + impl LazyKeyInner { + pub const fn new() -> LazyKeyInner { + LazyKeyInner { inner: UnsafeCell::new(None) } + } + + pub unsafe fn get(&self) -> Option<&'static T> { + // SAFETY: The caller must ensure no reference is ever handed out to + // the inner cell nor mutable reference to the Option inside said + // cell. This make it safe to hand a reference, though the lifetime + // of 'static is itself unsafe, making the get method unsafe. + unsafe { (*self.inner.get()).as_ref() } + } + + /// The caller must ensure that no reference is active: this method + /// needs unique access. + pub unsafe fn initialize T>(&self, init: F) -> &'static T { + // Execute the initialization up front, *then* move it into our slot, + // just in case initialization fails. + let value = init(); + let ptr = self.inner.get(); + + // SAFETY: + // + // note that this can in theory just be `*ptr = Some(value)`, but due to + // the compiler will currently codegen that pattern with something like: + // + // ptr::drop_in_place(ptr) + // ptr::write(ptr, Some(value)) + // + // Due to this pattern it's possible for the destructor of the value in + // `ptr` (e.g., if this is being recursively initialized) to re-access + // TLS, in which case there will be a `&` and `&mut` pointer to the same + // value (an aliasing violation). To avoid setting the "I'm running a + // destructor" flag we just use `mem::replace` which should sequence the + // operations a little differently and make this safe to call. + // + // The precondition also ensures that we are the only one accessing + // `self` at the moment so replacing is fine. + unsafe { + let _ = mem::replace(&mut *ptr, Some(value)); + } + + // SAFETY: With the call to `mem::replace` it is guaranteed there is + // a `Some` behind `ptr`, not a `None` so `unreachable_unchecked` + // will never be reached. + unsafe { + // After storing `Some` we want to get a reference to the contents of + // what we just stored. While we could use `unwrap` here and it should + // always work it empirically doesn't seem to always get optimized away, + // which means that using something like `try_with` can pull in + // panicking code and cause a large size bloat. + match *ptr { + Some(ref x) => x, + None => hint::unreachable_unchecked(), + } + } + } + + /// The other methods hand out references while taking &self. + /// As such, callers of this method must ensure no `&` and `&mut` are + /// available and used at the same time. + #[allow(unused)] + pub unsafe fn take(&mut self) -> Option { + // SAFETY: See doc comment for this method. + unsafe { (*self.inner.get()).take() } + } + } +} diff --git a/library/std/src/sys/common/thread_local/os_local.rs b/library/std/src/sys/common/thread_local/os_local.rs new file mode 100644 index 00000000000..6f6560c4aa9 --- /dev/null +++ b/library/std/src/sys/common/thread_local/os_local.rs @@ -0,0 +1,217 @@ +#[doc(hidden)] +#[macro_export] +#[allow_internal_unstable( + thread_local_internals, + cfg_target_thread_local, + thread_local, + libstd_thread_internals +)] +#[allow_internal_unsafe] +macro_rules! __thread_local_inner { + // used to generate the `LocalKey` value for const-initialized thread locals + (@key $t:ty, const $init:expr) => {{ + #[cfg_attr(not(windows), inline)] // see comments below + #[deny(unsafe_op_in_unsafe_fn)] + unsafe fn __getit( + _init: $crate::option::Option<&mut $crate::option::Option<$t>>, + ) -> $crate::option::Option<&'static $t> { + const INIT_EXPR: $t = $init; + + // On platforms without `#[thread_local]` we fall back to the + // same implementation as below for os thread locals. + #[inline] + const fn __init() -> $t { INIT_EXPR } + static __KEY: $crate::thread::__LocalKeyInner<$t> = + $crate::thread::__LocalKeyInner::new(); + #[allow(unused_unsafe)] + unsafe { + __KEY.get(move || { + if let $crate::option::Option::Some(init) = _init { + if let $crate::option::Option::Some(value) = init.take() { + return value; + } else if $crate::cfg!(debug_assertions) { + $crate::unreachable!("missing initial value"); + } + } + __init() + }) + } + } + + unsafe { + $crate::thread::LocalKey::new(__getit) + } + }}; + + // used to generate the `LocalKey` value for `thread_local!` + (@key $t:ty, $init:expr) => { + { + #[inline] + fn __init() -> $t { $init } + + // When reading this function you might ask "why is this inlined + // everywhere other than Windows?", and that's a very reasonable + // question to ask. The short story is that it segfaults rustc if + // this function is inlined. The longer story is that Windows looks + // to not support `extern` references to thread locals across DLL + // boundaries. This appears to at least not be supported in the ABI + // that LLVM implements. + // + // Because of this we never inline on Windows, but we do inline on + // other platforms (where external references to thread locals + // across DLLs are supported). A better fix for this would be to + // inline this function on Windows, but only for "statically linked" + // components. For example if two separately compiled rlibs end up + // getting linked into a DLL then it's fine to inline this function + // across that boundary. It's only not fine to inline this function + // across a DLL boundary. Unfortunately rustc doesn't currently + // have this sort of logic available in an attribute, and it's not + // clear that rustc is even equipped to answer this (it's more of a + // Cargo question kinda). This means that, unfortunately, Windows + // gets the pessimistic path for now where it's never inlined. + // + // The issue of "should enable on Windows sometimes" is #84933 + #[cfg_attr(not(windows), inline)] + unsafe fn __getit( + init: $crate::option::Option<&mut $crate::option::Option<$t>>, + ) -> $crate::option::Option<&'static $t> { + static __KEY: $crate::thread::__LocalKeyInner<$t> = + $crate::thread::__LocalKeyInner::new(); + + // FIXME: remove the #[allow(...)] marker when macros don't + // raise warning for missing/extraneous unsafe blocks anymore. + // See https://github.com/rust-lang/rust/issues/74838. + #[allow(unused_unsafe)] + unsafe { + __KEY.get(move || { + if let $crate::option::Option::Some(init) = init { + if let $crate::option::Option::Some(value) = init.take() { + return value; + } else if $crate::cfg!(debug_assertions) { + $crate::unreachable!("missing default value"); + } + } + __init() + }) + } + } + + unsafe { + $crate::thread::LocalKey::new(__getit) + } + } + }; + ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => { + $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> = + $crate::__thread_local_inner!(@key $t, $($init)*); + } +} + +#[doc(hidden)] +pub mod os { + use super::super::lazy::LazyKeyInner; + use crate::cell::Cell; + use crate::sys_common::thread_local_key::StaticKey as OsStaticKey; + use crate::{fmt, marker, panic, ptr}; + + /// Use a regular global static to store this key; the state provided will then be + /// thread-local. + pub struct Key { + // OS-TLS key that we'll use to key off. + os: OsStaticKey, + marker: marker::PhantomData>, + } + + impl fmt::Debug for Key { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Key").finish_non_exhaustive() + } + } + + unsafe impl Sync for Key {} + + struct Value { + inner: LazyKeyInner, + key: &'static Key, + } + + impl Key { + #[rustc_const_unstable(feature = "thread_local_internals", issue = "none")] + pub const fn new() -> Key { + Key { os: OsStaticKey::new(Some(destroy_value::)), marker: marker::PhantomData } + } + + /// It is a requirement for the caller to ensure that no mutable + /// reference is active when this method is called. + pub unsafe fn get(&'static self, init: impl FnOnce() -> T) -> Option<&'static T> { + // SAFETY: See the documentation for this method. + let ptr = unsafe { self.os.get() as *mut Value }; + if ptr.addr() > 1 { + // SAFETY: the check ensured the pointer is safe (its destructor + // is not running) + it is coming from a trusted source (self). + if let Some(ref value) = unsafe { (*ptr).inner.get() } { + return Some(value); + } + } + // SAFETY: At this point we are sure we have no value and so + // initializing (or trying to) is safe. + unsafe { self.try_initialize(init) } + } + + // `try_initialize` is only called once per os thread local variable, + // except in corner cases where thread_local dtors reference other + // thread_local's, or it is being recursively initialized. + unsafe fn try_initialize(&'static self, init: impl FnOnce() -> T) -> Option<&'static T> { + // SAFETY: No mutable references are ever handed out meaning getting + // the value is ok. + let ptr = unsafe { self.os.get() as *mut Value }; + if ptr.addr() == 1 { + // destructor is running + return None; + } + + let ptr = if ptr.is_null() { + // If the lookup returned null, we haven't initialized our own + // local copy, so do that now. + let ptr = Box::into_raw(Box::new(Value { inner: LazyKeyInner::new(), key: self })); + // SAFETY: At this point we are sure there is no value inside + // ptr so setting it will not affect anyone else. + unsafe { + self.os.set(ptr as *mut u8); + } + ptr + } else { + // recursive initialization + ptr + }; + + // SAFETY: ptr has been ensured as non-NUL just above an so can be + // dereferenced safely. + unsafe { Some((*ptr).inner.initialize(init)) } + } + } + + unsafe extern "C" fn destroy_value(ptr: *mut u8) { + // SAFETY: + // + // The OS TLS ensures that this key contains a null value when this + // destructor starts to run. We set it back to a sentinel value of 1 to + // ensure that any future calls to `get` for this thread will return + // `None`. + // + // Note that to prevent an infinite loop we reset it back to null right + // before we return from the destructor ourselves. + // + // Wrap the call in a catch to ensure unwinding is caught in the event + // a panic takes place in a destructor. + if let Err(_) = panic::catch_unwind(|| unsafe { + let ptr = Box::from_raw(ptr as *mut Value); + let key = ptr.key; + key.os.set(ptr::invalid_mut(1)); + drop(ptr); + key.os.set(ptr::null_mut()); + }) { + rtabort!("thread local panicked on drop"); + } + } +} diff --git a/library/std/src/sys/common/thread_local/static_local.rs b/library/std/src/sys/common/thread_local/static_local.rs new file mode 100644 index 00000000000..ec4f2a12b7e --- /dev/null +++ b/library/std/src/sys/common/thread_local/static_local.rs @@ -0,0 +1,115 @@ +#[doc(hidden)] +#[macro_export] +#[allow_internal_unstable( + thread_local_internals, + cfg_target_thread_local, + thread_local, + libstd_thread_internals +)] +#[allow_internal_unsafe] +macro_rules! __thread_local_inner { + // used to generate the `LocalKey` value for const-initialized thread locals + (@key $t:ty, const $init:expr) => {{ + #[inline] // see comments below + #[deny(unsafe_op_in_unsafe_fn)] + unsafe fn __getit( + _init: $crate::option::Option<&mut $crate::option::Option<$t>>, + ) -> $crate::option::Option<&'static $t> { + const INIT_EXPR: $t = $init; + + // wasm without atomics maps directly to `static mut`, and dtors + // aren't implemented because thread dtors aren't really a thing + // on wasm right now + // + // FIXME(#84224) this should come after the `target_thread_local` + // block. + static mut VAL: $t = INIT_EXPR; + unsafe { $crate::option::Option::Some(&VAL) } + } + + unsafe { + $crate::thread::LocalKey::new(__getit) + } + }}; + + // used to generate the `LocalKey` value for `thread_local!` + (@key $t:ty, $init:expr) => { + { + #[inline] + fn __init() -> $t { $init } + #[inline] + unsafe fn __getit( + init: $crate::option::Option<&mut $crate::option::Option<$t>>, + ) -> $crate::option::Option<&'static $t> { + static __KEY: $crate::thread::__LocalKeyInner<$t> = + $crate::thread::__LocalKeyInner::new(); + + // FIXME: remove the #[allow(...)] marker when macros don't + // raise warning for missing/extraneous unsafe blocks anymore. + // See https://github.com/rust-lang/rust/issues/74838. + #[allow(unused_unsafe)] + unsafe { + __KEY.get(move || { + if let $crate::option::Option::Some(init) = init { + if let $crate::option::Option::Some(value) = init.take() { + return value; + } else if $crate::cfg!(debug_assertions) { + $crate::unreachable!("missing default value"); + } + } + __init() + }) + } + } + + unsafe { + $crate::thread::LocalKey::new(__getit) + } + } + }; + ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => { + $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> = + $crate::__thread_local_inner!(@key $t, $($init)*); + } +} + +/// On some targets like wasm there's no threads, so no need to generate +/// thread locals and we can instead just use plain statics! +#[doc(hidden)] +pub mod statik { + use super::super::lazy::LazyKeyInner; + use crate::fmt; + + pub struct Key { + inner: LazyKeyInner, + } + + unsafe impl Sync for Key {} + + impl fmt::Debug for Key { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Key").finish_non_exhaustive() + } + } + + impl Key { + pub const fn new() -> Key { + Key { inner: LazyKeyInner::new() } + } + + pub unsafe fn get(&self, init: impl FnOnce() -> T) -> Option<&'static T> { + // SAFETY: The caller must ensure no reference is ever handed out to + // the inner cell nor mutable reference to the Option inside said + // cell. This make it safe to hand a reference, though the lifetime + // of 'static is itself unsafe, making the get method unsafe. + let value = unsafe { + match self.inner.get() { + Some(ref value) => value, + None => self.inner.initialize(init), + } + }; + + Some(value) + } + } +} diff --git a/library/std/src/thread/local.rs b/library/std/src/thread/local.rs index c82b5c2284f..7fdf03acc14 100644 --- a/library/std/src/thread/local.rs +++ b/library/std/src/thread/local.rs @@ -585,376 +585,3 @@ pub fn replace(&'static self, value: T) -> T { self.with(|cell| cell.replace(value)) } } - -mod lazy { - use crate::cell::UnsafeCell; - use crate::hint; - use crate::mem; - - pub struct LazyKeyInner { - inner: UnsafeCell>, - } - - impl LazyKeyInner { - pub const fn new() -> LazyKeyInner { - LazyKeyInner { inner: UnsafeCell::new(None) } - } - - pub unsafe fn get(&self) -> Option<&'static T> { - // SAFETY: The caller must ensure no reference is ever handed out to - // the inner cell nor mutable reference to the Option inside said - // cell. This make it safe to hand a reference, though the lifetime - // of 'static is itself unsafe, making the get method unsafe. - unsafe { (*self.inner.get()).as_ref() } - } - - /// The caller must ensure that no reference is active: this method - /// needs unique access. - pub unsafe fn initialize T>(&self, init: F) -> &'static T { - // Execute the initialization up front, *then* move it into our slot, - // just in case initialization fails. - let value = init(); - let ptr = self.inner.get(); - - // SAFETY: - // - // note that this can in theory just be `*ptr = Some(value)`, but due to - // the compiler will currently codegen that pattern with something like: - // - // ptr::drop_in_place(ptr) - // ptr::write(ptr, Some(value)) - // - // Due to this pattern it's possible for the destructor of the value in - // `ptr` (e.g., if this is being recursively initialized) to re-access - // TLS, in which case there will be a `&` and `&mut` pointer to the same - // value (an aliasing violation). To avoid setting the "I'm running a - // destructor" flag we just use `mem::replace` which should sequence the - // operations a little differently and make this safe to call. - // - // The precondition also ensures that we are the only one accessing - // `self` at the moment so replacing is fine. - unsafe { - let _ = mem::replace(&mut *ptr, Some(value)); - } - - // SAFETY: With the call to `mem::replace` it is guaranteed there is - // a `Some` behind `ptr`, not a `None` so `unreachable_unchecked` - // will never be reached. - unsafe { - // After storing `Some` we want to get a reference to the contents of - // what we just stored. While we could use `unwrap` here and it should - // always work it empirically doesn't seem to always get optimized away, - // which means that using something like `try_with` can pull in - // panicking code and cause a large size bloat. - match *ptr { - Some(ref x) => x, - None => hint::unreachable_unchecked(), - } - } - } - - /// The other methods hand out references while taking &self. - /// As such, callers of this method must ensure no `&` and `&mut` are - /// available and used at the same time. - #[allow(unused)] - pub unsafe fn take(&mut self) -> Option { - // SAFETY: See doc comment for this method. - unsafe { (*self.inner.get()).take() } - } - } -} - -/// On some targets like wasm there's no threads, so no need to generate -/// thread locals and we can instead just use plain statics! -#[doc(hidden)] -#[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] -pub mod statik { - use super::lazy::LazyKeyInner; - use crate::fmt; - - pub struct Key { - inner: LazyKeyInner, - } - - unsafe impl Sync for Key {} - - impl fmt::Debug for Key { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Key").finish_non_exhaustive() - } - } - - impl Key { - pub const fn new() -> Key { - Key { inner: LazyKeyInner::new() } - } - - pub unsafe fn get(&self, init: impl FnOnce() -> T) -> Option<&'static T> { - // SAFETY: The caller must ensure no reference is ever handed out to - // the inner cell nor mutable reference to the Option inside said - // cell. This make it safe to hand a reference, though the lifetime - // of 'static is itself unsafe, making the get method unsafe. - let value = unsafe { - match self.inner.get() { - Some(ref value) => value, - None => self.inner.initialize(init), - } - }; - - Some(value) - } - } -} - -#[doc(hidden)] -#[cfg(all(target_thread_local, not(all(target_family = "wasm", not(target_feature = "atomics"))),))] -pub mod fast { - use super::lazy::LazyKeyInner; - use crate::cell::Cell; - use crate::sys::thread_local_dtor::register_dtor; - use crate::{fmt, mem, panic}; - - #[derive(Copy, Clone)] - enum DtorState { - Unregistered, - Registered, - RunningOrHasRun, - } - - // This data structure has been carefully constructed so that the fast path - // only contains one branch on x86. That optimization is necessary to avoid - // duplicated tls lookups on OSX. - // - // LLVM issue: https://bugs.llvm.org/show_bug.cgi?id=41722 - pub struct Key { - // If `LazyKeyInner::get` returns `None`, that indicates either: - // * The value has never been initialized - // * The value is being recursively initialized - // * The value has already been destroyed or is being destroyed - // To determine which kind of `None`, check `dtor_state`. - // - // This is very optimizer friendly for the fast path - initialized but - // not yet dropped. - inner: LazyKeyInner, - - // Metadata to keep track of the state of the destructor. Remember that - // this variable is thread-local, not global. - dtor_state: Cell, - } - - impl fmt::Debug for Key { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Key").finish_non_exhaustive() - } - } - - impl Key { - pub const fn new() -> Key { - Key { inner: LazyKeyInner::new(), dtor_state: Cell::new(DtorState::Unregistered) } - } - - // note that this is just a publicly-callable function only for the - // const-initialized form of thread locals, basically a way to call the - // free `register_dtor` function defined elsewhere in std. - pub unsafe fn register_dtor(a: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) { - unsafe { - register_dtor(a, dtor); - } - } - - pub unsafe fn get T>(&self, init: F) -> Option<&'static T> { - // SAFETY: See the definitions of `LazyKeyInner::get` and - // `try_initialize` for more information. - // - // The caller must ensure no mutable references are ever active to - // the inner cell or the inner T when this is called. - // The `try_initialize` is dependant on the passed `init` function - // for this. - unsafe { - match self.inner.get() { - Some(val) => Some(val), - None => self.try_initialize(init), - } - } - } - - // `try_initialize` is only called once per fast thread local variable, - // except in corner cases where thread_local dtors reference other - // thread_local's, or it is being recursively initialized. - // - // Macos: Inlining this function can cause two `tlv_get_addr` calls to - // be performed for every call to `Key::get`. - // LLVM issue: https://bugs.llvm.org/show_bug.cgi?id=41722 - #[inline(never)] - unsafe fn try_initialize T>(&self, init: F) -> Option<&'static T> { - // SAFETY: See comment above (this function doc). - if !mem::needs_drop::() || unsafe { self.try_register_dtor() } { - // SAFETY: See comment above (this function doc). - Some(unsafe { self.inner.initialize(init) }) - } else { - None - } - } - - // `try_register_dtor` is only called once per fast thread local - // variable, except in corner cases where thread_local dtors reference - // other thread_local's, or it is being recursively initialized. - unsafe fn try_register_dtor(&self) -> bool { - match self.dtor_state.get() { - DtorState::Unregistered => { - // SAFETY: dtor registration happens before initialization. - // Passing `self` as a pointer while using `destroy_value` - // is safe because the function will build a pointer to a - // Key, which is the type of self and so find the correct - // size. - unsafe { register_dtor(self as *const _ as *mut u8, destroy_value::) }; - self.dtor_state.set(DtorState::Registered); - true - } - DtorState::Registered => { - // recursively initialized - true - } - DtorState::RunningOrHasRun => false, - } - } - } - - unsafe extern "C" fn destroy_value(ptr: *mut u8) { - let ptr = ptr as *mut Key; - - // SAFETY: - // - // The pointer `ptr` has been built just above and comes from - // `try_register_dtor` where it is originally a Key coming from `self`, - // making it non-NUL and of the correct type. - // - // Right before we run the user destructor be sure to set the - // `Option` to `None`, and `dtor_state` to `RunningOrHasRun`. This - // causes future calls to `get` to run `try_initialize_drop` again, - // which will now fail, and return `None`. - // - // Wrap the call in a catch to ensure unwinding is caught in the event - // a panic takes place in a destructor. - if let Err(_) = panic::catch_unwind(panic::AssertUnwindSafe(|| unsafe { - let value = (*ptr).inner.take(); - (*ptr).dtor_state.set(DtorState::RunningOrHasRun); - drop(value); - })) { - rtabort!("thread local panicked on drop"); - } - } -} - -#[doc(hidden)] -#[cfg(all( - not(target_thread_local), - not(all(target_family = "wasm", not(target_feature = "atomics"))), -))] -pub mod os { - use super::lazy::LazyKeyInner; - use crate::cell::Cell; - use crate::sys_common::thread_local_key::StaticKey as OsStaticKey; - use crate::{fmt, marker, panic, ptr}; - - /// Use a regular global static to store this key; the state provided will then be - /// thread-local. - pub struct Key { - // OS-TLS key that we'll use to key off. - os: OsStaticKey, - marker: marker::PhantomData>, - } - - impl fmt::Debug for Key { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Key").finish_non_exhaustive() - } - } - - unsafe impl Sync for Key {} - - struct Value { - inner: LazyKeyInner, - key: &'static Key, - } - - impl Key { - #[rustc_const_unstable(feature = "thread_local_internals", issue = "none")] - pub const fn new() -> Key { - Key { os: OsStaticKey::new(Some(destroy_value::)), marker: marker::PhantomData } - } - - /// It is a requirement for the caller to ensure that no mutable - /// reference is active when this method is called. - pub unsafe fn get(&'static self, init: impl FnOnce() -> T) -> Option<&'static T> { - // SAFETY: See the documentation for this method. - let ptr = unsafe { self.os.get() as *mut Value }; - if ptr.addr() > 1 { - // SAFETY: the check ensured the pointer is safe (its destructor - // is not running) + it is coming from a trusted source (self). - if let Some(ref value) = unsafe { (*ptr).inner.get() } { - return Some(value); - } - } - // SAFETY: At this point we are sure we have no value and so - // initializing (or trying to) is safe. - unsafe { self.try_initialize(init) } - } - - // `try_initialize` is only called once per os thread local variable, - // except in corner cases where thread_local dtors reference other - // thread_local's, or it is being recursively initialized. - unsafe fn try_initialize(&'static self, init: impl FnOnce() -> T) -> Option<&'static T> { - // SAFETY: No mutable references are ever handed out meaning getting - // the value is ok. - let ptr = unsafe { self.os.get() as *mut Value }; - if ptr.addr() == 1 { - // destructor is running - return None; - } - - let ptr = if ptr.is_null() { - // If the lookup returned null, we haven't initialized our own - // local copy, so do that now. - let ptr = Box::into_raw(Box::new(Value { inner: LazyKeyInner::new(), key: self })); - // SAFETY: At this point we are sure there is no value inside - // ptr so setting it will not affect anyone else. - unsafe { - self.os.set(ptr as *mut u8); - } - ptr - } else { - // recursive initialization - ptr - }; - - // SAFETY: ptr has been ensured as non-NUL just above an so can be - // dereferenced safely. - unsafe { Some((*ptr).inner.initialize(init)) } - } - } - - unsafe extern "C" fn destroy_value(ptr: *mut u8) { - // SAFETY: - // - // The OS TLS ensures that this key contains a null value when this - // destructor starts to run. We set it back to a sentinel value of 1 to - // ensure that any future calls to `get` for this thread will return - // `None`. - // - // Note that to prevent an infinite loop we reset it back to null right - // before we return from the destructor ourselves. - // - // Wrap the call in a catch to ensure unwinding is caught in the event - // a panic takes place in a destructor. - if let Err(_) = panic::catch_unwind(|| unsafe { - let ptr = Box::from_raw(ptr as *mut Value); - let key = ptr.key; - key.os.set(ptr::invalid_mut(1)); - drop(ptr); - key.os.set(ptr::null_mut()); - }) { - rtabort!("thread local panicked on drop"); - } - } -} diff --git a/library/std/src/thread/mod.rs b/library/std/src/thread/mod.rs index 489af776798..b9aaf5f6e15 100644 --- a/library/std/src/thread/mod.rs +++ b/library/std/src/thread/mod.rs @@ -203,44 +203,9 @@ // by the elf linker. "static" is for single-threaded platforms where a global // static is sufficient. -#[unstable(feature = "libstd_thread_internals", issue = "none")] -#[cfg(not(test))] -#[cfg(all( - target_thread_local, - not(all(target_family = "wasm", not(target_feature = "atomics"))), -))] #[doc(hidden)] -pub use self::local::fast::Key as __FastLocalKeyInner; -// when building for tests, use real std's type #[unstable(feature = "libstd_thread_internals", issue = "none")] -#[cfg(test)] -#[cfg(all( - target_thread_local, - not(all(target_family = "wasm", not(target_feature = "atomics"))), -))] -pub use realstd::thread::__FastLocalKeyInner; - -#[unstable(feature = "libstd_thread_internals", issue = "none")] -#[cfg(not(test))] -#[cfg(all( - not(target_thread_local), - not(all(target_family = "wasm", not(target_feature = "atomics"))), -))] -#[doc(hidden)] -pub use self::local::os::Key as __OsLocalKeyInner; -// when building for tests, use real std's type -#[unstable(feature = "libstd_thread_internals", issue = "none")] -#[cfg(test)] -#[cfg(all( - not(target_thread_local), - not(all(target_family = "wasm", not(target_feature = "atomics"))), -))] -pub use realstd::thread::__OsLocalKeyInner; - -#[unstable(feature = "libstd_thread_internals", issue = "none")] -#[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] -#[doc(hidden)] -pub use self::local::statik::Key as __StaticLocalKeyInner; +pub use crate::sys::common::thread_local::Key as __LocalKeyInner; //////////////////////////////////////////////////////////////////////////////// // Builder diff --git a/src/tools/tidy/src/pal.rs b/src/tools/tidy/src/pal.rs index 6d6d3c89a3c..868579b4b1a 100644 --- a/src/tools/tidy/src/pal.rs +++ b/src/tools/tidy/src/pal.rs @@ -62,8 +62,6 @@ "library/std/src/panic.rs", // fuchsia-specific panic backtrace handling "library/std/src/personality.rs", "library/std/src/personality/", - "library/std/src/thread/mod.rs", - "library/std/src/thread/local.rs", ]; pub fn check(path: &Path, bad: &mut bool) { diff --git a/tests/ui/threads-sendsync/issue-43733-2.rs b/tests/ui/threads-sendsync/issue-43733-2.rs index 32baeec4359..8f7a9c08375 100644 --- a/tests/ui/threads-sendsync/issue-43733-2.rs +++ b/tests/ui/threads-sendsync/issue-43733-2.rs @@ -21,7 +21,7 @@ const fn new() -> Self { } #[cfg(target_thread_local)] -use std::thread::__FastLocalKeyInner as Key; +use std::thread::__LocalKeyInner as Key; static __KEY: Key<()> = Key::new(); //~^ ERROR `UnsafeCell>` cannot be shared between threads diff --git a/tests/ui/threads-sendsync/issue-43733.rs b/tests/ui/threads-sendsync/issue-43733.rs index 935e02944b9..0eadef3e3e8 100644 --- a/tests/ui/threads-sendsync/issue-43733.rs +++ b/tests/ui/threads-sendsync/issue-43733.rs @@ -1,8 +1,8 @@ // ignore-wasm32 // revisions: mir thir // [thir]compile-flags: -Z thir-unsafeck -// normalize-stderr-test: "__FastLocalKeyInner::::get" -> "$$LOCALKEYINNER::::get" -// normalize-stderr-test: "__OsLocalKeyInner::::get" -> "$$LOCALKEYINNER::::get" +// normalize-stderr-test: "__LocalKeyInner::::get" -> "$$LOCALKEYINNER::::get" +// normalize-stderr-test: "__LocalKeyInner::::get" -> "$$LOCALKEYINNER::::get" #![feature(thread_local)] #![feature(cfg_target_thread_local, thread_local_internals)] @@ -12,10 +12,10 @@ #[cfg(target_thread_local)] #[thread_local] -static __KEY: std::thread::__FastLocalKeyInner = std::thread::__FastLocalKeyInner::new(); +static __KEY: std::thread::__LocalKeyInner = std::thread::__LocalKeyInner::new(); #[cfg(not(target_thread_local))] -static __KEY: std::thread::__OsLocalKeyInner = std::thread::__OsLocalKeyInner::new(); +static __KEY: std::thread::__LocalKeyInner = std::thread::__LocalKeyInner::new(); fn __getit(_: Option<&mut Option>>) -> std::option::Option<&'static Foo> { __KEY.get(Default::default) From d2e4b59e60aae6f47baae337a3a7f9f04c728592 Mon Sep 17 00:00:00 2001 From: Peter Jaszkowiak Date: Fri, 3 Feb 2023 00:08:57 -0700 Subject: [PATCH 10/14] rustdoc: sort deprecated items lower in search serialize `q` (`itemPaths`) sparsely overall 4% reduction in search index size --- src/librustdoc/formats/cache.rs | 1 + src/librustdoc/html/render/mod.rs | 1 + src/librustdoc/html/render/print_item.rs | 9 ++++---- src/librustdoc/html/render/search_index.rs | 23 +++++++++++++++++++- src/librustdoc/html/static/js/search.js | 25 ++++++++++++++++++---- 5 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 24752cddb33..e7480978c25 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -337,6 +337,7 @@ fn fold_item(&mut self, item: clean::Item) -> Option { self.cache, ), aliases: item.attrs.get_doc_aliases(), + deprecation: item.deprecation(self.tcx), }); } } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 816a8f4e274..dc96c452cd2 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -107,6 +107,7 @@ pub(crate) struct IndexItem { pub(crate) parent_idx: Option, pub(crate) search_type: Option, pub(crate) aliases: Box<[Symbol]>, + pub(crate) deprecation: Option, } /// A type used for the search index. diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index bd7548003ad..4c76ee156c9 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -470,10 +470,11 @@ fn tag_html(class: &str, title: &str, contents: &str) -> String { // The trailing space after each tag is to space it properly against the rest of the docs. if let Some(depr) = &item.deprecation(tcx) { - let mut message = "Deprecated"; - if !stability::deprecation_in_effect(depr) { - message = "Deprecation planned"; - } + let message = if stability::deprecation_in_effect(depr) { + "Deprecated" + } else { + "Deprecation planned" + }; tags += &tag_html("deprecated", "", message); } diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index 5b0caac099b..4ff39d6b143 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -42,6 +42,7 @@ pub(crate) fn build_index<'tcx>( parent_idx: None, search_type: get_function_type_for_search(item, tcx, impl_generics.as_ref(), cache), aliases: item.attrs.get_doc_aliases(), + deprecation: item.deprecation(tcx), }); } } @@ -244,7 +245,17 @@ fn serialize(&self, serializer: S) -> Result )?; crate_data.serialize_field( "q", - &self.items.iter().map(|item| &item.path).collect::>(), + &self + .items + .iter() + .enumerate() + // Serialize as an array of item indices and full paths + .filter_map( + |(index, item)| { + if item.path.is_empty() { None } else { Some((index, &item.path)) } + }, + ) + .collect::>(), )?; crate_data.serialize_field( "d", @@ -297,6 +308,16 @@ fn serialize(&self, serializer: S) -> Result }) .collect::>(), )?; + crate_data.serialize_field( + "c", + &self + .items + .iter() + .enumerate() + // Serialize as an array of deprecated item indices + .filter_map(|(index, item)| item.deprecation.map(|_| index)) + .collect::>(), + )?; crate_data.serialize_field( "p", &self.paths.iter().map(|(it, s)| (it, s.as_str())).collect::>(), diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 88592fa0c84..5697d005b33 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -811,6 +811,13 @@ function initSearch(rawSearchIndex) { return a - b; } + // sort deprecated items later + a = aaa.item.deprecated; + b = bbb.item.deprecated; + if (a !== b) { + return a - b; + } + // sort by crate (current crate comes first) a = (aaa.item.crate !== preferredCrate); b = (bbb.item.crate !== preferredCrate); @@ -1170,6 +1177,7 @@ function initSearch(rawSearchIndex) { parent: item.parent, type: item.type, is_alias: true, + deprecated: item.deprecated, }; } @@ -1965,10 +1973,11 @@ function initSearch(rawSearchIndex) { * n: Array, * t: Array, * d: Array, - * q: Array, + * q: Array<[Number, string]>, * i: Array, * f: Array, * p: Array, + * c: Array * }} */ const crateCorpus = rawSearchIndex[crate]; @@ -1987,6 +1996,7 @@ function initSearch(rawSearchIndex) { type: null, id: id, normalizedName: crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, ""), + deprecated: null, }; id += 1; searchIndex.push(crateRow); @@ -1996,14 +2006,20 @@ function initSearch(rawSearchIndex) { const itemTypes = crateCorpus.t; // an array of (String) item names const itemNames = crateCorpus.n; - // an array of (String) full paths (or empty string for previous path) - const itemPaths = crateCorpus.q; + // an array of [(Number) item index, + // (String) full path] + // an item whose index is not present will fall back to the previous present path + // i.e. if indices 4 and 11 are present, but 5-10 and 12-13 are not present, + // 5-10 will fall back to the path for 4 and 12-13 will fall back to the path for 11 + const itemPaths = new Map(crateCorpus.q); // an array of (String) descriptions const itemDescs = crateCorpus.d; // an array of (Number) the parent path index + 1 to `paths`, or 0 if none const itemParentIdxs = crateCorpus.i; // an array of (Object | null) the type of the function, if any const itemFunctionSearchTypes = crateCorpus.f; + // an array of (Number) indices for the deprecated items + const deprecatedItems = new Set(crateCorpus.c); // an array of [(Number) item type, // (String) name] const paths = crateCorpus.p; @@ -2045,12 +2061,13 @@ function initSearch(rawSearchIndex) { crate: crate, ty: itemTypes[i], name: itemNames[i], - path: itemPaths[i] ? itemPaths[i] : lastPath, + path: itemPaths.has(i) ? itemPaths.get(i) : lastPath, desc: itemDescs[i], parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined, type: buildFunctionSearchType(itemFunctionSearchTypes[i], lowercasePaths), id: id, normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""), + deprecated: deprecatedItems.has(i), }; id += 1; searchIndex.push(row); From 83da9a89d8316c76dd6262c6aa10b74e549308bd Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Thu, 9 Mar 2023 19:53:59 +0000 Subject: [PATCH 11/14] Directly construct Inherited. --- compiler/rustc_hir_typeck/src/inherited.rs | 39 +--- compiler/rustc_hir_typeck/src/lib.rs | 217 +++++++++--------- .../src/methods/unnecessary_to_owned.rs | 8 +- .../clippy_lints/src/transmute/utils.rs | 57 +++-- 4 files changed, 148 insertions(+), 173 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/inherited.rs b/compiler/rustc_hir_typeck/src/inherited.rs index 07fa7c55df6..4110b176b41 100644 --- a/compiler/rustc_hir_typeck/src/inherited.rs +++ b/compiler/rustc_hir_typeck/src/inherited.rs @@ -4,7 +4,6 @@ use rustc_hir as hir; use rustc_hir::def_id::LocalDefId; use rustc_hir::HirIdMap; -use rustc_infer::infer; use rustc_infer::infer::{DefiningAnchor, InferCtxt, InferOk, TyCtxtInferExt}; use rustc_middle::ty::visit::TypeVisitableExt; use rustc_middle::ty::{self, Ty, TyCtxt}; @@ -73,40 +72,16 @@ fn deref(&self) -> &Self::Target { } } -/// A temporary returned by `Inherited::build(...)`. This is necessary -/// for multiple `InferCtxt` to share the same `typeck_results` -/// without using `Rc` or something similar. -pub struct InheritedBuilder<'tcx> { - infcx: infer::InferCtxtBuilder<'tcx>, - typeck_results: RefCell>, -} - impl<'tcx> Inherited<'tcx> { - pub fn build(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> InheritedBuilder<'tcx> { + pub fn new(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Self { let hir_owner = tcx.hir().local_def_id_to_hir_id(def_id).owner; - InheritedBuilder { - infcx: tcx - .infer_ctxt() - .ignoring_regions() - .with_opaque_type_inference(DefiningAnchor::Bind(hir_owner.def_id)), - typeck_results: RefCell::new(ty::TypeckResults::new(hir_owner)), - } - } -} - -impl<'tcx> InheritedBuilder<'tcx> { - pub fn enter(mut self, f: F) -> R - where - F: FnOnce(&Inherited<'tcx>) -> R, - { - f(&Inherited::new(self.infcx.build(), self.typeck_results)) - } -} - -impl<'tcx> Inherited<'tcx> { - fn new(infcx: InferCtxt<'tcx>, typeck_results: RefCell>) -> Self { - let tcx = infcx.tcx; + let infcx = tcx + .infer_ctxt() + .ignoring_regions() + .with_opaque_type_inference(DefiningAnchor::Bind(hir_owner.def_id)) + .build(); + let typeck_results = RefCell::new(ty::TypeckResults::new(hir_owner)); Inherited { typeck_results, diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index e397dfd4570..1846ab4baf3 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -45,13 +45,14 @@ mod upvar; mod writeback; -pub use diverges::Diverges; -pub use expectation::Expectation; -pub use fn_ctxt::*; -pub use inherited::{Inherited, InheritedBuilder}; +pub use fn_ctxt::FnCtxt; +pub use inherited::Inherited; use crate::check::check_fn; use crate::coercion::DynamicCoerceMany; +use crate::diverges::Diverges; +use crate::expectation::Expectation; +use crate::fn_ctxt::RawTy; use crate::gather_locals::GatherLocalsVisitor; use rustc_data_structures::unord::UnordSet; use rustc_errors::{ @@ -206,135 +207,135 @@ fn typeck_with_fallback<'tcx>( }); let body = tcx.hir().body(body_id); - let typeck_results = Inherited::build(tcx, def_id).enter(|inh| { - let param_env = tcx.param_env(def_id); - let param_env = if tcx.has_attr(def_id.to_def_id(), sym::rustc_do_not_const_check) { - param_env.without_const() + let param_env = tcx.param_env(def_id); + let param_env = if tcx.has_attr(def_id.to_def_id(), sym::rustc_do_not_const_check) { + param_env.without_const() + } else { + param_env + }; + let inh = Inherited::new(tcx, def_id); + let mut fcx = FnCtxt::new(&inh, param_env, def_id); + + if let Some(hir::FnSig { header, decl, .. }) = fn_sig { + let fn_sig = if rustc_hir_analysis::collect::get_infer_ret_ty(&decl.output).is_some() { + fcx.astconv().ty_of_fn(id, header.unsafety, header.abi, decl, None, None) } else { - param_env + tcx.fn_sig(def_id).subst_identity() }; - let mut fcx = FnCtxt::new(&inh, param_env, def_id); - if let Some(hir::FnSig { header, decl, .. }) = fn_sig { - let fn_sig = if rustc_hir_analysis::collect::get_infer_ret_ty(&decl.output).is_some() { - fcx.astconv().ty_of_fn(id, header.unsafety, header.abi, decl, None, None) - } else { - tcx.fn_sig(def_id).subst_identity() - }; + check_abi(tcx, id, span, fn_sig.abi()); - check_abi(tcx, id, span, fn_sig.abi()); + // Compute the function signature from point of view of inside the fn. + let fn_sig = tcx.liberate_late_bound_regions(def_id.to_def_id(), fn_sig); + let fn_sig = fcx.normalize(body.value.span, fn_sig); - // Compute the function signature from point of view of inside the fn. - let fn_sig = tcx.liberate_late_bound_regions(def_id.to_def_id(), fn_sig); - let fn_sig = fcx.normalize(body.value.span, fn_sig); - - check_fn(&mut fcx, fn_sig, decl, def_id, body, None); - } else { - let expected_type = body_ty - .and_then(|ty| match ty.kind { - hir::TyKind::Infer => Some(fcx.astconv().ast_ty_to_ty(ty)), - _ => None, - }) - .unwrap_or_else(|| match tcx.hir().get(id) { - Node::AnonConst(_) => match tcx.hir().get(tcx.hir().parent_id(id)) { - Node::Expr(&hir::Expr { - kind: hir::ExprKind::ConstBlock(ref anon_const), - .. - }) if anon_const.hir_id == id => fcx.next_ty_var(TypeVariableOrigin { + check_fn(&mut fcx, fn_sig, decl, def_id, body, None); + } else { + let expected_type = body_ty + .and_then(|ty| match ty.kind { + hir::TyKind::Infer => Some(fcx.astconv().ast_ty_to_ty(ty)), + _ => None, + }) + .unwrap_or_else(|| match tcx.hir().get(id) { + Node::AnonConst(_) => match tcx.hir().get(tcx.hir().parent_id(id)) { + Node::Expr(&hir::Expr { + kind: hir::ExprKind::ConstBlock(ref anon_const), + .. + }) if anon_const.hir_id == id => fcx.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::TypeInference, + span, + }), + Node::Ty(&hir::Ty { kind: hir::TyKind::Typeof(ref anon_const), .. }) + if anon_const.hir_id == id => + { + fcx.next_ty_var(TypeVariableOrigin { kind: TypeVariableOriginKind::TypeInference, span, - }), - Node::Ty(&hir::Ty { - kind: hir::TyKind::Typeof(ref anon_const), .. - }) if anon_const.hir_id == id => fcx.next_ty_var(TypeVariableOrigin { - kind: TypeVariableOriginKind::TypeInference, - span, - }), - Node::Expr(&hir::Expr { kind: hir::ExprKind::InlineAsm(asm), .. }) - | Node::Item(&hir::Item { kind: hir::ItemKind::GlobalAsm(asm), .. }) => { - let operand_ty = - asm.operands.iter().find_map(|(op, _op_sp)| match op { - hir::InlineAsmOperand::Const { anon_const } - if anon_const.hir_id == id => - { - // Inline assembly constants must be integers. - Some(fcx.next_int_var()) - } - hir::InlineAsmOperand::SymFn { anon_const } - if anon_const.hir_id == id => - { - Some(fcx.next_ty_var(TypeVariableOrigin { - kind: TypeVariableOriginKind::MiscVariable, - span, - })) - } - _ => None, - }); - operand_ty.unwrap_or_else(fallback) - } - _ => fallback(), - }, + }) + } + Node::Expr(&hir::Expr { kind: hir::ExprKind::InlineAsm(asm), .. }) + | Node::Item(&hir::Item { kind: hir::ItemKind::GlobalAsm(asm), .. }) => { + let operand_ty = asm.operands.iter().find_map(|(op, _op_sp)| match op { + hir::InlineAsmOperand::Const { anon_const } + if anon_const.hir_id == id => + { + // Inline assembly constants must be integers. + Some(fcx.next_int_var()) + } + hir::InlineAsmOperand::SymFn { anon_const } + if anon_const.hir_id == id => + { + Some(fcx.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::MiscVariable, + span, + })) + } + _ => None, + }); + operand_ty.unwrap_or_else(fallback) + } _ => fallback(), - }); + }, + _ => fallback(), + }); - let expected_type = fcx.normalize(body.value.span, expected_type); - fcx.require_type_is_sized(expected_type, body.value.span, traits::ConstSized); + let expected_type = fcx.normalize(body.value.span, expected_type); + fcx.require_type_is_sized(expected_type, body.value.span, traits::ConstSized); - // Gather locals in statics (because of block expressions). - GatherLocalsVisitor::new(&fcx).visit_body(body); + // Gather locals in statics (because of block expressions). + GatherLocalsVisitor::new(&fcx).visit_body(body); - fcx.check_expr_coercable_to_type(&body.value, expected_type, None); + fcx.check_expr_coercable_to_type(&body.value, expected_type, None); - fcx.write_ty(id, expected_type); - }; + fcx.write_ty(id, expected_type); + }; - fcx.type_inference_fallback(); + fcx.type_inference_fallback(); - // Even though coercion casts provide type hints, we check casts after fallback for - // backwards compatibility. This makes fallback a stronger type hint than a cast coercion. - fcx.check_casts(); - fcx.select_obligations_where_possible(|_| {}); + // Even though coercion casts provide type hints, we check casts after fallback for + // backwards compatibility. This makes fallback a stronger type hint than a cast coercion. + fcx.check_casts(); + fcx.select_obligations_where_possible(|_| {}); - // Closure and generator analysis may run after fallback - // because they don't constrain other type variables. - // Closure analysis only runs on closures. Therefore they only need to fulfill non-const predicates (as of now) - let prev_constness = fcx.param_env.constness(); - fcx.param_env = fcx.param_env.without_const(); - fcx.closure_analyze(body); - fcx.param_env = fcx.param_env.with_constness(prev_constness); - assert!(fcx.deferred_call_resolutions.borrow().is_empty()); - // Before the generator analysis, temporary scopes shall be marked to provide more - // precise information on types to be captured. - fcx.resolve_rvalue_scopes(def_id.to_def_id()); + // Closure and generator analysis may run after fallback + // because they don't constrain other type variables. + // Closure analysis only runs on closures. Therefore they only need to fulfill non-const predicates (as of now) + let prev_constness = fcx.param_env.constness(); + fcx.param_env = fcx.param_env.without_const(); + fcx.closure_analyze(body); + fcx.param_env = fcx.param_env.with_constness(prev_constness); + assert!(fcx.deferred_call_resolutions.borrow().is_empty()); + // Before the generator analysis, temporary scopes shall be marked to provide more + // precise information on types to be captured. + fcx.resolve_rvalue_scopes(def_id.to_def_id()); - for (ty, span, code) in fcx.deferred_sized_obligations.borrow_mut().drain(..) { - let ty = fcx.normalize(span, ty); - fcx.require_type_is_sized(ty, span, code); - } + for (ty, span, code) in fcx.deferred_sized_obligations.borrow_mut().drain(..) { + let ty = fcx.normalize(span, ty); + fcx.require_type_is_sized(ty, span, code); + } - fcx.select_obligations_where_possible(|_| {}); + fcx.select_obligations_where_possible(|_| {}); - debug!(pending_obligations = ?fcx.fulfillment_cx.borrow().pending_obligations()); + debug!(pending_obligations = ?fcx.fulfillment_cx.borrow().pending_obligations()); - // This must be the last thing before `report_ambiguity_errors`. - fcx.resolve_generator_interiors(def_id.to_def_id()); + // This must be the last thing before `report_ambiguity_errors`. + fcx.resolve_generator_interiors(def_id.to_def_id()); - debug!(pending_obligations = ?fcx.fulfillment_cx.borrow().pending_obligations()); + debug!(pending_obligations = ?fcx.fulfillment_cx.borrow().pending_obligations()); - if let None = fcx.infcx.tainted_by_errors() { - fcx.report_ambiguity_errors(); - } + if let None = fcx.infcx.tainted_by_errors() { + fcx.report_ambiguity_errors(); + } - if let None = fcx.infcx.tainted_by_errors() { - fcx.check_transmutes(); - } + if let None = fcx.infcx.tainted_by_errors() { + fcx.check_transmutes(); + } - fcx.check_asms(); + fcx.check_asms(); - fcx.infcx.skip_region_resolution(); + fcx.infcx.skip_region_resolution(); - fcx.resolve_type_vars_in_body(body) - }); + let typeck_results = fcx.resolve_type_vars_in_body(body); // Consistency check our TypeckResults instance can hold all ItemLocalIds // it will need to hold. diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs index df26b36b7b3..4c4c003ca46 100644 --- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -369,10 +369,10 @@ fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty< Node::Item(item) => { if let ItemKind::Fn(_, _, body_id) = &item.kind && let output_ty = return_ty(cx, item.owner_id) - && Inherited::build(cx.tcx, item.owner_id.def_id).enter(|inherited| { - let fn_ctxt = FnCtxt::new(inherited, cx.param_env, item.owner_id.def_id); - fn_ctxt.can_coerce(ty, output_ty) - }) { + && let inherited = Inherited::new(cx.tcx, item.owner_id.def_id) + && let fn_ctxt = FnCtxt::new(&inherited, cx.param_env, item.owner_id.def_id) + && fn_ctxt.can_coerce(ty, output_ty) + { if has_lifetime(output_ty) && has_lifetime(ty) { return false; } diff --git a/src/tools/clippy/clippy_lints/src/transmute/utils.rs b/src/tools/clippy/clippy_lints/src/transmute/utils.rs index cddaf9450ea..62efd13b8d9 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/utils.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/utils.rs @@ -33,38 +33,37 @@ pub(super) fn check_cast<'tcx>( let hir_id = e.hir_id; let local_def_id = hir_id.owner.def_id; - Inherited::build(cx.tcx, local_def_id).enter(|inherited| { - let fn_ctxt = FnCtxt::new(inherited, cx.param_env, local_def_id); + let inherited = Inherited::new(cx.tcx, local_def_id); + let fn_ctxt = FnCtxt::new(&inherited, cx.param_env, local_def_id); - // If we already have errors, we can't be sure we can pointer cast. + // If we already have errors, we can't be sure we can pointer cast. + assert!( + !fn_ctxt.errors_reported_since_creation(), + "Newly created FnCtxt contained errors" + ); + + if let Ok(check) = cast::CastCheck::new( + &fn_ctxt, + e, + from_ty, + to_ty, + // We won't show any error to the user, so we don't care what the span is here. + DUMMY_SP, + DUMMY_SP, + hir::Constness::NotConst, + ) { + let res = check.do_check(&fn_ctxt); + + // do_check's documentation says that it might return Ok and create + // errors in the fcx instead of returning Err in some cases. Those cases + // should be filtered out before getting here. assert!( !fn_ctxt.errors_reported_since_creation(), - "Newly created FnCtxt contained errors" + "`fn_ctxt` contained errors after cast check!" ); - if let Ok(check) = cast::CastCheck::new( - &fn_ctxt, - e, - from_ty, - to_ty, - // We won't show any error to the user, so we don't care what the span is here. - DUMMY_SP, - DUMMY_SP, - hir::Constness::NotConst, - ) { - let res = check.do_check(&fn_ctxt); - - // do_check's documentation says that it might return Ok and create - // errors in the fcx instead of returning Err in some cases. Those cases - // should be filtered out before getting here. - assert!( - !fn_ctxt.errors_reported_since_creation(), - "`fn_ctxt` contained errors after cast check!" - ); - - res.ok() - } else { - None - } - }) + res.ok() + } else { + None + } } From 391ef47d8a8322fab1aede78bed4b5e7493ff15e Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Thu, 9 Mar 2023 20:15:10 +0000 Subject: [PATCH 12/14] Simplify typeck entry. --- compiler/rustc_hir_typeck/src/lib.rs | 97 +++++++++++++--------------- 1 file changed, 46 insertions(+), 51 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index 1846ab4baf3..70124a77364 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -106,10 +106,9 @@ pub struct LocalTy<'tcx> { /// (notably closures), `typeck_results(def_id)` would wind up /// redirecting to the owning function. fn primary_body_of( - tcx: TyCtxt<'_>, - id: hir::HirId, + node: Node<'_>, ) -> Option<(hir::BodyId, Option<&hir::Ty<'_>>, Option<&hir::FnSig<'_>>)> { - match tcx.hir().get(id) { + match node { Node::Item(item) => match item.kind { hir::ItemKind::Const(ty, body) | hir::ItemKind::Static(ty, _, body) => { Some((body, Some(ty), None)) @@ -143,8 +142,7 @@ fn has_typeck_results(tcx: TyCtxt<'_>, def_id: DefId) -> bool { } if let Some(def_id) = def_id.as_local() { - let id = tcx.hir().local_def_id_to_hir_id(def_id); - primary_body_of(tcx, id).is_some() + primary_body_of(tcx.hir().get_by_def_id(def_id)).is_some() } else { false } @@ -199,10 +197,11 @@ fn typeck_with_fallback<'tcx>( } let id = tcx.hir().local_def_id_to_hir_id(def_id); + let node = tcx.hir().get(id); let span = tcx.hir().span(id); // Figure out what primary body this item has. - let (body_id, body_ty, fn_sig) = primary_body_of(tcx, id).unwrap_or_else(|| { + let (body_id, body_ty, fn_sig) = primary_body_of(node).unwrap_or_else(|| { span_bug!(span, "can't type-check body of {:?}", def_id); }); let body = tcx.hir().body(body_id); @@ -231,53 +230,49 @@ fn typeck_with_fallback<'tcx>( check_fn(&mut fcx, fn_sig, decl, def_id, body, None); } else { - let expected_type = body_ty - .and_then(|ty| match ty.kind { - hir::TyKind::Infer => Some(fcx.astconv().ast_ty_to_ty(ty)), - _ => None, - }) - .unwrap_or_else(|| match tcx.hir().get(id) { - Node::AnonConst(_) => match tcx.hir().get(tcx.hir().parent_id(id)) { - Node::Expr(&hir::Expr { - kind: hir::ExprKind::ConstBlock(ref anon_const), - .. - }) if anon_const.hir_id == id => fcx.next_ty_var(TypeVariableOrigin { + let expected_type = if let Some(&hir::Ty { kind: hir::TyKind::Infer, span, .. }) = body_ty { + Some(fcx.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::TypeInference, + span, + })) + } else if let Node::AnonConst(_) = node { + match tcx.hir().get(tcx.hir().parent_id(id)) { + Node::Expr(&hir::Expr { + kind: hir::ExprKind::ConstBlock(ref anon_const), .. + }) if anon_const.hir_id == id => Some(fcx.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::TypeInference, + span, + })), + Node::Ty(&hir::Ty { kind: hir::TyKind::Typeof(ref anon_const), .. }) + if anon_const.hir_id == id => + { + Some(fcx.next_ty_var(TypeVariableOrigin { kind: TypeVariableOriginKind::TypeInference, span, - }), - Node::Ty(&hir::Ty { kind: hir::TyKind::Typeof(ref anon_const), .. }) - if anon_const.hir_id == id => - { - fcx.next_ty_var(TypeVariableOrigin { - kind: TypeVariableOriginKind::TypeInference, - span, - }) - } - Node::Expr(&hir::Expr { kind: hir::ExprKind::InlineAsm(asm), .. }) - | Node::Item(&hir::Item { kind: hir::ItemKind::GlobalAsm(asm), .. }) => { - let operand_ty = asm.operands.iter().find_map(|(op, _op_sp)| match op { - hir::InlineAsmOperand::Const { anon_const } - if anon_const.hir_id == id => - { - // Inline assembly constants must be integers. - Some(fcx.next_int_var()) - } - hir::InlineAsmOperand::SymFn { anon_const } - if anon_const.hir_id == id => - { - Some(fcx.next_ty_var(TypeVariableOrigin { - kind: TypeVariableOriginKind::MiscVariable, - span, - })) - } - _ => None, - }); - operand_ty.unwrap_or_else(fallback) - } - _ => fallback(), - }, - _ => fallback(), - }); + })) + } + Node::Expr(&hir::Expr { kind: hir::ExprKind::InlineAsm(asm), .. }) + | Node::Item(&hir::Item { kind: hir::ItemKind::GlobalAsm(asm), .. }) => { + asm.operands.iter().find_map(|(op, _op_sp)| match op { + hir::InlineAsmOperand::Const { anon_const } if anon_const.hir_id == id => { + // Inline assembly constants must be integers. + Some(fcx.next_int_var()) + } + hir::InlineAsmOperand::SymFn { anon_const } if anon_const.hir_id == id => { + Some(fcx.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::MiscVariable, + span, + })) + } + _ => None, + }) + } + _ => None, + } + } else { + None + }; + let expected_type = expected_type.unwrap_or_else(fallback); let expected_type = fcx.normalize(body.value.span, expected_type); fcx.require_type_is_sized(expected_type, body.value.span, traits::ConstSized); From bb37b600b35ce60ff948ac46b3994dd699008bdb Mon Sep 17 00:00:00 2001 From: clubby789 Date: Sat, 4 Mar 2023 22:55:06 +0000 Subject: [PATCH 13/14] Migrate `document_item_info` to templates --- src/librustdoc/html/render/mod.rs | 97 +++++++++++-------- src/librustdoc/html/templates/item_info.html | 7 ++ .../html/templates/short_item_info.html | 23 +++++ 3 files changed, 84 insertions(+), 43 deletions(-) create mode 100644 src/librustdoc/html/templates/item_info.html create mode 100644 src/librustdoc/html/templates/short_item_info.html diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index e6a040d02e5..ed02538fc78 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -46,6 +46,7 @@ use std::str; use std::string::ToString; +use askama::Template; use rustc_ast_pretty::pprust; use rustc_attr::{ConstStability, Deprecation, StabilityLevel}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; @@ -417,7 +418,7 @@ fn document( if let Some(ref name) = item.name { info!("Documenting {}", name); } - document_item_info(w, cx, item, parent); + document_item_info(cx, item, parent).render_into(w).unwrap(); if parent.is_none() { document_full_collapsible(w, item, cx, heading_offset); } else { @@ -459,7 +460,7 @@ fn document_short( parent: &clean::Item, show_def_docs: bool, ) { - document_item_info(w, cx, item, Some(parent)); + document_item_info(cx, item, Some(parent)).render_into(w).unwrap(); if !show_def_docs { return; } @@ -531,25 +532,23 @@ fn document_full_inner( } } +#[derive(Template)] +#[template(path = "item_info.html")] +struct ItemInfo { + items: Vec, +} /// Add extra information about an item such as: /// /// * Stability /// * Deprecated /// * Required features (through the `doc_cfg` feature) fn document_item_info( - w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, parent: Option<&clean::Item>, -) { - let item_infos = short_item_info(item, cx, parent); - if !item_infos.is_empty() { - w.write_str(""); - for info in item_infos { - w.write_str(&info); - } - w.write_str(""); - } +) -> ItemInfo { + let items = short_item_info(item, cx, parent); + ItemInfo { items } } fn portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option { @@ -567,7 +566,25 @@ fn portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option{}", cfg?.render_long_html())) + Some(cfg?.render_long_html()) +} + +#[derive(Template)] +#[template(path = "short_item_info.html")] +enum ShortItemInfo { + /// A message describing the deprecation of this item + Deprecation { + message: String, + }, + /// The feature corresponding to an unstable item, and optionally + /// a tracking issue URL and number. + Unstable { + feature: String, + tracking: Option<(String, u32)>, + }, + Portability { + message: String, + }, } /// Render the stability, deprecation and portability information that is displayed at the top of @@ -576,7 +593,7 @@ fn short_item_info( item: &clean::Item, cx: &mut Context<'_>, parent: Option<&clean::Item>, -) -> Vec { +) -> Vec { let mut extra_info = vec![]; if let Some(depr @ Deprecation { note, since, is_since_rustc_version: _, suggestion: _ }) = @@ -602,15 +619,10 @@ fn short_item_info( if let Some(note) = note { let note = note.as_str(); let html = MarkdownItemInfo(note, &mut cx.id_map); - message.push_str(&format!(": {}", html.into_string())); + message.push_str(": "); + message.push_str(&html.into_string()); } - extra_info.push(format!( - "
\ - 👎\ - {}\ -
", - message, - )); + extra_info.push(ShortItemInfo::Deprecation { message }); } // Render unstable items. But don't render "rustc_private" crates (internal compiler crates). @@ -621,26 +633,17 @@ fn short_item_info( .filter(|stab| stab.feature != sym::rustc_private) .map(|stab| (stab.level, stab.feature)) { - let mut message = "🔬\ - This is a nightly-only experimental API." - .to_owned(); - - let mut feature = format!("{}", Escape(feature.as_str())); - if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue) { - feature.push_str(&format!( - " #{issue}", - url = url, - issue = issue - )); - } - - message.push_str(&format!(" ({})", feature)); - - extra_info.push(format!("
{}
", message)); + let tracking = if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue) + { + Some((url.clone(), issue.get())) + } else { + None + }; + extra_info.push(ShortItemInfo::Unstable { feature: feature.to_string(), tracking }); } - if let Some(portability) = portability(item, parent) { - extra_info.push(portability); + if let Some(message) = portability(item, parent) { + extra_info.push(ShortItemInfo::Portability { message }); } extra_info @@ -1472,7 +1475,9 @@ fn doc_impl_item( // We need the stability of the item from the trait // because impls can't have a stability. if item.doc_value().is_some() { - document_item_info(&mut info_buffer, cx, it, Some(parent)); + document_item_info(cx, it, Some(parent)) + .render_into(&mut info_buffer) + .unwrap(); document_full(&mut doc_buffer, item, cx, HeadingOffset::H5); short_documented = false; } else { @@ -1489,7 +1494,9 @@ fn doc_impl_item( } } } else { - document_item_info(&mut info_buffer, cx, item, Some(parent)); + document_item_info(cx, item, Some(parent)) + .render_into(&mut info_buffer) + .unwrap(); if rendering_params.show_def_docs { document_full(&mut doc_buffer, item, cx, HeadingOffset::H5); short_documented = false; @@ -1862,7 +1869,11 @@ pub(crate) fn render_impl_summary( let is_trait = inner_impl.trait_.is_some(); if is_trait { if let Some(portability) = portability(&i.impl_item, Some(parent)) { - write!(w, "{}", portability); + write!( + w, + "
{}
", + portability + ); } } diff --git a/src/librustdoc/html/templates/item_info.html b/src/librustdoc/html/templates/item_info.html new file mode 100644 index 00000000000..d2ea9bdae9c --- /dev/null +++ b/src/librustdoc/html/templates/item_info.html @@ -0,0 +1,7 @@ +{% if !items.is_empty() %} + {# #} + {% for item in items %} + {{item|safe}} {# #} + {% endfor %} + +{% endif %} diff --git a/src/librustdoc/html/templates/short_item_info.html b/src/librustdoc/html/templates/short_item_info.html new file mode 100644 index 00000000000..e3125af0e47 --- /dev/null +++ b/src/librustdoc/html/templates/short_item_info.html @@ -0,0 +1,23 @@ +{% match self %} + {% when Self::Deprecation with { message } %} +
{# #} + 👎 {# #} + {{message}} {# #} +
{# #} + {% when Self::Unstable with { feature, tracking } %} +
{# #} + 🔬 {# #} + {# #} + This is a nightly-only experimental API. ({# #} + {{feature}} {# #} + {% match tracking %} + {% when Some with ((url, num)) %} +  #{{num}} {# #} + {% when None %} + {% endmatch %} + ) {# #} + {# #} +
{# #} + {% when Self::Portability with { message } %} +
{{message|safe}}
{# #} +{% endmatch %} From 2f166d1a15cf0bec0869f2e955ac656eeaec48d7 Mon Sep 17 00:00:00 2001 From: clubby789 Date: Sun, 5 Mar 2023 15:12:00 +0000 Subject: [PATCH 14/14] Render doc sidebar using Askama --- src/librustdoc/html/render/context.rs | 27 +- src/librustdoc/html/render/mod.rs | 633 +-------------------- src/librustdoc/html/render/sidebar.rs | 561 ++++++++++++++++++ src/librustdoc/html/templates/sidebar.html | 37 ++ 4 files changed, 615 insertions(+), 643 deletions(-) create mode 100644 src/librustdoc/html/render/sidebar.rs create mode 100644 src/librustdoc/html/templates/sidebar.html diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 1030fe74747..595ddddc7dd 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -17,10 +17,11 @@ use super::search_index::build_index; use super::write_shared::write_shared; use super::{ - collect_spans_and_sources, print_sidebar, scrape_examples_help, sidebar_module_like, AllTypes, - LinkFromSrc, StylePath, + collect_spans_and_sources, scrape_examples_help, + sidebar::print_sidebar, + sidebar::{sidebar_module_like, Sidebar}, + AllTypes, LinkFromSrc, StylePath, }; - use crate::clean::{self, types::ExternalLocation, ExternalCrate}; use crate::config::{ModuleSorting, RenderOptions}; use crate::docfs::{DocFS, PathError}; @@ -35,6 +36,7 @@ use crate::html::{layout, sources, static_files}; use crate::scrape_examples::AllCallLocations; use crate::try_err; +use askama::Template; /// Major driving force in all rustdoc rendering. This contains information /// about where in the tree-like hierarchy rendering is occurring and controls @@ -600,15 +602,18 @@ fn after_krate(&mut self) -> Result<(), Error> { }; let all = shared.all.replace(AllTypes::new()); let mut sidebar = Buffer::html(); - write!(sidebar, "

Crate {}

", crate_name); - let mut items = Buffer::html(); - sidebar_module_like(&mut items, all.item_sections()); - if !items.is_empty() { - sidebar.push_str("
"); - sidebar.push_buffer(items); - sidebar.push_str("
"); - } + let blocks = sidebar_module_like(all.item_sections()); + let bar = Sidebar { + title_prefix: "Crate ", + title: crate_name.as_str(), + is_crate: false, + version: "", + blocks: vec![blocks], + path: String::new(), + }; + + bar.render_into(&mut sidebar).unwrap(); let v = layout::render( &shared.layout, diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index e6a040d02e5..c389dbf379e 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -30,6 +30,7 @@ mod context; mod print_item; +mod sidebar; mod span_map; mod write_shared; @@ -49,11 +50,9 @@ use rustc_ast_pretty::pprust; use rustc_attr::{ConstStability, Deprecation, StabilityLevel}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_hir::def::CtorKind; use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_hir::Mutability; use rustc_middle::middle::stability; -use rustc_middle::ty; use rustc_middle::ty::TyCtxt; use rustc_span::{ symbol::{sym, Symbol}, @@ -1869,154 +1868,6 @@ pub(crate) fn render_impl_summary( w.write_str(""); } -fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) { - if it.is_struct() - || it.is_trait() - || it.is_primitive() - || it.is_union() - || it.is_enum() - || it.is_mod() - || it.is_typedef() - { - write!( - buffer, - "

{}{}

", - match *it.kind { - clean::ModuleItem(..) => - if it.is_crate() { - "Crate " - } else { - "Module " - }, - _ => "", - }, - it.name.as_ref().unwrap() - ); - } - - buffer.write_str("
"); - if it.is_crate() { - write!(buffer, "
    "); - if let Some(ref version) = cx.cache().crate_version { - write!(buffer, "
  • Version {}
  • ", Escape(version)); - } - write!(buffer, "
  • All Items
  • "); - buffer.write_str("
"); - } - - match *it.kind { - clean::StructItem(ref s) => sidebar_struct(cx, buffer, it, s), - clean::TraitItem(ref t) => sidebar_trait(cx, buffer, it, t), - clean::PrimitiveItem(_) => sidebar_primitive(cx, buffer, it), - clean::UnionItem(ref u) => sidebar_union(cx, buffer, it, u), - clean::EnumItem(ref e) => sidebar_enum(cx, buffer, it, e), - clean::TypedefItem(_) => sidebar_typedef(cx, buffer, it), - clean::ModuleItem(ref m) => sidebar_module(buffer, &m.items), - clean::ForeignTypeItem => sidebar_foreign_type(cx, buffer, it), - _ => {} - } - - // The sidebar is designed to display sibling functions, modules and - // other miscellaneous information. since there are lots of sibling - // items (and that causes quadratic growth in large modules), - // we refactor common parts into a shared JavaScript file per module. - // still, we don't move everything into JS because we want to preserve - // as much HTML as possible in order to allow non-JS-enabled browsers - // to navigate the documentation (though slightly inefficiently). - - if !it.is_mod() { - let path: String = cx.current.iter().map(|s| s.as_str()).intersperse("::").collect(); - - write!(buffer, "

In {}

", path); - } - - // Closes sidebar-elems div. - buffer.write_str("
"); -} - -fn get_next_url(used_links: &mut FxHashSet, url: String) -> String { - if used_links.insert(url.clone()) { - return url; - } - let mut add = 1; - while !used_links.insert(format!("{}-{}", url, add)) { - add += 1; - } - format!("{}-{}", url, add) -} - -struct SidebarLink { - name: Symbol, - url: String, -} - -impl fmt::Display for SidebarLink { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.url, self.name) - } -} - -impl PartialEq for SidebarLink { - fn eq(&self, other: &Self) -> bool { - self.url == other.url - } -} - -impl Eq for SidebarLink {} - -impl PartialOrd for SidebarLink { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for SidebarLink { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.url.cmp(&other.url) - } -} - -fn get_methods( - i: &clean::Impl, - for_deref: bool, - used_links: &mut FxHashSet, - deref_mut: bool, - tcx: TyCtxt<'_>, -) -> Vec { - i.items - .iter() - .filter_map(|item| match item.name { - Some(name) if !name.is_empty() && item.is_method() => { - if !for_deref || should_render_item(item, deref_mut, tcx) { - Some(SidebarLink { - name, - url: get_next_url(used_links, format!("{}.{}", ItemType::Method, name)), - }) - } else { - None - } - } - _ => None, - }) - .collect::>() -} - -fn get_associated_constants( - i: &clean::Impl, - used_links: &mut FxHashSet, -) -> Vec { - i.items - .iter() - .filter_map(|item| match item.name { - Some(name) if !name.is_empty() && item.is_associated_const() => Some(SidebarLink { - name, - url: get_next_url(used_links, format!("{}.{}", ItemType::AssocConst, name)), - }), - _ => None, - }) - .collect::>() -} - pub(crate) fn small_url_encode(s: String) -> String { // These characters don't need to be escaped in a URI. // See https://url.spec.whatwg.org/#query-percent-encode-set @@ -2082,232 +1933,6 @@ fn dont_escape(c: u8) -> bool { } } -pub(crate) fn sidebar_render_assoc_items( - cx: &Context<'_>, - out: &mut Buffer, - id_map: &mut IdMap, - concrete: Vec<&Impl>, - synthetic: Vec<&Impl>, - blanket_impl: Vec<&Impl>, -) { - let format_impls = |impls: Vec<&Impl>, id_map: &mut IdMap| { - let mut links = FxHashSet::default(); - - let mut ret = impls - .iter() - .filter_map(|it| { - let trait_ = it.inner_impl().trait_.as_ref()?; - let encoded = - id_map.derive(get_id_for_impl(&it.inner_impl().for_, Some(trait_), cx)); - - let i_display = format!("{:#}", trait_.print(cx)); - let out = Escape(&i_display); - let prefix = match it.inner_impl().polarity { - ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => "", - ty::ImplPolarity::Negative => "!", - }; - let generated = format!("{}{}", encoded, prefix, out); - if links.insert(generated.clone()) { Some(generated) } else { None } - }) - .collect::>(); - ret.sort(); - ret - }; - - let concrete_format = format_impls(concrete, id_map); - let synthetic_format = format_impls(synthetic, id_map); - let blanket_format = format_impls(blanket_impl, id_map); - - if !concrete_format.is_empty() { - print_sidebar_block( - out, - "trait-implementations", - "Trait Implementations", - concrete_format.iter(), - ); - } - - if !synthetic_format.is_empty() { - print_sidebar_block( - out, - "synthetic-implementations", - "Auto Trait Implementations", - synthetic_format.iter(), - ); - } - - if !blanket_format.is_empty() { - print_sidebar_block( - out, - "blanket-implementations", - "Blanket Implementations", - blanket_format.iter(), - ); - } -} - -fn sidebar_assoc_items(cx: &Context<'_>, out: &mut Buffer, it: &clean::Item) { - let did = it.item_id.expect_def_id(); - let cache = cx.cache(); - - if let Some(v) = cache.impls.get(&did) { - let mut used_links = FxHashSet::default(); - let mut id_map = IdMap::new(); - - { - let used_links_bor = &mut used_links; - let mut assoc_consts = v - .iter() - .filter(|i| i.inner_impl().trait_.is_none()) - .flat_map(|i| get_associated_constants(i.inner_impl(), used_links_bor)) - .collect::>(); - if !assoc_consts.is_empty() { - // We want links' order to be reproducible so we don't use unstable sort. - assoc_consts.sort(); - - print_sidebar_block( - out, - "implementations", - "Associated Constants", - assoc_consts.iter(), - ); - } - let mut methods = v - .iter() - .filter(|i| i.inner_impl().trait_.is_none()) - .flat_map(|i| get_methods(i.inner_impl(), false, used_links_bor, false, cx.tcx())) - .collect::>(); - if !methods.is_empty() { - // We want links' order to be reproducible so we don't use unstable sort. - methods.sort(); - - print_sidebar_block(out, "implementations", "Methods", methods.iter()); - } - } - - if v.iter().any(|i| i.inner_impl().trait_.is_some()) { - if let Some(impl_) = - v.iter().find(|i| i.trait_did() == cx.tcx().lang_items().deref_trait()) - { - let mut derefs = DefIdSet::default(); - derefs.insert(did); - sidebar_deref_methods(cx, out, impl_, v, &mut derefs, &mut used_links); - } - - let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) = - v.iter().partition::, _>(|i| i.inner_impl().kind.is_auto()); - let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) = - concrete.into_iter().partition::, _>(|i| i.inner_impl().kind.is_blanket()); - - sidebar_render_assoc_items(cx, out, &mut id_map, concrete, synthetic, blanket_impl); - } - } -} - -fn sidebar_deref_methods( - cx: &Context<'_>, - out: &mut Buffer, - impl_: &Impl, - v: &[Impl], - derefs: &mut DefIdSet, - used_links: &mut FxHashSet, -) { - let c = cx.cache(); - - debug!("found Deref: {:?}", impl_); - if let Some((target, real_target)) = - impl_.inner_impl().items.iter().find_map(|item| match *item.kind { - clean::AssocTypeItem(box ref t, _) => Some(match *t { - clean::Typedef { item_type: Some(ref type_), .. } => (type_, &t.type_), - _ => (&t.type_, &t.type_), - }), - _ => None, - }) - { - debug!("found target, real_target: {:?} {:?}", target, real_target); - if let Some(did) = target.def_id(c) && - let Some(type_did) = impl_.inner_impl().for_.def_id(c) && - // `impl Deref for S` - (did == type_did || !derefs.insert(did)) - { - // Avoid infinite cycles - return; - } - let deref_mut = v.iter().any(|i| i.trait_did() == cx.tcx().lang_items().deref_mut_trait()); - let inner_impl = target - .def_id(c) - .or_else(|| { - target.primitive_type().and_then(|prim| c.primitive_locations.get(&prim).cloned()) - }) - .and_then(|did| c.impls.get(&did)); - if let Some(impls) = inner_impl { - debug!("found inner_impl: {:?}", impls); - let mut ret = impls - .iter() - .filter(|i| i.inner_impl().trait_.is_none()) - .flat_map(|i| get_methods(i.inner_impl(), true, used_links, deref_mut, cx.tcx())) - .collect::>(); - if !ret.is_empty() { - let id = if let Some(target_def_id) = real_target.def_id(c) { - cx.deref_id_map.get(&target_def_id).expect("Deref section without derived id") - } else { - "deref-methods" - }; - let title = format!( - "Methods from {}<Target={}>", - Escape(&format!("{:#}", impl_.inner_impl().trait_.as_ref().unwrap().print(cx))), - Escape(&format!("{:#}", real_target.print(cx))), - ); - // We want links' order to be reproducible so we don't use unstable sort. - ret.sort(); - print_sidebar_block(out, id, &title, ret.iter()); - } - } - - // Recurse into any further impls that might exist for `target` - if let Some(target_did) = target.def_id(c) && - let Some(target_impls) = c.impls.get(&target_did) && - let Some(target_deref_impl) = target_impls.iter().find(|i| { - i.inner_impl() - .trait_ - .as_ref() - .map(|t| Some(t.def_id()) == cx.tcx().lang_items().deref_trait()) - .unwrap_or(false) - }) - { - sidebar_deref_methods( - cx, - out, - target_deref_impl, - target_impls, - derefs, - used_links, - ); - } - } -} - -fn sidebar_struct(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, s: &clean::Struct) { - let mut sidebar = Buffer::new(); - let fields = get_struct_fields_name(&s.fields); - - if !fields.is_empty() { - match s.ctor_kind { - None => { - print_sidebar_block(&mut sidebar, "fields", "Fields", fields.iter()); - } - Some(CtorKind::Fn) => print_sidebar_title(&mut sidebar, "fields", "Tuple Fields"), - Some(CtorKind::Const) => {} - } - } - - sidebar_assoc_items(cx, &mut sidebar, it); - - if !sidebar.is_empty() { - write!(buf, "
{}
", sidebar.into_inner()); - } -} - fn get_id_for_impl(for_: &clean::Type, trait_: Option<&clean::Path>, cx: &Context<'_>) -> String { match trait_ { Some(t) => small_url_encode(format!("impl-{:#}-for-{:#}", t.print(cx), for_.print(cx))), @@ -2328,131 +1953,6 @@ fn extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String } } -fn print_sidebar_title(buf: &mut Buffer, id: &str, title: &str) { - write!(buf, "

{}

", id, title); -} - -fn print_sidebar_block( - buf: &mut Buffer, - id: &str, - title: &str, - items: impl Iterator, -) { - print_sidebar_title(buf, id, title); - buf.push_str("
    "); - for item in items { - write!(buf, "
  • {}
  • ", item); - } - buf.push_str("
"); -} - -fn sidebar_trait(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, t: &clean::Trait) { - buf.write_str("
"); - - fn print_sidebar_section( - out: &mut Buffer, - items: &[clean::Item], - id: &str, - title: &str, - filter: impl Fn(&clean::Item) -> bool, - mapper: impl Fn(&str) -> String, - ) { - let mut items: Vec<&str> = items - .iter() - .filter_map(|m| match m.name { - Some(ref name) if filter(m) => Some(name.as_str()), - _ => None, - }) - .collect::>(); - - if !items.is_empty() { - items.sort_unstable(); - print_sidebar_block(out, id, title, items.into_iter().map(mapper)); - } - } - - print_sidebar_section( - buf, - &t.items, - "required-associated-types", - "Required Associated Types", - |m| m.is_ty_associated_type(), - |sym| format!("{0}", sym, ItemType::AssocType), - ); - - print_sidebar_section( - buf, - &t.items, - "provided-associated-types", - "Provided Associated Types", - |m| m.is_associated_type(), - |sym| format!("{0}", sym, ItemType::AssocType), - ); - - print_sidebar_section( - buf, - &t.items, - "required-associated-consts", - "Required Associated Constants", - |m| m.is_ty_associated_const(), - |sym| format!("{0}", sym, ItemType::AssocConst), - ); - - print_sidebar_section( - buf, - &t.items, - "provided-associated-consts", - "Provided Associated Constants", - |m| m.is_associated_const(), - |sym| format!("{0}", sym, ItemType::AssocConst), - ); - - print_sidebar_section( - buf, - &t.items, - "required-methods", - "Required Methods", - |m| m.is_ty_method(), - |sym| format!("{0}", sym, ItemType::TyMethod), - ); - - print_sidebar_section( - buf, - &t.items, - "provided-methods", - "Provided Methods", - |m| m.is_method(), - |sym| format!("{0}", sym, ItemType::Method), - ); - - if let Some(implementors) = cx.cache().implementors.get(&it.item_id.expect_def_id()) { - let mut res = implementors - .iter() - .filter(|i| !i.is_on_local_type(cx)) - .filter_map(|i| extract_for_impl_name(&i.impl_item, cx)) - .collect::>(); - - if !res.is_empty() { - res.sort(); - print_sidebar_block( - buf, - "foreign-impls", - "Implementations on Foreign Types", - res.iter().map(|(name, id)| format!("{}", id, Escape(name))), - ); - } - } - - sidebar_assoc_items(cx, buf, it); - - print_sidebar_title(buf, "implementors", "Implementors"); - if t.is_auto(cx.tcx()) { - print_sidebar_title(buf, "synthetic-implementors", "Auto Implementors"); - } - - buf.push_str("
") -} - /// Returns the list of implementations for the primitive reference type, filtering out any /// implementations that are on concrete or partially generic types, only keeping implementations /// of the form `impl Trait for &T`. @@ -2483,89 +1983,6 @@ pub(crate) fn get_filtered_impls_for_reference<'a>( (concrete, synthetic, blanket_impl) } -fn sidebar_primitive(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) { - let mut sidebar = Buffer::new(); - - if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) { - sidebar_assoc_items(cx, &mut sidebar, it); - } else { - let shared = Rc::clone(&cx.shared); - let (concrete, synthetic, blanket_impl) = get_filtered_impls_for_reference(&shared, it); - - sidebar_render_assoc_items( - cx, - &mut sidebar, - &mut IdMap::new(), - concrete, - synthetic, - blanket_impl, - ); - } - - if !sidebar.is_empty() { - write!(buf, "
{}
", sidebar.into_inner()); - } -} - -fn sidebar_typedef(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) { - let mut sidebar = Buffer::new(); - sidebar_assoc_items(cx, &mut sidebar, it); - - if !sidebar.is_empty() { - write!(buf, "
{}
", sidebar.into_inner()); - } -} - -fn get_struct_fields_name(fields: &[clean::Item]) -> Vec { - let mut fields = fields - .iter() - .filter(|f| matches!(*f.kind, clean::StructFieldItem(..))) - .filter_map(|f| { - f.name.map(|name| format!("{name}", name = name)) - }) - .collect::>(); - fields.sort(); - fields -} - -fn sidebar_union(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, u: &clean::Union) { - let mut sidebar = Buffer::new(); - let fields = get_struct_fields_name(&u.fields); - - if !fields.is_empty() { - print_sidebar_block(&mut sidebar, "fields", "Fields", fields.iter()); - } - - sidebar_assoc_items(cx, &mut sidebar, it); - - if !sidebar.is_empty() { - write!(buf, "
{}
", sidebar.into_inner()); - } -} - -fn sidebar_enum(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, e: &clean::Enum) { - let mut sidebar = Buffer::new(); - - let mut variants = e - .variants() - .filter_map(|v| { - v.name - .as_ref() - .map(|name| format!("{name}", name = name)) - }) - .collect::>(); - if !variants.is_empty() { - variants.sort_unstable(); - print_sidebar_block(&mut sidebar, "variants", "Variants", variants.iter()); - } - - sidebar_assoc_items(cx, &mut sidebar, it); - - if !sidebar.is_empty() { - write!(buf, "
{}
", sidebar.into_inner()); - } -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) enum ItemSection { Reexports, @@ -2719,54 +2136,6 @@ fn item_ty_to_section(ty: ItemType) -> ItemSection { } } -pub(crate) fn sidebar_module_like(buf: &mut Buffer, item_sections_in_use: FxHashSet) { - use std::fmt::Write as _; - - let mut sidebar = String::new(); - - for &sec in ItemSection::ALL.iter().filter(|sec| item_sections_in_use.contains(sec)) { - let _ = write!(sidebar, "
  • {}
  • ", sec.id(), sec.name()); - } - - if !sidebar.is_empty() { - write!( - buf, - "
    \ -
      {}
    \ -
    ", - sidebar - ); - } -} - -fn sidebar_module(buf: &mut Buffer, items: &[clean::Item]) { - let item_sections_in_use: FxHashSet<_> = items - .iter() - .filter(|it| { - !it.is_stripped() - && it - .name - .or_else(|| { - if let clean::ImportItem(ref i) = *it.kind && - let clean::ImportKind::Simple(s) = i.kind { Some(s) } else { None } - }) - .is_some() - }) - .map(|it| item_ty_to_section(it.type_())) - .collect(); - - sidebar_module_like(buf, item_sections_in_use); -} - -fn sidebar_foreign_type(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) { - let mut sidebar = Buffer::new(); - sidebar_assoc_items(cx, &mut sidebar, it); - - if !sidebar.is_empty() { - write!(buf, "
    {}
    ", sidebar.into_inner()); - } -} - /// Returns a list of all paths used in the type. /// This is used to help deduplicate imported impls /// for reexported types. If any of the contained diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs new file mode 100644 index 00000000000..94ad4753d7c --- /dev/null +++ b/src/librustdoc/html/render/sidebar.rs @@ -0,0 +1,561 @@ +use std::{borrow::Cow, rc::Rc}; + +use askama::Template; +use rustc_data_structures::fx::FxHashSet; +use rustc_hir::{def::CtorKind, def_id::DefIdSet}; +use rustc_middle::ty::{self, TyCtxt}; + +use crate::{ + clean, + formats::{item_type::ItemType, Impl}, + html::{format::Buffer, markdown::IdMap}, +}; + +use super::{item_ty_to_section, Context, ItemSection}; + +#[derive(Template)] +#[template(path = "sidebar.html")] +pub(super) struct Sidebar<'a> { + pub(super) title_prefix: &'static str, + pub(super) title: &'a str, + pub(super) is_crate: bool, + pub(super) version: &'a str, + pub(super) blocks: Vec>, + pub(super) path: String, +} + +impl<'a> Sidebar<'a> { + /// Only create a `
    ` if there are any blocks + /// which should actually be rendered. + pub fn should_render_blocks(&self) -> bool { + self.blocks.iter().any(LinkBlock::should_render) + } +} + +/// A sidebar section such as 'Methods'. +pub(crate) struct LinkBlock<'a> { + /// The name of this section, e.g. 'Methods' + /// as well as the link to it, e.g. `#implementations`. + /// Will be rendered inside an `

    ` tag + heading: Link<'a>, + links: Vec>, + /// Render the heading even if there are no links + force_render: bool, +} + +impl<'a> LinkBlock<'a> { + pub fn new(heading: Link<'a>, links: Vec>) -> Self { + Self { heading, links, force_render: false } + } + + pub fn forced(heading: Link<'a>) -> Self { + Self { heading, links: vec![], force_render: true } + } + + pub fn should_render(&self) -> bool { + self.force_render || !self.links.is_empty() + } +} + +/// A link to an item. Content should not be escaped. +#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone)] +pub(crate) struct Link<'a> { + /// The content for the anchor tag + name: Cow<'a, str>, + /// The id of an anchor within the page (without a `#` prefix) + href: Cow<'a, str>, +} + +impl<'a> Link<'a> { + pub fn new(href: impl Into>, name: impl Into>) -> Self { + Self { href: href.into(), name: name.into() } + } + pub fn empty() -> Link<'static> { + Link::new("", "") + } +} + +pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) { + let blocks: Vec> = match *it.kind { + clean::StructItem(ref s) => sidebar_struct(cx, it, s), + clean::TraitItem(ref t) => sidebar_trait(cx, it, t), + clean::PrimitiveItem(_) => sidebar_primitive(cx, it), + clean::UnionItem(ref u) => sidebar_union(cx, it, u), + clean::EnumItem(ref e) => sidebar_enum(cx, it, e), + clean::TypedefItem(_) => sidebar_typedef(cx, it), + clean::ModuleItem(ref m) => vec![sidebar_module(&m.items)], + clean::ForeignTypeItem => sidebar_foreign_type(cx, it), + _ => vec![], + }; + // The sidebar is designed to display sibling functions, modules and + // other miscellaneous information. since there are lots of sibling + // items (and that causes quadratic growth in large modules), + // we refactor common parts into a shared JavaScript file per module. + // still, we don't move everything into JS because we want to preserve + // as much HTML as possible in order to allow non-JS-enabled browsers + // to navigate the documentation (though slightly inefficiently). + let (title_prefix, title) = if it.is_struct() + || it.is_trait() + || it.is_primitive() + || it.is_union() + || it.is_enum() + || it.is_mod() + || it.is_typedef() + { + ( + match *it.kind { + clean::ModuleItem(..) if it.is_crate() => "Crate ", + clean::ModuleItem(..) => "Module ", + _ => "", + }, + it.name.as_ref().unwrap().as_str(), + ) + } else { + ("", "") + }; + let version = if it.is_crate() { + cx.cache().crate_version.as_ref().map(String::as_str).unwrap_or_default() + } else { + "" + }; + let path: String = if !it.is_mod() { + cx.current.iter().map(|s| s.as_str()).intersperse("::").collect() + } else { + "".into() + }; + let sidebar = Sidebar { title_prefix, title, is_crate: it.is_crate(), version, blocks, path }; + sidebar.render_into(buffer).unwrap(); +} + +fn get_struct_fields_name<'a>(fields: &'a [clean::Item]) -> Vec> { + let mut fields = fields + .iter() + .filter(|f| matches!(*f.kind, clean::StructFieldItem(..))) + .filter_map(|f| { + f.name.as_ref().map(|name| Link::new(format!("structfield.{name}"), name.as_str())) + }) + .collect::>>(); + fields.sort(); + fields +} + +fn sidebar_struct<'a>( + cx: &'a Context<'_>, + it: &'a clean::Item, + s: &'a clean::Struct, +) -> Vec> { + let fields = get_struct_fields_name(&s.fields); + let field_name = match s.ctor_kind { + Some(CtorKind::Fn) => Some("Tuple Fields"), + None => Some("Fields"), + _ => None, + }; + let mut items = vec![]; + if let Some(name) = field_name { + items.push(LinkBlock::new(Link::new("fields", name), fields)); + } + sidebar_assoc_items(cx, it, &mut items); + items +} + +fn sidebar_trait<'a>( + cx: &'a Context<'_>, + it: &'a clean::Item, + t: &'a clean::Trait, +) -> Vec> { + fn filter_items<'a>( + items: &'a [clean::Item], + filt: impl Fn(&clean::Item) -> bool, + ty: &str, + ) -> Vec> { + let mut res = items + .iter() + .filter_map(|m: &clean::Item| match m.name { + Some(ref name) if filt(m) => Some(Link::new(format!("{ty}.{name}"), name.as_str())), + _ => None, + }) + .collect::>>(); + res.sort(); + res + } + + let req_assoc = filter_items(&t.items, |m| m.is_ty_associated_type(), "associatedtype"); + let prov_assoc = filter_items(&t.items, |m| m.is_associated_type(), "associatedtype"); + let req_assoc_const = + filter_items(&t.items, |m| m.is_ty_associated_const(), "associatedconstant"); + let prov_assoc_const = + filter_items(&t.items, |m| m.is_associated_const(), "associatedconstant"); + let req_method = filter_items(&t.items, |m| m.is_ty_method(), "tymethod"); + let prov_method = filter_items(&t.items, |m| m.is_method(), "method"); + let mut foreign_impls = vec![]; + if let Some(implementors) = cx.cache().implementors.get(&it.item_id.expect_def_id()) { + foreign_impls.extend( + implementors + .iter() + .filter(|i| !i.is_on_local_type(cx)) + .filter_map(|i| super::extract_for_impl_name(&i.impl_item, cx)) + .map(|(name, id)| Link::new(id, name)), + ); + foreign_impls.sort(); + } + + let mut blocks: Vec> = [ + ("required-associated-types", "Required Associated Types", req_assoc), + ("provided-associated-types", "Provided Associated Types", prov_assoc), + ("required-associated-consts", "Required Associated Constants", req_assoc_const), + ("provided-associated-consts", "Provided Associated Constants", prov_assoc_const), + ("required-methods", "Required Methods", req_method), + ("provided-methods", "Provided Methods", prov_method), + ("foreign-impls", "Implementations on Foreign Types", foreign_impls), + ] + .into_iter() + .map(|(id, title, items)| LinkBlock::new(Link::new(id, title), items)) + .collect(); + sidebar_assoc_items(cx, it, &mut blocks); + blocks.push(LinkBlock::forced(Link::new("implementors", "Implementors"))); + if t.is_auto(cx.tcx()) { + blocks.push(LinkBlock::forced(Link::new("synthetic-implementors", "Auto Implementors"))); + } + blocks +} + +fn sidebar_primitive<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec> { + if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) { + let mut items = vec![]; + sidebar_assoc_items(cx, it, &mut items); + items + } else { + let shared = Rc::clone(&cx.shared); + let (concrete, synthetic, blanket_impl) = + super::get_filtered_impls_for_reference(&shared, it); + + sidebar_render_assoc_items(cx, &mut IdMap::new(), concrete, synthetic, blanket_impl).into() + } +} + +fn sidebar_typedef<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec> { + let mut items = vec![]; + sidebar_assoc_items(cx, it, &mut items); + items +} + +fn sidebar_union<'a>( + cx: &'a Context<'_>, + it: &'a clean::Item, + u: &'a clean::Union, +) -> Vec> { + let fields = get_struct_fields_name(&u.fields); + let mut items = vec![LinkBlock::new(Link::new("fields", "Fields"), fields)]; + sidebar_assoc_items(cx, it, &mut items); + items +} + +/// Adds trait implementations into the blocks of links +fn sidebar_assoc_items<'a>( + cx: &'a Context<'_>, + it: &'a clean::Item, + links: &mut Vec>, +) { + let did = it.item_id.expect_def_id(); + let cache = cx.cache(); + + let mut assoc_consts = Vec::new(); + let mut methods = Vec::new(); + if let Some(v) = cache.impls.get(&did) { + let mut used_links = FxHashSet::default(); + let mut id_map = IdMap::new(); + + { + let used_links_bor = &mut used_links; + assoc_consts.extend( + v.iter() + .filter(|i| i.inner_impl().trait_.is_none()) + .flat_map(|i| get_associated_constants(i.inner_impl(), used_links_bor)), + ); + // We want links' order to be reproducible so we don't use unstable sort. + assoc_consts.sort(); + + #[rustfmt::skip] // rustfmt makes the pipeline less readable + methods.extend( + v.iter() + .filter(|i| i.inner_impl().trait_.is_none()) + .flat_map(|i| get_methods(i.inner_impl(), false, used_links_bor, false, cx.tcx())), + ); + + // We want links' order to be reproducible so we don't use unstable sort. + methods.sort(); + } + + let mut deref_methods = Vec::new(); + let [concrete, synthetic, blanket] = if v.iter().any(|i| i.inner_impl().trait_.is_some()) { + if let Some(impl_) = + v.iter().find(|i| i.trait_did() == cx.tcx().lang_items().deref_trait()) + { + let mut derefs = DefIdSet::default(); + derefs.insert(did); + sidebar_deref_methods( + cx, + &mut deref_methods, + impl_, + v, + &mut derefs, + &mut used_links, + ); + } + + let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) = + v.iter().partition::, _>(|i| i.inner_impl().kind.is_auto()); + let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) = + concrete.into_iter().partition::, _>(|i| i.inner_impl().kind.is_blanket()); + + sidebar_render_assoc_items(cx, &mut id_map, concrete, synthetic, blanket_impl) + } else { + std::array::from_fn(|_| LinkBlock::new(Link::empty(), vec![])) + }; + + let mut blocks = vec![ + LinkBlock::new(Link::new("implementations", "Associated Constants"), assoc_consts), + LinkBlock::new(Link::new("implementations", "Methods"), methods), + ]; + blocks.append(&mut deref_methods); + blocks.extend([concrete, synthetic, blanket]); + links.append(&mut blocks); + } +} + +fn sidebar_deref_methods<'a>( + cx: &'a Context<'_>, + out: &mut Vec>, + impl_: &Impl, + v: &[Impl], + derefs: &mut DefIdSet, + used_links: &mut FxHashSet, +) { + let c = cx.cache(); + + debug!("found Deref: {:?}", impl_); + if let Some((target, real_target)) = + impl_.inner_impl().items.iter().find_map(|item| match *item.kind { + clean::AssocTypeItem(box ref t, _) => Some(match *t { + clean::Typedef { item_type: Some(ref type_), .. } => (type_, &t.type_), + _ => (&t.type_, &t.type_), + }), + _ => None, + }) + { + debug!("found target, real_target: {:?} {:?}", target, real_target); + if let Some(did) = target.def_id(c) && + let Some(type_did) = impl_.inner_impl().for_.def_id(c) && + // `impl Deref for S` + (did == type_did || !derefs.insert(did)) + { + // Avoid infinite cycles + return; + } + let deref_mut = v.iter().any(|i| i.trait_did() == cx.tcx().lang_items().deref_mut_trait()); + let inner_impl = target + .def_id(c) + .or_else(|| { + target.primitive_type().and_then(|prim| c.primitive_locations.get(&prim).cloned()) + }) + .and_then(|did| c.impls.get(&did)); + if let Some(impls) = inner_impl { + debug!("found inner_impl: {:?}", impls); + let mut ret = impls + .iter() + .filter(|i| i.inner_impl().trait_.is_none()) + .flat_map(|i| get_methods(i.inner_impl(), true, used_links, deref_mut, cx.tcx())) + .collect::>(); + if !ret.is_empty() { + let id = if let Some(target_def_id) = real_target.def_id(c) { + Cow::Borrowed( + cx.deref_id_map + .get(&target_def_id) + .expect("Deref section without derived id") + .as_str(), + ) + } else { + Cow::Borrowed("deref-methods") + }; + let title = format!( + "Methods from {:#}", + impl_.inner_impl().trait_.as_ref().unwrap().print(cx), + real_target.print(cx), + ); + // We want links' order to be reproducible so we don't use unstable sort. + ret.sort(); + out.push(LinkBlock::new(Link::new(id, title), ret)); + } + } + + // Recurse into any further impls that might exist for `target` + if let Some(target_did) = target.def_id(c) && + let Some(target_impls) = c.impls.get(&target_did) && + let Some(target_deref_impl) = target_impls.iter().find(|i| { + i.inner_impl() + .trait_ + .as_ref() + .map(|t| Some(t.def_id()) == cx.tcx().lang_items().deref_trait()) + .unwrap_or(false) + }) + { + sidebar_deref_methods( + cx, + out, + target_deref_impl, + target_impls, + derefs, + used_links, + ); + } + } +} + +fn sidebar_enum<'a>( + cx: &'a Context<'_>, + it: &'a clean::Item, + e: &'a clean::Enum, +) -> Vec> { + let mut variants = e + .variants() + .filter_map(|v| v.name) + .map(|name| Link::new(format!("variant.{name}"), name.to_string())) + .collect::>(); + variants.sort_unstable(); + + let mut items = vec![LinkBlock::new(Link::new("variants", "Variants"), variants)]; + sidebar_assoc_items(cx, it, &mut items); + items +} + +pub(crate) fn sidebar_module_like( + item_sections_in_use: FxHashSet, +) -> LinkBlock<'static> { + let item_sections = ItemSection::ALL + .iter() + .copied() + .filter(|sec| item_sections_in_use.contains(sec)) + .map(|sec| Link::new(sec.id(), sec.name())) + .collect(); + LinkBlock::new(Link::empty(), item_sections) +} + +fn sidebar_module(items: &[clean::Item]) -> LinkBlock<'static> { + let item_sections_in_use: FxHashSet<_> = items + .iter() + .filter(|it| { + !it.is_stripped() + && it + .name + .or_else(|| { + if let clean::ImportItem(ref i) = *it.kind && + let clean::ImportKind::Simple(s) = i.kind { Some(s) } else { None } + }) + .is_some() + }) + .map(|it| item_ty_to_section(it.type_())) + .collect(); + + sidebar_module_like(item_sections_in_use) +} + +fn sidebar_foreign_type<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec> { + let mut items = vec![]; + sidebar_assoc_items(cx, it, &mut items); + items +} + +/// Renders the trait implementations for this type +fn sidebar_render_assoc_items( + cx: &Context<'_>, + id_map: &mut IdMap, + concrete: Vec<&Impl>, + synthetic: Vec<&Impl>, + blanket_impl: Vec<&Impl>, +) -> [LinkBlock<'static>; 3] { + let format_impls = |impls: Vec<&Impl>, id_map: &mut IdMap| { + let mut links = FxHashSet::default(); + + let mut ret = impls + .iter() + .filter_map(|it| { + let trait_ = it.inner_impl().trait_.as_ref()?; + let encoded = + id_map.derive(super::get_id_for_impl(&it.inner_impl().for_, Some(trait_), cx)); + + let prefix = match it.inner_impl().polarity { + ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => "", + ty::ImplPolarity::Negative => "!", + }; + let generated = Link::new(encoded, format!("{prefix}{:#}", trait_.print(cx))); + if links.insert(generated.clone()) { Some(generated) } else { None } + }) + .collect::>>(); + ret.sort(); + ret + }; + + let concrete = format_impls(concrete, id_map); + let synthetic = format_impls(synthetic, id_map); + let blanket = format_impls(blanket_impl, id_map); + [ + LinkBlock::new(Link::new("trait-implementations", "Trait Implementations"), concrete), + LinkBlock::new( + Link::new("synthetic-implementations", "Auto Trait Implementations"), + synthetic, + ), + LinkBlock::new(Link::new("blanket-implementations", "Blanket Implementations"), blanket), + ] +} + +fn get_next_url(used_links: &mut FxHashSet, url: String) -> String { + if used_links.insert(url.clone()) { + return url; + } + let mut add = 1; + while !used_links.insert(format!("{}-{}", url, add)) { + add += 1; + } + format!("{}-{}", url, add) +} + +fn get_methods<'a>( + i: &'a clean::Impl, + for_deref: bool, + used_links: &mut FxHashSet, + deref_mut: bool, + tcx: TyCtxt<'_>, +) -> Vec> { + i.items + .iter() + .filter_map(|item| match item.name { + Some(ref name) if !name.is_empty() && item.is_method() => { + if !for_deref || super::should_render_item(item, deref_mut, tcx) { + Some(Link::new( + get_next_url(used_links, format!("{}.{}", ItemType::Method, name)), + name.as_str(), + )) + } else { + None + } + } + _ => None, + }) + .collect::>() +} + +fn get_associated_constants<'a>( + i: &'a clean::Impl, + used_links: &mut FxHashSet, +) -> Vec> { + i.items + .iter() + .filter_map(|item| match item.name { + Some(ref name) if !name.is_empty() && item.is_associated_const() => Some(Link::new( + get_next_url(used_links, format!("{}.{}", ItemType::AssocConst, name)), + name.as_str(), + )), + _ => None, + }) + .collect::>() +} diff --git a/src/librustdoc/html/templates/sidebar.html b/src/librustdoc/html/templates/sidebar.html new file mode 100644 index 00000000000..01d476ad29f --- /dev/null +++ b/src/librustdoc/html/templates/sidebar.html @@ -0,0 +1,37 @@ +{% if !title.is_empty() %} +

    {# #} + {{title_prefix}}{{title}} {# #} +

    +{% endif %} +