// Copyright 2020 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_TLS_H_ #define BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_TLS_H_ #include "base/allocator/partition_allocator/partition_alloc_base/compiler_specific.h" #include "base/allocator/partition_allocator/partition_alloc_base/component_export.h" #include "base/allocator/partition_allocator/partition_alloc_base/immediate_crash.h" #include "base/allocator/partition_allocator/partition_alloc_check.h" #include "build/build_config.h" #if BUILDFLAG(IS_POSIX) #include #endif #if BUILDFLAG(IS_WIN) #include "base/allocator/partition_allocator/partition_alloc_base/win/windows_types.h" #endif // Barebones TLS implementation for use in PartitionAlloc. This doesn't use the // general chromium TLS handling to avoid dependencies, but more importantly // because it allocates memory. namespace partition_alloc::internal { #if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA) using PartitionTlsKey = pthread_key_t; // Only on x86_64, the implementation is not stable on ARM64. For instance, in // macOS 11, the TPIDRRO_EL0 registers holds the CPU index in the low bits, // which is not the case in macOS 12. See libsyscall/os/tsd.h in XNU // (_os_tsd_get_direct() is used by pthread_getspecific() internally). #if BUILDFLAG(IS_MAC) && defined(ARCH_CPU_X86_64) namespace { PA_ALWAYS_INLINE void* FastTlsGet(PartitionTlsKey index) { // On macOS, pthread_getspecific() is in libSystem, so a call to it has to go // through PLT. However, and contrary to some other platforms, *all* TLS keys // are in a static array in the thread structure. So they are *always* at a // fixed offset from the segment register holding the thread structure // address. // // We could use _pthread_getspecific_direct(), but it is not // exported. However, on all macOS versions we support, the TLS array is at // %gs. This is used in V8 to back up InternalGetExistingThreadLocal(), and // can also be seen by looking at pthread_getspecific() disassembly: // // libsystem_pthread.dylib`pthread_getspecific: // libsystem_pthread.dylib[0x7ff800316099] <+0>: movq %gs:(,%rdi,8), %rax // libsystem_pthread.dylib[0x7ff8003160a2] <+9>: retq // // This function is essentially inlining the content of pthread_getspecific() // here. intptr_t result; static_assert(sizeof index <= sizeof(intptr_t)); asm("movq %%gs:(,%1,8), %0;" : "=r"(result) : "r"(static_cast(index))); return reinterpret_cast(result); } } // namespace #endif // BUILDFLAG(IS_MAC) && defined(ARCH_CPU_X86_64) PA_ALWAYS_INLINE bool PartitionTlsCreate(PartitionTlsKey* key, void (*destructor)(void*)) { return !pthread_key_create(key, destructor); } PA_ALWAYS_INLINE void* PartitionTlsGet(PartitionTlsKey key) { #if BUILDFLAG(IS_MAC) && defined(ARCH_CPU_X86_64) PA_DCHECK(pthread_getspecific(key) == FastTlsGet(key)); return FastTlsGet(key); #else return pthread_getspecific(key); #endif } PA_ALWAYS_INLINE void PartitionTlsSet(PartitionTlsKey key, void* value) { int ret = pthread_setspecific(key, value); PA_DCHECK(!ret); } #elif BUILDFLAG(IS_WIN) // Note: supports only a single TLS key on Windows. Not a hard constraint, may // be lifted. using PartitionTlsKey = unsigned long; PA_COMPONENT_EXPORT(PARTITION_ALLOC) bool PartitionTlsCreate(PartitionTlsKey* key, void (*destructor)(void*)); PA_ALWAYS_INLINE void* PartitionTlsGet(PartitionTlsKey key) { // Accessing TLS resets the last error, which then makes |GetLastError()| // return something misleading. While this means that properly using // |GetLastError()| is difficult, there is currently code in Chromium which // expects malloc() to *not* reset it. Meaning that we either have to fix this // code, or pay the cost of saving/restoring it. // // Source: // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-tlsgetvalue // "Functions that return indications of failure call SetLastError() when they // fail. They generally do not call SetLastError() when they succeed. The // TlsGetValue() function is an exception to this general rule. The // TlsGetValue() function calls SetLastError() to clear a thread's last error // when it succeeds." DWORD saved_error = GetLastError(); void* ret = TlsGetValue(key); // Only non-zero errors need to be restored. if (PA_UNLIKELY(saved_error)) { SetLastError(saved_error); } return ret; } PA_ALWAYS_INLINE void PartitionTlsSet(PartitionTlsKey key, void* value) { BOOL ret = TlsSetValue(key, value); PA_DCHECK(ret); } // Registers a callback for DLL_PROCESS_DETACH events. void PartitionTlsSetOnDllProcessDetach(void (*callback)()); #else // Not supported. using PartitionTlsKey = int; PA_ALWAYS_INLINE bool PartitionTlsCreate(PartitionTlsKey* key, void (*destructor)(void*)) { // NOTIMPLEMENTED() may allocate, crash instead. PA_IMMEDIATE_CRASH(); } PA_ALWAYS_INLINE void* PartitionTlsGet(PartitionTlsKey key) { PA_IMMEDIATE_CRASH(); } PA_ALWAYS_INLINE void PartitionTlsSet(PartitionTlsKey key, void* value) { PA_IMMEDIATE_CRASH(); } #endif // BUILDFLAG(IS_WIN) } // namespace partition_alloc::internal #endif // BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_TLS_H_