Skip to content

Commit 0b44bb4

Browse files
committed
---
yaml --- r: 13745 b: refs/heads/master c: 1ff6f9b h: refs/heads/master i: 13743: c4c2db0 v: v3
1 parent d0cc0ed commit 0b44bb4

File tree

3 files changed

+309
-2
lines changed

3 files changed

+309
-2
lines changed

[refs]

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
refs/heads/master: 1ba3028d8b7acb0c97859ea438f2beb4ccd364f9
2+
refs/heads/master: 1ff6f9b876f8024617ef002a5276199545cea96a
33
refs/heads/snap-stage1: e33de59e47c5076a89eadeb38f4934f58a3618a6
44
refs/heads/snap-stage3: 4a81779abd786ff22d71434c6d9a5917ea4cdfff
55
refs/heads/try: 2898dcc5d97da9427ac367542382b6239d9c0bbf

trunk/src/libcore/task.rs

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ spawn {||
2323
"];
2424

2525
import result::result;
26+
import dvec::extensions;
2627

2728
export task;
2829
export task_result;
@@ -53,6 +54,12 @@ export failing;
5354
export get_task;
5455
export unkillable;
5556

57+
export local_data_key;
58+
export local_data_pop;
59+
export local_data_get;
60+
export local_data_set;
61+
export local_data_modify;
62+
5663
/* Data types */
5764

5865
#[doc = "A handle to a task"]
@@ -573,6 +580,187 @@ fn spawn_raw(opts: task_opts, +f: fn~()) {
573580

574581
}
575582

583+
/****************************************************************************
584+
* Task local data management
585+
*
586+
* Allows storing boxes with arbitrary types inside, to be accessed anywhere
587+
* within a task, keyed by a pointer to a global finaliser function. Useful
588+
* for task-spawning metadata (tracking linked failure state), dynamic
589+
* variables, and interfacing with foreign code with bad callback interfaces.
590+
*
591+
* To use, declare a monomorphic global function at the type to store, and use
592+
* it as the 'key' when accessing. See the 'tls' tests below for examples.
593+
*
594+
* Casting 'Arcane Sight' reveals an overwhelming aura of Transmutation magic.
595+
****************************************************************************/
596+
597+
#[doc = "Indexes a task-local data slot. The function itself is used to
598+
automatically finalise stored values; also, its code pointer is used for
599+
comparison. Recommended use is to write an empty function for each desired
600+
task-local data slot (and use class destructors, instead of code inside the
601+
finaliser, if specific teardown is needed). DO NOT use multiple instantiations
602+
of a single polymorphic function to index data of different types; arbitrary
603+
type coercion is possible this way. The interface is safe as long as all key
604+
functions are monomorphic."]
605+
type local_data_key<T> = fn@(+@T);
606+
607+
// We use dvec because it's the best data structure in core. If TLS is used
608+
// heavily in future, this could be made more efficient with a proper map.
609+
type task_local_element = (*libc::c_void, *libc::c_void, fn@(+*libc::c_void));
610+
// Has to be a pointer at the outermost layer; the native call returns void *.
611+
type task_local_map = @dvec::dvec<option<task_local_element>>;
612+
613+
crust fn cleanup_task_local_map(map_ptr: *libc::c_void) unsafe {
614+
assert !map_ptr.is_null();
615+
// Get and keep the single reference that was created at the beginning.
616+
let map: task_local_map = unsafe::reinterpret_cast(map_ptr);
617+
for (*map).each {|entry|
618+
alt entry {
619+
// Finaliser drops data. We drop the finaliser implicitly here.
620+
some((_key, data, finalise_fn)) { finalise_fn(data); }
621+
none { }
622+
}
623+
}
624+
}
625+
626+
// Gets the map from the runtime. Lazily initialises if not done so already.
627+
unsafe fn get_task_local_map(task: *rust_task) -> task_local_map {
628+
// Relies on the runtime initialising the pointer to null.
629+
// NOTE: The map's box lives in TLS invisibly referenced once. Each time
630+
// we retrieve it for get/set, we make another reference, which get/set
631+
// drop when they finish. No "re-storing after modifying" is needed.
632+
let map_ptr = rustrt::rust_get_task_local_data(task);
633+
if map_ptr.is_null() {
634+
let map: task_local_map = @dvec::dvec();
635+
// Use reinterpret_cast -- transmute would take map away from us also.
636+
rustrt::rust_set_task_local_data(task, unsafe::reinterpret_cast(map));
637+
rustrt::rust_task_local_data_atexit(task, cleanup_task_local_map);
638+
// Also need to reference it an extra time to keep it for now.
639+
unsafe::bump_box_refcount(map);
640+
map
641+
} else {
642+
let map = unsafe::transmute(map_ptr);
643+
unsafe::bump_box_refcount(map);
644+
map
645+
}
646+
}
647+
648+
unsafe fn key_to_key_value<T>(key: local_data_key<T>) -> *libc::c_void {
649+
// Keys are closures, which are (fnptr,envptr) pairs. Use fnptr.
650+
// Use reintepret_cast -- transmute would leak (forget) the closure.
651+
let pair: (*libc::c_void, *libc::c_void) = unsafe::reinterpret_cast(key);
652+
tuple::first(pair)
653+
}
654+
655+
// If returning some(..), returns with @T with the map's reference. Careful!
656+
unsafe fn local_data_lookup<T>(map: task_local_map, key: local_data_key<T>)
657+
-> option<(uint, *libc::c_void, fn@(+*libc::c_void))> {
658+
let key_value = key_to_key_value(key);
659+
let map_pos = (*map).position {|entry|
660+
alt entry { some((k,_,_)) { k == key_value } none { false } }
661+
};
662+
map_pos.map {|index|
663+
// .get() is guaranteed because of "none { false }" above.
664+
let (_, data_ptr, finaliser) = (*map)[index].get();
665+
(index, data_ptr, finaliser)
666+
}
667+
}
668+
669+
unsafe fn local_get_helper<T>(task: *rust_task, key: local_data_key<T>,
670+
do_pop: bool) -> option<@T> {
671+
let map = get_task_local_map(task);
672+
// Interpret our findings from the map
673+
local_data_lookup(map, key).map {|result|
674+
// A reference count magically appears on 'data' out of thin air.
675+
// 'data' has the reference we originally stored it with. We either
676+
// need to erase it from the map or artificially bump the count.
677+
let (index, data_ptr, _) = result;
678+
let data: @T = unsafe::transmute(data_ptr);
679+
if do_pop {
680+
(*map).set_elt(index, none);
681+
} else {
682+
unsafe::bump_box_refcount(data);
683+
}
684+
data
685+
}
686+
}
687+
688+
unsafe fn local_pop<T>(task: *rust_task,
689+
key: local_data_key<T>) -> option<@T> {
690+
local_get_helper(task, key, true)
691+
}
692+
693+
unsafe fn local_get<T>(task: *rust_task,
694+
key: local_data_key<T>) -> option<@T> {
695+
local_get_helper(task, key, false)
696+
}
697+
698+
unsafe fn local_set<T>(task: *rust_task, key: local_data_key<T>, -data: @T) {
699+
let map = get_task_local_map(task);
700+
// Store key+data as *voids. Data is invisibly referenced once; key isn't.
701+
let keyval = key_to_key_value(key);
702+
let data_ptr = unsafe::transmute(data);
703+
// Finaliser is called at task exit to de-reference up remaining entries.
704+
let finaliser: fn@(+*libc::c_void) = unsafe::reinterpret_cast(key);
705+
// Construct new entry to store in the map.
706+
let new_entry = some((keyval, data_ptr, finaliser));
707+
// Find a place to put it.
708+
alt local_data_lookup(map, key) {
709+
some((index, old_data_ptr, old_finaliser)) {
710+
// Key already had a value set, old_data_ptr, whose reference we
711+
// need to drop. After that, overwriting its slot will be safe.
712+
// (The heap-allocated finaliser will be freed in the overwrite.)
713+
// FIXME(2734): just transmuting old_data_ptr to @T doesn't work,
714+
// similarly to the sample there (but more our/unsafety's fault?).
715+
old_finaliser(old_data_ptr);
716+
(*map).set_elt(index, new_entry);
717+
}
718+
none {
719+
// Find an empty slot. If not, grow the vector.
720+
alt (*map).position({|x| x == none}) {
721+
some(empty_index) {
722+
(*map).set_elt(empty_index, new_entry);
723+
}
724+
none {
725+
(*map).push(new_entry);
726+
}
727+
}
728+
}
729+
}
730+
}
731+
732+
unsafe fn local_modify<T>(task: *rust_task, key: local_data_key<T>,
733+
modify_fn: fn(option<@T>) -> option<@T>) {
734+
// Could be more efficient by doing the lookup work, but this is easy.
735+
let newdata = modify_fn(local_pop(task, key));
736+
if newdata.is_some() {
737+
local_set(task, key, option::unwrap(newdata));
738+
}
739+
}
740+
741+
/* Exported interface for task-local data (plus local_data_key above). */
742+
#[doc = "Remove a task-local data value from the table, returning the
743+
reference that was originally created to insert it."]
744+
unsafe fn local_data_pop<T>(key: local_data_key<T>) -> option<@T> {
745+
local_pop(rustrt::rust_get_task(), key)
746+
}
747+
#[doc = "Retrieve a task-local data value. It will also be kept alive in the
748+
table until explicitly removed."]
749+
unsafe fn local_data_get<T>(key: local_data_key<T>) -> option<@T> {
750+
local_get(rustrt::rust_get_task(), key)
751+
}
752+
#[doc = "Store a value in task-local data. If this key already has a value,
753+
that value is overwritten (and its destructor is run)."]
754+
unsafe fn local_data_set<T>(key: local_data_key<T>, -data: @T) {
755+
local_set(rustrt::rust_get_task(), key, data)
756+
}
757+
#[doc = "Modify a task-local data value. If the function returns 'none', the
758+
data is removed (and its reference dropped)."]
759+
unsafe fn local_data_modify<T>(key: local_data_key<T>,
760+
modify_fn: fn(option<@T>) -> option<@T>) {
761+
local_modify(rustrt::rust_get_task(), key, modify_fn)
762+
}
763+
576764
native mod rustrt {
577765
#[rust_stack]
578766
fn rust_task_yield(task: *rust_task, &killed: bool);
@@ -596,6 +784,13 @@ native mod rustrt {
596784
fn rust_osmain_sched_id() -> sched_id;
597785
fn rust_task_inhibit_kill();
598786
fn rust_task_allow_kill();
787+
788+
#[rust_stack]
789+
fn rust_get_task_local_data(task: *rust_task) -> *libc::c_void;
790+
#[rust_stack]
791+
fn rust_set_task_local_data(task: *rust_task, map: *libc::c_void);
792+
#[rust_stack]
793+
fn rust_task_local_data_atexit(task: *rust_task, cleanup_fn: *u8);
599794
}
600795

601796

@@ -997,3 +1192,94 @@ fn test_unkillable() {
9971192
// Now we can be killed
9981193
po.recv();
9991194
}
1195+
1196+
#[test]
1197+
fn test_tls_multitask() unsafe {
1198+
fn my_key(+_x: @str) { }
1199+
local_data_set(my_key, @"parent data");
1200+
task::spawn {||
1201+
assert local_data_get(my_key) == none; // TLS shouldn't carry over.
1202+
local_data_set(my_key, @"child data");
1203+
assert *(local_data_get(my_key).get()) == "child data";
1204+
// should be cleaned up for us
1205+
}
1206+
// Must work multiple times
1207+
assert *(local_data_get(my_key).get()) == "parent data";
1208+
assert *(local_data_get(my_key).get()) == "parent data";
1209+
assert *(local_data_get(my_key).get()) == "parent data";
1210+
}
1211+
1212+
#[test]
1213+
fn test_tls_overwrite() unsafe {
1214+
fn my_key(+_x: @str) { }
1215+
local_data_set(my_key, @"first data");
1216+
local_data_set(my_key, @"next data"); // Shouldn't leak.
1217+
assert *(local_data_get(my_key).get()) == "next data";
1218+
}
1219+
1220+
#[test]
1221+
fn test_tls_pop() unsafe {
1222+
fn my_key(+_x: @str) { }
1223+
local_data_set(my_key, @"weasel");
1224+
assert *(local_data_pop(my_key).get()) == "weasel";
1225+
// Pop must remove the data from the map.
1226+
assert local_data_pop(my_key) == none;
1227+
}
1228+
1229+
#[test]
1230+
fn test_tls_modify() unsafe {
1231+
fn my_key(+_x: @str) { }
1232+
local_data_modify(my_key) {|data|
1233+
alt data {
1234+
some(@val) { fail "unwelcome value: " + val }
1235+
none { some(@"first data") }
1236+
}
1237+
}
1238+
local_data_modify(my_key) {|data|
1239+
alt data {
1240+
some(@"first data") { some(@"next data") }
1241+
some(@val) { fail "wrong value: " + val }
1242+
none { fail "missing value" }
1243+
}
1244+
}
1245+
assert *(local_data_pop(my_key).get()) == "next data";
1246+
}
1247+
1248+
#[test]
1249+
fn test_tls_crust_automorestack_memorial_bug() unsafe {
1250+
// This might result in a stack-canary clobber if the runtime fails to set
1251+
// sp_limit to 0 when calling the cleanup crust - it might automatically
1252+
// jump over to the rust stack, which causes next_c_sp to get recorded as
1253+
// something within a rust stack segment. Then a subsequent upcall (esp.
1254+
// for logging, think vsnprintf) would run on a stack smaller than 1 MB.
1255+
fn my_key(+_x: @str) { }
1256+
task::spawn {||
1257+
unsafe { local_data_set(my_key, @"hax"); }
1258+
}
1259+
}
1260+
1261+
#[test]
1262+
fn test_tls_multiple_types() unsafe {
1263+
fn str_key(+_x: @str) { }
1264+
fn box_key(+_x: @@()) { }
1265+
fn int_key(+_x: @int) { }
1266+
task::spawn{||
1267+
local_data_set(str_key, @"string data");
1268+
local_data_set(box_key, @@());
1269+
local_data_set(int_key, @42);
1270+
}
1271+
}
1272+
1273+
#[test]
1274+
fn test_tls_overwrite_multiple_types() unsafe {
1275+
fn str_key(+_x: @str) { }
1276+
fn box_key(+_x: @@()) { }
1277+
fn int_key(+_x: @int) { }
1278+
task::spawn{||
1279+
local_data_set(str_key, @"string data");
1280+
local_data_set(int_key, @42);
1281+
// This could cause a segfault if overwriting-destruction is done with
1282+
// the crazy polymorphic transmute rather than the provided finaliser.
1283+
local_data_set(int_key, @31337);
1284+
}
1285+
}

trunk/src/libcore/unsafe.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#[doc = "Unsafe operations"];
22

3-
export reinterpret_cast, forget, transmute;
3+
export reinterpret_cast, forget, bump_box_refcount, transmute;
44

55
#[abi = "rust-intrinsic"]
66
native mod rusti {
@@ -27,6 +27,12 @@ reinterpret_cast on managed pointer types.
2727
#[inline(always)]
2828
unsafe fn forget<T>(-thing: T) { rusti::forget(thing); }
2929

30+
#[doc = "Force-increment the reference count on a shared box. If used
31+
uncarefully, this can leak the box. Use this in conjunction with transmute
32+
and/or reinterpret_cast when such calls would otherwise scramble a box's
33+
reference count"]
34+
unsafe fn bump_box_refcount<T>(+t: @T) { forget(t); }
35+
3036
#[doc = "
3137
Transform a value of one type into a value of another type.
3238
Both types must have the same size and alignment.
@@ -49,6 +55,21 @@ mod tests {
4955
assert unsafe { reinterpret_cast(1) } == 1u;
5056
}
5157

58+
#[test]
59+
fn test_bump_box_refcount() {
60+
unsafe {
61+
let box = @"box box box"; // refcount 1
62+
bump_box_refcount(box); // refcount 2
63+
let ptr: *int = transmute(box); // refcount 2
64+
let _box1: @str = reinterpret_cast(ptr);
65+
let _box2: @str = reinterpret_cast(ptr);
66+
assert *_box1 == "box box box";
67+
assert *_box2 == "box box box";
68+
// Will destroy _box1 and _box2. Without the bump, this would
69+
// use-after-free. With too many bumps, it would leak.
70+
}
71+
}
72+
5273
#[test]
5374
fn test_transmute() {
5475
unsafe {

0 commit comments

Comments
 (0)