Change:
Right now access to thread locals defined by aren't inlined across crates, causing performance problems that wouldn't otherwise be seen within one crate. This can probably be solved with a few new minor language features: First, the annotation could be processed on variables. If the variable does not have any internal mutability, then the definition can be inlined into other LLVM modules and tagged with . That means that the contents are available for optimization, but if you're taking the address it's available elsewhere. Second, when we inline the contents of a static across modules, function pointers should also be chased and inlined if they're tagged with . Those two pieces I believe should provide enough inlining opportunities to ensure that accesses are as fast when done from external crates as they are done with internal crates. This hurts performance for the locks in , as they call (which just reads a thread-local). For uncontended locks the cost is quite significant. There are two problems: isn't marked inline. This is trivial to solve. a goes through function pointers, which LLVM fails to see through. These are the functions in . Consider these two files: gets the following IR with everything compiled with full optimization. Note the call through a function pointer:
can you quantify the cost that you are seeing? For example this is what I get: When inlining everything with LTO the costs basically entirely go away, and when calling across the library boundary (note that there's no virtual call, and this is intentional!) the cost is quite negligible. I definitely agree that the current design requires a virtual call when inlined across crates, and I've experimented with tweaking it such that this is not necessary, but I don't think that it's easily possible due to various safety concerns. I just want to get an idea of what perf hit you're seeing.
wouldn't this be solved by marking the static containing the accessors, and the accessors themselves as ? AFAIK, we should still be generating only one global for the inner static.
Functionally, yes, but I don't think that does anything on globals today, so that's something that would have to be implemented. This would not be fixed by just adding to the function because it's only accessed through the static itself, whose value isn't currently inlined.
oh, I could've sworn I've seen before, is it silently ignored? Another option would be to switch to and turn on rvalue promotion.
I believe it is being silently ignored, yes. This may be possible, but the semantics of ELF-like TLS and OS-based TLS should be the same, and OS-based TLS requires a somewhere in some form.
the OS-based TLS is a hidden implementation detail, it would still be there and a reference to it would be returned by the accessor function.
It's somewhat of an implementation detail, but there still needs to be one true address of the TLS static. Right now a which is just the address of something is still re-translated in downstream crates, which could create duplicate keys.
just the pair of accessors would be , and that's not going to affect anything. It is a compatibility issue, though, because it allows which you can't do with a . So then, should be implemented for statics? IIRC I saw it being used in output.
At some point in the past I believe did something on statics, but that has since been removed. And also, yes, I want to make sure the semantics are the same regardless of platform, so if one requires a they should both use a static.
I believe we are talking past each other: I'm not suggesting we touch the platform-dependent hidden static at all. There is a truly immutable "static" (without a significant address, either) being used as a , and that is what needs to be inlined across crates. As it stands, we allow taking borrows and sending them across threads (because it's just a safe getter). That makes it backwards incompatible to remove the indirection of the getter, so that is out of the question (maybe not for , though). I would personally prefer using , methods taking (by-value), and storing a in the non- case (instead of a getter), but not all of that is doable now.
Could you elaborate on why you think this doesn't have a significant address? For OS-based TLS it needs a significant address as it mutates the memory and everyone needs to see the update. For ELF-based TLS it's also significant because of the mutations which need to be visible (and everyone needs to reference the same memory). Right, but we just need to inline the getter into destination crates, probably via an static which is marked constant in LLVM. This getter is trivially inlineable, so it's just a codegen problem to overcome. Unfortunately due to the current usage of , I think this is out of the question.
Oh another thing is that a is not allowed to reference the address of a :
Okay, is really out of the way, too many issues. As for the other thing, I'm talking about the outer holding which is a pair of accessors (the "initializer" and "getter" function pointers). For both OS-based and TLS, there is a hidden holding the , which does indeed have a significant address and interior mutability. I was never_ talking about that one in this thread. That doesn't need to be changed, it's perfect for all I care.
I've updated the description of this issue with a more detailed explanation about what I believe the solution here is (basically just a summary of the discussion and I had a year ago I believe)
Just how fast can TLS get? I thought that on x86-64 it could indirect off of a segment register or something like that, and be really fast.
Hi, any consensus on this one ?
no change to the updated description, which I believe is still the best solution
Rvalue promotion is getting close to stabilization (), so we'll use that + . The other concerns raised in the past seem inconsequential now - allowing references to s in s will be trivial once we start using , for example - there's nothing wrong with it, we can even allow reading a (non-!) from a - it'd just observe the initializer.
So this issue should be repurposed to address , I guess? Cc
I agree. We could try again now that we upgraded LLVM.
I'm writing an application whose performance is impacted by the fact that TLS accesses are not generated using a segment register, but go through a function call and are then not subject to usual optimizations like CSE. I found this issue that seems the culprit. It looks like fixed the problem for Linux and Mac but not Windows (as far as I can tell?). Given that 9 months are passed, would it be acceptable to refresh and activate it only for Linux/Mac?

Discussion:
macro_rules! __thread_local_inner { // used to generate the `LocalKey` value for const-initialized thread locals (@key $t:ty, const $init:expr) => {{ <ins> #[cfg_attr(not(target_env = "msvc"), inline)] // see comments below </ins> unsafe fn __getit() -> $crate::option::Option<&'static $t> { const _REQUIRE_UNSTABLE: () = $crate::thread::require_unstable_const_init_thread_local();

#[inline] fn __init() -> $t { $init } <ins> // When reading this function you might ask "why is this inlined // everywhere other than MSVC?", and that's a very reasonable // question to ask. The short story is that it segfaults rustc if // this function is inlined. The longer story is that MSVC looks to // not support `extern` references to thread locals across DLL // boundaries. This appears to at least not be supported in the ABI // that LLVM implements. // // Because of this we never inline on MVSC, but we do inline on // other platforms (where external references to thread locals // across DLLs are supported). A better fix for this would be to // inline this function on MSVC, but only for "statically linked" // components. For example if two separately compiled rlibs end up // getting linked into a DLL then it's fine to inline this function // across that boundary. It's only not fine to inline this function // across a DLL boundary. Unfortunately rustc doesn't currently have // this sort of logic available in an attribute, and it's not clear // that rustc is even equipped to answer this (it's more of a Cargo // question kinda). This means that, unfortunately, MSVC gets the // pessimistic path for now where it's never inlined. // // The issue of "should enable on MSVC sometimes" is #84933 #[cfg_attr(not(target_env = "msvc"), inline)] </ins> unsafe fn __getit() -> $crate::option::Option<&'static $t> { #[cfg(all(target_arch = "wasm32", not(target_feature = "atomics")))] static __KEY: $crate::thread::__StaticLocalKeyInner<$t> =

<ins> #![crate_type = "lib"] #![feature(thread_local_const_init)] use std::cell::Cell; thread_local!(pub static A: Cell<u64> = const { Cell::new(0) }); </ins>

<ins> // compile-flags: -O // aux-build:thread_local_aux.rs // ignore-windows FIXME(#84933) // ignore-wasm globals are used instead of thread locals // ignore-emscripten globals are used instead of thread locals // ignore-android does not use #[thread_local] #![crate_type = "lib"] #![feature(thread_local_const_init)] extern crate thread_local_aux as aux; use std::cell::Cell; thread_local!(static A: Cell<u32> = const { Cell::new(1) }); // CHECK: [[TLS_AUX:@.+]] = external thread_local local_unnamed_addr global i64 // CHECK: [[TLS:@.+]] = internal thread_local unnamed_addr global // CHECK-LABEL: @get #[no_mangle] fn get() -> u32 { // CHECK: %0 = load i32, i32* bitcast ({{.*}} [[TLS]] to i32*) // CHECK-NEXT: ret i32 %0 A.with(|a| a.get()) } // CHECK-LABEL: @set #[no_mangle] fn set(v: u32) { // CHECK: store i32 %0, i32* bitcast ({{.*}} [[TLS]] to i32*) // CHECK-NEXT: ret void A.with(|a| a.set(v)) } // CHECK-LABEL: @get_aux #[no_mangle] fn get_aux() -> u64 { // CHECK: %0 = load i64, i64* [[TLS_AUX]] // CHECK-NEXT: ret i64 %0 aux::A.with(|a| a.get()) } // CHECK-LABEL: @set_aux #[no_mangle] fn set_aux(v: u64) { // CHECK: store i64 %0, i64* [[TLS_AUX]] // CHECK-NEXT: ret void aux::A.with(|a| a.set(v)) } </ins>