<h2 dir="auto">Proposed solution</h2>
<p dir="auto">Right now access to thread locals defined by <code class="notranslate">thread_local!</code> 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:</p>
<ul dir="auto">
<li>First, the <code class="notranslate">#[inline]</code> annotation could be processed on <code class="notranslate">static</code> variables. If the variable does not have any internal mutability, then the definition can be inlined into other LLVM modules and tagged with <code class="notranslate">available_externally</code>. That means that the contents are available for optimization, but if you're taking the address it's available elsewhere.</li>
<li>Second, when we inline the contents of a static across modules, function pointers should also be chased and inlined if they're tagged with <code class="notranslate">#[inline]</code>.</li>
</ul>
<p dir="auto">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.</p>
<h2 dir="auto">Original description</h2>
<p dir="auto">This hurts performance for the locks in <code class="notranslate">std::sync</code>, as they call <code class="notranslate">std::rt::unwind::panicking()</code> (which just reads a thread-local). For uncontended locks the cost is quite significant.</p>
<p dir="auto">There are two problems:</p>
<ol dir="auto">
<li><code class="notranslate">std::rt::unwind::panicking()</code> isn't marked inline. This is trivial to solve.</li>
<li>Accessing a <code class="notranslate">thread_local!</code> goes through function pointers, which LLVM fails to see through. These are the <code class="notranslate">__getit</code> functions in <code class="notranslate">libstd/thread/local.rs</code>. Consider these two files:</li>
</ol>
<div class="highlight highlight-source-rust notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="// tls.rs
use std::cell::Cell;
thread_local! { static FOO: Cell&lt;bool&gt; = Cell::new(false) }
#[inline]
pub fn get_foo() -&gt; bool {
    FOO.with(|s| s.get())
}"><pre class="notranslate"><span class="pl-c">// tls.rs</span>
<span class="pl-k">use</span> std<span class="pl-kos">::</span>cell<span class="pl-kos">::</span><span class="pl-v">Cell</span><span class="pl-kos">;</span>
<span class="pl-en">thread_local</span><span class="pl-en">!</span> <span class="pl-kos">{</span> <span class="pl-k">static</span> <span class="pl-v">FOO</span>: <span class="pl-v">Cell</span>&lt;<span class="pl-smi">bool</span>&gt; = <span class="pl-v">Cell</span>::new<span class="pl-kos">(</span><span class="pl-c1">false</span><span class="pl-kos">)</span> <span class="pl-kos">}</span>
<span class="pl-c1">#<span class="pl-kos">[</span>inline<span class="pl-kos">]</span></span>
<span class="pl-k">pub</span> <span class="pl-k">fn</span> <span class="pl-en">get_foo</span><span class="pl-kos">(</span><span class="pl-kos">)</span> -&gt; <span class="pl-smi">bool</span> <span class="pl-kos">{</span>
    <span class="pl-v">FOO</span><span class="pl-kos">.</span><span class="pl-en">with</span><span class="pl-kos">(</span>|s| s<span class="pl-kos">.</span><span class="pl-en">get</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span></pre></div>
<div class="highlight highlight-source-rust notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="// other.rs
extern crate tls;
#[inline(never)]
fn call_foo() -&gt; bool {
    tls::get_foo()
}"><pre class="notranslate"><span class="pl-c">// other.rs</span>
<span class="pl-k">extern</span> <span class="pl-k">crate</span> tls<span class="pl-kos">;</span>
<span class="pl-c1">#<span class="pl-kos">[</span>inline<span class="pl-kos">(</span>never<span class="pl-kos">)</span><span class="pl-kos">]</span></span>
<span class="pl-k">fn</span> <span class="pl-en">call_foo</span><span class="pl-kos">(</span><span class="pl-kos">)</span> -&gt; <span class="pl-smi">bool</span> <span class="pl-kos">{</span>
    tls<span class="pl-kos">::</span><span class="pl-en">get_foo</span><span class="pl-kos">(</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto"><code class="notranslate">call_foo</code> gets the following IR with everything compiled with full optimization. Note the call through a function pointer:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="define internal fastcc zeroext i1 @_ZN8call_foo20h55fb2c2ba5981f51PaaE() unnamed_addr #3 {
entry-block:
  %0 = load %&quot;1.std::thread::local::imp::Key&lt;core::cell::UnsafeCell&lt;core::option::Option&lt;core::cell::Cell&lt;bool&gt;&gt;&gt;&gt;&quot;* ()** getelementptr inbounds (%&quot;1.std::thread::local::LocalKey&lt;core::cell::Cell&lt;bool&gt;&gt;&quot;* @_ZN3FOO20hefc7cdadc988b7defaaE, i64 0, i32 0), align 8
  %1 = tail call dereferenceable(4) %&quot;1.std::thread::local::imp::Key&lt;core::cell::UnsafeCell&lt;core::option::Option&lt;core::cell::Cell&lt;bool&gt;&gt;&gt;&gt;&quot;* %0()
  %2 = getelementptr inbounds %&quot;1.std::thread::local::imp::Key&lt;core::cell::UnsafeCell&lt;core::option::Option&lt;core::cell::Cell&lt;bool&gt;&gt;&gt;&gt;&quot;* %1, i64 0, i32 0, i32 0, i32 0, i32 0
  %3 = load i8* %2, align 1, !range !13
  %cond.i.i = icmp eq i8 %3, 1
  br i1 %cond.i.i, label %match_case.i.i, label %match_else.i.i

match_else.i.i:                                   ; preds = %entry-block
  %4 = load i8 ()** getelementptr inbounds (%&quot;1.std::thread::local::LocalKey&lt;core::cell::Cell&lt;bool&gt;&gt;&quot;* @_ZN3FOO20hefc7cdadc988b7defaaE, i64 0, i32 1), align 8
  %5 = tail call i8 %4()
  %6 = zext i8 %5 to i16
  %7 = shl nuw i16 %6, 8
  %8 = or i16 %7, 1
  %9 = bitcast %&quot;1.std::thread::local::imp::Key&lt;core::cell::UnsafeCell&lt;core::option::Option&lt;core::cell::Cell&lt;bool&gt;&gt;&gt;&gt;&quot;* %1 to i16*
  store i16 %8, i16* %9, align 1
  %10 = getelementptr inbounds %&quot;1.std::thread::local::imp::Key&lt;core::cell::UnsafeCell&lt;core::option::Option&lt;core::cell::Cell&lt;bool&gt;&gt;&gt;&gt;&quot;* %1, i64 0, i32 0, i32 0, i32 0, i32 2, i64 0
  %11 = bitcast i8* %10 to %&quot;2.core::cell::Cell&lt;bool&gt;&quot;*
  br label %_ZN7get_foo20h73631102cae2c5b4lbaE.exit

match_case.i.i:                                   ; preds = %entry-block
  %12 = getelementptr inbounds %&quot;1.std::thread::local::imp::Key&lt;core::cell::UnsafeCell&lt;core::option::Option&lt;core::cell::Cell&lt;bool&gt;&gt;&gt;&gt;&quot;* %1, i64 0, i32 0, i32 0, i32 0, i32 2
  %13 = bitcast [1 x i8]* %12 to %&quot;2.core::cell::Cell&lt;bool&gt;&quot;*
  br label %_ZN7get_foo20h73631102cae2c5b4lbaE.exit

_ZN7get_foo20h73631102cae2c5b4lbaE.exit:          ; preds = %match_else.i.i, %match_case.i.i
  %.0.i.i = phi %&quot;2.core::cell::Cell&lt;bool&gt;&quot;* [ %13, %match_case.i.i ], [ %11, %match_else.i.i ]
  %.0.idx.i.i = getelementptr %&quot;2.core::cell::Cell&lt;bool&gt;&quot;* %.0.i.i, i64 0, i32 0, i32 0
  %.0.idx.val.i.i = load i8* %.0.idx.i.i, align 1
  %14 = icmp ne i8 %.0.idx.val.i.i, 0
  ret i1 %14
}"><pre class="notranslate"><code class="notranslate">define internal fastcc zeroext i1 @_ZN8call_foo20h55fb2c2ba5981f51PaaE() unnamed_addr #3 {
entry-block:
  %0 = load %"1.std::thread::local::imp::Key&lt;core::cell::UnsafeCell&lt;core::option::Option&lt;core::cell::Cell&lt;bool&gt;&gt;&gt;&gt;"* ()** getelementptr inbounds (%"1.std::thread::local::LocalKey&lt;core::cell::Cell&lt;bool&gt;&gt;"* @_ZN3FOO20hefc7cdadc988b7defaaE, i64 0, i32 0), align 8
  %1 = tail call dereferenceable(4) %"1.std::thread::local::imp::Key&lt;core::cell::UnsafeCell&lt;core::option::Option&lt;core::cell::Cell&lt;bool&gt;&gt;&gt;&gt;"* %0()
  %2 = getelementptr inbounds %"1.std::thread::local::imp::Key&lt;core::cell::UnsafeCell&lt;core::option::Option&lt;core::cell::Cell&lt;bool&gt;&gt;&gt;&gt;"* %1, i64 0, i32 0, i32 0, i32 0, i32 0
  %3 = load i8* %2, align 1, !range !13
  %cond.i.i = icmp eq i8 %3, 1
  br i1 %cond.i.i, label %match_case.i.i, label %match_else.i.i

match_else.i.i:                                   ; preds = %entry-block
  %4 = load i8 ()** getelementptr inbounds (%"1.std::thread::local::LocalKey&lt;core::cell::Cell&lt;bool&gt;&gt;"* @_ZN3FOO20hefc7cdadc988b7defaaE, i64 0, i32 1), align 8
  %5 = tail call i8 %4()
  %6 = zext i8 %5 to i16
  %7 = shl nuw i16 %6, 8
  %8 = or i16 %7, 1
  %9 = bitcast %"1.std::thread::local::imp::Key&lt;core::cell::UnsafeCell&lt;core::option::Option&lt;core::cell::Cell&lt;bool&gt;&gt;&gt;&gt;"* %1 to i16*
  store i16 %8, i16* %9, align 1
  %10 = getelementptr inbounds %"1.std::thread::local::imp::Key&lt;core::cell::UnsafeCell&lt;core::option::Option&lt;core::cell::Cell&lt;bool&gt;&gt;&gt;&gt;"* %1, i64 0, i32 0, i32 0, i32 0, i32 2, i64 0
  %11 = bitcast i8* %10 to %"2.core::cell::Cell&lt;bool&gt;"*
  br label %_ZN7get_foo20h73631102cae2c5b4lbaE.exit

match_case.i.i:                                   ; preds = %entry-block
  %12 = getelementptr inbounds %"1.std::thread::local::imp::Key&lt;core::cell::UnsafeCell&lt;core::option::Option&lt;core::cell::Cell&lt;bool&gt;&gt;&gt;&gt;"* %1, i64 0, i32 0, i32 0, i32 0, i32 2
  %13 = bitcast [1 x i8]* %12 to %"2.core::cell::Cell&lt;bool&gt;"*
  br label %_ZN7get_foo20h73631102cae2c5b4lbaE.exit

_ZN7get_foo20h73631102cae2c5b4lbaE.exit:          ; preds = %match_else.i.i, %match_case.i.i
  %.0.i.i = phi %"2.core::cell::Cell&lt;bool&gt;"* [ %13, %match_case.i.i ], [ %11, %match_else.i.i ]
  %.0.idx.i.i = getelementptr %"2.core::cell::Cell&lt;bool&gt;"* %.0.i.i, i64 0, i32 0, i32 0
  %.0.idx.val.i.i = load i8* %.0.idx.i.i, align 1
  %14 = icmp ne i8 %.0.idx.val.i.i, 0
  ret i1 %14
}
</code></pre></div>
<p dir="auto">There are multiple instructions for building on Windows, and they are not in agreement, as described here: <a href="http://www.reddit.com/r/rust/comments/2e62f6/compiling_rust_on_windows_aka_why_does_this_have/" rel="nofollow">http://www.reddit.com/r/rust/comments/2e62f6/compiling_rust_on_windows_aka_why_does_this_have/</a></p>