mirror of
https://github.com/microsoft/mimalloc.git
synced 2025-05-04 22:49:32 +03:00
240 lines
9.2 KiB
C
240 lines
9.2 KiB
C
/* ----------------------------------------------------------------------------
|
|
Copyright (c) 2019, Microsoft Research, Daan Leijen
|
|
This is free software; you can redistribute it and/or modify it under the
|
|
terms of the MIT license. A copy of the license can be found in the file
|
|
"LICENSE" at the root of this distribution.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
/* ----------------------------------------------------------------------------
|
|
This file is meant to be included in other files for efficiency.
|
|
It implements a bitmap that can set/reset sequences of bits atomically
|
|
and is used to concurrently claim memory ranges.
|
|
|
|
A bitmap is an array of fields where each field is a machine word (`uintptr_t`)
|
|
|
|
A current limitation is that the bit sequences cannot cross fields
|
|
and that the sequence must be smaller or equal to the bits in a field.
|
|
---------------------------------------------------------------------------- */
|
|
#pragma once
|
|
#ifndef MI_BITMAP_C
|
|
#define MI_BITMAP_C
|
|
|
|
#include "mimalloc.h"
|
|
#include "mimalloc-internal.h"
|
|
|
|
/* -----------------------------------------------------------
|
|
Bitmap definition
|
|
----------------------------------------------------------- */
|
|
|
|
#define MI_BITMAP_FIELD_BITS (8*MI_INTPTR_SIZE)
|
|
#define MI_BITMAP_FIELD_FULL (~((uintptr_t)0)) // all bits set
|
|
|
|
// An atomic bitmap of `uintptr_t` fields
|
|
typedef volatile _Atomic(uintptr_t) mi_bitmap_field_t;
|
|
typedef mi_bitmap_field_t* mi_bitmap_t;
|
|
|
|
// A bitmap index is the index of the bit in a bitmap.
|
|
typedef size_t mi_bitmap_index_t;
|
|
|
|
// Create a bit index.
|
|
static inline mi_bitmap_index_t mi_bitmap_index_create(size_t idx, size_t bitidx) {
|
|
mi_assert_internal(bitidx < MI_BITMAP_FIELD_BITS);
|
|
return (idx*MI_BITMAP_FIELD_BITS) + bitidx;
|
|
}
|
|
|
|
// Get the field index from a bit index.
|
|
static inline size_t mi_bitmap_index_field(mi_bitmap_index_t bitmap_idx) {
|
|
return (bitmap_idx / MI_BITMAP_FIELD_BITS);
|
|
}
|
|
|
|
// Get the bit index in a bitmap field
|
|
static inline size_t mi_bitmap_index_bit_in_field(mi_bitmap_index_t bitmap_idx) {
|
|
return (bitmap_idx % MI_BITMAP_FIELD_BITS);
|
|
}
|
|
|
|
// Get the full bit index
|
|
static inline size_t mi_bitmap_index_bit(mi_bitmap_index_t bitmap_idx) {
|
|
return bitmap_idx;
|
|
}
|
|
|
|
|
|
// The bit mask for a given number of blocks at a specified bit index.
|
|
static inline uintptr_t mi_bitmap_mask_(size_t count, size_t bitidx) {
|
|
mi_assert_internal(count + bitidx <= MI_BITMAP_FIELD_BITS);
|
|
if (count == MI_BITMAP_FIELD_BITS) return MI_BITMAP_FIELD_FULL;
|
|
return ((((uintptr_t)1 << count) - 1) << bitidx);
|
|
}
|
|
|
|
|
|
/* -----------------------------------------------------------
|
|
Use bit scan forward/reverse to quickly find the first zero bit if it is available
|
|
----------------------------------------------------------- */
|
|
#if defined(_MSC_VER)
|
|
#define MI_HAVE_BITSCAN
|
|
#include <intrin.h>
|
|
static inline size_t mi_bsf(uintptr_t x) {
|
|
if (x==0) return 8*MI_INTPTR_SIZE;
|
|
DWORD idx;
|
|
MI_64(_BitScanForward)(&idx, x);
|
|
return idx;
|
|
}
|
|
static inline size_t mi_bsr(uintptr_t x) {
|
|
if (x==0) return 8*MI_INTPTR_SIZE;
|
|
DWORD idx;
|
|
MI_64(_BitScanReverse)(&idx, x);
|
|
return idx;
|
|
}
|
|
#elif defined(__GNUC__) || defined(__clang__)
|
|
#include <limits.h> // LONG_MAX
|
|
#define MI_HAVE_BITSCAN
|
|
#if (INTPTR_MAX == LONG_MAX)
|
|
# define MI_L(x) x##l
|
|
#else
|
|
# define MI_L(x) x##ll
|
|
#endif
|
|
static inline size_t mi_bsf(uintptr_t x) {
|
|
return (x==0 ? 8*MI_INTPTR_SIZE : MI_L(__builtin_ctz)(x));
|
|
}
|
|
static inline size_t mi_bsr(uintptr_t x) {
|
|
return (x==0 ? 8*MI_INTPTR_SIZE : (8*MI_INTPTR_SIZE - 1) - MI_L(__builtin_clz)(x));
|
|
}
|
|
#endif
|
|
|
|
/* -----------------------------------------------------------
|
|
Claim a bit sequence atomically
|
|
----------------------------------------------------------- */
|
|
|
|
// Try to atomically claim a sequence of `count` bits at in `idx`
|
|
// in the bitmap field. Returns `true` on success.
|
|
static inline bool mi_bitmap_try_claim_field(mi_bitmap_t bitmap, size_t bitmap_fields, const size_t count, mi_bitmap_index_t bitmap_idx) {
|
|
const size_t idx = mi_bitmap_index_field(bitmap_idx);
|
|
const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx);
|
|
const uintptr_t mask = mi_bitmap_mask_(count, bitidx);
|
|
mi_assert_internal(bitmap_fields > idx); UNUSED(bitmap_fields);
|
|
mi_assert_internal(bitidx + count <= MI_BITMAP_FIELD_BITS);
|
|
|
|
uintptr_t field = mi_atomic_read_relaxed(&bitmap[idx]);
|
|
if ((field & mask) == 0) { // free?
|
|
if (mi_atomic_cas_strong(&bitmap[idx], (field|mask), field)) {
|
|
// claimed!
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// Try to atomically claim a sequence of `count` bits in a single
|
|
// field at `idx` in `bitmap`. Returns `true` on success.
|
|
static inline bool mi_bitmap_try_find_claim_field(mi_bitmap_t bitmap, size_t idx, const size_t count, mi_bitmap_index_t* bitmap_idx)
|
|
{
|
|
mi_assert_internal(bitmap_idx != NULL);
|
|
volatile _Atomic(uintptr_t)* field = &bitmap[idx];
|
|
uintptr_t map = mi_atomic_read(field);
|
|
if (map==MI_BITMAP_FIELD_FULL) return false; // short cut
|
|
|
|
// search for 0-bit sequence of length count
|
|
const uintptr_t mask = mi_bitmap_mask_(count, 0);
|
|
const size_t bitidx_max = MI_BITMAP_FIELD_BITS - count;
|
|
|
|
#ifdef MI_HAVE_BITSCAN
|
|
size_t bitidx = mi_bsf(~map); // quickly find the first zero bit if possible
|
|
#else
|
|
size_t bitidx = 0; // otherwise start at 0
|
|
#endif
|
|
uintptr_t m = (mask << bitidx); // invariant: m == mask shifted by bitidx
|
|
|
|
// scan linearly for a free range of zero bits
|
|
while (bitidx <= bitidx_max) {
|
|
if ((map & m) == 0) { // are the mask bits free at bitidx?
|
|
mi_assert_internal((m >> bitidx) == mask); // no overflow?
|
|
const uintptr_t newmap = map | m;
|
|
mi_assert_internal((newmap^map) >> bitidx == mask);
|
|
if (!mi_atomic_cas_weak(field, newmap, map)) { // TODO: use strong cas here?
|
|
// no success, another thread claimed concurrently.. keep going
|
|
map = mi_atomic_read(field);
|
|
continue;
|
|
}
|
|
else {
|
|
// success, we claimed the bits!
|
|
*bitmap_idx = mi_bitmap_index_create(idx, bitidx);
|
|
return true;
|
|
}
|
|
}
|
|
else {
|
|
// on to the next bit range
|
|
#ifdef MI_HAVE_BITSCAN
|
|
const size_t shift = (count == 1 ? 1 : mi_bsr(map & m) - bitidx + 1);
|
|
mi_assert_internal(shift > 0 && shift <= count);
|
|
#else
|
|
const size_t shift = 1;
|
|
#endif
|
|
bitidx += shift;
|
|
m <<= shift;
|
|
}
|
|
}
|
|
// no bits found
|
|
return false;
|
|
}
|
|
|
|
|
|
// Find `count` bits of 0 and set them to 1 atomically; returns `true` on success.
|
|
// For now, `count` can be at most MI_BITMAP_FIELD_BITS and will never span fields.
|
|
static inline bool mi_bitmap_try_find_claim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t* bitmap_idx) {
|
|
for (size_t idx = 0; idx < bitmap_fields; idx++) {
|
|
if (mi_bitmap_try_find_claim_field(bitmap, idx, count, bitmap_idx)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Set `count` bits at `bitmap_idx` to 0 atomically
|
|
// Returns `true` if all `count` bits were 1 previously.
|
|
static inline bool mi_bitmap_unclaim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) {
|
|
const size_t idx = mi_bitmap_index_field(bitmap_idx);
|
|
const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx);
|
|
const uintptr_t mask = mi_bitmap_mask_(count, bitidx);
|
|
mi_assert_internal(bitmap_fields > idx); UNUSED(bitmap_fields);
|
|
// mi_assert_internal((bitmap[idx] & mask) == mask);
|
|
uintptr_t prev = mi_atomic_and(&bitmap[idx], ~mask);
|
|
return ((prev & mask) == mask);
|
|
}
|
|
|
|
|
|
// Set `count` bits at `bitmap_idx` to 1 atomically
|
|
// Returns `true` if all `count` bits were 0 previously. `any_zero` is `true` if there was at least one zero bit.
|
|
static inline bool mi_bitmap_claim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* any_zero) {
|
|
const size_t idx = mi_bitmap_index_field(bitmap_idx);
|
|
const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx);
|
|
const uintptr_t mask = mi_bitmap_mask_(count, bitidx);
|
|
mi_assert_internal(bitmap_fields > idx); UNUSED(bitmap_fields);
|
|
//mi_assert_internal(any_zero != NULL || (bitmap[idx] & mask) == 0);
|
|
uintptr_t prev = mi_atomic_or(&bitmap[idx], mask);
|
|
if (any_zero != NULL) *any_zero = ((prev & mask) != mask);
|
|
return ((prev & mask) == 0);
|
|
}
|
|
|
|
// Returns `true` if all `count` bits were 1. `any_ones` is `true` if there was at least one bit set to one.
|
|
static inline 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) {
|
|
const size_t idx = mi_bitmap_index_field(bitmap_idx);
|
|
const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx);
|
|
const uintptr_t mask = mi_bitmap_mask_(count, bitidx);
|
|
mi_assert_internal(bitmap_fields > idx); UNUSED(bitmap_fields);
|
|
uintptr_t field = mi_atomic_read_relaxed(&bitmap[idx]);
|
|
if (any_ones != NULL) *any_ones = ((field & mask) != 0);
|
|
return ((field & mask) == mask);
|
|
}
|
|
|
|
static inline bool mi_bitmap_is_claimed(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) {
|
|
return mi_bitmap_is_claimedx(bitmap, bitmap_fields, count, bitmap_idx, NULL);
|
|
}
|
|
|
|
static inline bool mi_bitmap_is_any_claimed(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) {
|
|
bool any_ones;
|
|
mi_bitmap_is_claimedx(bitmap, bitmap_fields, count, bitmap_idx, &any_ones);
|
|
return any_ones;
|
|
}
|
|
|
|
|
|
#endif
|