Support #[rustc_align_static] inside thread_local!
This commit is contained in:
parent
ae12bc21d8
commit
a4e87e9406
15 changed files with 873 additions and 75 deletions
|
|
@ -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};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)*);
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 { () };
|
||||
|
|
|
|||
|
|
@ -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())));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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())));
|
||||
}
|
||||
|
|
|
|||
266
tests/ui/thread-local/long-docs.rs
Normal file
266
tests/ui/thread-local/long-docs.rs
Normal 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() {}
|
||||
17
tests/ui/thread-local/no-unstable.rs
Normal file
17
tests/ui/thread-local/no-unstable.rs
Normal 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() {}
|
||||
57
tests/ui/thread-local/no-unstable.stderr
Normal file
57
tests/ui/thread-local/no-unstable.stderr
Normal 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`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue