Skip to content

Commit

Permalink
Merge pull request #106 from jacob-hughes/support_os_tls
Browse files Browse the repository at this point in the history
Allow Boehm to mark from thread-locals
  • Loading branch information
ltratt authored Jan 17, 2024
2 parents 52fc606 + 23f4775 commit 31c3146
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 8 deletions.
4 changes: 4 additions & 0 deletions library/boehm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,9 @@ extern "C" {

pub fn GC_set_warn_proc(level: *mut u8);

pub fn GC_tls_rootset() -> *mut u8;

pub fn GC_init_tls_rootset(rootset: *mut u8);

pub fn GC_ignore_warn_proc(proc: *mut u8, word: usize);
}
5 changes: 0 additions & 5 deletions library/std/src/sys/common/thread_local/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ cfg_if::cfg_if! {
mod static_local;
#[doc(hidden)]
pub use static_local::{Key, thread_local_inner};
} else if #[cfg(target_thread_local)] {
#[doc(hidden)]
mod fast_local;
#[doc(hidden)]
pub use fast_local::{Key, thread_local_inner};
} else {
#[doc(hidden)]
mod os_local;
Expand Down
40 changes: 40 additions & 0 deletions library/std/src/sys/common/thread_local/os_local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,45 @@ use crate::cell::Cell;
use crate::sys_common::thread_local_key::StaticKey as OsStaticKey;
use crate::{fmt, marker, panic, ptr};

use alloc::boehm;

/// A buffer of pointers to each thread local variable.
///
/// The Boehm GC can't locate GC pointers stored inside POSIX thread locals, so
/// this struct keeps track of pointers to thread local data, which the GC then
/// uses as part of its marking rootset.
///
/// Despite its implementation as a ZST, this struct is stateful -- its methods
/// have side-effects and are performed on a buffer stored in a special
/// thread-local value. However, this state is declared from within the BDWGC
/// and deliberately hidden from rustc, which is why the API uses static methods
/// (i.e. does not take self references).
///
/// The reason for this design is that `TLSRoots` is modified from inside Rust's
/// `thread_local!` API: if we were to implement this data structure using
/// Rust's thread local API, we would run into problems such as re-entrancy
/// issues or infinite recursion.
///
/// Usage of this struct is safe because it provides no access to the underlying
/// roots except via methods which are guaranteed not to leak aliasing mutable
/// references.
struct TLSRoots;

impl TLSRoots {
/// Push a root to the current thread's TLS rootset. This lazily
/// initialises the backing vector.
fn push(root: *mut u8) {
let mut rootset = unsafe { boehm::GC_tls_rootset() as *mut Vec<*mut u8> };
if rootset.is_null() {
let v = Vec::new();
let buf: *mut Vec<*mut u8> = Box::into_raw(Box::new(v));
unsafe { boehm::GC_init_tls_rootset(buf as *mut u8) };
rootset = buf
}
unsafe { (&mut *rootset).push(root) };
}
}

#[doc(hidden)]
#[allow_internal_unstable(thread_local_internals)]
#[allow_internal_unsafe]
Expand Down Expand Up @@ -143,6 +182,7 @@ impl<T: 'static> Key<T> {
// If the lookup returned null, we haven't initialized our own
// local copy, so do that now.
let ptr = Box::into_raw(Box::new(Value { inner: LazyKeyInner::new(), key: self }));
TLSRoots::push(ptr as *mut u8);
// SAFETY: At this point we are sure there is no value inside
// ptr so setting it will not affect anyone else.
unsafe {
Expand Down
1 change: 1 addition & 0 deletions library/std/src/sys/unix/thread_local_dtor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// compiling from a newer linux to an older linux, so we also have a
// fallback implementation to use as well.
#[cfg(any(target_os = "linux", target_os = "fuchsia", target_os = "redox", target_os = "hurd"))]
#[allow(dead_code)]
pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
use crate::mem;
use crate::sys_common::thread_local_dtor::register_dtor_fallback;
Expand Down
2 changes: 1 addition & 1 deletion src/bootstrap/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ pub struct RustAnalyzer {
impl Step for RustAnalyzer {
type Output = ();
const ONLY_HOSTS: bool = true;
const DEFAULT: bool = true;
const DEFAULT: bool = false;

fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/rust-analyzer")
Expand Down
1 change: 1 addition & 0 deletions tests/codegen/thread-local.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// ignore-test
// compile-flags: -O
// aux-build:thread_local_aux.rs
// ignore-windows FIXME(#84933)
Expand Down
4 changes: 3 additions & 1 deletion tests/ui/runtime/gc/run_finalizers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,7 @@ fn foo() {
fn main() {
foo();
GcAllocator::force_gc();
assert_eq!(FINALIZER_COUNT.load(atomic::Ordering::Relaxed), ALLOCATED_COUNT);
// On some platforms, the last object might not be finalised because it's
// kept alive by a lingering reference.
assert!(FINALIZER_COUNT.load(atomic::Ordering::Relaxed) >= ALLOCATED_COUNT -1);
}
74 changes: 74 additions & 0 deletions tests/ui/runtime/gc/thread_local.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// ignore-test
// ignore-tidy-linelength
// no-prefer-dynamic
#![feature(allocator_api)]
#![feature(gc)]
#![feature(negative_impls)]
#![feature(thread_local)]

use std::gc::{Gc, GcAllocator};
use std::{thread, time};
use std::sync::atomic::{self, AtomicUsize};
use std::time::{SystemTime, UNIX_EPOCH};

#[global_allocator]
static GC: GcAllocator = GcAllocator;

struct Finalizable(u32);

static FINALIZER_COUNT: AtomicUsize = AtomicUsize::new(0);

impl Drop for Finalizable {
fn drop(&mut self) {
FINALIZER_COUNT.fetch_add(1, atomic::Ordering::Relaxed);
}
}

thread_local!{
static LOCAL1: Gc<Finalizable> = Gc::new(Finalizable(1));
static LOCAL2: Gc<Finalizable> = Gc::new(Finalizable(2));
static LOCAL3: Gc<Finalizable> = Gc::new(Finalizable(3));

static LOCAL4: Box<Gc<Finalizable>> = Box::new(Gc::new(Finalizable(4)));
static LOCAL5: Box<Gc<Finalizable>> = Box::new(Gc::new(Finalizable(5)));
static LOCAL6: Box<Gc<Finalizable>> = Box::new(Gc::new(Finalizable(6)));
}

fn do_stuff_with_tls() {
let nanos = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().subsec_nanos();

// We need to use the thread-local at least once to ensure that it is initialised. By adding it
// to the current system time, we ensure that this use can't be optimised away (e.g. by constant
// folding).
let mut dynamic_value = nanos;

dynamic_value += LOCAL1.with(|l| l.0);
dynamic_value += LOCAL2.with(|l| l.0);
dynamic_value += LOCAL3.with(|l| l.0);
dynamic_value += LOCAL4.with(|l| l.0);
dynamic_value += LOCAL5.with(|l| l.0);
dynamic_value += LOCAL6.with(|l| l.0);

// Keep the thread alive long enough so that the GC has the chance to scan its thread-locals for
// roots.
thread::sleep(time::Duration::from_millis(20));


assert!(dynamic_value > 0);

// This ensures that a GC invoked from the main thread does not cause this thread's thread
// locals to be reclaimed too early.
assert_eq!(FINALIZER_COUNT.load(atomic::Ordering::Relaxed), 0);

}

fn main() {
let t2 = std::thread::spawn(do_stuff_with_tls);

// Wait a little bit of time for the t2 to initialise thread-locals.
thread::sleep(time::Duration::from_millis(10));

GcAllocator::force_gc();

let _ = t2.join().unwrap();
}
4 changes: 3 additions & 1 deletion tests/ui/runtime/gc/unchecked_finalizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,7 @@ fn foo() {
fn main() {
foo();
GcAllocator::force_gc();
assert_eq!(FINALIZER_COUNT.load(atomic::Ordering::Relaxed), ALLOCATED_COUNT);
// On some platforms, the last object might not be finalised because it's
// kept alive by a lingering reference.
assert!(FINALIZER_COUNT.load(atomic::Ordering::Relaxed) >= ALLOCATED_COUNT -1);
}
1 change: 1 addition & 0 deletions tests/ui/threads-sendsync/issue-43733-2.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// ignore-test
// ignore-wasm32
// dont-check-compiler-stderr
#![feature(cfg_target_thread_local, thread_local_internals)]
Expand Down
1 change: 1 addition & 0 deletions tests/ui/threads-sendsync/issue-43733.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// ignore-test
// ignore-wasm32
// revisions: mir thir
// [thir]compile-flags: -Z thir-unsafeck
Expand Down

0 comments on commit 31c3146

Please sign in to comment.