Support #[rustc_align_static] inside thread_local!

This commit is contained in:
Jules Bertholet 2025-09-10 17:11:47 -04:00
parent ae12bc21d8
commit a4e87e9406
No known key found for this signature in database
GPG key ID: 32034DAFC38C1BFC
15 changed files with 873 additions and 75 deletions

View file

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

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,9 +108,9 @@ pub macro thread_local_inner {
})
}
}},
($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => {
($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $(#[$align_attr:meta])*, $($init:tt)*) => {
$(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> =
$crate::thread::local_impl::thread_local_inner!(@key $t, $($init)*);
$crate::thread::local_impl::thread_local_inner!(@key $t, $(#[$align_attr])*, $($init)*);
},
}

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,27 @@ 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:meta])* $vis:vis $name:ident, $t:ty, $(#[$align_attr:meta])*, $($init:tt)*) => {
$(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> =
$crate::thread::local_impl::thread_local_inner!(@key $t, $($init)*);
$crate::thread::local_impl::thread_local_inner!(@key $t, $(#[$align_attr])*, $($init)*);
},
}
#[allow(missing_debug_implementations)]
#[repr(transparent)] // Required for correctness of `#[rustc_align_static]`
pub struct EagerStorage<T> {
pub value: T,
}
@ -53,14 +55,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 +85,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,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,99 @@ 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)*) => {
// 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)+);)?
},
($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $(#[$($align_attr:tt)*])*, $($init:tt)*) => {
$(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> =
$crate::thread::local_impl::thread_local_inner!(@key $t, $($init)*);
$crate::thread::local_impl::thread_local_inner!(@key $t, $(#[$($align_attr)*])*, $($init)*);
},
}
/// 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>>()
}
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 +153,15 @@ 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);
// Manually allocate with the requested alignment
let layout = Layout::new::<Value<T>>().align_to(ALIGN).unwrap();
let ptr: *mut Value<T> = (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 +179,10 @@ 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) });
unsafe {
old.drop_in_place();
crate::alloc::dealloc(old.cast(), layout);
}
}
// SAFETY: We just created this value above.
@ -122,7 +190,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,13 +201,22 @@ 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 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<T> = 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::<Value<T>>(), 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();

View file

@ -132,6 +132,212 @@ 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)+)?)?) => (
$crate::thread::local_impl::thread_local_inner!($($other_attrs)* $vis $name, $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)+)?)?) => (
$crate::thread::local_impl::thread_local_inner!($($other_attrs)* $vis $name, $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 +388,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

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

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

@ -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_inner` 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_inner` 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_inner` 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`.