Path: blob/main/system/lib/mimalloc/src/threadlocal.c
14369 views
/* ----------------------------------------------------------------------------1Copyright (c) 2019-2026, Microsoft Research, Daan Leijen2This is free software; you can redistribute it and/or modify it under the3terms of the MIT license. A copy of the license can be found in the file4"LICENSE" at the root of this distribution.5-----------------------------------------------------------------------------*/67/* ----------------------------------------------------------------------------8Implement dynamic thread local variables (for heap's).9Unlike most OS native implementations there is no limit on the number10that can be allocated.11-----------------------------------------------------------------------------*/1213#include "mimalloc.h"14#include "mimalloc/internal.h"15#include "mimalloc/prim.h"1617/* -----------------------------------------------------------18Each thread can have (a dynamically expanding) array of19thread-local values. Each slot has a value and a version.20The version is used to safely reuse slots.21----------------------------------------------------------- */22typedef struct mi_tls_slot_s {23size_t version;24void* value;25} mi_tls_slot_t;2627typedef struct mi_thread_locals_s {28size_t count;29mi_tls_slot_t slots[1];30} mi_thread_locals_t;3132static mi_thread_locals_t mi_thread_locals_empty = { 0, {{0,NULL}} };3334mi_decl_thread mi_thread_locals_t* mi_thread_locals = &mi_thread_locals_empty; // always point to a valid `mi_thread_locals_t`353637/* -----------------------------------------------------------38Each key consists of the slot index in the lower bits,39and its version it the top bits. When we get a value40the version must match or we return NULL. When we set41a value, we also set the version of the key.42----------------------------------------------------------- */4344#define MI_TLS_IDX_BITS (MI_SIZE_BITS/2)45#define MI_TLS_IDX_MASK ((MI_ZU(1)<<MI_TLS_IDX_BITS)-1)4647static size_t mi_key_index( size_t key ) {48return (key & MI_TLS_IDX_MASK);49}5051static size_t mi_key_version( size_t key ) {52return (key >> MI_TLS_IDX_BITS);53}5455static mi_thread_local_t mi_key_create( size_t index, size_t version ) {56mi_assert_internal(version != 0);57mi_assert_internal(index <= MI_TLS_IDX_MASK);58const mi_thread_local_t key = ((version << MI_TLS_IDX_BITS) | index);59mi_assert_internal(key != 0);60return key;61}626364// dynamically reallocate the thread local slots when needed65static mi_thread_locals_t* mi_thread_locals_expand(size_t least_idx) {66mi_thread_locals_t* tls_old = mi_thread_locals;67const size_t count_old = tls_old->count;68size_t count;69if (count_old==0) {70tls_old = NULL; // so we allocate fresh from mi_thread_locals_empty71count = 16; // start with 16 slots72}73else if (count_old >= MI_TLS_IDX_MASK - 1024) {74return NULL; // too large75}76else if (count_old >= 1024) {77count = count_old + 1024; // at some point increase linearly78}79else {80count = 2*count_old; // and double initially81}82if (count <= least_idx) {83count = least_idx + 1;84}85mi_thread_locals_t* tls = (mi_thread_locals_t*)mi_rezalloc(tls_old, sizeof(mi_thread_locals_t) + count*sizeof(mi_tls_slot_t));86if mi_unlikely(tls==NULL) return NULL;87tls->count = count;88mi_thread_locals = tls;89return tls;90}9192static mi_decl_noinline bool mi_thread_local_set_expand( mi_thread_local_t key, void* val ) {93if (val==NULL) return true;94const size_t idx = mi_key_index(key);95mi_thread_locals_t* tls = mi_thread_locals_expand(idx);96if (tls==NULL) return false;97mi_assert_internal(tls == mi_thread_locals);98mi_assert_internal(idx < tls->count);99tls->slots[idx].value = val;100tls->slots[idx].version = mi_key_version(key);101return true;102}103104// set a tls slot; returns `true` if successful.105// Can return `false` if we could not reallocate the slots array.106bool _mi_thread_local_set( mi_thread_local_t key, void* val ) {107mi_thread_locals_t* tls = mi_thread_locals;108mi_assert_internal(tls!=NULL);109mi_assert_internal(key!=0);110const size_t idx = mi_key_index(key);111if mi_likely(idx < tls->count) {112tls->slots[idx].value = val;113tls->slots[idx].version = mi_key_version(key);114return true;115}116else {117return mi_thread_local_set_expand( key, val ); // tailcall118}119}120121// get a tls slot value122void* _mi_thread_local_get( mi_thread_local_t key ) {123const mi_thread_locals_t* const tls = mi_thread_locals;124mi_assert_internal(tls!=NULL);125mi_assert_internal(key!=0);126const size_t idx = mi_key_index(key);127if mi_likely(idx < tls->count && mi_key_version(key) == tls->slots[idx].version) {128return tls->slots[idx].value;129}130else {131return NULL;132}133}134135void _mi_thread_locals_thread_done(void) {136mi_thread_locals_t* const tls = mi_thread_locals;137if (tls!=NULL && tls->count > 0) {138mi_free(tls);139mi_thread_locals = &mi_thread_locals_empty;140}141}142143/* -----------------------------------------------------------144Create and free fresh TLS key's145----------------------------------------------------------- */146#include "bitmap.h"147148static mi_lock_t mi_thread_locals_lock; // we need a lock in order to re-allocate the slot bits149static mi_bitmap_t* mi_thread_locals_free; // reuse an arena bitmap to track which slots were assigned (1=free, 0=in-use)150static size_t mi_thread_locals_version; // version to be able to reuse slots safely151152void _mi_thread_locals_init(void) {153mi_lock_init(&mi_thread_locals_lock);154}155156void _mi_thread_locals_done(void) {157mi_lock(&mi_thread_locals_lock) {158mi_bitmap_t* const slots = mi_thread_locals_free;159mi_free(slots);160}161mi_lock_done(&mi_thread_locals_lock);162}163164// strange signature but allows us to reuse the arena code for claiming free pages165static bool mi_thread_local_claim_fun(size_t _slice_index, mi_arena_t* _arena, bool* keep_set) {166MI_UNUSED(_slice_index); MI_UNUSED(_arena);167*keep_set = false;168return true;169}170171// When we claim a free slot, we increase the global version counter172// (so if we reuse a slot it will be returning NULL initially when a thread tries to get it)173static mi_thread_local_t mi_thread_local_claim(void) {174size_t idx = 0;175if (mi_thread_locals_free != NULL && mi_bitmap_try_find_and_claim(mi_thread_locals_free,0,&idx,&mi_thread_local_claim_fun,NULL)) {176mi_thread_locals_version++;177if (mi_thread_locals_version == SIZE_MAX/2) { mi_thread_locals_version = 1; }178return mi_key_create( idx, mi_thread_locals_version);179}180else {181return 0;182}183}184185static bool mi_thread_local_create_expand(void) {186mi_bitmap_t* slots = mi_thread_locals_free;187// 1024 bits at a time188const size_t oldcount = (slots==NULL ? 0 : mi_bitmap_max_bits(slots));189const size_t newcount = 1024 + oldcount;190if (newcount > MI_TLS_IDX_MASK) { return false; }191const size_t newsize = mi_bitmap_size( newcount, NULL );192slots = (mi_bitmap_t*)mi_realloc_aligned(slots, newsize, MI_BCHUNK_SIZE);193if (slots == NULL) { return false; }194mi_bitmap_init(slots, newcount, true /* or otherwise we would zero all old entries */);195mi_bitmap_unsafe_setN(slots, oldcount, newcount - oldcount);196mi_thread_locals_free = slots;197return true;198}199200201// create a fresh key202mi_thread_local_t _mi_thread_local_create(void) {203mi_thread_local_t key = 0;204mi_lock(&mi_thread_locals_lock) {205key = mi_thread_local_claim();206if (key==0) {207if (mi_thread_local_create_expand()) {208key = mi_thread_local_claim();209}210}211}212return key;213}214215// free a key216void _mi_thread_local_free(mi_thread_local_t key) {217if (key==0) return;218const size_t idx = mi_key_index(key);219mi_lock(&mi_thread_locals_lock) {220mi_bitmap_t* const slots = mi_thread_locals_free;221if (slots!=NULL && idx < mi_bitmap_max_bits(slots)) {222mi_bitmap_set(slots,idx);223}224}225}226227228229