blob: a22a4b38bf1a88a1e4871e283882d968af57288b [file] [log] [blame]
// Copyright 2015 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "starboard/shared/starboard/thread_local_storage_internal.h"
#include <functional>
#include <limits>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "starboard/common/flat_map.h"
#include "starboard/common/log.h"
#include "starboard/memory.h"
#include "starboard/once.h"
struct SbThreadLocalKeyPrivate {
int index;
};
namespace starboard {
namespace shared {
namespace {
TLSKeyManager* s_instance = NULL;
SbOnceControl s_instance_control = SB_ONCE_INITIALIZER;
void InitializeTLS() {
s_instance = new TLSKeyManager();
}
// RawAllocator uses the SbMemoryAllocateNoReport() and
// SbMemoryDeallocateNoReport() allocation functions. This allows the
// TLSKeyManager to be used with the memory tracking system.
// Without this allocator, the TLSKeyManager could receive an event for memory
// allocation, which will cause memory to be allocated in the map, which will
// generate an event for memory allocation ... which results in an infinite
// loop and a crash.
template <typename T>
class RawAllocator : public std::allocator<T> {
public:
typedef typename std::allocator<T>::pointer pointer;
typedef typename std::allocator<T>::const_pointer const_pointer;
typedef typename std::allocator<T>::reference reference;
typedef typename std::allocator<T>::const_reference const_reference;
typedef typename std::allocator<T>::size_type size_type;
typedef typename std::allocator<T>::value_type value_type;
typedef typename std::allocator<T>::difference_type difference_type;
RawAllocator() {}
// Constructor used for rebinding
template <typename U>
RawAllocator(const RawAllocator<U>& x) {}
pointer allocate(size_type n,
std::allocator<void>::const_pointer hint = NULL) {
void* ptr = SbMemoryAllocateNoReport(n * sizeof(value_type));
return static_cast<pointer>(ptr);
}
void deallocate(pointer p, size_type n) { SbMemoryDeallocateNoReport(p); }
template <typename U>
struct rebind {
typedef RawAllocator<U> other;
};
};
// A map of ThreadId -> int. This map is highly concurrent and allows
// access of elements without much contention.
class ConcurrentThreadIdMap {
public:
ConcurrentThreadIdMap() {
// Prime number reduces collisions.
static const size_t kNumBuckets = 101;
map_vector_.resize(kNumBuckets);
for (size_t i = 0; i < map_vector_.size(); ++i) {
void* memory_block = SbMemoryAllocateNoReport(sizeof(LockedMap));
map_vector_[i] = new (memory_block) LockedMap;
}
}
~ConcurrentThreadIdMap() {
for (size_t i = 0; i < map_vector_.size(); ++i) {
LockedMap* obj = map_vector_[i];
obj->~LockedMap();
SbMemoryDeallocateNoReport(obj);
}
}
bool GetIfExists(SbThreadId key, int* value) const {
const LockedMap& map = GetBucket(key);
ScopedLock lock(map.mutex_);
ThreadIdMap::const_iterator it = map.map_.find(key);
if (it != map.map_.end()) {
*value = it->second;
return true;
} else {
return false;
}
}
void Insert(SbThreadId key, int value) {
LockedMap& map = GetBucket(key);
ScopedLock lock(map.mutex_);
map.map_[key] = value;
}
void Erase(SbThreadId key) {
LockedMap& map = GetBucket(key);
ScopedLock lock(map.mutex_);
map.map_.erase(key);
}
private:
typedef std::map<SbThreadId,
int,
std::less<SbThreadId>,
RawAllocator<std::pair<const SbThreadId, int> > >
ThreadIdMap;
struct LockedMap {
Mutex mutex_;
ThreadIdMap map_;
};
// Simple hashing function for 32 bit numbers.
// Based off of Jenkins hash found at this url:
// https://gist.github.com/badboy/6267743#file-inthash-md
static size_t Hash(SbThreadId id) {
static const uint32_t kMagicNum1 = 0x7ed55d16;
static const uint32_t kMagicNum2 = 0xc761c23c;
static const uint32_t kMagicNum3 = 0x165667b1;
static const uint32_t kMagicNum4 = 0xd3a2646c;
static const uint32_t kMagicNum5 = 0xfd7046c5;
static const uint32_t kMagicNum6 = 0xfd7046c5;
static const uint32_t kMagicShift1 = 12;
static const uint32_t kMagicShift2 = 19;
static const uint32_t kMagicShift3 = 5;
static const uint32_t kMagicShift4 = 9;
static const uint32_t kMagicShift5 = 3;
static const uint32_t kMagicShift6 = 16;
uint32_t key = static_cast<uint32_t>(id);
key = (key + kMagicNum1) + (key << kMagicShift1);
key = (key ^ kMagicNum2) ^ (key >> kMagicShift2);
key = (key + kMagicNum3) + (key << kMagicShift3);
key = (key + kMagicNum4) ^ (key << kMagicShift4);
key = (key + kMagicNum5) + (key << kMagicShift5);
key = (key ^ kMagicNum6) ^ (key >> kMagicShift6);
return static_cast<size_t>(key);
}
const LockedMap& GetBucket(SbThreadId key) const {
size_t bucket_index = Hash(key) % map_vector_.size();
return *map_vector_[bucket_index];
}
LockedMap& GetBucket(SbThreadId key) {
size_t bucket_index = Hash(key) % map_vector_.size();
return *map_vector_[bucket_index];
}
typedef std::vector<LockedMap*, RawAllocator<LockedMap*> > MapVector;
MapVector map_vector_;
};
} // namespace
struct TLSKeyManager::InternalData {
// These data structures bypass memory reporting. If this wasn't here then
// any platform using this TLSKeyManager will crash during memory reporting.
typedef std::vector<KeyRecord, RawAllocator<KeyRecord> > VectorKeyRecord;
typedef std::vector<int, RawAllocator<int> > VectorInt;
typedef ConcurrentThreadIdMap ThreadIdMap;
// Overrides new/delete for InternalData to bypass memory reporting.
static void* operator new(size_t n) { return SbMemoryAllocateNoReport(n); }
static void operator delete(void* p) { SbMemoryDeallocateNoReport(p); }
// The key record tracks all key values among all threads, along with their
// destructors, if specified.
VectorKeyRecord key_table_;
// Tracks all thread IDs that are still available.
VectorInt available_thread_ids_;
// This maps Starboard thread IDs to TLS thread ids.
ThreadIdMap thread_id_map_;
};
TLSKeyManager* TLSKeyManager::Get() {
SbOnce(&s_instance_control, &InitializeTLS);
return s_instance;
}
TLSKeyManager::TLSKeyManager() {
data_.reset(new InternalData);
data_->available_thread_ids_.reserve(kMaxThreads);
for (int i = 0; i < kMaxThreads; ++i) {
data_->available_thread_ids_.push_back(i);
}
}
SbThreadLocalKey TLSKeyManager::CreateKey(SbThreadLocalDestructor destructor) {
// Allocate key and bypass the the normal allocator. Otherwise there
// could be a re-entrant loop that kills the process.
void* memory_block =
SbMemoryAllocateNoReport(sizeof(SbThreadLocalKeyPrivate));
SbThreadLocalKey key = new (memory_block) SbThreadLocalKeyPrivate();
ScopedLock lock(mutex_);
key->index = GetUnusedKeyIndex();
KeyRecord* record = &data_->key_table_[key->index];
record->destructor = destructor;
#if SB_API_VERSION >= 12
SbMemorySet(record->values.data(), 0,
record->values.size() * sizeof(record->values[0]));
#else // SB_API_VERSION >= 12
SbMemorySet(record->values, 0, sizeof(record->values));
#endif // SB_API_VERSION >= 12
record->valid = true;
return key;
}
void TLSKeyManager::DestroyKey(SbThreadLocalKey key) {
if (!SbThreadIsValidLocalKey(key)) {
return;
}
ScopedLock lock(mutex_);
SB_DCHECK(IsKeyActive(key));
data_->key_table_[key->index].valid = false;
key.~SbThreadLocalKey();
SbMemoryDeallocateNoReport(key);
}
bool TLSKeyManager::SetLocalValue(SbThreadLocalKey key, void* value) {
if (!SbThreadIsValidLocalKey(key)) {
return false;
}
int current_thread_id = GetCurrentThreadId();
ScopedLock lock(mutex_);
if (!IsKeyActive(key)) {
return false;
}
data_->key_table_[key->index].values[current_thread_id] = value;
return true;
}
void* TLSKeyManager::GetLocalValue(SbThreadLocalKey key) {
if (!SbThreadIsValidLocalKey(key)) {
return NULL;
}
int current_thread_id = GetCurrentThreadId();
ScopedLock lock(mutex_);
if (!IsKeyActive(key)) {
return NULL;
}
return data_->key_table_[key->index].values[current_thread_id];
}
void TLSKeyManager::InitializeTLSForThread() {
int current_thread_id = GetCurrentThreadId();
ScopedLock lock(mutex_);
const size_t table_size = data_->key_table_.size();
for (int i = 0; i < table_size; ++i) {
KeyRecord* key_record = &data_->key_table_[i];
if (key_record->valid) {
key_record->values[current_thread_id] = NULL;
}
}
}
void TLSKeyManager::ShutdownTLSForThread() {
int current_thread_id = GetCurrentThreadId();
ScopedLock lock(mutex_);
// Apply the destructors multiple times (4 is the minimum value
// according to the specifications). This is necessary if one of
// the destructors adds new values to the key map.
for (int d = 0; d < 4; ++d) {
// Move the map into a new temporary map so that we can iterate
// through that while the original s_tls_thread_keys may have more
// values added to it via destructor calls.
const size_t table_size = data_->key_table_.size();
for (int i = 0; i < table_size; ++i) {
KeyRecord* key_record = &data_->key_table_[i];
if (key_record->valid) {
void* value = key_record->values[current_thread_id];
key_record->values[current_thread_id] = NULL;
SbThreadLocalDestructor destructor = key_record->destructor;
if (value && destructor) {
mutex_.Release();
destructor(value);
mutex_.Acquire();
}
}
}
}
data_->thread_id_map_.Erase(SbThreadGetId());
data_->available_thread_ids_.push_back(current_thread_id);
}
bool TLSKeyManager::IsKeyActive(SbThreadLocalKey key) {
return data_->key_table_[key->index].valid;
}
int TLSKeyManager::GetUnusedKeyIndex() {
const size_t key_table_size = data_->key_table_.size();
for (int i = 0; i < key_table_size; ++i) {
if (!data_->key_table_[i].valid) {
return i;
}
}
data_->key_table_.push_back(KeyRecord());
data_->key_table_.back().valid = false;
return data_->key_table_.size() - 1;
}
int TLSKeyManager::GetCurrentThreadId() {
const SbThreadId thread_id = SbThreadGetId();
int value = -1;
if (data_->thread_id_map_.GetIfExists(thread_id, &value)) {
return value;
}
ScopedLock lock(mutex_);
SB_DCHECK(!data_->available_thread_ids_.empty());
int thread_tls_id = data_->available_thread_ids_.back();
data_->available_thread_ids_.pop_back();
data_->thread_id_map_.Insert(thread_id, thread_tls_id);
return thread_tls_id;
}
} // namespace shared
} // namespace starboard