about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/libcore/core.rc4
-rw-r--r--src/libcore/task.rs351
-rw-r--r--src/libcore/task/local_data.rs346
-rw-r--r--src/libsyntax/ast.rs8
-rw-r--r--src/libsyntax/parse/token.rs8
-rw-r--r--src/rustdoc/extract.rs6
6 files changed, 372 insertions, 351 deletions
diff --git a/src/libcore/core.rc b/src/libcore/core.rc
index 09677084257..9bb13af5a82 100644
--- a/src/libcore/core.rc
+++ b/src/libcore/core.rc
@@ -219,7 +219,9 @@ mod send_map;
 
 // Concurrency
 mod comm;
-mod task;
+mod task {
+    mod local_data;
+}
 mod future;
 mod pipes;
 
diff --git a/src/libcore/task.rs b/src/libcore/task.rs
index fbac2328964..788802a5fe9 100644
--- a/src/libcore/task.rs
+++ b/src/libcore/task.rs
@@ -66,11 +66,7 @@ export get_task;
 export unkillable, rekillable;
 export atomically;
 
-export local_data_key;
-export local_data_pop;
-export local_data_get;
-export local_data_set;
-export local_data_modify;
+export local_data;
 
 export SingleThreaded;
 export ThreadPerCore;
@@ -1213,7 +1209,7 @@ fn gen_child_taskgroup(linked: bool, supervised: bool)
     /*######################################################################*
      * Step 1. Get spawner's taskgroup info.
      *######################################################################*/
-    let spawner_group = match unsafe { local_get(spawner,
+    let spawner_group = match unsafe { local_data::local_get(spawner,
                                                  taskgroup_key!()) } {
         None => {
             // Main task, doing first spawn ever. Lazily initialise here.
@@ -1225,7 +1221,9 @@ fn gen_child_taskgroup(linked: bool, supervised: bool)
             // Main task/group has no ancestors, no notifier, etc.
             let group =
                 @TCB(spawner, move tasks, AncestorList(None), true, None);
-            unsafe { local_set(spawner, taskgroup_key!(), group); }
+            unsafe {
+                local_data::local_set(spawner, taskgroup_key!(), group);
+            }
             group
         }
         Some(group) => group
@@ -1352,7 +1350,9 @@ fn spawn_raw(+opts: TaskOpts, +f: fn~()) {
             if enlist_many(child, &child_arc, &mut ancestors) {
                 let group = @TCB(child, move child_arc, move ancestors,
                                  is_main, move notifier);
-                unsafe { local_set(child, taskgroup_key!(), group); }
+                unsafe {
+                    local_data::local_set(child, taskgroup_key!(), group);
+                }
 
                 // Run the child's body.
                 f();
@@ -1429,230 +1429,6 @@ fn spawn_raw(+opts: TaskOpts, +f: fn~()) {
     }
 }
 
-/* **************************************************************************
- * 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 task-spawning metadata (tracking linked failure state), dynamic
- * variables, 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.
- *
- * Casting 'Arcane Sight' reveals an overwhelming aura of Transmutation magic.
- ****************************************************************************/
-
-/**
- * Indexes a task-local data slot. The function's code pointer is used for
- * comparison. Recommended use is to write an empty function for each desired
- * task-local data slot (and use class destructors, not code inside the
- * function, if specific teardown is needed). DO NOT use multiple
- * instantiations of a single polymorphic function to index data of different
- * types; arbitrary type coercion is possible this way.
- *
- * One other exception is that this global state can be used in a destructor
- * context to create a circular @-box reference, which will crash during task
- * failure (see issue #3039).
- *
- * These two cases aside, the interface is safe.
- */
-type LocalDataKey<T: Owned> = &fn(+@T);
-
-trait LocalData { }
-impl<T: Owned> @T: LocalData { }
-
-impl LocalData: Eq {
-    pure fn eq(&&other: LocalData) -> bool unsafe {
-        let ptr_a: (uint, uint) = cast::reinterpret_cast(&self);
-        let ptr_b: (uint, uint) = cast::reinterpret_cast(&other);
-        return ptr_a == ptr_b;
-    }
-    pure fn ne(&&other: LocalData) -> bool { !self.eq(other) }
-}
-
-// We use dvec because it's the best data structure in core. 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 = @dvec::DVec<Option<TaskLocalElement>>;
-
-extern 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::reinterpret_cast(&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_task_local_map(task: *rust_task) -> TaskLocalMap {
-
-    // 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 = rustrt::rust_get_task_local_data(task);
-    if map_ptr.is_null() {
-        let map: TaskLocalMap = @dvec::DVec();
-        // Use reinterpret_cast -- transmute would take map away from us also.
-        rustrt::rust_set_task_local_data(
-            task, cast::reinterpret_cast(&map));
-        rustrt::rust_task_local_data_atexit(task, cleanup_task_local_map);
-        // Also need to reference it an extra time to keep it for now.
-        cast::bump_box_refcount(map);
-        map
-    } else {
-        let map = cast::transmute(move map_ptr);
-        cast::bump_box_refcount(map);
-        map
-    }
-}
-
-unsafe fn key_to_key_value<T: Owned>(
-    key: LocalDataKey<T>) -> *libc::c_void {
-
-    // Keys are closures, which are (fnptr,envptr) pairs. Use fnptr.
-    // Use reintepret_cast -- transmute would leak (forget) the closure.
-    let pair: (*libc::c_void, *libc::c_void) = cast::reinterpret_cast(&key);
-    pair.first()
-}
-
-// If returning Some(..), returns with @T with the map's reference. Careful!
-unsafe fn local_data_lookup<T: Owned>(
-    map: TaskLocalMap, key: LocalDataKey<T>)
-    -> Option<(uint, *libc::c_void)> {
-
-    let key_value = key_to_key_value(key);
-    let map_pos = (*map).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: Owned>(
-    task: *rust_task, key: LocalDataKey<T>,
-    do_pop: bool) -> Option<@T> {
-
-    let map = get_task_local_map(task);
-    // 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(move data_ptr);
-        cast::bump_box_refcount(data);
-        if do_pop {
-            (*map).set_elt(index, None);
-        }
-        data
-    }
-}
-
-unsafe fn local_pop<T: Owned>(
-    task: *rust_task,
-    key: LocalDataKey<T>) -> Option<@T> {
-
-    local_get_helper(task, key, true)
-}
-
-unsafe fn local_get<T: Owned>(
-    task: *rust_task,
-    key: LocalDataKey<T>) -> Option<@T> {
-
-    local_get_helper(task, key, false)
-}
-
-unsafe fn local_set<T: Owned>(
-    task: *rust_task, key: LocalDataKey<T>, +data: @T) {
-
-    let map = get_task_local_map(task);
-    // 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::reinterpret_cast(&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).set_elt(index, new_entry);
-        }
-        None => {
-            // Find an empty slot. If not, grow the vector.
-            match (*map).position(|x| x.is_none()) {
-                Some(empty_index) => (*map).set_elt(empty_index, new_entry),
-                None => (*map).push(new_entry)
-            }
-        }
-    }
-}
-
-unsafe fn local_modify<T: Owned>(
-    task: *rust_task, 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(task, key));
-    if newdata.is_some() {
-        local_set(task, key, option::unwrap(move newdata));
-    }
-}
-
-/* Exported interface for task-local data (plus local_data_key above). */
-/**
- * Remove a task-local data value from the table, returning the
- * reference that was originally created to insert it.
- */
-unsafe fn local_data_pop<T: Owned>(
-    key: LocalDataKey<T>) -> Option<@T> {
-
-    local_pop(rustrt::rust_get_task(), key)
-}
-/**
- * Retrieve a task-local data value. It will also be kept alive in the
- * table until explicitly removed.
- */
-unsafe fn local_data_get<T: Owned>(
-    key: LocalDataKey<T>) -> Option<@T> {
-
-    local_get(rustrt::rust_get_task(), key)
-}
-/**
- * Store a value in task-local data. If this key already has a value,
- * that value is overwritten (and its destructor is run).
- */
-unsafe fn local_data_set<T: Owned>(
-    key: LocalDataKey<T>, +data: @T) {
-
-    local_set(rustrt::rust_get_task(), key, data)
-}
-/**
- * Modify a task-local data value. If the function returns 'None', the
- * data is removed (and its reference dropped).
- */
-unsafe fn local_data_modify<T: Owned>(
-    key: LocalDataKey<T>,
-    modify_fn: fn(Option<@T>) -> Option<@T>) {
-
-    local_modify(rustrt::rust_get_task(), key, modify_fn)
-}
-
 extern mod rustrt {
     #[rust_stack]
     fn rust_task_yield(task: *rust_task) -> bool;
@@ -2318,117 +2094,6 @@ fn test_child_doesnt_ref_parent() {
 }
 
 #[test]
-fn test_tls_multitask() unsafe {
-    fn my_key(+_x: @~str) { }
-    local_data_set(my_key, @~"parent data");
-    do task::spawn unsafe {
-        assert local_data_get(my_key).is_none(); // TLS shouldn't carry over.
-        local_data_set(my_key, @~"child data");
-        assert *(local_data_get(my_key).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";
-}
-
-#[test]
-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";
-}
-
-#[test]
-fn test_tls_pop() unsafe {
-    fn my_key(+_x: @~str) { }
-    local_data_set(my_key, @~"weasel");
-    assert *(local_data_pop(my_key).get()) == ~"weasel";
-    // Pop must remove the data from the map.
-    assert local_data_pop(my_key).is_none();
-}
-
-#[test]
-fn test_tls_modify() unsafe {
-    fn my_key(+_x: @~str) { }
-    local_data_modify(my_key, |data| {
-        match data {
-            Some(@val) => fail ~"unwelcome value: " + val,
-            None       => Some(@~"first data")
-        }
-    });
-    local_data_modify(my_key, |data| {
-        match data {
-            Some(@~"first data") => Some(@~"next data"),
-            Some(@val)           => fail ~"wrong value: " + val,
-            None                 => fail ~"missing value"
-        }
-    });
-    assert *(local_data_pop(my_key).get()) == ~"next data";
-}
-
-#[test]
-fn test_tls_crust_automorestack_memorial_bug() unsafe {
-    // This might result in a stack-canary clobber if the runtime fails to set
-    // sp_limit to 0 when calling the cleanup extern - it might automatically
-    // jump over to the rust stack, which causes next_c_sp to get recorded as
-    // Something within a rust stack segment. Then a subsequent upcall (esp.
-    // for logging, think vsnprintf) would run on a stack smaller than 1 MB.
-    fn my_key(+_x: @~str) { }
-    do task::spawn {
-        unsafe { local_data_set(my_key, @~"hax"); }
-    }
-}
-
-#[test]
-fn test_tls_multiple_types() unsafe {
-    fn str_key(+_x: @~str) { }
-    fn box_key(+_x: @@()) { }
-    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);
-    }
-}
-
-#[test]
-fn test_tls_overwrite_multiple_types() {
-    fn str_key(+_x: @~str) { }
-    fn box_key(+_x: @@()) { }
-    fn int_key(+_x: @int) { }
-    do task::spawn unsafe {
-        local_data_set(str_key, @~"string data");
-        local_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);
-    }
-}
-
-#[test]
-#[should_fail]
-#[ignore(cfg(windows))]
-fn test_tls_cleanup_on_failure() unsafe {
-    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, @@());
-    do task::spawn unsafe { // spawn_linked
-        local_data_set(str_key, @~"string data");
-        local_data_set(box_key, @@());
-        local_data_set(int_key, @42);
-        fail;
-    }
-    // Not quite nondeterministic.
-    local_data_set(int_key, @31337);
-    fail;
-}
-
-#[test]
 fn test_sched_thread_per_core() {
     let (chan, port) = pipes::stream();
 
diff --git a/src/libcore/task/local_data.rs b/src/libcore/task/local_data.rs
new file mode 100644
index 00000000000..4fbf0475c0a
--- /dev/null
+++ b/src/libcore/task/local_data.rs
@@ -0,0 +1,346 @@
+/*!
+
+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 task-spawning metadata (tracking linked failure state), dynamic
+variables, 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.
+
+Casting 'Arcane Sight' reveals an overwhelming aura of Transmutation magic.
+
+*/
+
+export local_data_key;
+export local_data_pop;
+export local_data_get;
+export local_data_set;
+export local_data_modify;
+
+// XXX: These shouldn't be exported but they are used by task.rs
+export local_get;
+export local_set;
+
+/**
+ * Indexes a task-local data slot. The function's code pointer is used for
+ * comparison. Recommended use is to write an empty function for each desired
+ * task-local data slot (and use class destructors, not code inside the
+ * function, if specific teardown is needed). DO NOT use multiple
+ * instantiations of a single polymorphic function to index data of different
+ * types; arbitrary type coercion is possible this way.
+ *
+ * One other exception is that this global state can be used in a destructor
+ * context to create a circular @-box reference, which will crash during task
+ * failure (see issue #3039).
+ *
+ * These two cases aside, the interface is safe.
+ */
+type LocalDataKey<T: Owned> = &fn(+@T);
+
+trait LocalData { }
+impl<T: Owned> @T: LocalData { }
+
+impl LocalData: Eq {
+    pure fn eq(&&other: LocalData) -> bool unsafe {
+        let ptr_a: (uint, uint) = cast::reinterpret_cast(&self);
+        let ptr_b: (uint, uint) = cast::reinterpret_cast(&other);
+        return ptr_a == ptr_b;
+    }
+    pure fn ne(&&other: LocalData) -> bool { !self.eq(other) }
+}
+
+// We use dvec because it's the best data structure in core. 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 = @dvec::DVec<Option<TaskLocalElement>>;
+
+extern 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::reinterpret_cast(&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_task_local_map(task: *rust_task) -> TaskLocalMap {
+
+    // 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 = rustrt::rust_get_task_local_data(task);
+    if map_ptr.is_null() {
+        let map: TaskLocalMap = @dvec::DVec();
+        // Use reinterpret_cast -- transmute would take map away from us also.
+        rustrt::rust_set_task_local_data(
+            task, cast::reinterpret_cast(&map));
+        rustrt::rust_task_local_data_atexit(task, cleanup_task_local_map);
+        // Also need to reference it an extra time to keep it for now.
+        cast::bump_box_refcount(map);
+        map
+    } else {
+        let map = cast::transmute(move map_ptr);
+        cast::bump_box_refcount(map);
+        map
+    }
+}
+
+unsafe fn key_to_key_value<T: Owned>(
+    key: LocalDataKey<T>) -> *libc::c_void {
+
+    // Keys are closures, which are (fnptr,envptr) pairs. Use fnptr.
+    // Use reintepret_cast -- transmute would leak (forget) the closure.
+    let pair: (*libc::c_void, *libc::c_void) = cast::reinterpret_cast(&key);
+    pair.first()
+}
+
+// If returning Some(..), returns with @T with the map's reference. Careful!
+unsafe fn local_data_lookup<T: Owned>(
+    map: TaskLocalMap, key: LocalDataKey<T>)
+    -> Option<(uint, *libc::c_void)> {
+
+    let key_value = key_to_key_value(key);
+    let map_pos = (*map).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: Owned>(
+    task: *rust_task, key: LocalDataKey<T>,
+    do_pop: bool) -> Option<@T> {
+
+    let map = get_task_local_map(task);
+    // 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(move data_ptr);
+        cast::bump_box_refcount(data);
+        if do_pop {
+            (*map).set_elt(index, None);
+        }
+        data
+    }
+}
+
+unsafe fn local_pop<T: Owned>(
+    task: *rust_task,
+    key: LocalDataKey<T>) -> Option<@T> {
+
+    local_get_helper(task, key, true)
+}
+
+unsafe fn local_get<T: Owned>(
+    task: *rust_task,
+    key: LocalDataKey<T>) -> Option<@T> {
+
+    local_get_helper(task, key, false)
+}
+
+unsafe fn local_set<T: Owned>(
+    task: *rust_task, key: LocalDataKey<T>, +data: @T) {
+
+    let map = get_task_local_map(task);
+    // 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::reinterpret_cast(&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).set_elt(index, new_entry);
+        }
+        None => {
+            // Find an empty slot. If not, grow the vector.
+            match (*map).position(|x| x.is_none()) {
+                Some(empty_index) => (*map).set_elt(empty_index, new_entry),
+                None => (*map).push(new_entry)
+            }
+        }
+    }
+}
+
+unsafe fn local_modify<T: Owned>(
+    task: *rust_task, 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(task, key));
+    if newdata.is_some() {
+        local_set(task, key, option::unwrap(move newdata));
+    }
+}
+
+/* Exported interface for task-local data (plus local_data_key above). */
+/**
+ * Remove a task-local data value from the table, returning the
+ * reference that was originally created to insert it.
+ */
+unsafe fn local_data_pop<T: Owned>(
+    key: LocalDataKey<T>) -> Option<@T> {
+
+    local_pop(rustrt::rust_get_task(), key)
+}
+/**
+ * Retrieve a task-local data value. It will also be kept alive in the
+ * table until explicitly removed.
+ */
+unsafe fn local_data_get<T: Owned>(
+    key: LocalDataKey<T>) -> Option<@T> {
+
+    local_get(rustrt::rust_get_task(), key)
+}
+/**
+ * Store a value in task-local data. If this key already has a value,
+ * that value is overwritten (and its destructor is run).
+ */
+unsafe fn local_data_set<T: Owned>(
+    key: LocalDataKey<T>, +data: @T) {
+
+    local_set(rustrt::rust_get_task(), key, data)
+}
+/**
+ * Modify a task-local data value. If the function returns 'None', the
+ * data is removed (and its reference dropped).
+ */
+unsafe fn local_data_modify<T: Owned>(
+    key: LocalDataKey<T>,
+    modify_fn: fn(Option<@T>) -> Option<@T>) {
+
+    local_modify(rustrt::rust_get_task(), key, modify_fn)
+}
+
+#[test]
+fn test_tls_multitask() unsafe {
+    fn my_key(+_x: @~str) { }
+    local_data_set(my_key, @~"parent data");
+    do task::spawn unsafe {
+        assert local_data_get(my_key).is_none(); // TLS shouldn't carry over.
+        local_data_set(my_key, @~"child data");
+        assert *(local_data_get(my_key).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";
+}
+
+#[test]
+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";
+}
+
+#[test]
+fn test_tls_pop() unsafe {
+    fn my_key(+_x: @~str) { }
+    local_data_set(my_key, @~"weasel");
+    assert *(local_data_pop(my_key).get()) == ~"weasel";
+    // Pop must remove the data from the map.
+    assert local_data_pop(my_key).is_none();
+}
+
+#[test]
+fn test_tls_modify() unsafe {
+    fn my_key(+_x: @~str) { }
+    local_data_modify(my_key, |data| {
+        match data {
+            Some(@val) => fail ~"unwelcome value: " + val,
+            None       => Some(@~"first data")
+        }
+    });
+    local_data_modify(my_key, |data| {
+        match data {
+            Some(@~"first data") => Some(@~"next data"),
+            Some(@val)           => fail ~"wrong value: " + val,
+            None                 => fail ~"missing value"
+        }
+    });
+    assert *(local_data_pop(my_key).get()) == ~"next data";
+}
+
+#[test]
+fn test_tls_crust_automorestack_memorial_bug() unsafe {
+    // This might result in a stack-canary clobber if the runtime fails to set
+    // sp_limit to 0 when calling the cleanup extern - it might automatically
+    // jump over to the rust stack, which causes next_c_sp to get recorded as
+    // Something within a rust stack segment. Then a subsequent upcall (esp.
+    // for logging, think vsnprintf) would run on a stack smaller than 1 MB.
+    fn my_key(+_x: @~str) { }
+    do task::spawn {
+        unsafe { local_data_set(my_key, @~"hax"); }
+    }
+}
+
+#[test]
+fn test_tls_multiple_types() unsafe {
+    fn str_key(+_x: @~str) { }
+    fn box_key(+_x: @@()) { }
+    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);
+    }
+}
+
+#[test]
+fn test_tls_overwrite_multiple_types() {
+    fn str_key(+_x: @~str) { }
+    fn box_key(+_x: @@()) { }
+    fn int_key(+_x: @int) { }
+    do task::spawn unsafe {
+        local_data_set(str_key, @~"string data");
+        local_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);
+    }
+}
+
+#[test]
+#[should_fail]
+#[ignore(cfg(windows))]
+fn test_tls_cleanup_on_failure() unsafe {
+    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, @@());
+    do task::spawn unsafe { // spawn_linked
+        local_data_set(str_key, @~"string data");
+        local_data_set(box_key, @@());
+        local_data_set(int_key, @42);
+        fail;
+    }
+    // Not quite nondeterministic.
+    local_data_set(int_key, @31337);
+    fail;
+}
diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs
index 15b025420db..f7aa0f7555e 100644
--- a/src/libsyntax/ast.rs
+++ b/src/libsyntax/ast.rs
@@ -39,7 +39,9 @@ macro_rules! interner_key (
 )
 
 fn serialize_ident<S: Serializer>(s: S, i: ident) {
-    let intr = match unsafe{task::local_data_get(interner_key!())}{
+    let intr = match unsafe{
+        task::local_data::local_data_get(interner_key!())
+    } {
         None => fail ~"serialization: TLS interner not set up",
         Some(intr) => intr
     };
@@ -47,7 +49,9 @@ fn serialize_ident<S: Serializer>(s: S, i: ident) {
     s.emit_str(*(*intr).get(i));
 }
 fn deserialize_ident<D: Deserializer>(d: D) -> ident  {
-    let intr = match unsafe{task::local_data_get(interner_key!())}{
+    let intr = match unsafe{
+        task::local_data::local_data_get(interner_key!())
+    } {
         None => fail ~"deserialization: TLS interner not set up",
         Some(intr) => intr
     };
diff --git a/src/libsyntax/parse/token.rs b/src/libsyntax/parse/token.rs
index 570915e657f..5629907ff14 100644
--- a/src/libsyntax/parse/token.rs
+++ b/src/libsyntax/parse/token.rs
@@ -348,8 +348,12 @@ fn mk_ident_interner() -> ident_interner {
     let rv = interner::mk_prefill::<@~str>(init_vec);
 
     /* having multiple interners will just confuse the serializer */
-    unsafe{ assert task::local_data_get(interner_key!()).is_none() };
-    unsafe{ task::local_data_set(interner_key!(), @rv) };
+    unsafe {
+        assert task::local_data::local_data_get(interner_key!()).is_none()
+    };
+    unsafe {
+        task::local_data::local_data_set(interner_key!(), @rv)
+    };
     rv
 }
 
diff --git a/src/rustdoc/extract.rs b/src/rustdoc/extract.rs
index c35f59864f6..ba617b7cce6 100644
--- a/src/rustdoc/extract.rs
+++ b/src/rustdoc/extract.rs
@@ -2,10 +2,10 @@
 
 use syntax::ast;
 use doc::ItemUtils;
+use task::local_data::local_data_get;
 
 export from_srv, extract, to_str, interner;
 
-
 /* can't import macros yet, so this is copied from token.rs. See its comment
  * there. */
 macro_rules! interner_key (
@@ -16,13 +16,13 @@ macro_rules! interner_key (
 // Hack; rather than thread an interner through everywhere, rely on
 // thread-local data
 fn to_str(id: ast::ident) -> ~str {
-    let intr = unsafe{ task::local_data_get(interner_key!()) };
+    let intr = unsafe{ local_data_get(interner_key!()) };
 
     return *(*intr.get()).get(id);
 }
 
 fn interner() -> syntax::parse::token::ident_interner {
-    return *(unsafe{ task::local_data_get(interner_key!()) }).get();
+    return *(unsafe{ local_data_get(interner_key!()) }).get();
 }
 
 fn from_srv(