]> git.lizzy.rs Git - rust.git/commitdiff
Change TLS to almost be able to contain owned types
authorAlex Crichton <alex@alexcrichton.com>
Tue, 9 Jul 2013 21:52:01 +0000 (14:52 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Wed, 10 Jul 2013 00:31:01 +0000 (17:31 -0700)
src/libstd/condition.rs
src/libstd/local_data.rs
src/libstd/task/local_data_priv.rs
src/libstd/task/spawn.rs

index 04f2d815d081587b20d1ae3f0e932e771ffae19b..89f918204418c7058dff0aef6b0aa8b98d77694f 100644 (file)
@@ -26,7 +26,7 @@ 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::LocalDataKey<'self, @Handler<T, U>>
 }
 
 impl<'self, T, U> Condition<'self, T, U> {
index fbb11dfaa34c4279c6ca89113385a1acaa850e5c..77bbe4f5b979842592891ea9a6f2f96eb0fd1a2c 100644 (file)
  *
  * These two cases aside, the interface is safe.
  */
-pub type LocalDataKey<'self,T> = &'self fn:Copy(v: @T);
+pub type LocalDataKey<'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> {
-
+pub unsafe fn local_data_pop<T: 'static>(key: LocalDataKey<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)
+pub unsafe fn local_data_get<T: 'static>(key: LocalDataKey<@T>) -> Option<@T> {
+    local_get(Handle::new(), key, |loc| loc.map(|&x| *x))
 }
 /**
  * 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) {
-
+pub unsafe fn local_data_set<T: 'static>(key: LocalDataKey<@T>, data: @T) {
     local_set(Handle::new(), key, data)
 }
 /**
@@ -80,7 +74,7 @@ pub unsafe fn local_data_set<T: 'static>(
  * data is removed (and its reference dropped).
  */
 pub unsafe fn local_data_modify<T: 'static>(
-    key: LocalDataKey<T>,
+    key: LocalDataKey<@T>,
     modify_fn: &fn(Option<@T>) -> Option<@T>) {
 
     let cur = local_data_pop(key);
index 16600ffab0635e7b796a87516ebf6237bbd880ff..07eebea4a6238dc63af16b220e76408d666686b4 100644 (file)
 use cast;
 use libc;
 use local_data::LocalDataKey;
+use managed::raw::BoxRepr;
 use prelude::*;
+use ptr;
 use sys;
 use task::rt;
+use unstable::intrinsics;
+use util;
 
 use super::rt::rust_task;
 use rt::task::{Task, LocalStorage};
@@ -47,15 +51,24 @@ trait LocalData {}
 impl<T: 'static> LocalData for T {}
 
 // The task-local-map actuall stores all TLS information. Right now it's a list
-// of key-value pairs. Each value is an actual Rust type so that when the map is
-// destroyed all of the contents are destroyed. Each of the keys are actually
-// addresses which don't need to be destroyed.
+// of triples of (key, value, loans). The key is a code pointer (right now at
+// least), the value is a trait so destruction can work, and the loans value
+// is a count of the number of times the value is currently on loan via
+// `local_data_get`.
+//
+// TLS is designed to be able to store owned data, so `local_data_get` must
+// return a borrowed pointer to this data. In order to have a proper lifetime, a
+// borrowed pointer is insted yielded to a closure specified to the `get`
+// function. As a result, it would be unsound to perform `local_data_set` on the
+// same key inside of a `local_data_get`, so we ensure at runtime that this does
+// not happen.
 //
 // n.b. Has to be 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, @LocalData)>];
+type TaskLocalMap = ~[Option<(*libc::c_void, TLSValue, uint)>];
+type TLSValue = @LocalData;
 
 fn cleanup_task_local_map(map_ptr: *libc::c_void) {
     unsafe {
@@ -123,35 +136,65 @@ unsafe fn key_to_key_value<T: 'static>(key: LocalDataKey<T>) -> *libc::c_void {
     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: &TaskLocalMap,
-                                        key: LocalDataKey<T>)
-                                            -> Option<(uint, @T)>
-{
-    use managed::raw::BoxRepr;
+unsafe fn transmute_back<'a, T>(data: &'a TLSValue) -> (*BoxRepr, &'a T) {
+    // Currently, a TLSValue is an '@Trait' instance which means that its actual
+    // representation is a pair of (vtable, box). Also, because of issue #7673
+    // the box actually points to another box which has the data. Hence, to get
+    // a pointer to the actual value that we're interested in, we decode the
+    // trait pointer and pass through one layer of boxes to get to the actual
+    // data we're interested in.
+    //
+    // The reference count of the containing @Trait box is already taken care of
+    // because the TLSValue is owned by the containing TLS map which means that
+    // the reference count is at least one. Extra protections are then added at
+    // runtime to ensure that once a loan on a value in TLS has been given out,
+    // the value isn't modified by another user.
+    let (_vt, box) = *cast::transmute::<&TLSValue, &(uint, *BoxRepr)>(data);
+
+    return (box, cast::transmute(&(*box).data));
+}
 
+pub unsafe fn local_pop<T: 'static>(handle: Handle,
+                                    key: LocalDataKey<T>) -> Option<T> {
+    // If you've never seen horrendously unsafe code written in rust before,
+    // just feel free to look a bit farther...
+    let map = get_local_map(handle);
     let key_value = key_to_key_value(key);
-    for map.iter().enumerate().advance |(i, entry)| {
+
+    for map.mut_iter().advance |entry| {
         match *entry {
-            Some((k, ref data)) if k == key_value => {
-                // We now have the correct 'data' as type @LocalData which we
-                // need to somehow transmute this back to @T. This was
-                // originally stored into the map as:
-                //
-                //    let data = @T;
-                //    let element = @data as @LocalData;
-                //    insert(key, element);
-                //
-                // This means that the element stored is a 2-word pair (because
-                // it's a trait). The second element is the vtable (we don't
-                // need it), and the first element is actually '@@T'. Not only
-                // is this @@T, but it's a pointer to the base of the @@T (box
-                // and all), so we have to traverse this to find the actual
-                // pointer that we want.
-                let (_vtable, box) =
-                    *cast::transmute::<&@LocalData, &(uint, *BoxRepr)>(data);
-                let ptr: &@T = cast::transmute(&(*box).data);
-                return Some((i, *ptr));
+            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(),
+                };
+
+                // First, via some various cheats/hacks, we extract the value
+                // contained within the TLS box. This leaves a big chunk of
+                // memory which needs to be deallocated now.
+                let (chunk, inside) = transmute_back(&data);
+                let inside = cast::transmute_mut(inside);
+                let ret = ptr::read_ptr(inside);
+
+                // Forget the trait box because we're about to manually
+                // deallocate the other box. And for my next trick (kids don't
+                // try this at home), transmute the chunk of @ memory from the
+                // @-trait box to a pointer to a zero-sized '@' block which will
+                // then cause it to get properly deallocated, but it won't touch
+                // any of the uninitialized memory beyond the end.
+                cast::forget(data);
+                let chunk: *mut BoxRepr = cast::transmute(chunk);
+                (*chunk).header.type_desc =
+                    cast::transmute(intrinsics::get_tydesc::<()>());
+                let _: @() = cast::transmute(chunk);
+
+                return Some(ret);
             }
             _ => {}
         }
@@ -159,28 +202,32 @@ unsafe fn local_data_lookup<T: 'static>(map: &TaskLocalMap,
     return None;
 }
 
-pub unsafe fn local_pop<T: 'static>(handle: Handle,
-                                    key: LocalDataKey<T>) -> Option<@T> {
+pub unsafe fn local_get<T: 'static, U>(handle: Handle,
+                                       key: LocalDataKey<T>,
+                                       f: &fn(Option<&T>) -> U) -> U {
+    // This does in theory take multiple mutable loans on the tls map, but the
+    // references returned are never removed because the map is only increasing
+    // in size (it never shrinks).
     let map = get_local_map(handle);
-    match local_data_lookup(map, key) {
-        Some((index, data)) => {
-            map[index] = None;
-            Some(data)
+    let key_value = key_to_key_value(key);
+    for map.mut_iter().advance |entry| {
+        match *entry {
+            Some((k, ref data, ref mut loans)) if k == key_value => {
+                *loans = *loans + 1;
+                let (_, val) = transmute_back(data);
+                let ret = f(Some(val));
+                *loans = *loans - 1;
+                return ret;
+            }
+            _ => {}
         }
-        None => None
-    }
-}
-
-pub unsafe fn local_get<T: 'static>(handle: Handle,
-                                    key: LocalDataKey<T>) -> Option<@T> {
-    match local_data_lookup(get_local_map(handle), key) {
-        Some((_, data)) => Some(data),
-        None => None
     }
+    return f(None);
 }
 
+// FIXME(#7673): This shouldn't require '@', it should use '~'
 pub unsafe fn local_set<T: 'static>(handle: Handle,
-                                    key: LocalDataKey<T>,
+                                    key: LocalDataKey<@T>,
                                     data: @T) {
     let map = get_local_map(handle);
     let keyval = key_to_key_value(key);
@@ -191,16 +238,31 @@ pub unsafe fn local_set<T: 'static>(handle: Handle,
     // 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.
-    let entry = Some((keyval, @data as @LocalData));
-
-    match local_data_lookup(map, key) {
-        Some((index, _)) => { map[index] = 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] = entry; }
-                None => { map.push(entry); }
+    let data = @data as @LocalData;
+
+    // First, try to insert it if we already have it.
+    for map.mut_iter().advance |entry| {
+        match *entry {
+            Some((key, ref mut value, loans)) if key == keyval => {
+                if loans != 0 {
+                    fail!("TLS value has been loaned via get already");
+                }
+                util::replace(value, data);
+                return;
+            }
+            _ => {}
+        }
+    }
+    // Next, search for an open spot
+    for map.mut_iter().advance |entry| {
+        match *entry {
+            Some(*) => {}
+            None => {
+                *entry = Some((keyval, data, 0));
+                return;
             }
         }
     }
+    // Finally push it on the end of the list
+    map.push(Some((keyval, data, 0)));
 }
index 190485a720aa726e849d0ad4476656cdc19a0e9d..7fe640dbf8c5bfe53c1c125616dc10b81dbfb6f3 100644 (file)
@@ -477,26 +477,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;