Path: blob/main/system/lib/mimalloc/src/bitmap.c
6175 views
/* ----------------------------------------------------------------------------1Copyright (c) 2019-2023 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/* ----------------------------------------------------------------------------8Concurrent bitmap that can set/reset sequences of bits atomically,9represented as an array of fields where each field is a machine word (`size_t`)1011There are two api's; the standard one cannot have sequences that cross12between the bitmap fields (and a sequence must be <= MI_BITMAP_FIELD_BITS).1314The `_across` postfixed functions do allow sequences that can cross over15between the fields. (This is used in arena allocation)16---------------------------------------------------------------------------- */1718#include "mimalloc.h"19#include "mimalloc/internal.h"20#include "bitmap.h"2122/* -----------------------------------------------------------23Bitmap definition24----------------------------------------------------------- */2526// The bit mask for a given number of blocks at a specified bit index.27static inline size_t mi_bitmap_mask_(size_t count, size_t bitidx) {28mi_assert_internal(count + bitidx <= MI_BITMAP_FIELD_BITS);29mi_assert_internal(count > 0);30if (count >= MI_BITMAP_FIELD_BITS) return MI_BITMAP_FIELD_FULL;31if (count == 0) return 0;32return ((((size_t)1 << count) - 1) << bitidx);33}343536/* -----------------------------------------------------------37Claim a bit sequence atomically38----------------------------------------------------------- */3940// Try to atomically claim a sequence of `count` bits in a single41// field at `idx` in `bitmap`. Returns `true` on success.42inline bool _mi_bitmap_try_find_claim_field(mi_bitmap_t bitmap, size_t idx, const size_t count, mi_bitmap_index_t* bitmap_idx)43{44mi_assert_internal(bitmap_idx != NULL);45mi_assert_internal(count <= MI_BITMAP_FIELD_BITS);46mi_assert_internal(count > 0);47mi_bitmap_field_t* field = &bitmap[idx];48size_t map = mi_atomic_load_relaxed(field);49if (map==MI_BITMAP_FIELD_FULL) return false; // short cut5051// search for 0-bit sequence of length count52const size_t mask = mi_bitmap_mask_(count, 0);53const size_t bitidx_max = MI_BITMAP_FIELD_BITS - count;5455#ifdef MI_HAVE_FAST_BITSCAN56size_t bitidx = mi_ctz(~map); // quickly find the first zero bit if possible57#else58size_t bitidx = 0; // otherwise start at 059#endif60size_t m = (mask << bitidx); // invariant: m == mask shifted by bitidx6162// scan linearly for a free range of zero bits63while (bitidx <= bitidx_max) {64const size_t mapm = (map & m);65if (mapm == 0) { // are the mask bits free at bitidx?66mi_assert_internal((m >> bitidx) == mask); // no overflow?67const size_t newmap = (map | m);68mi_assert_internal((newmap^map) >> bitidx == mask);69if (!mi_atomic_cas_strong_acq_rel(field, &map, newmap)) { // TODO: use weak cas here?70// no success, another thread claimed concurrently.. keep going (with updated `map`)71continue;72}73else {74// success, we claimed the bits!75*bitmap_idx = mi_bitmap_index_create(idx, bitidx);76return true;77}78}79else {80// on to the next bit range81#ifdef MI_HAVE_FAST_BITSCAN82mi_assert_internal(mapm != 0);83const size_t shift = (count == 1 ? 1 : (MI_INTPTR_BITS - mi_clz(mapm) - bitidx));84mi_assert_internal(shift > 0 && shift <= count);85#else86const size_t shift = 1;87#endif88bitidx += shift;89m <<= shift;90}91}92// no bits found93return false;94}9596// Find `count` bits of 0 and set them to 1 atomically; returns `true` on success.97// Starts at idx, and wraps around to search in all `bitmap_fields` fields.98// `count` can be at most MI_BITMAP_FIELD_BITS and will never cross fields.99bool _mi_bitmap_try_find_from_claim(mi_bitmap_t bitmap, const size_t bitmap_fields, const size_t start_field_idx, const size_t count, mi_bitmap_index_t* bitmap_idx) {100size_t idx = start_field_idx;101for (size_t visited = 0; visited < bitmap_fields; visited++, idx++) {102if (idx >= bitmap_fields) { idx = 0; } // wrap103if (_mi_bitmap_try_find_claim_field(bitmap, idx, count, bitmap_idx)) {104return true;105}106}107return false;108}109110// Like _mi_bitmap_try_find_from_claim but with an extra predicate that must be fullfilled111bool _mi_bitmap_try_find_from_claim_pred(mi_bitmap_t bitmap, const size_t bitmap_fields,112const size_t start_field_idx, const size_t count,113mi_bitmap_pred_fun_t pred_fun, void* pred_arg,114mi_bitmap_index_t* bitmap_idx) {115size_t idx = start_field_idx;116for (size_t visited = 0; visited < bitmap_fields; visited++, idx++) {117if (idx >= bitmap_fields) idx = 0; // wrap118if (_mi_bitmap_try_find_claim_field(bitmap, idx, count, bitmap_idx)) {119if (pred_fun == NULL || pred_fun(*bitmap_idx, pred_arg)) {120return true;121}122// predicate returned false, unclaim and look further123_mi_bitmap_unclaim(bitmap, bitmap_fields, count, *bitmap_idx);124}125}126return false;127}128129// Set `count` bits at `bitmap_idx` to 0 atomically130// Returns `true` if all `count` bits were 1 previously.131bool _mi_bitmap_unclaim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) {132const size_t idx = mi_bitmap_index_field(bitmap_idx);133const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx);134const size_t mask = mi_bitmap_mask_(count, bitidx);135mi_assert_internal(bitmap_fields > idx); MI_UNUSED(bitmap_fields);136// mi_assert_internal((bitmap[idx] & mask) == mask);137const size_t prev = mi_atomic_and_acq_rel(&bitmap[idx], ~mask);138return ((prev & mask) == mask);139}140141142// Set `count` bits at `bitmap_idx` to 1 atomically143// Returns `true` if all `count` bits were 0 previously. `any_zero` is `true` if there was at least one zero bit.144bool _mi_bitmap_claim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* any_zero) {145const size_t idx = mi_bitmap_index_field(bitmap_idx);146const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx);147const size_t mask = mi_bitmap_mask_(count, bitidx);148mi_assert_internal(bitmap_fields > idx); MI_UNUSED(bitmap_fields);149//mi_assert_internal(any_zero != NULL || (bitmap[idx] & mask) == 0);150size_t prev = mi_atomic_or_acq_rel(&bitmap[idx], mask);151if (any_zero != NULL) { *any_zero = ((prev & mask) != mask); }152return ((prev & mask) == 0);153}154155// Returns `true` if all `count` bits were 1. `any_ones` is `true` if there was at least one bit set to one.156static bool mi_bitmap_is_claimedx(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* any_ones) {157const size_t idx = mi_bitmap_index_field(bitmap_idx);158const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx);159const size_t mask = mi_bitmap_mask_(count, bitidx);160mi_assert_internal(bitmap_fields > idx); MI_UNUSED(bitmap_fields);161const size_t field = mi_atomic_load_relaxed(&bitmap[idx]);162if (any_ones != NULL) { *any_ones = ((field & mask) != 0); }163return ((field & mask) == mask);164}165166// Try to set `count` bits at `bitmap_idx` from 0 to 1 atomically.167// Returns `true` if successful when all previous `count` bits were 0.168bool _mi_bitmap_try_claim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) {169const size_t idx = mi_bitmap_index_field(bitmap_idx);170const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx);171const size_t mask = mi_bitmap_mask_(count, bitidx);172mi_assert_internal(bitmap_fields > idx); MI_UNUSED(bitmap_fields);173size_t expected = mi_atomic_load_relaxed(&bitmap[idx]);174do {175if ((expected & mask) != 0) return false;176}177while (!mi_atomic_cas_strong_acq_rel(&bitmap[idx], &expected, expected | mask));178mi_assert_internal((expected & mask) == 0);179return true;180}181182183bool _mi_bitmap_is_claimed(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) {184return mi_bitmap_is_claimedx(bitmap, bitmap_fields, count, bitmap_idx, NULL);185}186187bool _mi_bitmap_is_any_claimed(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) {188bool any_ones;189mi_bitmap_is_claimedx(bitmap, bitmap_fields, count, bitmap_idx, &any_ones);190return any_ones;191}192193194//--------------------------------------------------------------------------195// the `_across` functions work on bitmaps where sequences can cross over196// between the fields. This is used in arena allocation197//--------------------------------------------------------------------------198199// Try to atomically claim a sequence of `count` bits starting from the field200// at `idx` in `bitmap` and crossing into subsequent fields. Returns `true` on success.201// Only needs to consider crossing into the next fields (see `mi_bitmap_try_find_from_claim_across`)202static bool mi_bitmap_try_find_claim_field_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t idx, const size_t count, const size_t retries, mi_bitmap_index_t* bitmap_idx, mi_stats_t* stats)203{204mi_assert_internal(bitmap_idx != NULL);205206// check initial trailing zeros207mi_bitmap_field_t* field = &bitmap[idx];208size_t map = mi_atomic_load_relaxed(field);209const size_t initial = mi_clz(map); // count of initial zeros starting at idx210mi_assert_internal(initial <= MI_BITMAP_FIELD_BITS);211if (initial == 0) return false;212if (initial >= count) return _mi_bitmap_try_find_claim_field(bitmap, idx, count, bitmap_idx); // no need to cross fields (this case won't happen for us)213if (_mi_divide_up(count - initial, MI_BITMAP_FIELD_BITS) >= (bitmap_fields - idx)) return false; // not enough entries214215// scan ahead216size_t found = initial;217size_t mask = 0; // mask bits for the final field218while(found < count) {219field++;220map = mi_atomic_load_relaxed(field);221const size_t mask_bits = (found + MI_BITMAP_FIELD_BITS <= count ? MI_BITMAP_FIELD_BITS : (count - found));222mi_assert_internal(mask_bits > 0 && mask_bits <= MI_BITMAP_FIELD_BITS);223mask = mi_bitmap_mask_(mask_bits, 0);224if ((map & mask) != 0) return false; // some part is already claimed225found += mask_bits;226}227mi_assert_internal(field < &bitmap[bitmap_fields]);228229// we found a range of contiguous zeros up to the final field; mask contains mask in the final field230// now try to claim the range atomically231mi_bitmap_field_t* const final_field = field;232const size_t final_mask = mask;233mi_bitmap_field_t* const initial_field = &bitmap[idx];234const size_t initial_idx = MI_BITMAP_FIELD_BITS - initial;235const size_t initial_mask = mi_bitmap_mask_(initial, initial_idx);236237// initial field238size_t newmap;239field = initial_field;240map = mi_atomic_load_relaxed(field);241do {242newmap = (map | initial_mask);243if ((map & initial_mask) != 0) { goto rollback; };244} while (!mi_atomic_cas_strong_acq_rel(field, &map, newmap));245246// intermediate fields247while (++field < final_field) {248newmap = MI_BITMAP_FIELD_FULL;249map = 0;250if (!mi_atomic_cas_strong_acq_rel(field, &map, newmap)) { goto rollback; }251}252253// final field254mi_assert_internal(field == final_field);255map = mi_atomic_load_relaxed(field);256do {257newmap = (map | final_mask);258if ((map & final_mask) != 0) { goto rollback; }259} while (!mi_atomic_cas_strong_acq_rel(field, &map, newmap));260261// claimed!262mi_stat_counter_increase(stats->arena_crossover_count,1);263*bitmap_idx = mi_bitmap_index_create(idx, initial_idx);264return true;265266rollback:267// roll back intermediate fields268// (we just failed to claim `field` so decrement first)269while (--field > initial_field) {270newmap = 0;271map = MI_BITMAP_FIELD_FULL;272mi_assert_internal(mi_atomic_load_relaxed(field) == map);273mi_atomic_store_release(field, newmap);274}275if (field == initial_field) { // (if we failed on the initial field, `field + 1 == initial_field`)276map = mi_atomic_load_relaxed(field);277do {278mi_assert_internal((map & initial_mask) == initial_mask);279newmap = (map & ~initial_mask);280} while (!mi_atomic_cas_strong_acq_rel(field, &map, newmap));281}282mi_stat_counter_increase(stats->arena_rollback_count,1);283// retry? (we make a recursive call instead of goto to be able to use const declarations)284if (retries <= 2) {285return mi_bitmap_try_find_claim_field_across(bitmap, bitmap_fields, idx, count, retries+1, bitmap_idx, stats);286}287else {288return false;289}290}291292293// Find `count` bits of zeros and set them to 1 atomically; returns `true` on success.294// Starts at idx, and wraps around to search in all `bitmap_fields` fields.295bool _mi_bitmap_try_find_from_claim_across(mi_bitmap_t bitmap, const size_t bitmap_fields, const size_t start_field_idx, const size_t count, mi_bitmap_index_t* bitmap_idx, mi_stats_t* stats) {296mi_assert_internal(count > 0);297if (count <= 2) {298// we don't bother with crossover fields for small counts299return _mi_bitmap_try_find_from_claim(bitmap, bitmap_fields, start_field_idx, count, bitmap_idx);300}301302// visit the fields303size_t idx = start_field_idx;304for (size_t visited = 0; visited < bitmap_fields; visited++, idx++) {305if (idx >= bitmap_fields) { idx = 0; } // wrap306// first try to claim inside a field307/*308if (count <= MI_BITMAP_FIELD_BITS) {309if (_mi_bitmap_try_find_claim_field(bitmap, idx, count, bitmap_idx)) {310return true;311}312}313*/314// if that fails, then try to claim across fields315if (mi_bitmap_try_find_claim_field_across(bitmap, bitmap_fields, idx, count, 0, bitmap_idx, stats)) {316return true;317}318}319return false;320}321322// Helper for masks across fields; returns the mid count, post_mask may be 0323static size_t mi_bitmap_mask_across(mi_bitmap_index_t bitmap_idx, size_t bitmap_fields, size_t count, size_t* pre_mask, size_t* mid_mask, size_t* post_mask) {324MI_UNUSED(bitmap_fields);325const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx);326if mi_likely(bitidx + count <= MI_BITMAP_FIELD_BITS) {327*pre_mask = mi_bitmap_mask_(count, bitidx);328*mid_mask = 0;329*post_mask = 0;330mi_assert_internal(mi_bitmap_index_field(bitmap_idx) < bitmap_fields);331return 0;332}333else {334const size_t pre_bits = MI_BITMAP_FIELD_BITS - bitidx;335mi_assert_internal(pre_bits < count);336*pre_mask = mi_bitmap_mask_(pre_bits, bitidx);337count -= pre_bits;338const size_t mid_count = (count / MI_BITMAP_FIELD_BITS);339*mid_mask = MI_BITMAP_FIELD_FULL;340count %= MI_BITMAP_FIELD_BITS;341*post_mask = (count==0 ? 0 : mi_bitmap_mask_(count, 0));342mi_assert_internal(mi_bitmap_index_field(bitmap_idx) + mid_count + (count==0 ? 0 : 1) < bitmap_fields);343return mid_count;344}345}346347// Set `count` bits at `bitmap_idx` to 0 atomically348// Returns `true` if all `count` bits were 1 previously.349bool _mi_bitmap_unclaim_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) {350size_t idx = mi_bitmap_index_field(bitmap_idx);351size_t pre_mask;352size_t mid_mask;353size_t post_mask;354size_t mid_count = mi_bitmap_mask_across(bitmap_idx, bitmap_fields, count, &pre_mask, &mid_mask, &post_mask);355bool all_one = true;356mi_bitmap_field_t* field = &bitmap[idx];357size_t prev = mi_atomic_and_acq_rel(field++, ~pre_mask); // clear first part358if ((prev & pre_mask) != pre_mask) all_one = false;359while(mid_count-- > 0) {360prev = mi_atomic_and_acq_rel(field++, ~mid_mask); // clear mid part361if ((prev & mid_mask) != mid_mask) all_one = false;362}363if (post_mask!=0) {364prev = mi_atomic_and_acq_rel(field, ~post_mask); // clear end part365if ((prev & post_mask) != post_mask) all_one = false;366}367return all_one;368}369370// Set `count` bits at `bitmap_idx` to 1 atomically371// Returns `true` if all `count` bits were 0 previously. `any_zero` is `true` if there was at least one zero bit.372bool _mi_bitmap_claim_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* pany_zero) {373size_t idx = mi_bitmap_index_field(bitmap_idx);374size_t pre_mask;375size_t mid_mask;376size_t post_mask;377size_t mid_count = mi_bitmap_mask_across(bitmap_idx, bitmap_fields, count, &pre_mask, &mid_mask, &post_mask);378bool all_zero = true;379bool any_zero = false;380_Atomic(size_t)*field = &bitmap[idx];381size_t prev = mi_atomic_or_acq_rel(field++, pre_mask);382if ((prev & pre_mask) != 0) all_zero = false;383if ((prev & pre_mask) != pre_mask) any_zero = true;384while (mid_count-- > 0) {385prev = mi_atomic_or_acq_rel(field++, mid_mask);386if ((prev & mid_mask) != 0) all_zero = false;387if ((prev & mid_mask) != mid_mask) any_zero = true;388}389if (post_mask!=0) {390prev = mi_atomic_or_acq_rel(field, post_mask);391if ((prev & post_mask) != 0) all_zero = false;392if ((prev & post_mask) != post_mask) any_zero = true;393}394if (pany_zero != NULL) { *pany_zero = any_zero; }395return all_zero;396}397398399// Returns `true` if all `count` bits were 1.400// `any_ones` is `true` if there was at least one bit set to one.401static bool mi_bitmap_is_claimedx_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* pany_ones) {402size_t idx = mi_bitmap_index_field(bitmap_idx);403size_t pre_mask;404size_t mid_mask;405size_t post_mask;406size_t mid_count = mi_bitmap_mask_across(bitmap_idx, bitmap_fields, count, &pre_mask, &mid_mask, &post_mask);407bool all_ones = true;408bool any_ones = false;409mi_bitmap_field_t* field = &bitmap[idx];410size_t prev = mi_atomic_load_relaxed(field++);411if ((prev & pre_mask) != pre_mask) all_ones = false;412if ((prev & pre_mask) != 0) any_ones = true;413while (mid_count-- > 0) {414prev = mi_atomic_load_relaxed(field++);415if ((prev & mid_mask) != mid_mask) all_ones = false;416if ((prev & mid_mask) != 0) any_ones = true;417}418if (post_mask!=0) {419prev = mi_atomic_load_relaxed(field);420if ((prev & post_mask) != post_mask) all_ones = false;421if ((prev & post_mask) != 0) any_ones = true;422}423if (pany_ones != NULL) { *pany_ones = any_ones; }424return all_ones;425}426427bool _mi_bitmap_is_claimed_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) {428return mi_bitmap_is_claimedx_across(bitmap, bitmap_fields, count, bitmap_idx, NULL);429}430431bool _mi_bitmap_is_any_claimed_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) {432bool any_ones;433mi_bitmap_is_claimedx_across(bitmap, bitmap_fields, count, bitmap_idx, &any_ones);434return any_ones;435}436437438