Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

start work on a new implementation of TLS #17583

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion mk/crates.mk
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,14 @@
TARGET_CRATES := libc std green native flate arena glob term semver \
uuid serialize sync getopts collections num test time rand \
url log regex graphviz core rbml rlibc alloc debug rustrt \
unicode
unicode tls
HOST_CRATES := syntax rustc rustdoc fourcc hexfloat regex_macros fmt_macros \
rustc_llvm rustc_back
CRATES := $(TARGET_CRATES) $(HOST_CRATES)
TOOLS := compiletest rustdoc rustc

DEPS_core :=
DEPS_tls := core
DEPS_libc := core
DEPS_rlibc := core
DEPS_unicode := core
Expand Down Expand Up @@ -115,6 +116,7 @@ ONLY_RLIB_alloc := 1
ONLY_RLIB_rand := 1
ONLY_RLIB_collections := 1
ONLY_RLIB_unicode := 1
ONLY_RLIB_tls := 1

################################################################################
# You should not need to edit below this line
Expand Down
290 changes: 290 additions & 0 deletions src/libtls/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
// Copyright 2014 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.

//! Safe thread-local storage library

// FIXME: #17572: add support for TLS variables with destructors

#![crate_name = "tls"]
#![crate_type = "rlib"]
#![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
html_favicon_url = "http://www.rust-lang.org/favicon.ico",
html_root_url = "http://doc.rust-lang.org/")]

#![no_std]
#![experimental]
#![feature(phase, macro_rules)]

#[phase(plugin, link)]
extern crate core;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it true that absolutely nothing is needed from a system libc to make this crate work? I would have expected LLVM to inject some silent dependencies, but that's pretty awesome if it works standalone!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends on a function provided by the linker in a dynamic library. In a static executable there are no external dependencies. Due to undefined behaviour in the standard library, Rust currently doesn't tell LLVM that it's not building a library so the linker call overhead isn't optimized out without -C dynamic-no-pic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback code for iOS / Android will add a dependency on libc though.


// Allow testing this library

#[cfg(test)] extern crate debug;
#[cfg(test)] extern crate native;
#[cfg(test)] #[phase(plugin, link)] extern crate std;
#[cfg(test)] #[phase(plugin, link)] extern crate log;

/// Thread local Cell with a constant initializer.
#[macro_export]
macro_rules! tls_cell(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general I'm not a huge fan of having a huge amount of code generated by macros, it's got downsides like being difficult to document, exposing implementation details, various hygiene issues, surprising access patterns, lack of macro import/export, etc.

I'm not sure if we currently have a better way of doing this, but it may be worth exploring possibilities like generating a static struct which has methods, or similar. If we have limitations which prevent that today, I'm curious what the minimum would be to get over that (if possible).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could use a type but then it would end up defining both a type name and the variable name.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking something more along the lines of this library exports a type T which the macro crates static instances of T<U>, that way you can easily see what to do with a static via the documentation of libtls (where one might expect the documentation to live).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rust isn't capable of doing it that way. It has no way to create a static from a generic type parameter.

($name:ident, $t:ty, $init:expr) => {
#[allow(dead_code)]
mod $name {
#[thread_local]
static mut VALUE: $t = $init;

#[inline(always)]
pub fn set(value: $t) {
unsafe {
VALUE = value;
}
}

#[inline(always)]
pub fn get() -> $t {
unsafe {
VALUE
}
}
}
}
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These macros will probably need a pub variant as well to allow exporting the module. The syntax may also be more intuitive with something like:

tls_cell!(FOO: int = 0)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using pub tls_cell!(...) works, although that's a bit weird.


/// Thread local Cell with a dynamic initializer.
#[macro_export]
macro_rules! tls_cell_dynamic(
($name:ident, $t:ty, $init:expr) => {
#[allow(dead_code)]
mod $name {
use core::option::{Option, None, Some};

#[thread_local]
static mut VALUE: Option<$t> = None;

#[inline]
fn init() {
if unsafe { VALUE.is_none() } {
let tmp = $init;
unsafe { VALUE = Some(tmp) }
}
}

#[inline]
pub fn set(value: $t) {
init();
unsafe {
VALUE = Some(value);
}
}

#[inline]
pub fn get() -> $t {
init();
unsafe {
VALUE.unwrap()
}
}
}
}
)

/// Thread local RefCell with a constant initializer.
#[macro_export]
macro_rules! tls_refcell(
($name:ident, $t:ty, $init:expr) => {
#[allow(dead_code)]
mod $name {
use core::cell::{RefCell, Ref, RefMut, UnsafeCell};
use core::kinds::marker;
use core::option::Option;

// no way to ignore privacy
type BorrowFlag = uint;
static UNUSED: BorrowFlag = 0;

// no way to ignore privacy
pub struct NotCell<T> {
value: UnsafeCell<T>,
noshare: marker::NoSync,
}

// no way to ignore privacy
struct NotRefCell<T> {
value: UnsafeCell<T>,
borrow: NotCell<BorrowFlag>,
nocopy: marker::NoCopy,
noshare: marker::NoSync,
}

// cannot call RefCell::new in a constant expression
#[thread_local]
static mut VALUE: NotRefCell<$t> = NotRefCell {
value: UnsafeCell { value: $init },
borrow: NotCell {
value: UnsafeCell { value: UNUSED },
noshare: marker::NoSync
},
nocopy: marker::NoCopy,
noshare: marker::NoSync
};

#[inline]
pub fn try_borrow() -> Option<Ref<'static, $t>> {
unsafe {
let ptr: &RefCell<$t> = ::core::mem::transmute(&VALUE);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to the compiler being able to re-order struct fields, I don't think that this is a valid cast.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same thing applies to all of the types in core::raw. There are missing features making it impossible to avoid relying on an implementation detail like this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The types in core::raw likely need #[repr(C)], but no matter what this is still a problem with this code, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is the lack of a way to initialize RefCell in a static variable. This code is working around that problem by doing something that's currently perfectly safe - assuming that while the struct representation is not defined, it will not vary for a type defined a certain way. It's hardly the only unsafe code relying on guarantees that are not explicitly stated anywhere. If someone wants to add a feature like field randomization, then they need to improve the compiler implementation to avoid needing hacks like this. Support for unsafe fields would do it, as would the ability to override privacy in unsafe code - we already allow overriding privacy in safe code via {:?}.

ptr.try_borrow()
}
}

#[inline]
pub fn borrow() -> Ref<'static, $t> {
unsafe {
let ptr: &RefCell<$t> = ::core::mem::transmute(&VALUE);
ptr.borrow()
}
}

#[inline]
pub fn try_borrow_mut() -> Option<RefMut<'static, $t>> {
unsafe {
let ptr: &mut RefCell<$t> = ::core::mem::transmute(&mut VALUE);
ptr.try_borrow_mut()
}
}

#[inline]
pub fn borrow_mut() -> RefMut<'static, $t> {
unsafe {
let ptr: &mut RefCell<$t> = ::core::mem::transmute(&mut VALUE);
ptr.borrow_mut()
}
}
}
}
)

/// Thread local RefCell with a dynamic initializer.
#[macro_export]
macro_rules! tls_refcell_dynamic(
($name:ident, $t:ty, $init:expr) => {
#[allow(dead_code)]
mod $name {
use core::cell::{RefCell, Ref, RefMut};
use core::option::{Option, None, Some};

#[thread_local]
static mut VALUE: Option<RefCell<$t>> = None;

#[inline]
fn init() {
if unsafe { VALUE.is_none() } {
let tmp = $init;
unsafe { VALUE = Some(RefCell::new(tmp)) }
}
}

#[inline]
pub fn try_borrow() -> Option<Ref<'static, $t>> {
init();
unsafe {
VALUE.as_ref().unwrap().try_borrow()
}
}

#[inline]
pub fn borrow() -> Ref<'static, $t> {
init();
unsafe {
VALUE.as_ref().unwrap().borrow()
}
}

#[inline]
pub fn try_borrow_mut() -> Option<RefMut<'static, $t>> {
init();
unsafe {
VALUE.as_mut().unwrap().try_borrow_mut()
}
}

#[inline]
pub fn borrow_mut() -> RefMut<'static, $t> {
init();
unsafe {
VALUE.as_mut().unwrap().borrow_mut()
}
}
}
}
)

// FIXME: #17579: need a fallback path on iOS and Android for now
#[cfg(all(test, not(target_os = "android"), not(target_os = "ios")))]
mod tests {
use core::iter::range;
use std::task::spawn;

fn five() -> u32 {
5
}

#[test]
fn basic_tls_cell() {
tls_cell!(a, u32, 5)

for _ in range(0, 10u) {
spawn(proc() {
assert_eq!(a::get(), 5);
a::set(10);
assert_eq!(a::get(), 10);
});
}
}

#[test]
fn basic_tls_cell_dynamic() {
tls_cell_dynamic!(b, u32, super::five())

for _ in range(0, 10u) {
spawn(proc() {
assert_eq!(b::get(), 5);
b::set(10);
assert_eq!(b::get(), 10);
});
}
}

#[test]
fn basic_tls_refcell() {
tls_refcell!(c, u32, 5)

for _ in range(0, 10u) {
spawn(proc() {
assert_eq!(*c::borrow(), 5);
*c::borrow_mut() = 10;
assert_eq!(*c::borrow(), 10);
});
}
}

#[test]
fn basic_tls_refcell_dynamic() {
tls_refcell_dynamic!(d, u32, super::five())

for _ in range(0, 10u) {
spawn(proc() {
assert_eq!(*d::borrow(), 5);
*d::borrow_mut() = 10;
assert_eq!(*d::borrow(), 10);
});
}
}
}