diff --git a/library/std/src/sys/thread_local/mod.rs b/library/std/src/sys/thread_local/mod.rs index cff74857c4733..970022233cfaf 100644 --- a/library/std/src/sys/thread_local/mod.rs +++ b/library/std/src/sys/thread_local/mod.rs @@ -41,7 +41,7 @@ cfg_select! { } _ => { mod os; - pub use os::{Storage, thread_local_inner}; + pub use os::{Storage, thread_local_inner, value_align}; pub(crate) use os::{LocalPointer, local_pointer}; } } diff --git a/library/std/src/sys/thread_local/native/eager.rs b/library/std/src/sys/thread_local/native/eager.rs index fd48c4f720216..23abad645c1a3 100644 --- a/library/std/src/sys/thread_local/native/eager.rs +++ b/library/std/src/sys/thread_local/native/eager.rs @@ -10,9 +10,11 @@ enum State { } #[allow(missing_debug_implementations)] +#[repr(C)] pub struct Storage { - state: Cell, + // This field must be first, for correctness of `#[rustc_align_static]` val: UnsafeCell, + state: Cell, } impl Storage { diff --git a/library/std/src/sys/thread_local/native/lazy.rs b/library/std/src/sys/thread_local/native/lazy.rs index b556dd9aa25ed..02939a74fc089 100644 --- a/library/std/src/sys/thread_local/native/lazy.rs +++ b/library/std/src/sys/thread_local/native/lazy.rs @@ -27,9 +27,11 @@ enum State { } #[allow(missing_debug_implementations)] +#[repr(C)] pub struct Storage { - state: Cell>, + // This field must be first, for correctness of `#[rustc_align_static]` value: UnsafeCell>, + state: Cell>, } impl Storage diff --git a/library/std/src/sys/thread_local/native/mod.rs b/library/std/src/sys/thread_local/native/mod.rs index a5dffe3c45883..5dc142408047e 100644 --- a/library/std/src/sys/thread_local/native/mod.rs +++ b/library/std/src/sys/thread_local/native/mod.rs @@ -54,7 +54,7 @@ pub macro thread_local_inner { // test in `tests/thread.rs` if these types are renamed. // Used to generate the `LocalKey` value for const-initialized thread locals. - (@key $t:ty, const $init:expr) => {{ + (@key $t:ty, $(#[$align_attr:meta])*, const $init:expr) => {{ const __INIT: $t = $init; unsafe { @@ -62,6 +62,7 @@ pub macro thread_local_inner { if $crate::mem::needs_drop::<$t>() { |_| { #[thread_local] + $(#[$align_attr])* static VAL: $crate::thread::local_impl::EagerStorage<$t> = $crate::thread::local_impl::EagerStorage::new(__INIT); VAL.get() @@ -69,6 +70,7 @@ pub macro thread_local_inner { } else { |_| { #[thread_local] + $(#[$align_attr])* static VAL: $t = __INIT; &VAL } @@ -78,7 +80,7 @@ pub macro thread_local_inner { }}, // used to generate the `LocalKey` value for `thread_local!` - (@key $t:ty, $init:expr) => {{ + (@key $t:ty, $(#[$align_attr:meta])*, $init:expr) => {{ #[inline] fn __init() -> $t { $init @@ -89,6 +91,7 @@ pub macro thread_local_inner { if $crate::mem::needs_drop::<$t>() { |init| { #[thread_local] + $(#[$align_attr])* static VAL: $crate::thread::local_impl::LazyStorage<$t, ()> = $crate::thread::local_impl::LazyStorage::new(); VAL.get_or_init(init, __init) @@ -96,6 +99,7 @@ pub macro thread_local_inner { } else { |init| { #[thread_local] + $(#[$align_attr])* static VAL: $crate::thread::local_impl::LazyStorage<$t, !> = $crate::thread::local_impl::LazyStorage::new(); VAL.get_or_init(init, __init) @@ -104,10 +108,6 @@ pub macro thread_local_inner { }) } }}, - ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => { - $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> = - $crate::thread::local_impl::thread_local_inner!(@key $t, $($init)*); - }, } #[rustc_macro_transparency = "semitransparent"] diff --git a/library/std/src/sys/thread_local/no_threads.rs b/library/std/src/sys/thread_local/no_threads.rs index 4da01a84acf68..409dfb19518d9 100644 --- a/library/std/src/sys/thread_local/no_threads.rs +++ b/library/std/src/sys/thread_local/no_threads.rs @@ -2,6 +2,7 @@ //! thread locals and we can instead just use plain statics! use crate::cell::{Cell, UnsafeCell}; +use crate::mem::MaybeUninit; use crate::ptr; #[doc(hidden)] @@ -11,12 +12,13 @@ use crate::ptr; #[rustc_macro_transparency = "semitransparent"] pub macro thread_local_inner { // used to generate the `LocalKey` value for const-initialized thread locals - (@key $t:ty, const $init:expr) => {{ + (@key $t:ty, $(#[$align_attr:meta])*, const $init:expr) => {{ const __INIT: $t = $init; // NOTE: Please update the shadowing test in `tests/thread.rs` if these types are renamed. unsafe { $crate::thread::LocalKey::new(|_| { + $(#[$align_attr])* static VAL: $crate::thread::local_impl::EagerStorage<$t> = $crate::thread::local_impl::EagerStorage { value: __INIT }; &VAL.value @@ -25,27 +27,22 @@ pub macro thread_local_inner { }}, // used to generate the `LocalKey` value for `thread_local!` - (@key $t:ty, $init:expr) => {{ + (@key $t:ty, $(#[$align_attr:meta])*, $init:expr) => {{ #[inline] fn __init() -> $t { $init } unsafe { - use $crate::thread::LocalKey; - use $crate::thread::local_impl::LazyStorage; - - LocalKey::new(|init| { - static VAL: LazyStorage<$t> = LazyStorage::new(); + $crate::thread::LocalKey::new(|init| { + $(#[$align_attr])* + static VAL: $crate::thread::local_impl::LazyStorage<$t> = $crate::thread::local_impl::LazyStorage::new(); VAL.get(init, __init) }) } }}, - ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => { - $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> = - $crate::thread::local_impl::thread_local_inner!(@key $t, $($init)*); - }, } #[allow(missing_debug_implementations)] +#[repr(transparent)] // Required for correctness of `#[rustc_align_static]` pub struct EagerStorage { pub value: T, } @@ -53,14 +50,27 @@ pub struct EagerStorage { // SAFETY: the target doesn't have threads. unsafe impl Sync for EagerStorage {} +#[derive(Clone, Copy, PartialEq, Eq)] +enum State { + Initial, + Alive, + Destroying, +} + #[allow(missing_debug_implementations)] +#[repr(C)] pub struct LazyStorage { - value: UnsafeCell>, + // This field must be first, for correctness of `#[rustc_align_static]` + value: UnsafeCell>, + state: Cell, } impl LazyStorage { pub const fn new() -> LazyStorage { - LazyStorage { value: UnsafeCell::new(None) } + LazyStorage { + value: UnsafeCell::new(MaybeUninit::uninit()), + state: Cell::new(State::Initial), + } } /// Gets a pointer to the TLS value, potentially initializing it with the @@ -70,24 +80,39 @@ impl LazyStorage { /// has occurred. #[inline] pub fn get(&'static self, i: Option<&mut Option>, f: impl FnOnce() -> T) -> *const T { - let value = unsafe { &*self.value.get() }; - match value { - Some(v) => v, - None => self.initialize(i, f), + if self.state.get() == State::Alive { + self.value.get() as *const T + } else { + self.initialize(i, f) } } #[cold] fn initialize(&'static self, i: Option<&mut Option>, f: impl FnOnce() -> T) -> *const T { let value = i.and_then(Option::take).unwrap_or_else(f); - // Destroy the old value, after updating the TLS variable as the - // destructor might reference it. + + // Destroy the old value if it is initialized // FIXME(#110897): maybe panic on recursive initialization. + if self.state.get() == State::Alive { + self.state.set(State::Destroying); + // Safety: we check for no initialization during drop below + unsafe { + ptr::drop_in_place(self.value.get() as *mut T); + } + self.state.set(State::Initial); + } + + // Guard against initialization during drop + if self.state.get() == State::Destroying { + panic!("Attempted to initialize thread-local while it is being dropped"); + } + unsafe { - self.value.get().replace(Some(value)); + self.value.get().write(MaybeUninit::new(value)); } - // SAFETY: we just set this to `Some`. - unsafe { (*self.value.get()).as_ref().unwrap_unchecked() } + self.state.set(State::Alive); + + self.value.get() as *const T } } diff --git a/library/std/src/sys/thread_local/os.rs b/library/std/src/sys/thread_local/os.rs index fe6af27db3a17..b488f12fe841f 100644 --- a/library/std/src/sys/thread_local/os.rs +++ b/library/std/src/sys/thread_local/os.rs @@ -1,5 +1,6 @@ use super::key::{Key, LazyKey, get, set}; use super::{abort_on_dtor_unwind, guard}; +use crate::alloc::Layout; use crate::cell::Cell; use crate::marker::PhantomData; use crate::ptr; @@ -10,17 +11,12 @@ use crate::ptr; #[unstable(feature = "thread_local_internals", issue = "none")] #[rustc_macro_transparency = "semitransparent"] pub macro thread_local_inner { - // used to generate the `LocalKey` value for const-initialized thread locals - (@key $t:ty, const $init:expr) => { - $crate::thread::local_impl::thread_local_inner!(@key $t, { const INIT_EXPR: $t = $init; INIT_EXPR }) - }, - // NOTE: we cannot import `Storage` or `LocalKey` with a `use` because that can shadow user // provided type or type alias with a matching name. Please update the shadowing test in // `tests/thread.rs` if these types are renamed. // used to generate the `LocalKey` value for `thread_local!`. - (@key $t:ty, $init:expr) => {{ + (@key $t:ty, $($(#[$($align_attr:tt)*])+)?, $init:expr) => {{ #[inline] fn __init() -> $t { $init } @@ -29,37 +25,94 @@ pub macro thread_local_inner { // in `tests/thread.rs` if these types are renamed. unsafe { $crate::thread::LocalKey::new(|init| { - static VAL: $crate::thread::local_impl::Storage<$t> + static VAL: $crate::thread::local_impl::Storage<$t, { + $({ + // Ensure that attributes have valid syntax + // and that the proper feature gate is enabled + $(#[$($align_attr)*])+ + #[allow(unused)] + static DUMMY: () = (); + })? + + #[allow(unused_mut)] + let mut final_align = $crate::thread::local_impl::value_align::<$t>(); + $($($crate::thread::local_impl::thread_local_inner!(@align final_align, $($align_attr)*);)+)? + final_align + }> = $crate::thread::local_impl::Storage::new(); VAL.get(init, __init) }) } }}, - ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => { - $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> = - $crate::thread::local_impl::thread_local_inner!(@key $t, $($init)*); + + // process a single `rustc_align_static` attribute + (@align $final_align:ident, rustc_align_static($($align:tt)*) $(, $($attr_rest:tt)+)?) => { + let new_align: $crate::primitive::usize = $($align)*; + if new_align> $final_align { + $final_align = new_align; + } + + $($crate::thread::local_impl::thread_local_inner!(@align $final_align, $($attr_rest)+);)? + }, + + // process a single `cfg_attr` attribute + // by translating it into a `cfg`ed block and recursing. + // https://doc.rust-lang.org/reference/conditional-compilation.html#railroad-ConfigurationPredicate + + (@align $final_align:ident, cfg_attr(true, $($cfg_rhs:tt)*) $(, $($attr_rest:tt)+)?) => { + #[cfg(true)] + { + $crate::thread::local_impl::thread_local_inner!(@align $final_align, $($cfg_rhs)*); + } + + $($crate::thread::local_impl::thread_local_inner!(@align $final_align, $($attr_rest)+);)? + }, + + (@align $final_align:ident, cfg_attr(false, $($cfg_rhs:tt)*) $(, $($attr_rest:tt)+)?) => { + #[cfg(false)] + { + $crate::thread::local_impl::thread_local_inner!(@align $final_align, $($cfg_rhs)*); + } + + $($crate::thread::local_impl::thread_local_inner!(@align $final_align, $($attr_rest)+);)? + }, + + (@align $final_align:ident, cfg_attr($cfg_pred:meta, $($cfg_rhs:tt)*) $(, $($attr_rest:tt)+)?) => { + #[cfg($cfg_pred)] + { + $crate::thread::local_impl::thread_local_inner!(@align $final_align, $($cfg_rhs)*); + } + + $($crate::thread::local_impl::thread_local_inner!(@align $final_align, $($attr_rest)+);)? }, } /// Use a regular global static to store this key; the state provided will then be /// thread-local. +/// INVARIANT: ALIGN must be a valid alignment, and no less than `value_align::`. #[allow(missing_debug_implementations)] -pub struct Storage { +pub struct Storage { key: LazyKey, marker: PhantomData>, } -unsafe impl Sync for Storage {} +unsafe impl Sync for Storage {} +#[repr(C)] struct Value { + // This field must be first, for correctness of `#[rustc_align_static]` value: T, // INVARIANT: if this value is stored under a TLS key, `key` must be that `key`. key: Key, } -impl Storage { - pub const fn new() -> Storage { - Storage { key: LazyKey::new(Some(destroy_value::)), marker: PhantomData } +pub const fn value_align() -> usize { + crate::mem::align_of::>() +} + +impl Storage { + pub const fn new() -> Storage { + Storage { key: LazyKey::new(Some(destroy_value::)), marker: PhantomData } } /// Gets a pointer to the TLS value, potentially initializing it with the @@ -95,8 +148,15 @@ impl Storage { return ptr::null(); } - let value = Box::new(Value { value: i.and_then(Option::take).unwrap_or_else(f), key }); - let ptr = Box::into_raw(value); + // Manually allocate with the requested alignment + let layout = Layout::new::>().align_to(ALIGN).unwrap(); + let ptr: *mut Value = (unsafe { crate::alloc::alloc(layout) }).cast(); + if ptr.is_null() { + crate::alloc::handle_alloc_error(layout); + } + unsafe { + ptr.write(Value { value: i.and_then(Option::take).unwrap_or_else(f), key }); + } // SAFETY: // * key came from a `LazyKey` and is thus correct. @@ -114,7 +174,10 @@ impl Storage { // initializer has already returned and the next scope only starts // after we return the pointer. Therefore, there can be no references // to the old value. - drop(unsafe { Box::from_raw(old) }); + unsafe { + old.drop_in_place(); + crate::alloc::dealloc(old.cast(), layout); + } } // SAFETY: We just created this value above. @@ -122,7 +185,7 @@ impl Storage { } } -unsafe extern "C" fn destroy_value(ptr: *mut u8) { +unsafe extern "C" fn destroy_value(ptr: *mut u8) { // SAFETY: // // The OS TLS ensures that this key contains a null value when this @@ -133,13 +196,22 @@ unsafe extern "C" fn destroy_value(ptr: *mut u8) { // Note that to prevent an infinite loop we reset it back to null right // before we return from the destructor ourselves. abort_on_dtor_unwind(|| { - let ptr = unsafe { Box::from_raw(ptr as *mut Value) }; - let key = ptr.key; - // SAFETY: `key` is the TLS key `ptr` was stored under. - unsafe { set(key, ptr::without_provenance_mut(1)) }; - drop(ptr); - // SAFETY: `key` is the TLS key `ptr` was stored under. - unsafe { set(key, ptr::null_mut()) }; + let value_ptr: *mut Value = ptr.cast(); + unsafe { + let key = (*value_ptr).key; + + // SAFETY: `key` is the TLS key `ptr` was stored under. + set(key, ptr::without_provenance_mut(1)); + + // drop and deallocate the value + let layout = + Layout::from_size_align_unchecked(crate::mem::size_of::>(), ALIGN); + value_ptr.drop_in_place(); + crate::alloc::dealloc(ptr, layout); + + // SAFETY: `key` is the TLS key `ptr` was stored under. + set(key, ptr::null_mut()); + }; // Make sure that the runtime cleanup will be performed // after the next round of TLS destruction. guard::enable(); diff --git a/library/std/src/thread/local.rs b/library/std/src/thread/local.rs index 0a6f2e5d5088b..4259a4d1f3b7c 100644 --- a/library/std/src/thread/local.rs +++ b/library/std/src/thread/local.rs @@ -132,6 +132,216 @@ impl fmt::Debug for LocalKey { } } +#[doc(hidden)] +#[allow_internal_unstable(thread_local_internals)] +#[unstable(feature = "thread_local_internals", issue = "none")] +#[rustc_macro_transparency = "semitransparent"] +pub macro thread_local_process_attrs { + + // Parse `cfg_attr` to figure out whether it's a `rustc_align_static`. + // Each `cfg_attr` can have zero or more attributes on the RHS, and can be nested. + + // finished parsing the `cfg_attr`, it had no `rustc_align_static` + ( + [] [$(#[$($prev_other_attrs:tt)*])*]; + @processing_cfg_attr { pred: ($($predicate:tt)*), rhs: [] }; + [$($prev_align_attrs_ret:tt)*] [$($prev_other_attrs_ret:tt)*]; + $($rest:tt)* + ) => ( + $crate::thread::local_impl::thread_local_process_attrs!( + [$($prev_align_attrs_ret)*] [$($prev_other_attrs_ret)* #[cfg_attr($($predicate)*, $($($prev_other_attrs)*),*)]]; + $($rest)* + ); + ), + + // finished parsing the `cfg_attr`, it had nothing but `rustc_align_static` + ( + [$(#[$($prev_align_attrs:tt)*])+] []; + @processing_cfg_attr { pred: ($($predicate:tt)*), rhs: [] }; + [$($prev_align_attrs_ret:tt)*] [$($prev_other_attrs_ret:tt)*]; + $($rest:tt)* + ) => ( + $crate::thread::local_impl::thread_local_process_attrs!( + [$($prev_align_attrs_ret)* #[cfg_attr($($predicate)*, $($($prev_align_attrs)*),+)]] [$($prev_other_attrs_ret)*]; + $($rest)* + ); + ), + + // finished parsing the `cfg_attr`, it had a mix of `rustc_align_static` and other attrs + ( + [$(#[$($prev_align_attrs:tt)*])+] [$(#[$($prev_other_attrs:tt)*])+]; + @processing_cfg_attr { pred: ($($predicate:tt)*), rhs: [] }; + [$($prev_align_attrs_ret:tt)*] [$($prev_other_attrs_ret:tt)*]; + $($rest:tt)* + ) => ( + $crate::thread::local_impl::thread_local_process_attrs!( + [$($prev_align_attrs_ret)* #[cfg_attr($($predicate)*, $($($prev_align_attrs)*),+)]] [$($prev_other_attrs_ret)* #[cfg_attr($($predicate)*, $($($prev_other_attrs)*),+)]]; + $($rest)* + ); + ), + + // it's a `rustc_align_static` + ( + [$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*]; + @processing_cfg_attr { pred: ($($predicate:tt)*), rhs: [rustc_align_static($($align_static_args:tt)*) $(, $($attr_rhs:tt)*)?] }; + $($rest:tt)* + ) => ( + $crate::thread::local_impl::thread_local_process_attrs!( + [$($prev_align_attrs)* #[rustc_align_static($($align_static_args)*)]] [$($prev_other_attrs)*]; + @processing_cfg_attr { pred: ($($predicate)*), rhs: [$($($attr_rhs)*)?] }; + $($rest)* + ); + ), + + // it's a nested `cfg_attr(true, ...)`; recurse into RHS + ( + [$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*]; + @processing_cfg_attr { pred: ($($predicate:tt)*), rhs: [cfg_attr(true, $($cfg_rhs:tt)*) $(, $($attr_rhs:tt)*)?] }; + $($rest:tt)* + ) => ( + $crate::thread::local_impl::thread_local_process_attrs!( + [] []; + @processing_cfg_attr { pred: (true), rhs: [$($cfg_rhs)*] }; + [$($prev_align_attrs)*] [$($prev_other_attrs)*]; + @processing_cfg_attr { pred: ($($predicate)*), rhs: [$($($attr_rhs)*)?] }; + $($rest)* + ); + ), + + // it's a nested `cfg_attr(false, ...)`; recurse into RHS + ( + [$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*]; + @processing_cfg_attr { pred: ($($predicate:tt)*), rhs: [cfg_attr(false, $($cfg_rhs:tt)*) $(, $($attr_rhs:tt)*)?] }; + $($rest:tt)* + ) => ( + $crate::thread::local_impl::thread_local_process_attrs!( + [] []; + @processing_cfg_attr { pred: (false), rhs: [$($cfg_rhs)*] }; + [$($prev_align_attrs)*] [$($prev_other_attrs)*]; + @processing_cfg_attr { pred: ($($predicate)*), rhs: [$($($attr_rhs)*)?] }; + $($rest)* + ); + ), + + + // it's a nested `cfg_attr(..., ...)`; recurse into RHS + ( + [$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*]; + @processing_cfg_attr { pred: ($($predicate:tt)*), rhs: [cfg_attr($cfg_lhs:meta, $($cfg_rhs:tt)*) $(, $($attr_rhs:tt)*)?] }; + $($rest:tt)* + ) => ( + $crate::thread::local_impl::thread_local_process_attrs!( + [] []; + @processing_cfg_attr { pred: ($cfg_lhs), rhs: [$($cfg_rhs)*] }; + [$($prev_align_attrs)*] [$($prev_other_attrs)*]; + @processing_cfg_attr { pred: ($($predicate)*), rhs: [$($($attr_rhs)*)?] }; + $($rest)* + ); + ), + + // it's some other attribute + ( + [$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*]; + @processing_cfg_attr { pred: ($($predicate:tt)*), rhs: [$meta:meta $(, $($attr_rhs:tt)*)?] }; + $($rest:tt)* + ) => ( + $crate::thread::local_impl::thread_local_process_attrs!( + [$($prev_align_attrs)*] [$($prev_other_attrs)* #[$meta]]; + @processing_cfg_attr { pred: ($($predicate)*), rhs: [$($($attr_rhs)*)?] }; + $($rest)* + ); + ), + + + // Separate attributes into `rustc_align_static` and everything else: + + // `rustc_align_static` attribute + ([$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*]; #[rustc_align_static $($attr_rest:tt)*] $($rest:tt)*) => ( + $crate::thread::local_impl::thread_local_process_attrs!( + [$($prev_align_attrs)* #[rustc_align_static $($attr_rest)*]] [$($prev_other_attrs)*]; + $($rest)* + ); + ), + + // `cfg_attr(true, ...)` attribute; parse it + ([$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*]; #[cfg_attr(true, $($cfg_rhs:tt)*)] $($rest:tt)*) => ( + $crate::thread::local_impl::thread_local_process_attrs!( + [] []; + @processing_cfg_attr { pred: (true), rhs: [$($cfg_rhs)*] }; + [$($prev_align_attrs)*] [$($prev_other_attrs)*]; + $($rest)* + ); + ), + + // `cfg_attr(false, ...)` attribute; parse it + ([$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*]; #[cfg_attr(false, $($cfg_rhs:tt)*)] $($rest:tt)*) => ( + $crate::thread::local_impl::thread_local_process_attrs!( + [] []; + @processing_cfg_attr { pred: (false), rhs: [$($cfg_rhs)*] }; + [$($prev_align_attrs)*] [$($prev_other_attrs)*]; + $($rest)* + ); + ), + + // `cfg_attr(..., ...)` attribute; parse it + ([$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*]; #[cfg_attr($cfg_pred:meta, $($cfg_rhs:tt)*)] $($rest:tt)*) => ( + $crate::thread::local_impl::thread_local_process_attrs!( + [] []; + @processing_cfg_attr { pred: ($cfg_pred), rhs: [$($cfg_rhs)*] }; + [$($prev_align_attrs)*] [$($prev_other_attrs)*]; + $($rest)* + ); + ), + + // doc comment not followed by any other attributes; process it all at once to avoid blowing recursion limit + ([$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*]; $(#[doc $($doc_rhs:tt)*])+ $vis:vis static $($rest:tt)*) => ( + $crate::thread::local_impl::thread_local_process_attrs!( + [$($prev_align_attrs)*] [$($prev_other_attrs)* $(#[doc $($doc_rhs)*])+]; + $vis static $($rest)* + ); + ), + + // 8 lines of doc comment; process them all at once to avoid blowing recursion limit + ([$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*]; + #[doc $($doc_rhs_1:tt)*] #[doc $($doc_rhs_2:tt)*] #[doc $($doc_rhs_3:tt)*] #[doc $($doc_rhs_4:tt)*] + #[doc $($doc_rhs_5:tt)*] #[doc $($doc_rhs_6:tt)*] #[doc $($doc_rhs_7:tt)*] #[doc $($doc_rhs_8:tt)*] + $($rest:tt)*) => ( + $crate::thread::local_impl::thread_local_process_attrs!( + [$($prev_align_attrs)*] [$($prev_other_attrs)* + #[doc $($doc_rhs_1)*] #[doc $($doc_rhs_2)*] #[doc $($doc_rhs_3)*] #[doc $($doc_rhs_4)*] + #[doc $($doc_rhs_5)*] #[doc $($doc_rhs_6)*] #[doc $($doc_rhs_7)*] #[doc $($doc_rhs_8)*]]; + $($rest)* + ); + ), + + // other attribute + ([$($prev_align_attrs:tt)*] [$($prev_other_attrs:tt)*]; #[$($attr:tt)*] $($rest:tt)*) => ( + $crate::thread::local_impl::thread_local_process_attrs!( + [$($prev_align_attrs)*] [$($prev_other_attrs)* #[$($attr)*]]; + $($rest)* + ); + ), + + + // Delegate to `thread_local_inner` once attributes are fully categorized: + + // process `const` declaration and recurse + ([$($align_attrs:tt)*] [$($other_attrs:tt)*]; $vis:vis static $name:ident: $t:ty = const $init:block $(; $($($rest:tt)+)?)?) => ( + $($other_attrs)* $vis const $name: $crate::thread::LocalKey<$t> = + $crate::thread::local_impl::thread_local_inner!(@key $t, $($align_attrs)*, const $init); + + $($($crate::thread::local_impl::thread_local_process_attrs!([] []; $($rest)+);)?)? + ), + + // process non-`const` declaration and recurse + ([$($align_attrs:tt)*] [$($other_attrs:tt)*]; $vis:vis static $name:ident: $t:ty = $init:expr $(; $($($rest:tt)+)?)?) => ( + $($other_attrs)* $vis const $name: $crate::thread::LocalKey<$t> = + $crate::thread::local_impl::thread_local_inner!(@key $t, $($align_attrs)*, $init); + + $($($crate::thread::local_impl::thread_local_process_attrs!([] []; $($rest)+);)?)? + ), +} + /// Declare a new thread local storage key of type [`std::thread::LocalKey`]. /// /// # Syntax @@ -182,28 +392,11 @@ impl fmt::Debug for LocalKey { #[cfg_attr(not(test), rustc_diagnostic_item = "thread_local_macro")] #[allow_internal_unstable(thread_local_internals)] macro_rules! thread_local { - // empty (base case for the recursion) () => {}; - ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = const $init:block; $($rest:tt)*) => ( - $crate::thread::local_impl::thread_local_inner!($(#[$attr])* $vis $name, $t, const $init); - $crate::thread_local!($($rest)*); - ); - - ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = const $init:block) => ( - $crate::thread::local_impl::thread_local_inner!($(#[$attr])* $vis $name, $t, const $init); - ); - - // process multiple declarations - ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr; $($rest:tt)*) => ( - $crate::thread::local_impl::thread_local_inner!($(#[$attr])* $vis $name, $t, $init); - $crate::thread_local!($($rest)*); - ); - - // handle a single declaration - ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr) => ( - $crate::thread::local_impl::thread_local_inner!($(#[$attr])* $vis $name, $t, $init); - ); + ($($tt:tt)+) => { + $crate::thread::local_impl::thread_local_process_attrs!([] []; $($tt)+); + }; } /// An error returned by [`LocalKey::try_with`](struct.LocalKey.html#method.try_with). diff --git a/library/std/src/thread/mod.rs b/library/std/src/thread/mod.rs index 4d09b2b4e9d2e..f320c287db32f 100644 --- a/library/std/src/thread/mod.rs +++ b/library/std/src/thread/mod.rs @@ -205,6 +205,7 @@ pub use self::local::{AccessError, LocalKey}; #[doc(hidden)] #[unstable(feature = "thread_local_internals", issue = "none")] pub mod local_impl { + pub use super::local::thread_local_process_attrs; pub use crate::sys::thread_local::*; } diff --git a/library/std/tests/thread.rs b/library/std/tests/thread.rs index 29f220d8a70a0..dc8eadd75148b 100644 --- a/library/std/tests/thread.rs +++ b/library/std/tests/thread.rs @@ -66,6 +66,8 @@ fn thread_local_hygeiene() { type Storage = (); type LazyStorage = (); type EagerStorage = (); + #[allow(non_camel_case_types)] + type usize = (); thread_local! { static A: LocalKey = const { () }; static B: Storage = const { () }; diff --git a/src/tools/miri/tests/pass/static_align.rs b/src/tools/miri/tests/pass/static_align.rs index f292f028568b6..bc6a9bf8af7dc 100644 --- a/src/tools/miri/tests/pass/static_align.rs +++ b/src/tools/miri/tests/pass/static_align.rs @@ -1,4 +1,7 @@ #![feature(static_align)] +#![deny(non_upper_case_globals)] + +use std::cell::Cell; // When a static uses `align(N)`, its address should be a multiple of `N`. @@ -8,7 +11,64 @@ static FOO: u64 = 0; #[rustc_align_static(512)] static BAR: u64 = 0; +struct HasDrop(*const HasDrop); + +impl Drop for HasDrop { + fn drop(&mut self) { + assert_eq!(core::ptr::from_mut(self).cast_const(), self.0); + } +} + +thread_local! { + #[rustc_align_static(4096)] + static LOCAL: u64 = 0; + + #[allow(unused_mut, reason = "test attribute handling")] + #[cfg_attr(true, rustc_align_static(4096))] + static CONST_LOCAL: u64 = const { 0 }; + + #[cfg_attr(any(true), cfg_attr(true, rustc_align_static(4096)))] + #[allow(unused_mut, reason = "test attribute handling")] + static HASDROP_LOCAL: Cell = Cell::new(HasDrop(core::ptr::null())); + + /// I love doc comments. + #[allow(unused_mut, reason = "test attribute handling")] + #[cfg_attr(all(), + cfg_attr(any(true), + cfg_attr(true, rustc_align_static(4096))))] + #[allow(unused_mut, reason = "test attribute handling")] + /// I love doc comments. + static HASDROP_CONST_LOCAL: Cell = const { Cell::new(HasDrop(core::ptr::null())) }; + + #[cfg_attr(true,)] + #[cfg_attr(false,)] + #[cfg_attr( + true, + rustc_align_static(32), + cfg_attr(true, allow(non_upper_case_globals, reason = "test attribute handling")), + cfg_attr(false,) + )] + #[cfg_attr(false, rustc_align_static(0))] + static more_attr_testing: u64 = 0; +} + +fn thread_local_ptr(key: &'static std::thread::LocalKey) -> *const T { + key.with(|local| core::ptr::from_ref::(local)) +} + fn main() { assert!(core::ptr::from_ref(&FOO).addr().is_multiple_of(256)); assert!(core::ptr::from_ref(&BAR).addr().is_multiple_of(512)); + + assert!(thread_local_ptr(&LOCAL).addr().is_multiple_of(4096)); + assert!(thread_local_ptr(&CONST_LOCAL).addr().is_multiple_of(4096)); + assert!(thread_local_ptr(&HASDROP_LOCAL).addr().is_multiple_of(4096)); + assert!(thread_local_ptr(&HASDROP_CONST_LOCAL).addr().is_multiple_of(4096)); + assert!(thread_local_ptr(&more_attr_testing).addr().is_multiple_of(32)); + + // Test that address (and therefore alignment) is maintained during drop + let hasdrop_ptr = thread_local_ptr(&HASDROP_LOCAL); + core::mem::forget(HASDROP_LOCAL.replace(HasDrop(hasdrop_ptr.cast()))); + let hasdrop_const_ptr = thread_local_ptr(&HASDROP_CONST_LOCAL); + core::mem::forget(HASDROP_CONST_LOCAL.replace(HasDrop(hasdrop_const_ptr.cast()))); } diff --git a/tests/ui/feature-gates/feature-gate-static_align-thread_local.rs b/tests/ui/feature-gates/feature-gate-static_align-thread_local.rs new file mode 100644 index 0000000000000..29d4facffce95 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-static_align-thread_local.rs @@ -0,0 +1,11 @@ +// The feature gate error may be emitted twice, but only on certain targets +//@ dont-require-annotations: ERROR +//@ dont-check-compiler-stderr + +#![crate_type = "lib"] + +thread_local! { + //~^ ERROR the `#[rustc_align_static]` attribute is an experimental feature + #[rustc_align_static(16)] + static THREAD_LOCAL: u16 = 0; +} diff --git a/tests/ui/macros/macro-local-data-key-priv.stderr b/tests/ui/macros/macro-local-data-key-priv.stderr index e93bd11046d09..8df1aec140d0e 100644 --- a/tests/ui/macros/macro-local-data-key-priv.stderr +++ b/tests/ui/macros/macro-local-data-key-priv.stderr @@ -9,7 +9,7 @@ note: the constant `baz` is defined here | LL | thread_local!(static baz: f64 = 0.0); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: this error originates in the macro `$crate::thread::local_impl::thread_local_inner` which comes from the expansion of the macro `thread_local` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::thread::local_impl::thread_local_process_attrs` which comes from the expansion of the macro `thread_local` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 1 previous error diff --git a/tests/ui/static/static-align.rs b/tests/ui/static/static-align.rs index 93241db09f949..e2db7c01adf29 100644 --- a/tests/ui/static/static-align.rs +++ b/tests/ui/static/static-align.rs @@ -1,10 +1,14 @@ //@ run-pass +//@ compile-flags: --cfg FOURTY_TWO="42" --cfg TRUE --check-cfg=cfg(FOURTY_TWO,values("42")) --check-cfg=cfg(TRUE) #![feature(static_align)] +#![deny(non_upper_case_globals)] + +use std::cell::Cell; #[rustc_align_static(64)] static A: u8 = 0; -#[rustc_align_static(64)] +#[rustc_align_static(4096)] static B: u8 = 0; #[rustc_align_static(128)] @@ -17,10 +21,86 @@ unsafe extern "C" { static C: u64; } +struct HasDrop(*const HasDrop); + +impl Drop for HasDrop { + fn drop(&mut self) { + assert_eq!(core::ptr::from_mut(self).cast_const(), self.0); + } +} + +thread_local! { + #[rustc_align_static(4096)] + static LOCAL: u64 = 0; + + #[allow(unused_mut, reason = "test attribute handling")] + #[cfg_attr(true, rustc_align_static(4096))] + static CONST_LOCAL: u64 = const { 0 }; + + #[cfg_attr(any(true), cfg_attr(true, rustc_align_static(4096)))] + #[allow(unused_mut, reason = "test attribute handling")] + static HASDROP_LOCAL: Cell = Cell::new(HasDrop(core::ptr::null())); + + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + #[allow(unused_mut, reason = "test attribute handling")] + #[cfg_attr(TRUE, + cfg_attr(FOURTY_TWO = "42", + cfg_attr(all(), + cfg_attr(any(true), + cfg_attr(true, rustc_align_static(4096))))))] + #[allow(unused_mut, reason = "test attribute handling")] + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + static HASDROP_CONST_LOCAL: Cell = const { Cell::new(HasDrop(core::ptr::null())) }; + + #[cfg_attr(TRUE,)] + #[cfg_attr(true,)] + #[cfg_attr(false,)] + #[cfg_attr( + TRUE, + rustc_align_static(32), + cfg_attr(true, allow(non_upper_case_globals, reason = "test attribute handling")), + cfg_attr(false,) + )] + #[cfg_attr(false, rustc_align_static(0))] + static more_attr_testing: u64 = 0; +} + +fn thread_local_ptr(key: &'static std::thread::LocalKey) -> *const T { + key.with(|local| core::ptr::from_ref::(local)) +} + fn main() { assert!(core::ptr::from_ref(&A).addr().is_multiple_of(64)); - assert!(core::ptr::from_ref(&B).addr().is_multiple_of(64)); + assert!(core::ptr::from_ref(&B).addr().is_multiple_of(4096)); assert!(core::ptr::from_ref(&EXPORTED).addr().is_multiple_of(128)); unsafe { assert!(core::ptr::from_ref(&C).addr().is_multiple_of(128)) }; + + assert!(thread_local_ptr(&LOCAL).addr().is_multiple_of(4096)); + assert!(thread_local_ptr(&CONST_LOCAL).addr().is_multiple_of(4096)); + assert!(thread_local_ptr(&HASDROP_LOCAL).addr().is_multiple_of(4096)); + assert!(thread_local_ptr(&HASDROP_CONST_LOCAL).addr().is_multiple_of(4096)); + assert!(thread_local_ptr(&more_attr_testing).addr().is_multiple_of(32)); + + // Test that address (and therefore alignment) is maintained during drop + let hasdrop_ptr = thread_local_ptr(&HASDROP_LOCAL); + core::mem::forget(HASDROP_LOCAL.replace(HasDrop(hasdrop_ptr.cast()))); + let hasdrop_const_ptr = thread_local_ptr(&HASDROP_CONST_LOCAL); + core::mem::forget(HASDROP_CONST_LOCAL.replace(HasDrop(hasdrop_const_ptr.cast()))); } diff --git a/tests/ui/thread-local/long-docs.rs b/tests/ui/thread-local/long-docs.rs new file mode 100644 index 0000000000000..0577d0b27c285 --- /dev/null +++ b/tests/ui/thread-local/long-docs.rs @@ -0,0 +1,266 @@ +//@ check-pass + +thread_local! { + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + pub static LONG_DOCS: () = (); + + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + /// I love doc comments. + #[allow(unused_mut, reason = "testing")] + pub static LONG_DOCS_2: () = (); +} + +fn main() {} diff --git a/tests/ui/thread-local/no-unstable.rs b/tests/ui/thread-local/no-unstable.rs new file mode 100644 index 0000000000000..3de7985e62dbd --- /dev/null +++ b/tests/ui/thread-local/no-unstable.rs @@ -0,0 +1,17 @@ +thread_local! { + //~^ ERROR: use of an internal attribute [E0658] + //~| ERROR: use of an internal attribute [E0658] + //~| ERROR: `#[used(linker)]` is currently unstable [E0658] + //~| ERROR: `#[used]` attribute cannot be used on constants + + #[rustc_dummy = 17] + pub static FOO: () = (); + + #[cfg_attr(true, rustc_dummy = 17)] + pub static BAR: () = (); + + #[used(linker)] + pub static BAZ: () = (); +} + +fn main() {} diff --git a/tests/ui/thread-local/no-unstable.stderr b/tests/ui/thread-local/no-unstable.stderr new file mode 100644 index 0000000000000..fbcd804d91785 --- /dev/null +++ b/tests/ui/thread-local/no-unstable.stderr @@ -0,0 +1,57 @@ +error[E0658]: use of an internal attribute + --> $DIR/no-unstable.rs:1:1 + | +LL | / thread_local! { +... | +LL | | pub static BAZ: () = (); +LL | | } + | |_^ + | + = help: add `#![feature(rustc_attrs)]` to the crate attributes to enable + = note: the `#[rustc_dummy]` attribute is an internal implementation detail that will never be stable + = note: the `#[rustc_dummy]` attribute is used for rustc unit tests + = note: this error originates in the macro `$crate::thread::local_impl::thread_local_process_attrs` which comes from the expansion of the macro `thread_local` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0658]: use of an internal attribute + --> $DIR/no-unstable.rs:1:1 + | +LL | / thread_local! { +... | +LL | | pub static BAZ: () = (); +LL | | } + | |_^ + | + = help: add `#![feature(rustc_attrs)]` to the crate attributes to enable + = note: the `#[rustc_dummy]` attribute is an internal implementation detail that will never be stable + = note: the `#[rustc_dummy]` attribute is used for rustc unit tests + = note: this error originates in the macro `$crate::thread::local_impl::thread_local_process_attrs` which comes from the expansion of the macro `thread_local` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0658]: `#[used(linker)]` is currently unstable + --> $DIR/no-unstable.rs:1:1 + | +LL | / thread_local! { +... | +LL | | pub static BAZ: () = (); +LL | | } + | |_^ + | + = note: see issue #93798 for more information + = help: add `#![feature(used_with_arg)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + = note: this error originates in the macro `$crate::thread::local_impl::thread_local_process_attrs` which comes from the expansion of the macro `thread_local` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `#[used]` attribute cannot be used on constants + --> $DIR/no-unstable.rs:1:1 + | +LL | / thread_local! { +... | +LL | | pub static BAZ: () = (); +LL | | } + | |_^ + | + = help: `#[used]` can only be applied to statics + = note: this error originates in the macro `$crate::thread::local_impl::thread_local_process_attrs` which comes from the expansion of the macro `thread_local` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0658`.

AltStyle によって変換されたページ (->オリジナル) /