auto merge of #7677 : alexcrichton/rust/tls-gc, r=pcwalton

cc #6004 and #3273

This is a rewrite of TLS to get towards not requiring `@` when using task local storage. Most of the rewrite is straightforward, although there are two caveats:

1. Changing `local_set` to not require `@` is blocked on #7673
2. The code in `local_pop` is some of the most unsafe code I've written. A second set of eyes should definitely scrutinize it...

The public-facing interface currently hasn't changed, although it will have to change because `local_data::get` cannot return `Option<T>`, nor can it return `Option<&T>` (the lifetime isn't known). This will have to be changed to be given a closure which yield `&T` (or as an Option). I didn't do this part of the api rewrite in this pull request as I figured that it could wait until when `@` is fully removed.

This also doesn't deal with the issue of using something other than functions as keys, but I'm looking into using static slices (as mentioned in the issues).
This commit is contained in:
bors 2013-07-11 19:52:37 -07:00
commit 07183ea6e7
20 changed files with 636 additions and 293 deletions

View file

@ -72,11 +72,11 @@ fn complete_key(_v: @CompletionCb) {}
/// Bind to the main completion callback
pub unsafe fn complete(cb: CompletionCb) {
local_data::local_data_set(complete_key, @(cb));
local_data::set(complete_key, @(cb));
extern fn callback(line: *c_char, completions: *()) {
unsafe {
let cb = *local_data::local_data_get(complete_key)
let cb = *local_data::get(complete_key, |k| k.map(|&k| *k))
.get();
do cb(str::raw::from_c_str(line)) |suggestion| {

View file

@ -1204,11 +1204,11 @@ mod big_tests {
#[unsafe_destructor]
impl<'self> Drop for LVal<'self> {
fn drop(&self) {
let x = unsafe { local_data::local_data_get(self.key) };
let x = unsafe { local_data::get(self.key, |k| k.map(|&k| *k)) };
match x {
Some(@y) => {
unsafe {
local_data::local_data_set(self.key, @(y+1));
local_data::set(self.key, @(y+1));
}
}
_ => fail!("Expected key to work"),

View file

@ -91,7 +91,7 @@ fn task_local_insn_key(_v: @~[&'static str]) {}
pub fn with_insn_ctxt(blk: &fn(&[&'static str])) {
unsafe {
let opt = local_data::local_data_get(task_local_insn_key);
let opt = local_data::get(task_local_insn_key, |k| k.map(|&k| *k));
if opt.is_some() {
blk(*opt.unwrap());
}
@ -100,7 +100,7 @@ pub fn with_insn_ctxt(blk: &fn(&[&'static str])) {
pub fn init_insn_ctxt() {
unsafe {
local_data::local_data_set(task_local_insn_key, @~[]);
local_data::set(task_local_insn_key, @~[]);
}
}
@ -110,7 +110,7 @@ pub struct _InsnCtxt { _x: () }
impl Drop for _InsnCtxt {
fn drop(&self) {
unsafe {
do local_data::local_data_modify(task_local_insn_key) |c| {
do local_data::modify(task_local_insn_key) |c| {
do c.map_consume |ctx| {
let mut ctx = copy *ctx;
ctx.pop();
@ -124,7 +124,7 @@ impl Drop for _InsnCtxt {
pub fn push_ctxt(s: &'static str) -> _InsnCtxt {
debug!("new InsnCtxt: %s", s);
unsafe {
do local_data::local_data_modify(task_local_insn_key) |c| {
do local_data::modify(task_local_insn_key) |c| {
do c.map_consume |ctx| {
let mut ctx = copy *ctx;
ctx.push(s);

View file

@ -240,14 +240,14 @@ impl Drop for CrateContext {
fn task_local_llcx_key(_v: @ContextRef) {}
pub fn task_llcx() -> ContextRef {
let opt = unsafe { local_data::local_data_get(task_local_llcx_key) };
let opt = unsafe { local_data::get(task_local_llcx_key, |k| k.map(|&k| *k)) };
*opt.expect("task-local LLVMContextRef wasn't ever set!")
}
unsafe fn set_task_llcx(c: ContextRef) {
local_data::local_data_set(task_local_llcx_key, @c);
local_data::set(task_local_llcx_key, @c);
}
unsafe fn unset_task_llcx() {
local_data::local_data_pop(task_local_llcx_key);
local_data::pop(task_local_llcx_key);
}

View file

@ -58,7 +58,7 @@ struct LocalVariable {
}
type LocalCache = @mut HashMap<~str, @~[u8]>;
fn tls_key(_k: @LocalCache) {}
fn tls_key(_k: LocalCache) {}
impl Program {
pub fn new() -> Program {
@ -132,7 +132,7 @@ impl Program {
");
let key: sys::Closure = unsafe {
let tls_key: &'static fn(@LocalCache) = tls_key;
let tls_key: &'static fn(LocalCache) = tls_key;
cast::transmute(tls_key)
};
// First, get a handle to the tls map which stores all the local
@ -144,7 +144,7 @@ impl Program {
let key = ::std::sys::Closure{ code: %? as *(),
env: ::std::ptr::null() };
let key = ::std::cast::transmute(key);
*::std::local_data::local_data_get(key).unwrap()
::std::local_data::get(key, |k| k.map(|&x| *x)).unwrap()
};\n", key.code as uint));
// Using this __tls_map handle, deserialize each variable binding that
@ -227,7 +227,7 @@ impl Program {
map.insert(copy *name, @copy value.data);
}
unsafe {
local_data::local_data_set(tls_key, @map);
local_data::set(tls_key, map);
}
}
@ -236,7 +236,7 @@ impl Program {
/// it updates this cache with the new values of each local variable.
pub fn consume_cache(&mut self) {
let map = unsafe {
local_data::local_data_pop(tls_key).expect("tls is empty")
local_data::pop(tls_key).expect("tls is empty")
};
do map.consume |name, value| {
match self.local_vars.find_mut(&name) {

View file

@ -12,7 +12,6 @@
#[allow(missing_doc)];
use local_data::{local_data_pop, local_data_set};
use local_data;
use prelude::*;
@ -26,14 +25,14 @@ pub struct Handler<T, U> {
pub struct Condition<'self, T, U> {
name: &'static str,
key: local_data::LocalDataKey<'self, Handler<T, U>>
key: local_data::Key<'self, @Handler<T, U>>
}
impl<'self, T, U> Condition<'self, T, U> {
pub fn trap(&'self self, h: &'self fn(T) -> U) -> Trap<'self, T, U> {
unsafe {
let p : *RustClosure = ::cast::transmute(&h);
let prev = local_data::local_data_get(self.key);
let prev = local_data::get(self.key, |k| k.map(|&x| *x));
let h = @Handler { handle: *p, prev: prev };
Trap { cond: self, handler: h }
}
@ -46,7 +45,7 @@ impl<'self, T, U> Condition<'self, T, U> {
pub fn raise_default(&self, t: T, default: &fn() -> U) -> U {
unsafe {
match local_data_pop(self.key) {
match local_data::pop(self.key) {
None => {
debug!("Condition.raise: found no handler");
default()
@ -55,12 +54,12 @@ impl<'self, T, U> Condition<'self, T, U> {
debug!("Condition.raise: found handler");
match handler.prev {
None => {}
Some(hp) => local_data_set(self.key, hp)
Some(hp) => local_data::set(self.key, hp)
}
let handle : &fn(T) -> U =
::cast::transmute(handler.handle);
let u = handle(t);
local_data_set(self.key, handler);
local_data::set(self.key, handler);
u
}
}
@ -78,7 +77,7 @@ impl<'self, T, U> Trap<'self, T, U> {
unsafe {
let _g = Guard { cond: self.cond };
debug!("Trap: pushing handler to TLS");
local_data_set(self.cond.key, self.handler);
local_data::set(self.cond.key, self.handler);
inner()
}
}
@ -93,12 +92,12 @@ impl<'self, T, U> Drop for Guard<'self, T, U> {
fn drop(&self) {
unsafe {
debug!("Guard: popping handler from TLS");
let curr = local_data_pop(self.cond.key);
let curr = local_data::pop(self.cond.key);
match curr {
None => {}
Some(h) => match h.prev {
None => {}
Some(hp) => local_data_set(self.cond.key, hp)
Some(hp) => local_data::set(self.cond.key, hp)
}
}
}

View file

@ -12,14 +12,28 @@
Task local data management
Allows storing boxes with arbitrary types inside, to be accessed
anywhere within a task, keyed by a pointer to a global finaliser
function. Useful for dynamic variables, singletons, and interfacing
with foreign code with bad callback interfaces.
Allows storing boxes with arbitrary types inside, to be accessed anywhere within
a task, keyed by a pointer to a global finaliser function. Useful for dynamic
variables, singletons, and interfacing with foreign code with bad callback
interfaces.
To use, declare a monomorphic global function at the type to store,
and use it as the 'key' when accessing. See the 'tls' tests below for
examples.
To use, declare a monomorphic (no type parameters) global function at the type
to store, and use it as the 'key' when accessing.
~~~{.rust}
use std::local_data;
fn key_int(_: @int) {}
fn key_vector(_: @~[int]) {}
unsafe {
local_data::set(key_int, @3);
assert!(local_data::get(key_int) == Some(@3));
local_data::set(key_vector, @~[3]);
assert!(local_data::get(key_vector).unwrap()[0] == 3);
}
~~~
Casting 'Arcane Sight' reveals an overwhelming aura of Transmutation
magic.
@ -28,7 +42,7 @@ magic.
use prelude::*;
use task::local_data_priv::{local_get, local_pop, local_modify, local_set, Handle};
use task::local_data_priv::{local_get, local_pop, local_set, Handle};
#[cfg(test)] use task;
@ -46,63 +60,98 @@ use task::local_data_priv::{local_get, local_pop, local_modify, local_set, Handl
*
* These two cases aside, the interface is safe.
*/
pub type LocalDataKey<'self,T> = &'self fn:Copy(v: @T);
pub type Key<'self,T> = &'self fn:Copy(v: T);
/**
* Remove a task-local data value from the table, returning the
* reference that was originally created to insert it.
*/
pub unsafe fn local_data_pop<T: 'static>(
key: LocalDataKey<T>) -> Option<@T> {
#[cfg(stage0)]
pub unsafe fn pop<T: 'static>(key: Key<@T>) -> Option<@T> {
local_pop(Handle::new(), key)
}
/**
* Remove a task-local data value from the table, returning the
* reference that was originally created to insert it.
*/
#[cfg(not(stage0))]
pub unsafe fn pop<T: 'static>(key: Key<T>) -> Option<T> {
local_pop(Handle::new(), key)
}
/**
* Retrieve a task-local data value. It will also be kept alive in the
* table until explicitly removed.
*/
pub unsafe fn local_data_get<T: 'static>(
key: LocalDataKey<T>) -> Option<@T> {
local_get(Handle::new(), key)
#[cfg(stage0)]
pub unsafe fn get<T: 'static, U>(key: Key<@T>, f: &fn(Option<&@T>) -> U) -> U {
local_get(Handle::new(), key, f)
}
/**
* Retrieve a task-local data value. It will also be kept alive in the
* table until explicitly removed.
*/
#[cfg(not(stage0))]
pub unsafe fn get<T: 'static, U>(key: Key<T>, f: &fn(Option<&T>) -> U) -> U {
local_get(Handle::new(), key, f)
}
/**
* Store a value in task-local data. If this key already has a value,
* that value is overwritten (and its destructor is run).
*/
pub unsafe fn local_data_set<T: 'static>(
key: LocalDataKey<T>, data: @T) {
#[cfg(stage0)]
pub unsafe fn set<T: 'static>(key: Key<@T>, data: @T) {
local_set(Handle::new(), key, data)
}
/**
* Store a value in task-local data. If this key already has a value,
* that value is overwritten (and its destructor is run).
*/
#[cfg(not(stage0))]
pub unsafe fn set<T: 'static>(key: Key<T>, data: T) {
local_set(Handle::new(), key, data)
}
/**
* Modify a task-local data value. If the function returns 'None', the
* data is removed (and its reference dropped).
*/
pub unsafe fn local_data_modify<T: 'static>(
key: LocalDataKey<T>,
modify_fn: &fn(Option<@T>) -> Option<@T>) {
local_modify(Handle::new(), key, modify_fn)
#[cfg(stage0)]
pub unsafe fn modify<T: 'static>(key: Key<@T>,
f: &fn(Option<@T>) -> Option<@T>) {
match f(pop(key)) {
Some(next) => { set(key, next); }
None => {}
}
}
/**
* Modify a task-local data value. If the function returns 'None', the
* data is removed (and its reference dropped).
*/
#[cfg(not(stage0))]
pub unsafe fn modify<T: 'static>(key: Key<T>,
f: &fn(Option<T>) -> Option<T>) {
match f(pop(key)) {
Some(next) => { set(key, next); }
None => {}
}
}
#[test]
fn test_tls_multitask() {
unsafe {
fn my_key(_x: @~str) { }
local_data_set(my_key, @~"parent data");
set(my_key, @~"parent data");
do task::spawn {
// TLS shouldn't carry over.
assert!(local_data_get(my_key).is_none());
local_data_set(my_key, @~"child data");
assert!(*(local_data_get(my_key).get()) ==
assert!(get(my_key, |k| k.map(|&k| *k)).is_none());
set(my_key, @~"child data");
assert!(*(get(my_key, |k| k.map(|&k| *k)).get()) ==
~"child data");
// should be cleaned up for us
}
// Must work multiple times
assert!(*(local_data_get(my_key).get()) == ~"parent data");
assert!(*(local_data_get(my_key).get()) == ~"parent data");
assert!(*(local_data_get(my_key).get()) == ~"parent data");
assert!(*(get(my_key, |k| k.map(|&k| *k)).get()) == ~"parent data");
assert!(*(get(my_key, |k| k.map(|&k| *k)).get()) == ~"parent data");
assert!(*(get(my_key, |k| k.map(|&k| *k)).get()) == ~"parent data");
}
}
@ -110,9 +159,9 @@ fn test_tls_multitask() {
fn test_tls_overwrite() {
unsafe {
fn my_key(_x: @~str) { }
local_data_set(my_key, @~"first data");
local_data_set(my_key, @~"next data"); // Shouldn't leak.
assert!(*(local_data_get(my_key).get()) == ~"next data");
set(my_key, @~"first data");
set(my_key, @~"next data"); // Shouldn't leak.
assert!(*(get(my_key, |k| k.map(|&k| *k)).get()) == ~"next data");
}
}
@ -120,10 +169,10 @@ fn test_tls_overwrite() {
fn test_tls_pop() {
unsafe {
fn my_key(_x: @~str) { }
local_data_set(my_key, @~"weasel");
assert!(*(local_data_pop(my_key).get()) == ~"weasel");
set(my_key, @~"weasel");
assert!(*(pop(my_key).get()) == ~"weasel");
// Pop must remove the data from the map.
assert!(local_data_pop(my_key).is_none());
assert!(pop(my_key).is_none());
}
}
@ -131,20 +180,20 @@ fn test_tls_pop() {
fn test_tls_modify() {
unsafe {
fn my_key(_x: @~str) { }
local_data_modify(my_key, |data| {
modify(my_key, |data| {
match data {
Some(@ref val) => fail!("unwelcome value: %s", *val),
None => Some(@~"first data")
}
});
local_data_modify(my_key, |data| {
modify(my_key, |data| {
match data {
Some(@~"first data") => Some(@~"next data"),
Some(@ref val) => fail!("wrong value: %s", *val),
None => fail!("missing value")
}
});
assert!(*(local_data_pop(my_key).get()) == ~"next data");
assert!(*(pop(my_key).get()) == ~"next data");
}
}
@ -158,7 +207,7 @@ fn test_tls_crust_automorestack_memorial_bug() {
// a stack smaller than 1 MB.
fn my_key(_x: @~str) { }
do task::spawn {
unsafe { local_data_set(my_key, @~"hax"); }
unsafe { set(my_key, @~"hax"); }
}
}
@ -169,9 +218,9 @@ fn test_tls_multiple_types() {
fn int_key(_x: @int) { }
do task::spawn {
unsafe {
local_data_set(str_key, @~"string data");
local_data_set(box_key, @@());
local_data_set(int_key, @42);
set(str_key, @~"string data");
set(box_key, @@());
set(int_key, @42);
}
}
}
@ -183,12 +232,12 @@ fn test_tls_overwrite_multiple_types() {
fn int_key(_x: @int) { }
do task::spawn {
unsafe {
local_data_set(str_key, @~"string data");
local_data_set(int_key, @42);
set(str_key, @~"string data");
set(int_key, @42);
// This could cause a segfault if overwriting-destruction is done
// with the crazy polymorphic transmute rather than the provided
// finaliser.
local_data_set(int_key, @31337);
set(int_key, @31337);
}
}
}
@ -201,17 +250,17 @@ fn test_tls_cleanup_on_failure() {
fn str_key(_x: @~str) { }
fn box_key(_x: @@()) { }
fn int_key(_x: @int) { }
local_data_set(str_key, @~"parent data");
local_data_set(box_key, @@());
set(str_key, @~"parent data");
set(box_key, @@());
do task::spawn {
// spawn_linked
local_data_set(str_key, @~"string data");
local_data_set(box_key, @@());
local_data_set(int_key, @42);
set(str_key, @~"string data");
set(box_key, @@());
set(int_key, @42);
fail!();
}
// Not quite nondeterministic.
local_data_set(int_key, @31337);
set(int_key, @31337);
fail!();
}
}
@ -221,6 +270,14 @@ fn test_static_pointer() {
unsafe {
fn key(_x: @&'static int) { }
static VALUE: int = 0;
local_data_set(key, @&VALUE);
set(key, @&VALUE);
}
}
#[test]
fn test_owned() {
unsafe {
fn key(_x: ~int) { }
set(key, ~1);
}
}

View file

@ -1248,7 +1248,7 @@ fn overridden_arg_key(_v: @OverriddenArgs) {}
/// `os::set_args` function.
pub fn args() -> ~[~str] {
unsafe {
match local_data::local_data_get(overridden_arg_key) {
match local_data::get(overridden_arg_key, |k| k.map(|&k| *k)) {
None => real_args(),
Some(args) => copy args.val
}
@ -1261,7 +1261,7 @@ pub fn args() -> ~[~str] {
pub fn set_args(new_args: ~[~str]) {
unsafe {
let overridden_args = @OverriddenArgs { val: copy new_args };
local_data::local_data_set(overridden_arg_key, overridden_args);
local_data::set(overridden_arg_key, overridden_args);
}
}

View file

@ -862,13 +862,13 @@ fn tls_rng_state(_v: @@mut IsaacRng) {}
pub fn task_rng() -> @mut IsaacRng {
let r : Option<@@mut IsaacRng>;
unsafe {
r = local_data::local_data_get(tls_rng_state);
r = local_data::get(tls_rng_state, |k| k.map(|&k| *k));
}
match r {
None => {
unsafe {
let rng = @@mut IsaacRng::new_seeded(seed());
local_data::local_data_set(tls_rng_state, rng);
local_data::set(tls_rng_state, rng);
*rng
}
}

View file

@ -57,7 +57,7 @@ pub enum SchedHome {
}
pub struct GarbageCollector;
pub struct LocalStorage(*c_void, Option<~fn(*c_void)>);
pub struct LocalStorage(*c_void, Option<extern "Rust" fn(*c_void)>);
pub struct Unwinder {
unwinding: bool,
@ -346,15 +346,15 @@ mod test {
#[test]
fn tls() {
use local_data::*;
use local_data;
do run_in_newsched_task() {
unsafe {
fn key(_x: @~str) { }
local_data_set(key, @~"data");
assert!(*local_data_get(key).get() == ~"data");
local_data::set(key, @~"data");
assert!(*local_data::get(key, |k| k.map(|&k| *k)).get() == ~"data");
fn key2(_x: @~str) { }
local_data_set(key2, @~"data");
assert!(*local_data_get(key2).get() == ~"data");
local_data::set(key2, @~"data");
assert!(*local_data::get(key2, |k| k.map(|&k| *k)).get() == ~"data");
}
}
}

View file

@ -11,11 +11,13 @@
#[allow(missing_doc)];
use cast;
use cmp::Eq;
use libc;
use local_data;
use prelude::*;
use ptr;
use sys;
use task::rt;
use local_data::LocalDataKey;
use util;
use super::rt::rust_task;
use rt::task::{Task, LocalStorage};
@ -43,25 +45,41 @@ impl Handle {
}
}
pub trait LocalData { }
impl<T: 'static> LocalData for @T { }
trait LocalData {}
impl<T: 'static> LocalData for T {}
impl Eq for @LocalData {
fn eq(&self, other: &@LocalData) -> bool {
unsafe {
let ptr_a: &(uint, uint) = cast::transmute(self);
let ptr_b: &(uint, uint) = cast::transmute(other);
return ptr_a == ptr_b;
}
}
fn ne(&self, other: &@LocalData) -> bool { !(*self).eq(other) }
}
// If TLS is used heavily in future, this could be made more efficient with a
// proper map.
type TaskLocalElement = (*libc::c_void, *libc::c_void, @LocalData);
// Has to be a pointer at outermost layer; the foreign call returns void *.
type TaskLocalMap = @mut ~[Option<TaskLocalElement>];
// The task-local-map stores all TLS information for the currently running task.
// It is stored as an owned pointer into the runtime, and it's only allocated
// when TLS is used for the first time. This map must be very carefully
// constructed because it has many mutable loans unsoundly handed out on it to
// the various invocations of TLS requests.
//
// One of the most important operations is loaning a value via `get` to a
// caller. In doing so, the slot that the TLS entry is occupying cannot be
// invalidated because upon returning it's loan state must be updated. Currently
// the TLS map is a vector, but this is possibly dangerous because the vector
// can be reallocated/moved when new values are pushed onto it.
//
// This problem currently isn't solved in a very elegant way. Inside the `get`
// function, it internally "invalidates" all references after the loan is
// finished and looks up into the vector again. In theory this will prevent
// pointers from being moved under our feet so long as LLVM doesn't go too crazy
// with the optimizations.
//
// n.b. Other structures are not sufficient right now:
// * HashMap uses ~[T] internally (push reallocates/moves)
// * TreeMap is plausible, but it's in extra
// * dlist plausible, but not in std
// * a custom owned linked list was attempted, but difficult to write
// and involved a lot of extra code bloat
//
// n.b. Has to be stored with a pointer at outermost layer; the foreign call
// returns void *.
//
// n.b. If TLS is used heavily in future, this could be made more efficient with
// a proper map.
type TaskLocalMap = ~[Option<(*libc::c_void, TLSValue, uint)>];
type TLSValue = ~LocalData:;
fn cleanup_task_local_map(map_ptr: *libc::c_void) {
unsafe {
@ -74,162 +92,203 @@ fn cleanup_task_local_map(map_ptr: *libc::c_void) {
}
// Gets the map from the runtime. Lazily initialises if not done so already.
unsafe fn get_local_map(handle: Handle) -> TaskLocalMap {
match handle {
OldHandle(task) => get_task_local_map(task),
NewHandle(local_storage) => get_newsched_local_map(local_storage)
}
}
unsafe fn get_local_map(handle: Handle) -> &mut TaskLocalMap {
unsafe fn get_task_local_map(task: *rust_task) -> TaskLocalMap {
extern fn cleanup_task_local_map_extern_cb(map_ptr: *libc::c_void) {
cleanup_task_local_map(map_ptr);
}
// Relies on the runtime initialising the pointer to null.
// Note: The map's box lives in TLS invisibly referenced once. Each time
// we retrieve it for get/set, we make another reference, which get/set
// drop when they finish. No "re-storing after modifying" is needed.
let map_ptr = rt::rust_get_task_local_data(task);
if map_ptr.is_null() {
let map: TaskLocalMap = @mut ~[];
// NB: This bumps the ref count before converting to an unsafe pointer,
// keeping the map alive until TLS is destroyed
rt::rust_set_task_local_data(task, cast::transmute(map));
rt::rust_task_local_data_atexit(task, cleanup_task_local_map_extern_cb);
map
} else {
let map = cast::transmute(map_ptr);
let nonmut = cast::transmute::<TaskLocalMap,
@~[Option<TaskLocalElement>]>(map);
cast::bump_box_refcount(nonmut);
map
}
}
unsafe fn get_newsched_local_map(local: *mut LocalStorage) -> TaskLocalMap {
match &mut *local {
&LocalStorage(map_ptr, Some(_)) => {
assert!(map_ptr.is_not_null());
let map = cast::transmute(map_ptr);
let nonmut = cast::transmute::<TaskLocalMap,
@~[Option<TaskLocalElement>]>(map);
cast::bump_box_refcount(nonmut);
return map;
unsafe fn oldsched_map(task: *rust_task) -> &mut TaskLocalMap {
extern fn cleanup_extern_cb(map_ptr: *libc::c_void) {
cleanup_task_local_map(map_ptr);
}
&LocalStorage(ref mut map_ptr, ref mut at_exit) => {
assert!((*map_ptr).is_null());
let map: TaskLocalMap = @mut ~[];
// Relies on the runtime initialising the pointer to null.
// Note: the map is an owned pointer and is "owned" by TLS. It is moved
// into the tls slot for this task, and then mutable loans are taken
// from this slot to modify the map.
let map_ptr = rt::rust_get_task_local_data(task);
if (*map_ptr).is_null() {
// First time TLS is used, create a new map and set up the necessary
// TLS information for its safe destruction
let map: TaskLocalMap = ~[];
*map_ptr = cast::transmute(map);
let at_exit_fn: ~fn(*libc::c_void) = |p|cleanup_task_local_map(p);
*at_exit = Some(at_exit_fn);
return map;
rt::rust_task_local_data_atexit(task, cleanup_extern_cb);
}
return cast::transmute(map_ptr);
}
}
unsafe fn key_to_key_value<T: 'static>(key: LocalDataKey<T>) -> *libc::c_void {
// Keys are closures, which are (fnptr,envptr) pairs. Use fnptr.
// Use reinterpret_cast -- transmute would leak (forget) the closure.
let pair: (*libc::c_void, *libc::c_void) = cast::transmute_copy(&key);
pair.first()
}
// If returning Some(..), returns with @T with the map's reference. Careful!
unsafe fn local_data_lookup<T: 'static>(
map: TaskLocalMap, key: LocalDataKey<T>)
-> Option<(uint, *libc::c_void)> {
let key_value = key_to_key_value(key);
let map_pos = (*map).iter().position(|entry|
match *entry {
Some((k,_,_)) => k == key_value,
None => false
}
);
do map_pos.map |index| {
// .get() is guaranteed because of "None { false }" above.
let (_, data_ptr, _) = (*map)[*index].get();
(*index, data_ptr)
}
}
unsafe fn local_get_helper<T: 'static>(
handle: Handle, key: LocalDataKey<T>,
do_pop: bool) -> Option<@T> {
let map = get_local_map(handle);
// Interpreturn our findings from the map
do local_data_lookup(map, key).map |result| {
// A reference count magically appears on 'data' out of thin air. It
// was referenced in the local_data box, though, not here, so before
// overwriting the local_data_box we need to give an extra reference.
// We must also give an extra reference when not removing.
let (index, data_ptr) = *result;
let data: @T = cast::transmute(data_ptr);
cast::bump_box_refcount(data);
if do_pop {
map[index] = None;
}
data
}
}
pub unsafe fn local_pop<T: 'static>(
handle: Handle,
key: LocalDataKey<T>) -> Option<@T> {
local_get_helper(handle, key, true)
}
pub unsafe fn local_get<T: 'static>(
handle: Handle,
key: LocalDataKey<T>) -> Option<@T> {
local_get_helper(handle, key, false)
}
pub unsafe fn local_set<T: 'static>(
handle: Handle, key: LocalDataKey<T>, data: @T) {
let map = get_local_map(handle);
// Store key+data as *voids. Data is invisibly referenced once; key isn't.
let keyval = key_to_key_value(key);
// We keep the data in two forms: one as an unsafe pointer, so we can get
// it back by casting; another in an existential box, so the reference we
// own on it can be dropped when the box is destroyed. The unsafe pointer
// does not have a reference associated with it, so it may become invalid
// when the box is destroyed.
let data_ptr = *cast::transmute::<&@T, &*libc::c_void>(&data);
let data_box = @data as @LocalData;
// Construct new entry to store in the map.
let new_entry = Some((keyval, data_ptr, data_box));
// Find a place to put it.
match local_data_lookup(map, key) {
Some((index, _old_data_ptr)) => {
// Key already had a value set, _old_data_ptr, whose reference
// will get dropped when the local_data box is overwritten.
map[index] = new_entry;
}
None => {
// Find an empty slot. If not, grow the vector.
match (*map).iter().position(|x| x.is_none()) {
Some(empty_index) => { map[empty_index] = new_entry; }
None => { map.push(new_entry); }
unsafe fn newsched_map(local: *mut LocalStorage) -> &mut TaskLocalMap {
// This is based on the same idea as the oldsched code above.
match &mut *local {
// If the at_exit function is already set, then we just need to take
// a loan out on the TLS map stored inside
&LocalStorage(ref mut map_ptr, Some(_)) => {
assert!(map_ptr.is_not_null());
return cast::transmute(map_ptr);
}
// If this is the first time we've accessed TLS, perform similar
// actions to the oldsched way of doing things.
&LocalStorage(ref mut map_ptr, ref mut at_exit) => {
assert!(map_ptr.is_null());
assert!(at_exit.is_none());
let map: TaskLocalMap = ~[];
*map_ptr = cast::transmute(map);
*at_exit = Some(cleanup_task_local_map);
return cast::transmute(map_ptr);
}
}
}
}
pub unsafe fn local_modify<T: 'static>(
handle: Handle, key: LocalDataKey<T>,
modify_fn: &fn(Option<@T>) -> Option<@T>) {
// Could be more efficient by doing the lookup work, but this is easy.
let newdata = modify_fn(local_pop(handle, key));
if newdata.is_some() {
local_set(handle, key, newdata.unwrap());
match handle {
OldHandle(task) => oldsched_map(task),
NewHandle(local_storage) => newsched_map(local_storage)
}
}
unsafe fn key_to_key_value<T: 'static>(key: local_data::Key<T>) -> *libc::c_void {
let pair: sys::Closure = cast::transmute(key);
return pair.code as *libc::c_void;
}
pub unsafe fn local_pop<T: 'static>(handle: Handle,
key: local_data::Key<T>) -> Option<T> {
let map = get_local_map(handle);
let key_value = key_to_key_value(key);
for map.mut_iter().advance |entry| {
match *entry {
Some((k, _, loans)) if k == key_value => {
if loans != 0 {
fail!("TLS value has been loaned via get already");
}
// Move the data out of the `entry` slot via util::replace. This
// is guaranteed to succeed because we already matched on `Some`
// above.
let data = match util::replace(entry, None) {
Some((_, data, _)) => data,
None => libc::abort(),
};
// Move `data` into transmute to get out the memory that it
// owns, we must free it manually later.
let (_vtable, box): (uint, ~~T) = cast::transmute(data);
// Read the box's value (using the compiler's built-in
// auto-deref functionality to obtain a pointer to the base)
let ret = ptr::read_ptr(cast::transmute::<&T, *mut T>(*box));
// Finally free the allocated memory. we don't want this to
// actually touch the memory inside because it's all duplicated
// now, so the box is transmuted to a 0-sized type. We also use
// a type which references `T` because currently the layout
// could depend on whether T contains managed pointers or not.
let _: ~~[T, ..0] = cast::transmute(box);
// Everything is now deallocated, and we own the value that was
// located inside TLS, so we now return it.
return Some(ret);
}
_ => {}
}
}
return None;
}
pub unsafe fn local_get<T: 'static, U>(handle: Handle,
key: local_data::Key<T>,
f: &fn(Option<&T>) -> U) -> U {
// This function must be extremely careful. Because TLS can store owned
// values, and we must have some form of `get` function other than `pop`,
// this function has to give a `&` reference back to the caller.
//
// One option is to return the reference, but this cannot be sound because
// the actual lifetime of the object is not known. The slot in TLS could not
// be modified until the object goes out of scope, but the TLS code cannot
// know when this happens.
//
// For this reason, the reference is yielded to a specified closure. This
// way the TLS code knows exactly what the lifetime of the yielded pointer
// is, allowing callers to acquire references to owned data. This is also
// sound so long as measures are taken to ensure that while a TLS slot is
// loaned out to a caller, it's not modified recursively.
let map = get_local_map(handle);
let key_value = key_to_key_value(key);
let pos = map.iter().position(|entry| {
match *entry {
Some((k, _, _)) if k == key_value => true, _ => false
}
});
match pos {
None => { return f(None); }
Some(i) => {
let ret;
match map[i] {
Some((_, ref data, ref mut loans)) => {
*loans = *loans + 1;
// data was created with `~~T as ~LocalData`, so we extract
// pointer part of the trait, (as ~~T), and then use
// compiler coercions to achieve a '&' pointer
match *cast::transmute::<&TLSValue, &(uint, ~~T)>(data) {
(_vtable, ref box) => {
let value: &T = **box;
ret = f(Some(value));
}
}
}
_ => libc::abort()
}
// n.b. 'data' and 'loans' are both invalid pointers at the point
// 'f' returned because `f` could have appended more TLS items which
// in turn relocated the vector. Hence we do another lookup here to
// fixup the loans.
match map[i] {
Some((_, _, ref mut loans)) => { *loans = *loans - 1; }
None => { libc::abort(); }
}
return ret;
}
}
}
pub unsafe fn local_set<T: 'static>(handle: Handle,
key: local_data::Key<T>,
data: T) {
let map = get_local_map(handle);
let keyval = key_to_key_value(key);
// When the task-local map is destroyed, all the data needs to be cleaned
// up. For this reason we can't do some clever tricks to store '~T' as a
// '*c_void' or something like that. To solve the problem, we cast
// everything to a trait (LocalData) which is then stored inside the map.
// Upon destruction of the map, all the objects will be destroyed and the
// traits have enough information about them to destroy themselves.
//
// FIXME(#7673): This should be "~data as ~LocalData" (without the colon at
// the end, and only one sigil)
let data = ~~data as ~LocalData:;
fn insertion_position(map: &mut TaskLocalMap,
key: *libc::c_void) -> Option<uint> {
// First see if the map contains this key already
let curspot = map.iter().position(|entry| {
match *entry {
Some((ekey, _, loans)) if key == ekey => {
if loans != 0 {
fail!("TLS value has been loaned via get already");
}
true
}
_ => false,
}
});
// If it doesn't contain the key, just find a slot that's None
match curspot {
Some(i) => Some(i),
None => map.iter().position(|entry| entry.is_none())
}
}
match insertion_position(map, keyval) {
Some(i) => { map[i] = Some((keyval, data, 0)); }
None => { map.push(Some((keyval, data, 0))); }
}
}

View file

@ -0,0 +1,229 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#[allow(missing_doc)];
use cast;
use cmp::Eq;
use libc;
use local_data;
use prelude::*;
use sys;
use task::rt;
use super::rt::rust_task;
use rt::task::{Task, LocalStorage};
pub enum Handle {
OldHandle(*rust_task),
NewHandle(*mut LocalStorage)
}
impl Handle {
pub fn new() -> Handle {
use rt::{context, OldTaskContext};
use rt::local::Local;
unsafe {
match context() {
OldTaskContext => {
OldHandle(rt::rust_get_task())
}
_ => {
let task = Local::unsafe_borrow::<Task>();
NewHandle(&mut (*task).storage)
}
}
}
}
}
pub trait LocalData { }
impl<T: 'static> LocalData for @T { }
impl Eq for @LocalData {
fn eq(&self, other: &@LocalData) -> bool {
unsafe {
let ptr_a: &(uint, uint) = cast::transmute(self);
let ptr_b: &(uint, uint) = cast::transmute(other);
return ptr_a == ptr_b;
}
}
fn ne(&self, other: &@LocalData) -> bool { !(*self).eq(other) }
}
// If TLS is used heavily in future, this could be made more efficient with a
// proper map.
type TaskLocalElement = (*libc::c_void, *libc::c_void, @LocalData);
// Has to be a pointer at outermost layer; the foreign call returns void *.
type TaskLocalMap = ~[Option<TaskLocalElement>];
fn cleanup_task_local_map(map_ptr: *libc::c_void) {
unsafe {
assert!(!map_ptr.is_null());
// Get and keep the single reference that was created at the
// beginning.
let _map: TaskLocalMap = cast::transmute(map_ptr);
// All local_data will be destroyed along with the map.
}
}
// Gets the map from the runtime. Lazily initialises if not done so already.
unsafe fn get_local_map(handle: Handle) -> &mut TaskLocalMap {
match handle {
OldHandle(task) => get_task_local_map(task),
NewHandle(local_storage) => get_newsched_local_map(local_storage)
}
}
unsafe fn get_task_local_map(task: *rust_task) -> &mut TaskLocalMap {
extern fn cleanup_task_local_map_extern_cb(map_ptr: *libc::c_void) {
cleanup_task_local_map(map_ptr);
}
// Relies on the runtime initialising the pointer to null.
// Note: the map is an owned pointer and is "owned" by TLS. It is moved
// into the tls slot for this task, and then mutable loans are taken from
// this slot to modify the map.
let map_ptr = rt::rust_get_task_local_data(task);
if (*map_ptr).is_null() {
// First time TLS is used, create a new map and set up the necessary
// TLS information for its safe destruction
let map: TaskLocalMap = ~[];
*map_ptr = cast::transmute(map);
rt::rust_task_local_data_atexit(task, cleanup_task_local_map_extern_cb);
}
return cast::transmute(map_ptr);
}
unsafe fn get_newsched_local_map(local: *mut LocalStorage) -> &mut TaskLocalMap {
// This is based on the same idea as the oldsched code above.
match &mut *local {
// If the at_exit function is already set, then we just need to take a
// loan out on the TLS map stored inside
&LocalStorage(ref mut map_ptr, Some(_)) => {
assert!(map_ptr.is_not_null());
return cast::transmute(map_ptr);
}
// If this is the first time we've accessed TLS, perform similar
// actions to the oldsched way of doing things.
&LocalStorage(ref mut map_ptr, ref mut at_exit) => {
assert!(map_ptr.is_null());
assert!(at_exit.is_none());
let map: TaskLocalMap = ~[];
*map_ptr = cast::transmute(map);
*at_exit = Some(cleanup_task_local_map);
return cast::transmute(map_ptr);
}
}
}
unsafe fn key_to_key_value<T: 'static>(key: local_data::Key<@T>) -> *libc::c_void {
let pair: sys::Closure = cast::transmute(key);
return pair.code as *libc::c_void;
}
// If returning Some(..), returns with @T with the map's reference. Careful!
unsafe fn local_data_lookup<T: 'static>(
map: &mut TaskLocalMap, key: local_data::Key<@T>)
-> Option<(uint, *libc::c_void)> {
let key_value = key_to_key_value(key);
for map.iter().enumerate().advance |(i, entry)| {
match *entry {
Some((k, data, _)) if k == key_value => { return Some((i, data)); }
_ => {}
}
}
return None;
}
unsafe fn local_get_helper<T: 'static>(
handle: Handle, key: local_data::Key<@T>,
do_pop: bool) -> Option<@T> {
let map = get_local_map(handle);
// Interpreturn our findings from the map
do local_data_lookup(map, key).map |result| {
// A reference count magically appears on 'data' out of thin air. It
// was referenced in the local_data box, though, not here, so before
// overwriting the local_data_box we need to give an extra reference.
// We must also give an extra reference when not removing.
let (index, data_ptr) = *result;
let data: @T = cast::transmute(data_ptr);
cast::bump_box_refcount(data);
if do_pop {
map[index] = None;
}
data
}
}
pub unsafe fn local_pop<T: 'static>(
handle: Handle,
key: local_data::Key<@T>) -> Option<@T> {
local_get_helper(handle, key, true)
}
pub unsafe fn local_get<T: 'static, U>(
handle: Handle,
key: local_data::Key<@T>,
f: &fn(Option<&@T>) -> U) -> U {
match local_get_helper(handle, key, false) {
Some(ref x) => f(Some(x)),
None => f(None)
}
}
pub unsafe fn local_set<T: 'static>(
handle: Handle, key: local_data::Key<@T>, data: @T) {
let map = get_local_map(handle);
// Store key+data as *voids. Data is invisibly referenced once; key isn't.
let keyval = key_to_key_value(key);
// We keep the data in two forms: one as an unsafe pointer, so we can get
// it back by casting; another in an existential box, so the reference we
// own on it can be dropped when the box is destroyed. The unsafe pointer
// does not have a reference associated with it, so it may become invalid
// when the box is destroyed.
let data_ptr = *cast::transmute::<&@T, &*libc::c_void>(&data);
let data_box = @data as @LocalData;
// Construct new entry to store in the map.
let new_entry = Some((keyval, data_ptr, data_box));
// Find a place to put it.
match local_data_lookup(map, key) {
Some((index, _old_data_ptr)) => {
// Key already had a value set, _old_data_ptr, whose reference
// will get dropped when the local_data box is overwritten.
map[index] = new_entry;
}
None => {
// Find an empty slot. If not, grow the vector.
match map.iter().position(|x| x.is_none()) {
Some(empty_index) => { map[empty_index] = new_entry; }
None => { map.push(new_entry); }
}
}
}
}
pub unsafe fn local_modify<T: 'static>(
handle: Handle, key: local_data::Key<@T>,
modify_fn: &fn(Option<@T>) -> Option<@T>) {
// Could be more efficient by doing the lookup work, but this is easy.
let newdata = modify_fn(local_pop(handle, key));
if newdata.is_some() {
local_set(handle, key, newdata.unwrap());
}
}

View file

@ -54,6 +54,10 @@ use util;
#[cfg(test)] use ptr;
#[cfg(test)] use task;
#[cfg(stage0)]
#[path="local_data_priv_stage0.rs"]
mod local_data_priv;
#[cfg(not(stage0))]
mod local_data_priv;
pub mod rt;
pub mod spawn;

View file

@ -63,9 +63,7 @@ pub extern {
fn rust_task_kill_all(task: *rust_task);
#[rust_stack]
fn rust_get_task_local_data(task: *rust_task) -> *libc::c_void;
#[rust_stack]
fn rust_set_task_local_data(task: *rust_task, map: *libc::c_void);
fn rust_get_task_local_data(task: *rust_task) -> *mut *libc::c_void;
#[rust_stack]
fn rust_task_local_data_atexit(task: *rust_task, cleanup_fn: *u8);
}

View file

@ -478,26 +478,28 @@ fn gen_child_taskgroup(linked: bool, supervised: bool)
* Step 1. Get spawner's taskgroup info.
*##################################################################*/
let spawner_group: @@mut TCB =
match local_get(OldHandle(spawner), taskgroup_key!()) {
None => {
// Main task, doing first spawn ever. Lazily initialise
// here.
let mut members = new_taskset();
taskset_insert(&mut members, spawner);
let tasks = exclusive(Some(TaskGroupData {
members: members,
descendants: new_taskset(),
}));
// Main task/group has no ancestors, no notifier, etc.
let group = @@mut TCB(spawner,
tasks,
AncestorList(None),
true,
None);
local_set(OldHandle(spawner), taskgroup_key!(), group);
group
do local_get(OldHandle(spawner), taskgroup_key!()) |group| {
match group {
None => {
// Main task, doing first spawn ever. Lazily initialise
// here.
let mut members = new_taskset();
taskset_insert(&mut members, spawner);
let tasks = exclusive(Some(TaskGroupData {
members: members,
descendants: new_taskset(),
}));
// Main task/group has no ancestors, no notifier, etc.
let group = @@mut TCB(spawner,
tasks,
AncestorList(None),
true,
None);
local_set(OldHandle(spawner), taskgroup_key!(), group);
group
}
Some(&group) => group
}
Some(group) => group
};
let spawner_group: &mut TCB = *spawner_group;

View file

@ -698,10 +698,10 @@ pub fn get_sctable() -> @mut SCTable {
let sctable_key = (cast::transmute::<(uint, uint),
&fn:Copy(v: @@mut SCTable)>(
(-4 as uint, 0u)));
match local_data::local_data_get(sctable_key) {
match local_data::get(sctable_key, |k| k.map(|&k| *k)) {
None => {
let new_table = @@mut new_sctable_internal();
local_data::local_data_set(sctable_key,new_table);
local_data::set(sctable_key,new_table);
*new_table
},
Some(intr) => *intr

View file

@ -490,11 +490,11 @@ pub fn get_ident_interner() -> @ident_interner {
(cast::transmute::<(uint, uint),
&fn:Copy(v: @@::parse::token::ident_interner)>(
(-3 as uint, 0u)));
match local_data::local_data_get(key) {
match local_data::get(key, |k| k.map(|&k| *k)) {
Some(interner) => *interner,
None => {
let interner = mk_fresh_ident_interner();
local_data::local_data_set(key, @interner);
local_data::set(key, @interner);
interner
}
}

View file

@ -671,14 +671,10 @@ rust_unlock_little_lock(lock_and_signal *lock) {
lock->unlock();
}
// set/get/atexit task_local_data can run on the rust stack for speed.
extern "C" void *
// get/atexit task_local_data can run on the rust stack for speed.
extern "C" void **
rust_get_task_local_data(rust_task *task) {
return task->task_local_data;
}
extern "C" void
rust_set_task_local_data(rust_task *task, void *data) {
task->task_local_data = data;
return &task->task_local_data;
}
extern "C" void
rust_task_local_data_atexit(rust_task *task, void (*cleanup_fn)(void *data)) {

View file

@ -195,7 +195,6 @@ rust_destroy_little_lock
rust_lock_little_lock
rust_unlock_little_lock
rust_get_task_local_data
rust_set_task_local_data
rust_task_local_data_atexit
rust_task_ref
rust_task_deref
@ -271,4 +270,4 @@ rust_current_boxed_region
rust_take_global_args_lock
rust_drop_global_args_lock
rust_set_exit_status_newrt
rust_get_exit_status_newrt
rust_get_exit_status_newrt

View file

@ -10,12 +10,12 @@
// Testing that we can't store a borrowed pointer it task-local storage
use std::local_data::*;
use std::local_data;
fn key(_x: @&int) { }
fn main() {
unsafe {
local_data_set(key, @&0); //~ ERROR does not fulfill `'static`
local_data::set(key, @&0); //~ ERROR does not fulfill `'static`
}
}