michael@0: // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: #include "base/thread_local_storage.h" michael@0: michael@0: #include michael@0: michael@0: #include "base/logging.h" michael@0: michael@0: // In order to make TLS destructors work, we need to keep function michael@0: // pointers to the destructor for each TLS that we allocate. michael@0: // We make this work by allocating a single OS-level TLS, which michael@0: // contains an array of slots for the application to use. In michael@0: // parallel, we also allocate an array of destructors, which we michael@0: // keep track of and call when threads terminate. michael@0: michael@0: // tls_key_ is the one native TLS that we use. It stores our michael@0: // table. michael@0: long ThreadLocalStorage::tls_key_ = TLS_OUT_OF_INDEXES; michael@0: michael@0: // tls_max_ is the high-water-mark of allocated thread local storage. michael@0: // We intentionally skip 0 so that it is not confused with an michael@0: // unallocated TLS slot. michael@0: long ThreadLocalStorage::tls_max_ = 1; michael@0: michael@0: // An array of destructor function pointers for the slots. If michael@0: // a slot has a destructor, it will be stored in its corresponding michael@0: // entry in this array. michael@0: ThreadLocalStorage::TLSDestructorFunc michael@0: ThreadLocalStorage::tls_destructors_[kThreadLocalStorageSize]; michael@0: michael@0: void** ThreadLocalStorage::Initialize() { michael@0: if (tls_key_ == TLS_OUT_OF_INDEXES) { michael@0: long value = TlsAlloc(); michael@0: DCHECK(value != TLS_OUT_OF_INDEXES); michael@0: michael@0: // Atomically test-and-set the tls_key. If the key is TLS_OUT_OF_INDEXES, michael@0: // go ahead and set it. Otherwise, do nothing, as another michael@0: // thread already did our dirty work. michael@0: if (InterlockedCompareExchange(&tls_key_, value, TLS_OUT_OF_INDEXES) != michael@0: TLS_OUT_OF_INDEXES) { michael@0: // We've been shortcut. Another thread replaced tls_key_ first so we need michael@0: // to destroy our index and use the one the other thread got first. michael@0: TlsFree(value); michael@0: } michael@0: } michael@0: DCHECK(TlsGetValue(tls_key_) == NULL); michael@0: michael@0: // Create an array to store our data. michael@0: void** tls_data = new void*[kThreadLocalStorageSize]; michael@0: memset(tls_data, 0, sizeof(void*[kThreadLocalStorageSize])); michael@0: TlsSetValue(tls_key_, tls_data); michael@0: return tls_data; michael@0: } michael@0: michael@0: ThreadLocalStorage::Slot::Slot(TLSDestructorFunc destructor) michael@0: : initialized_(false) { michael@0: Initialize(destructor); michael@0: } michael@0: michael@0: bool ThreadLocalStorage::Slot::Initialize(TLSDestructorFunc destructor) { michael@0: if (tls_key_ == TLS_OUT_OF_INDEXES || !TlsGetValue(tls_key_)) michael@0: ThreadLocalStorage::Initialize(); michael@0: michael@0: // Grab a new slot. michael@0: slot_ = InterlockedIncrement(&tls_max_) - 1; michael@0: if (slot_ >= kThreadLocalStorageSize) { michael@0: NOTREACHED(); michael@0: return false; michael@0: } michael@0: michael@0: // Setup our destructor. michael@0: tls_destructors_[slot_] = destructor; michael@0: initialized_ = true; michael@0: return true; michael@0: } michael@0: michael@0: void ThreadLocalStorage::Slot::Free() { michael@0: // At this time, we don't reclaim old indices for TLS slots. michael@0: // So all we need to do is wipe the destructor. michael@0: tls_destructors_[slot_] = NULL; michael@0: initialized_ = false; michael@0: } michael@0: michael@0: void* ThreadLocalStorage::Slot::Get() const { michael@0: void** tls_data = static_cast(TlsGetValue(tls_key_)); michael@0: if (!tls_data) michael@0: tls_data = ThreadLocalStorage::Initialize(); michael@0: DCHECK(slot_ >= 0 && slot_ < kThreadLocalStorageSize); michael@0: return tls_data[slot_]; michael@0: } michael@0: michael@0: void ThreadLocalStorage::Slot::Set(void* value) { michael@0: void** tls_data = static_cast(TlsGetValue(tls_key_)); michael@0: if (!tls_data) michael@0: tls_data = ThreadLocalStorage::Initialize(); michael@0: DCHECK(slot_ >= 0 && slot_ < kThreadLocalStorageSize); michael@0: tls_data[slot_] = value; michael@0: } michael@0: michael@0: void ThreadLocalStorage::ThreadExit() { michael@0: if (tls_key_ == TLS_OUT_OF_INDEXES) michael@0: return; michael@0: michael@0: void** tls_data = static_cast(TlsGetValue(tls_key_)); michael@0: michael@0: // Maybe we have never initialized TLS for this thread. michael@0: if (!tls_data) michael@0: return; michael@0: michael@0: for (int slot = 0; slot < tls_max_; slot++) { michael@0: if (tls_destructors_[slot] != NULL) { michael@0: void* value = tls_data[slot]; michael@0: tls_destructors_[slot](value); michael@0: } michael@0: } michael@0: michael@0: delete[] tls_data; michael@0: michael@0: // In case there are other "onexit" handlers... michael@0: TlsSetValue(tls_key_, NULL); michael@0: } michael@0: michael@0: // Thread Termination Callbacks. michael@0: // Windows doesn't support a per-thread destructor with its michael@0: // TLS primitives. So, we build it manually by inserting a michael@0: // function to be called on each thread's exit. michael@0: // This magic is from http://www.codeproject.com/threads/tls.asp michael@0: // and it works for VC++ 7.0 and later. michael@0: michael@0: #ifdef _WIN64 michael@0: michael@0: // This makes the linker create the TLS directory if it's not already michael@0: // there. (e.g. if __declspec(thread) is not used). michael@0: #pragma comment(linker, "/INCLUDE:_tls_used") michael@0: michael@0: #else // _WIN64 michael@0: michael@0: // This makes the linker create the TLS directory if it's not already michael@0: // there. (e.g. if __declspec(thread) is not used). michael@0: #pragma comment(linker, "/INCLUDE:__tls_used") michael@0: michael@0: #endif // _WIN64 michael@0: michael@0: // Static callback function to call with each thread termination. michael@0: void NTAPI OnThreadExit(PVOID module, DWORD reason, PVOID reserved) michael@0: { michael@0: // On XP SP0 & SP1, the DLL_PROCESS_ATTACH is never seen. It is sent on SP2+ michael@0: // and on W2K and W2K3. So don't assume it is sent. michael@0: if (DLL_THREAD_DETACH == reason || DLL_PROCESS_DETACH == reason) michael@0: ThreadLocalStorage::ThreadExit(); michael@0: } michael@0: michael@0: // .CRT$XLA to .CRT$XLZ is an array of PIMAGE_TLS_CALLBACK pointers that are michael@0: // called automatically by the OS loader code (not the CRT) when the module is michael@0: // loaded and on thread creation. They are NOT called if the module has been michael@0: // loaded by a LoadLibrary() call. It must have implicitly been loaded at michael@0: // process startup. michael@0: // By implicitly loaded, I mean that it is directly referenced by the main EXE michael@0: // or by one of its dependent DLLs. Delay-loaded DLL doesn't count as being michael@0: // implicitly loaded. michael@0: // michael@0: // See VC\crt\src\tlssup.c for reference. michael@0: #ifdef _WIN64 michael@0: michael@0: // .CRT section is merged with .rdata on x64 so it must be constant data. michael@0: #pragma const_seg(".CRT$XLB") michael@0: // When defining a const variable, it must have external linkage to be sure the michael@0: // linker doesn't discard it. If this value is discarded, the OnThreadExit michael@0: // function will never be called. michael@0: extern const PIMAGE_TLS_CALLBACK p_thread_callback; michael@0: const PIMAGE_TLS_CALLBACK p_thread_callback = OnThreadExit; michael@0: michael@0: // Reset the default section. michael@0: #pragma const_seg() michael@0: michael@0: #else // _WIN64 michael@0: michael@0: #pragma data_seg(".CRT$XLB") michael@0: PIMAGE_TLS_CALLBACK p_thread_callback = OnThreadExit; michael@0: michael@0: // Reset the default section. michael@0: #pragma data_seg() michael@0: michael@0: #endif // _WIN64