#![feature(allow_internal_unsafe)]
#![feature(const_fn)]
#![feature(core_intrinsics)]
#![feature(test)]
#![feature(thread_local)]
#[macro_use]
extern crate alloc_fmt;
use std::cell::UnsafeCell;
use std::mem;
use std::ptr;
#[macro_export]
#[allow_internal_unsafe]
macro_rules! alloc_thread_local {
(static $name:ident: $t: ty = $init:expr;) => (
#[thread_local]
static $name: $crate::TLSSlot<$t> = {
fn __init() -> $t { $init }
unsafe fn __drop() { $name.drop(); }
thread_local!{ static DROPPER: $crate::CallOnDrop = unsafe { $crate::CallOnDrop::new(__drop) }; }
fn __register_dtor() { DROPPER.with(|_| {}); }
$crate::TLSSlot::new(__init, __register_dtor)
};
)
}
#[derive(Eq, PartialEq)]
enum TLSValue<T> {
Uninitialized,
Initializing,
Initialized(T),
Dropped,
}
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
enum TLSState {
Uninitialized,
Initializing,
Initialized,
Dropped,
}
impl<T> TLSValue<T> {
fn state(&self) -> TLSState {
match self {
&TLSValue::Uninitialized => TLSState::Uninitialized,
&TLSValue::Initializing => TLSState::Initializing,
&TLSValue::Initialized(_) => TLSState::Initialized,
&TLSValue::Dropped => TLSState::Dropped,
}
}
}
#[doc(hidden)]
pub use std::intrinsics::{likely, unlikely};
#[macro_export]
macro_rules! alloc_tls_fast_with {
($slot:expr, $name:ident, $blk:block) => {
{
#[cfg(all(feature = "dylib", target_os = "macos"))]
{
if $crate::unlikely(!$crate::dyld_loaded()) {
None
} else if $crate::likely(!(*$slot.ptr.get()).is_null()) {
let $name = &**$slot.ptr.get();
Some($blk)
} else {
$slot.with_slow(|$name| {
if $name as *const _ == &**$slot.ptr.get() as *const _ {}
$blk
})
}
}
#[cfg(not(all(feature = "dylib", target_os = "macos")))]
{
if $crate::likely(!(*$slot.ptr.get()).is_null()) {
let $name = &**$slot.ptr.get();
Some($blk)
} else {
$slot.with_slow(|$name| {
if $name as *const _ == &**$slot.ptr.get() as *const _ {}
$blk
})
}
}
}
};
}
pub struct TLSSlot<T> {
#[doc(hidden)]
pub ptr: UnsafeCell<*const T>,
slot: UnsafeCell<TLSValue<T>>,
init: fn() -> T,
register_dtor: fn(),
}
impl<T> TLSSlot<T> {
#[doc(hidden)]
pub const fn new(init: fn() -> T, register_dtor: fn()) -> TLSSlot<T> {
TLSSlot {
slot: UnsafeCell::new(TLSValue::Uninitialized),
ptr: UnsafeCell::new(ptr::null_mut()),
init,
register_dtor,
}
}
#[inline]
pub unsafe fn with<R, F: FnOnce(&T) -> R>(&self, f: F) -> Option<R> {
#[cfg(all(feature = "dylib", target_os = "macos"))]
{
use std::intrinsics::unlikely;
if unlikely(!dyld_loaded()) {
return None;
}
}
if likely(!(*self.ptr.get()).is_null()) {
let ptr = *self.ptr.get();
Some(f(&*ptr))
} else {
self.with_slow(f)
}
}
#[doc(hidden)]
#[cold]
pub unsafe fn with_slow<R, F: FnOnce(&T) -> R>(&self, f: F) -> Option<R> {
let ptr = self.slot.get();
match &*ptr {
&TLSValue::Initialized(_) => unreachable!(),
&TLSValue::Uninitialized => {
*ptr = TLSValue::Initializing;
*ptr = TLSValue::Initialized((self.init)());
if let &TLSValue::Initialized(ref t) = &*ptr {
*self.ptr.get() = t as *const _;
}
(self.register_dtor)();
self.with(f)
}
&TLSValue::Initializing | &TLSValue::Dropped => return None,
}
}
#[doc(hidden)]
pub unsafe fn drop(&self) {
let state = (&*self.slot.get()).state();
alloc_assert!(
state == TLSState::Uninitialized || state == TLSState::Initialized,
"TLSValue dropped while in state {:?}",
state
);
if state == TLSState::Uninitialized {
return;
}
alloc_assert!(
!(*self.ptr.get()).is_null(),
"null ptr in state: {:?}",
state
);
let tmp = mem::replace(&mut *self.slot.get(), TLSValue::Dropped);
*self.ptr.get() = ptr::null_mut();
mem::drop(tmp);
}
}
unsafe impl<T> Sync for TLSSlot<T> {}
#[doc(hidden)]
pub struct CallOnDrop(unsafe fn());
impl CallOnDrop {
pub unsafe fn new(f: unsafe fn()) -> CallOnDrop {
CallOnDrop(f)
}
}
impl Drop for CallOnDrop {
fn drop(&mut self) {
unsafe {
(self.0)();
}
}
}
#[cfg(all(feature = "dylib", target_os = "macos"))]
static mut DYLD_LOADED: bool = false;
#[cfg(all(feature = "dylib", target_os = "macos"))]
#[doc(hidden)]
pub fn dyld_loaded() -> bool {
unsafe { DYLD_LOADED }
}
#[cfg(all(feature = "dylib", target_os = "macos"))]
#[must_use]
#[no_mangle]
pub extern "C" fn dyld_init() {
alloc_eprintln!("alloc-tls: dyld loaded");
unsafe {
DYLD_LOADED = true;
}
}
#[cfg(test)]
mod tests {
extern crate test;
use std::sync::mpsc::{channel, Sender};
use std::cell::UnsafeCell;
use std::thread;
use super::*;
use self::test::{black_box, Bencher};
struct Foo(Sender<()>);
impl Drop for Foo {
fn drop(&mut self) {
let Foo(ref s) = *self;
s.send(()).unwrap();
}
}
#[test]
fn smoke_dtor() {
alloc_thread_local!{ static FOO: UnsafeCell<Option<Foo>> = UnsafeCell::new(None); }
let (tx, rx) = channel();
let _t = thread::spawn(move || unsafe {
let mut tx = Some(tx);
FOO.with(|f| {
*f.get() = Some(Foo(tx.take().unwrap()));
});
});
rx.recv().unwrap();
}
#[test]
fn lifecycle() {
static mut DROPPED: bool = false;
fn drop() {
unsafe { DROPPED = true }
}
alloc_thread_local!{ static FOO: CallOnDrop = CallOnDrop(drop); }
thread::spawn(|| unsafe {
assert_eq!((&*FOO.slot.get()).state(), TLSState::Uninitialized);
FOO.with(|_| {}).unwrap();
assert_eq!((&*FOO.slot.get()).state(), TLSState::Initialized);
}).join()
.unwrap();
assert_eq!(unsafe { DROPPED }, true);
}
#[bench]
fn bench_tls(b: &mut Bencher) {
alloc_thread_local!{ static FOO: UnsafeCell<usize> = UnsafeCell::new(0); }
b.iter(|| unsafe {
FOO.with(|foo| {
let inner = foo.get();
(*inner) += 1;
black_box(*inner);
});
})
}
#[bench]
fn bench_tls_fast_with(b: &mut Bencher) {
alloc_thread_local!{ static FOO: UnsafeCell<usize> = UnsafeCell::new(0); }
b.iter(|| unsafe {
alloc_tls_fast_with!(FOO, foo, {
let inner = foo.get();
(*inner) += 1;
black_box(*inner);
});
})
}
}