Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lazy_static macro #125

Merged
merged 2 commits into from
Apr 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions src/lazy_static.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//! Mock implementation of `std::thread`.

use crate::rt;
pub use crate::rt::thread::AccessError;
pub use crate::rt::yield_now;
use crate::sync::atomic::Ordering;

pub use std::thread::panicking;

use std::fmt;
use std::marker::PhantomData;

/// Mock implementation of `lazy_static::Lazy`.
pub struct Lazy<T> {
// Sadly, these fields have to be public, since function pointers in const
// fns are unstable. When fn pointer arguments to const fns stabilize, these
// should be made private and replaced with a `const fn new`.
//
// User code should not rely on the existence of these fields.
#[doc(hidden)]
pub init: fn() -> T,
#[doc(hidden)]
pub _p: PhantomData<fn(T)>,
}

impl<T: 'static> Lazy<T> {
/// Mock implementation of `lazy_static::Lazy::get`.
pub fn get(&'static self) -> &'static T {
// This is not great. Specifically, we're returning a 'static reference to a value that
// only lives for the duration of the execution. Unfortunately, the semantics of lazy
// static is that, well, you get a value that is in fact 'static. If we did not provide
// that, then this replacement wouldn't actually work.
//
// The "upside" here is that _if_ the code compiled with `lazy_static::lazy_static!`,
// _then_ this is safe. That's not super satisfying, but I'm not sure how we do better
// without changing the API pretty drastically. We could perhaps here provide a
// `with(closure)` like we do for `UnsafeCell`, and require people to wrap the "real"
// `lazy_static` the same way, but that seems like its own kind of unfortunate as I'm sure
// users sometimes _rely_ on the returned reference being 'static. If we provided something
// that used a closure to give the user a non-`'static` reference, we wouldn't be all that
// much further along.
match unsafe { self.try_get() } {
Some(v) => v,
None => {
// Init the value out of the `rt::execution`
let mut sv = crate::rt::lazy_static::StaticValue::new((self.init)());

rt::execution(|execution| {
// lazy_static uses std::sync::Once, which does a swap(AcqRel) to set
sv.sync.sync_store(&mut execution.threads, Ordering::AcqRel);

execution.lazy_statics.init_static(self, sv);
});

unsafe { self.try_get() }.expect("bug")
}
}
}

unsafe fn try_get(&'static self) -> Option<&'static T> {
unsafe fn transmute_lt<'a, 'b, T>(t: &'a T) -> &'b T {
std::mem::transmute::<&'a T, &'b T>(t)
}

let sv = rt::execution(|execution| {
let sv = execution.lazy_statics.get_static(self)?;

// lazy_static uses std::sync::Once, which does a load(Acquire) to get
sv.sync.sync_load(&mut execution.threads, Ordering::Acquire);

Some(transmute_lt(sv))
})?;

Some(sv.get::<T>())
}
}

impl<T: 'static> fmt::Debug for Lazy<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("Lazy { .. }")
}
}
54 changes: 54 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ mod rt;

pub mod alloc;
pub mod cell;
pub mod lazy_static;
pub mod model;
pub mod sync;
pub mod thread;
Expand Down Expand Up @@ -189,6 +190,22 @@ macro_rules! thread_local {
);
}

/// Mock version of `lazy_static::lazy_static!`.
#[macro_export]
macro_rules! lazy_static {
($(#[$attr:meta])* static ref $N:ident : $T:ty = $e:expr; $($t:tt)*) => {
// use `()` to explicitly forward the information about private items
$crate::__lazy_static_internal!($(#[$attr])* () static ref $N : $T = $e; $($t)*);
};
($(#[$attr:meta])* pub static ref $N:ident : $T:ty = $e:expr; $($t:tt)*) => {
$crate::__lazy_static_internal!($(#[$attr])* (pub) static ref $N : $T = $e; $($t)*);
};
($(#[$attr:meta])* pub ($($vis:tt)+) static ref $N:ident : $T:ty = $e:expr; $($t:tt)*) => {
$crate::__lazy_static_internal!($(#[$attr])* (pub ($($vis)+)) static ref $N : $T = $e; $($t)*);
};
() => ()
}

#[macro_export]
#[doc(hidden)]
macro_rules! __thread_local_inner {
Expand All @@ -200,3 +217,40 @@ macro_rules! __thread_local_inner {
};
};
}

#[macro_export]
#[doc(hidden)]
macro_rules! __lazy_static_internal {
// optional visibility restrictions are wrapped in `()` to allow for
// explicitly passing otherwise implicit information about private items
($(#[$attr:meta])* ($($vis:tt)*) static ref $N:ident : $T:ty = $init:expr; $($t:tt)*) => {
#[allow(missing_copy_implementations)]
#[allow(non_camel_case_types)]
#[allow(dead_code)]
$(#[$attr])*
$($vis)* struct $N {__private_field: ()}
#[doc(hidden)]
$($vis)* static $N: $N = $N {__private_field: ()};
impl ::core::ops::Deref for $N {
type Target = $T;
// this and the two __ functions below should really also be #[track_caller]
fn deref(&self) -> &$T {
#[inline(always)]
fn __static_ref_initialize() -> $T { $init }

#[inline(always)]
fn __stability() -> &'static $T {
static LAZY: $crate::lazy_static::Lazy<$T> =
$crate::lazy_static::Lazy {
init: __static_ref_initialize,
_p: std::marker::PhantomData,
};
LAZY.get()
}
__stability()
}
}
$crate::lazy_static!($($t)*);
};
() => ()
}
6 changes: 6 additions & 0 deletions src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@ impl Builder {

scheduler.run(&mut execution, move || {
f();

let lazy_statics = rt::execution(|execution| execution.lazy_statics.drop());

// drop outside of execution
drop(lazy_statics);

rt::thread_done();
});

Expand Down
8 changes: 7 additions & 1 deletion src/rt/execution.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::rt::alloc::Allocation;
use crate::rt::{object, thread, Path};
use crate::rt::{lazy_static, object, thread, Path};

use std::collections::HashMap;
use std::convert::TryInto;
Expand All @@ -14,6 +14,8 @@ pub(crate) struct Execution {

pub(crate) threads: thread::Set,

pub(crate) lazy_statics: lazy_static::Set,

/// All loom aware objects part of this execution run.
pub(super) objects: object::Store,

Expand Down Expand Up @@ -55,6 +57,7 @@ impl Execution {
id,
path: Path::new(max_branches, preemption_bound),
threads,
lazy_statics: lazy_static::Set::new(),
objects: object::Store::with_capacity(max_branches),
raw_allocations: HashMap::new(),
max_threads,
Expand Down Expand Up @@ -91,6 +94,7 @@ impl Execution {
let log = self.log;
let mut path = self.path;
let mut objects = self.objects;
let mut lazy_statics = self.lazy_statics;
let mut raw_allocations = self.raw_allocations;

let mut threads = self.threads;
Expand All @@ -100,6 +104,7 @@ impl Execution {
}

objects.clear();
lazy_statics.reset();
raw_allocations.clear();

threads.clear(id);
Expand All @@ -109,6 +114,7 @@ impl Execution {
path,
threads,
objects,
lazy_statics,
raw_allocations,
max_threads,
max_history,
Expand Down
82 changes: 82 additions & 0 deletions src/rt/lazy_static.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use crate::rt::synchronize::Synchronize;
use std::{any::Any, collections::HashMap};

pub(crate) struct Set {
/// Registered statics.
statics: Option<HashMap<StaticKeyId, StaticValue>>,
}

#[derive(Eq, PartialEq, Hash, Copy, Clone)]
pub(crate) struct StaticKeyId(usize);

pub(crate) struct StaticValue {
pub(crate) sync: Synchronize,
v: Box<dyn Any>,
}

impl Set {
/// Create an empty statics set.
pub(crate) fn new() -> Set {
Set {
statics: Some(HashMap::new()),
}
}

pub(crate) fn reset(&mut self) {
assert!(
self.statics.is_none(),
"lazy_static was not dropped during execution"
);
self.statics = Some(HashMap::new());
}

pub(crate) fn drop(&mut self) -> HashMap<StaticKeyId, StaticValue> {
self.statics
.take()
.expect("lazy_statics were dropped twice in one execution")
}

pub(crate) fn get_static<T: 'static>(
&mut self,
key: &'static crate::lazy_static::Lazy<T>,
) -> Option<&mut StaticValue> {
self.statics
.as_mut()
.expect("attempted to access lazy_static during shutdown")
.get_mut(&StaticKeyId::new(key))
}

pub(crate) fn init_static<T: 'static>(
&mut self,
key: &'static crate::lazy_static::Lazy<T>,
value: StaticValue,
) {
assert!(self
.statics
.as_mut()
.expect("attempted to access lazy_static during shutdown")
.insert(StaticKeyId::new(key), value)
.is_none())
}
}

impl StaticKeyId {
fn new<T>(key: &'static crate::lazy_static::Lazy<T>) -> Self {
Self(key as *const _ as usize)
}
}

impl StaticValue {
pub(crate) fn new<T: 'static>(value: T) -> Self {
Self {
sync: Synchronize::new(),
v: Box::new(value),
}
}

pub(crate) fn get<T: 'static>(&self) -> &T {
self.v
.downcast_ref::<T>()
.expect("lazy value must downcast to expected type")
}
}
1 change: 1 addition & 0 deletions src/rt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pub(crate) use self::scheduler::Scheduler;
mod synchronize;
pub(crate) use self::synchronize::Synchronize;

pub(crate) mod lazy_static;
pub(crate) mod thread;

mod vv;
Expand Down
30 changes: 30 additions & 0 deletions tests/atomic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,36 @@ use loom::thread;
use std::sync::atomic::Ordering::{AcqRel, Acquire, Relaxed, Release};
use std::sync::Arc;

loom::lazy_static! {
static ref A: AtomicUsize = AtomicUsize::new(0);
static ref NO_LEAK: loom::sync::Arc<usize> = Default::default();
}

loom::thread_local! {
static B: usize = A.load(Relaxed);
}

#[test]
fn lazy_static_arc_doesnt_leak() {
loom::model(|| {
assert_eq!(**NO_LEAK, 0);
});
}

#[test]
fn legal_load_after_lazy_static() {
loom::model(|| {
let t1 = thread::spawn(|| {
B.try_with(|h| *h).unwrap_or_else(|_| A.load(Relaxed));
});
let t2 = thread::spawn(|| {
B.try_with(|h| *h).unwrap_or_else(|_| A.load(Relaxed));
});
t1.join().unwrap();
t2.join().unwrap();
});
}

#[test]
#[should_panic]
fn invalid_unsync_load_relaxed() {
Expand Down