Rollup merge of #146281 - Jules-Bertholet:static-align-thread-local, r=Mark-Simulacrum

Support `#[rustc_align_static]` inside `thread_local!`

Tracking issue: rust-lang/rust#146177

```rust
thread_local! {
    #[rustc_align_static(64)]
    static SO_ALIGNED: u64 = const { 0 };
}
```

This increases the amount of recursion the macro performs (once per attribute in addition to the previous once per item), making it easier to hit the recursion limit. I’ve added workarounds to limit the impact in the case of long doc comments, but this still needs a crater run just in case.

r? libs

``@rustbot`` label A-attributes A-macros A-thread-locals F-static_align T-libs
This commit is contained in:
Matthias Krüger 2025-10-02 10:27:48 +02:00 committed by GitHub
commit 92aac1bdf6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 918 additions and 76 deletions

View file

@ -42,7 +42,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};
}
}

View file

@ -10,9 +10,11 @@ enum State {
}
#[allow(missing_debug_implementations)]
#[repr(C)]
pub struct Storage<T> {
state: Cell<State>,
// This field must be first, for correctness of `#[rustc_align_static]`
val: UnsafeCell<T>,
state: Cell<State>,
}
impl<T> Storage<T> {

View file

@ -27,9 +27,11 @@ enum State<D> {
}
#[allow(missing_debug_implementations)]
#[repr(C)]
pub struct Storage<T, D> {
state: Cell<State<D>>,
// This field must be first, for correctness of `#[rustc_align_static]`
value: UnsafeCell<MaybeUninit<T>>,
state: Cell<State<D>>,
}
impl<T, D> Storage<T, D>

View file

@ -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"]

View file

@ -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<T> {
pub value: T,
}
@ -53,14 +50,27 @@ pub struct EagerStorage<T> {
// SAFETY: the target doesn't have threads.
unsafe impl<T> Sync for EagerStorage<T> {}
#[derive(Clone, Copy, PartialEq, Eq)]
enum State {
Initial,
Alive,
Destroying,
}
#[allow(missing_debug_implementations)]
#[repr(C)]
pub struct LazyStorage<T> {
value: UnsafeCell<Option<T>>,
// This field must be first, for correctness of `#[rustc_align_static]`
value: UnsafeCell<MaybeUninit<T>>,
state: Cell<State>,
}
impl<T> LazyStorage<T> {
pub const fn new() -> LazyStorage<T> {
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<T> LazyStorage<T> {
/// has occurred.
#[inline]
pub fn get(&'static self, i: Option<&mut Option<T>>, 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<T>>, 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.
unsafe {
self.value.get().replace(Some(value));
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);
}
// SAFETY: we just set this to `Some`.
unsafe { (*self.value.get()).as_ref().unwrap_unchecked() }
// 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().write(MaybeUninit::new(value));
}
self.state.set(State::Alive);
self.value.get() as *const T
}
}

View file

@ -1,8 +1,12 @@
use super::key::{Key, LazyKey, get, set};
use super::{abort_on_dtor_unwind, guard};
use crate::alloc::{self, Layout};
use crate::cell::Cell;
use crate::marker::PhantomData;
use crate::ptr;
use crate::mem::ManuallyDrop;
use crate::ops::Deref;
use crate::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
use crate::ptr::{self, NonNull};
#[doc(hidden)]
#[allow_internal_unstable(thread_local_internals)]
@ -10,17 +14,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 +28,148 @@ 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::<T>`.
#[allow(missing_debug_implementations)]
pub struct Storage<T> {
pub struct Storage<T, const ALIGN: usize> {
key: LazyKey,
marker: PhantomData<Cell<T>>,
}
unsafe impl<T> Sync for Storage<T> {}
unsafe impl<T, const ALIGN: usize> Sync for Storage<T, ALIGN> {}
#[repr(C)]
struct Value<T: 'static> {
// 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<T: 'static> Storage<T> {
pub const fn new() -> Storage<T> {
Storage { key: LazyKey::new(Some(destroy_value::<T>)), marker: PhantomData }
pub const fn value_align<T: 'static>() -> usize {
crate::mem::align_of::<Value<T>>()
}
/// Equivalent to `Box<Value<T>>`, but potentially over-aligned.
struct AlignedBox<T: 'static, const ALIGN: usize> {
ptr: NonNull<Value<T>>,
}
impl<T: 'static, const ALIGN: usize> AlignedBox<T, ALIGN> {
#[inline]
fn new(v: Value<T>) -> Self {
let layout = Layout::new::<Value<T>>().align_to(ALIGN).unwrap();
let ptr: *mut Value<T> = (unsafe { alloc::alloc(layout) }).cast();
let Some(ptr) = NonNull::new(ptr) else {
alloc::handle_alloc_error(layout);
};
unsafe { ptr.write(v) };
Self { ptr }
}
#[inline]
fn into_raw(b: Self) -> *mut Value<T> {
let md = ManuallyDrop::new(b);
md.ptr.as_ptr()
}
#[inline]
unsafe fn from_raw(ptr: *mut Value<T>) -> Self {
Self { ptr: unsafe { NonNull::new_unchecked(ptr) } }
}
}
impl<T: 'static, const ALIGN: usize> Deref for AlignedBox<T, ALIGN> {
type Target = Value<T>;
#[inline]
fn deref(&self) -> &Self::Target {
unsafe { &*(self.ptr.as_ptr()) }
}
}
impl<T: 'static, const ALIGN: usize> Drop for AlignedBox<T, ALIGN> {
#[inline]
fn drop(&mut self) {
let layout = Layout::new::<Value<T>>().align_to(ALIGN).unwrap();
unsafe {
let unwind_result = catch_unwind(AssertUnwindSafe(|| self.ptr.drop_in_place()));
alloc::dealloc(self.ptr.as_ptr().cast(), layout);
if let Err(payload) = unwind_result {
resume_unwind(payload);
}
}
}
}
impl<T: 'static, const ALIGN: usize> Storage<T, ALIGN> {
pub const fn new() -> Storage<T, ALIGN> {
Storage { key: LazyKey::new(Some(destroy_value::<T, ALIGN>)), marker: PhantomData }
}
/// Gets a pointer to the TLS value, potentially initializing it with the
@ -95,8 +205,11 @@ impl<T: 'static> Storage<T> {
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);
let value = AlignedBox::<T, ALIGN>::new(Value {
value: i.and_then(Option::take).unwrap_or_else(f),
key,
});
let ptr = AlignedBox::into_raw(value);
// SAFETY:
// * key came from a `LazyKey` and is thus correct.
@ -114,7 +227,7 @@ impl<T: 'static> Storage<T> {
// 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) });
drop(unsafe { AlignedBox::<T, ALIGN>::from_raw(old) });
}
// SAFETY: We just created this value above.
@ -122,7 +235,7 @@ impl<T: 'static> Storage<T> {
}
}
unsafe extern "C" fn destroy_value<T: 'static>(ptr: *mut u8) {
unsafe extern "C" fn destroy_value<T: 'static, const ALIGN: usize>(ptr: *mut u8) {
// SAFETY:
//
// The OS TLS ensures that this key contains a null value when this
@ -133,7 +246,7 @@ unsafe extern "C" fn destroy_value<T: 'static>(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<T>) };
let ptr = unsafe { AlignedBox::<T, ALIGN>::from_raw(ptr as *mut Value<T>) };
let key = ptr.key;
// SAFETY: `key` is the TLS key `ptr` was stored under.
unsafe { set(key, ptr::without_provenance_mut(1)) };

View file

@ -132,6 +132,216 @@ impl<T: 'static> fmt::Debug for LocalKey<T> {
}
}
#[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<T: 'static> fmt::Debug for LocalKey<T> {
#[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).

View file

@ -207,6 +207,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::*;
}

View file

@ -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 { () };

View file

@ -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<HasDrop> = 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<HasDrop> = 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<T>(key: &'static std::thread::LocalKey<T>) -> *const T {
key.with(|local| core::ptr::from_ref::<T>(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())));
}

View file

@ -0,0 +1,8 @@
thread_local! {
static LOCAL: u64 = panic!();
}
fn main() {
let _ = std::panic::catch_unwind(|| LOCAL.with(|_| {}));
}

View file

@ -0,0 +1,5 @@
thread 'main' ($TID) panicked at tests/pass/thread_local-panic.rs:LL:CC:
explicit panic
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
note: in Miri, you may have to set `MIRIFLAGS=-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect

View file

@ -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;
}

View file

@ -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

View file

@ -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<HasDrop> = 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<HasDrop> = 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<T>(key: &'static std::thread::LocalKey<T>) -> *const T {
key.with(|local| core::ptr::from_ref::<T>(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())));
}

View file

@ -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() {}

View file

@ -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() {}

View file

@ -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 <https://github.com/rust-lang/rust/issues/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`.