blob: aa94e4076428a2698d76e50fa7c5b5c844133868 [file] [log] [blame]
/**
* Contains a bitfield used by the GC.
*
* Copyright: D Language Foundation 2005 - 2021.
* License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
* Authors: Walter Bright, David Friedman, Sean Kelly
*/
module core.internal.gc.bits;
import core.internal.gc.os : os_mem_map, os_mem_unmap, HaveFork;
import core.bitop;
import core.stdc.string;
import core.stdc.stdlib;
import core.exception : onOutOfMemoryError;
// use version gcbitsSingleBitOperation to disable optimizations that use
// word operands on bulk operation copyRange, setRange, clrRange, etc.
// version = gcbitsSingleBitOperation;
struct GCBits
{
@nogc:
alias size_t wordtype;
enum BITS_PER_WORD = (wordtype.sizeof * 8);
enum BITS_SHIFT = (wordtype.sizeof == 8 ? 6 : 5);
enum BITS_MASK = (BITS_PER_WORD - 1);
enum BITS_0 = cast(wordtype)0;
enum BITS_1 = cast(wordtype)1;
enum BITS_2 = cast(wordtype)2;
wordtype* data;
size_t nbits;
void Dtor(bool share = false) nothrow
{
if (data)
{
static if (!HaveFork)
free(data);
else if (share)
os_mem_unmap(data, nwords * data[0].sizeof);
else
free(data);
data = null;
}
}
void alloc(size_t nbits, bool share = false) nothrow
{
this.nbits = nbits;
static if (!HaveFork)
data = cast(typeof(data[0])*)calloc(nwords, data[0].sizeof);
else if (share)
data = cast(typeof(data[0])*)os_mem_map(nwords * data[0].sizeof, true); // Allocate as MAP_SHARED
else
data = cast(typeof(data[0])*)calloc(nwords, data[0].sizeof);
if (!data)
onOutOfMemoryError();
}
wordtype test(size_t i) const scope @trusted pure nothrow @nogc
in
{
assert(i < nbits);
}
do
{
return core.bitop.bt(data, i);
}
int set(size_t i) scope @trusted pure nothrow @nogc
in
{
assert(i < nbits);
}
do
{
return core.bitop.bts(data, i);
}
int clear(size_t i) scope @trusted pure nothrow @nogc
in
{
assert(i <= nbits);
}
do
{
return core.bitop.btr(data, i);
}
// return non-zero if bit already set
size_t setLocked(size_t i) scope @trusted pure nothrow @nogc
{
version (GNU)
{
import gcc.builtins;
const pos = i >> BITS_SHIFT;
const mask = BITS_1 << (i & BITS_MASK);
mixin("auto val = __atomic_fetch_or_" ~ size_t.sizeof.stringof[0]
~ "(cast(shared)(data + pos), mask, 3);");
return (val & mask) != 0;
}
else version (LDC)
{
import ldc.intrinsics;
const pos = i >> BITS_SHIFT;
const mask = BITS_1 << (i & BITS_MASK);
auto val = llvm_atomic_rmw_or(cast(shared)(data + pos), mask);
return (val & mask) != 0;
}
else version (D_InlineAsm_X86)
{
asm pure @nogc nothrow {
mov EAX, this;
mov ECX, data[EAX];
mov EDX, i;
lock;
bts dword ptr[ECX], EDX;
sbb EAX,EAX;
}
}
else version (D_InlineAsm_X86_64)
{
asm pure @nogc nothrow {
mov RAX, this;
mov RAX, data[RAX];
mov RDX, i;
lock;
bts qword ptr[RAX], RDX;
sbb RAX,RAX;
}
}
else
{
auto pos = i >> BITS_SHIFT;
auto pdata = cast(shared)(data + pos);
auto mask = BITS_1 << (i & BITS_MASK);
auto state = *pdata;
if (state & mask)
return state;
import core.atomic;
auto newstate = state | mask;
while (!cas(pdata, state, newstate))
{
state = *pdata;
if (state & mask)
return state;
newstate = state | mask;
}
return 0;
}
}
template testAndSet(bool locked)
{
static if (locked)
alias testAndSet = setLocked;
else
alias testAndSet = set;
}
mixin template RangeVars()
{
size_t firstWord = (target >> BITS_SHIFT);
size_t firstOff = target & BITS_MASK;
size_t last = target + len - 1;
size_t lastWord = (last >> BITS_SHIFT);
size_t lastOff = last & BITS_MASK;
}
// extract loops to allow inlining the rest
void clearWords(size_t firstWord, size_t lastWord) nothrow
{
for (size_t w = firstWord; w < lastWord; w++)
data[w] = 0;
}
void setWords(size_t firstWord, size_t lastWord) nothrow
{
for (size_t w = firstWord; w < lastWord; w++)
data[w] = ~0;
}
void copyWords(size_t firstWord, size_t lastWord, const(wordtype)* source) nothrow
{
for (size_t w = firstWord; w < lastWord; w++)
data[w] = source[w - firstWord];
}
void copyWordsShifted(size_t firstWord, size_t cntWords, size_t firstOff, const(wordtype)* source) nothrow
{
wordtype mask = ~BITS_0 << firstOff;
data[firstWord] = (data[firstWord] & ~mask) | (source[0] << firstOff);
for (size_t w = 1; w < cntWords; w++)
data[firstWord + w] = (source[w - 1] >> (BITS_PER_WORD - firstOff)) | (source[w] << firstOff);
}
// target = the biti to start the copy to
// destlen = the number of bits to copy from source
void copyRange(size_t target, size_t len, const(wordtype)* source) nothrow
{
version (gcbitsSingleBitOperation)
{
for (size_t i = 0; i < len; i++)
if (source[(i >> BITS_SHIFT)] & (BITS_1 << (i & BITS_MASK)))
set(target+i);
else
clear(target+i);
}
else
{
if (len > 0)
copyRangeZ(target, len, source);
}
}
void copyRangeZ(size_t target, size_t len, const(wordtype)* source) nothrow
{
mixin RangeVars!();
if (firstWord == lastWord)
{
wordtype mask = ((BITS_2 << (lastOff - firstOff)) - 1) << firstOff;
data[firstWord] = (data[firstWord] & ~mask) | ((source[0] << firstOff) & mask);
}
else if (firstOff == 0)
{
copyWords(firstWord, lastWord, source);
wordtype mask = (BITS_2 << lastOff) - 1;
data[lastWord] = (data[lastWord] & ~mask) | (source[lastWord - firstWord] & mask);
}
else
{
size_t cntWords = lastWord - firstWord;
copyWordsShifted(firstWord, cntWords, firstOff, source);
wordtype src = (source[cntWords - 1] >> (BITS_PER_WORD - firstOff));
if (lastOff >= firstOff) // prevent buffer overread
src |= (source[cntWords] << firstOff);
wordtype mask = (BITS_2 << lastOff) - 1;
data[lastWord] = (data[lastWord] & ~mask) | (src & mask);
}
}
void copyRangeRepeating(size_t target, size_t destlen, const(wordtype)* source, size_t sourcelen) nothrow
{
version (gcbitsSingleBitOperation)
{
for (size_t i=0; i < destlen; i++)
{
bool b;
size_t j = i % sourcelen;
b = (source[j >> BITS_SHIFT] & (BITS_1 << (j & BITS_MASK))) != 0;
if (b) set(target+i);
else clear(target+i);
}
}
else
{
while (destlen > sourcelen)
{
copyRange(target, sourcelen, source);
target += sourcelen;
destlen -= sourcelen;
}
copyRange(target, destlen, source);
}
}
unittest
{
// simulate broken array append test case in vibe.d
GCBits bits;
bits.alloc(10000);
auto data = bits.data;
GCBits src;
src.alloc(67);
src.data[0] = 0x4;
bits.copyRangeRepeating(2, 10000, src.data, 67);
foreach (i; 0 .. 10000)
if ((i - 2) % 67 == 2)
assert(bits.test(i));
else
assert(!bits.test(i));
}
void setRange(size_t target, size_t len) nothrow
{
version (gcbitsSingleBitOperation)
{
for (size_t i = 0; i < len; i++)
set(target+i);
}
else
{
if (len > 0)
setRangeZ(target, len);
}
}
void setRangeZ(size_t target, size_t len) nothrow
{
mixin RangeVars!();
if (firstWord == lastWord)
{
wordtype mask = ((BITS_2 << (lastOff - firstOff)) - 1) << firstOff;
data[firstWord] |= mask;
}
else
{
data[firstWord] |= ~BITS_0 << firstOff;
setWords(firstWord + 1, lastWord);
wordtype mask = (BITS_2 << lastOff) - 1;
data[lastWord] |= mask;
}
}
void clrRange(size_t target, size_t len) nothrow
{
version (gcbitsSingleBitOperation)
{
for (size_t i = 0; i < len; i++)
clear(target+i);
}
else
{
if (len > 0)
clrRangeZ(target, len);
}
}
void clrRangeZ(size_t target, size_t len) nothrow
{
mixin RangeVars!();
if (firstWord == lastWord)
{
wordtype mask = ((BITS_2 << (lastOff - firstOff)) - 1) << firstOff;
data[firstWord] &= ~mask;
}
else
{
data[firstWord] &= ~(~BITS_0 << firstOff);
clearWords(firstWord + 1, lastWord);
wordtype mask = (BITS_2 << lastOff) - 1;
data[lastWord] &= ~mask;
}
}
unittest
{
GCBits bits;
bits.alloc(1000);
auto data = bits.data;
bits.setRange(0,1);
assert(data[0] == 1);
bits.clrRange(0,1);
assert(data[0] == 0);
bits.setRange(BITS_PER_WORD-1,1);
assert(data[0] == BITS_1 << (BITS_PER_WORD-1));
bits.clrRange(BITS_PER_WORD-1,1);
assert(data[0] == 0);
bits.setRange(12,7);
assert(data[0] == 0b0111_1111_0000_0000_0000);
bits.clrRange(14,4);
assert(data[0] == 0b0100_0011_0000_0000_0000);
bits.clrRange(0,BITS_PER_WORD);
assert(data[0] == 0);
bits.setRange(0,BITS_PER_WORD);
assert(data[0] == ~0);
assert(data[1] == 0);
bits.setRange(BITS_PER_WORD,BITS_PER_WORD);
assert(data[0] == ~0);
assert(data[1] == ~0);
assert(data[2] == 0);
bits.clrRange(BITS_PER_WORD/2,BITS_PER_WORD);
assert(data[0] == (BITS_1 << (BITS_PER_WORD/2)) - 1);
assert(data[1] == ~data[0]);
assert(data[2] == 0);
bits.setRange(8*BITS_PER_WORD+1,4*BITS_PER_WORD-2);
assert(data[8] == ~0 << 1);
assert(data[9] == ~0);
assert(data[10] == ~0);
assert(data[11] == cast(wordtype)~0 >> 1);
bits.clrRange(9*BITS_PER_WORD+1,2*BITS_PER_WORD);
assert(data[8] == ~0 << 1);
assert(data[9] == 1);
assert(data[10] == 0);
assert(data[11] == ((cast(wordtype)~0 >> 1) & ~1));
wordtype[4] src = [ 0xa, 0x5, 0xaa, 0x55 ];
void testCopyRange(size_t start, size_t len, int repeat = 1)
{
bits.setRange(0, bits.nbits);
if (repeat > 1)
bits.copyRangeRepeating(start, repeat * len, src.ptr, len);
else
bits.copyRange(start, len, src.ptr);
foreach (i; 0 .. start)
assert(bits.test(i));
foreach (r; 0 .. repeat)
foreach (i; 0 .. len)
assert(!bits.test(start + r*len + i) == !core.bitop.bt(src.ptr, i));
foreach (i; start + repeat*len .. 10*BITS_PER_WORD)
assert(bits.test(i));
}
testCopyRange(20, 10); // short copy range within same word
testCopyRange(50, 20); // short copy range spanning two words
testCopyRange(64, 3 * BITS_PER_WORD + 3); // aligned copy range
testCopyRange(77, 2 * BITS_PER_WORD + 15); // unaligned copy range
testCopyRange(64, 127); // copy range within critical end alignment
testCopyRange(10, 4, 5); // repeating small range within same word
testCopyRange(20, 5, 10); // repeating small range spanning two words
testCopyRange(40, 21, 7); // repeating medium range
testCopyRange(73, 2 * BITS_PER_WORD + 15, 5); // repeating multi-word range
testCopyRange(2, 3, 166); // failed with assert
}
void zero() nothrow
{
memset(data, 0, nwords * wordtype.sizeof);
}
void setAll() nothrow
{
memset(data, 0xFF, nwords * wordtype.sizeof);
}
void copy(GCBits *f) nothrow
in
{
assert(nwords == f.nwords);
}
do
{
memcpy(data, f.data, nwords * wordtype.sizeof);
}
@property size_t nwords() const pure nothrow
{
return (nbits + (BITS_PER_WORD - 1)) >> BITS_SHIFT;
}
}
unittest
{
GCBits b;
b.alloc(786);
assert(!b.test(123));
assert(!b.clear(123));
assert(!b.set(123));
assert(b.test(123));
assert(b.clear(123));
assert(!b.test(123));
b.set(785);
b.set(0);
assert(b.test(785));
assert(b.test(0));
b.zero();
assert(!b.test(785));
assert(!b.test(0));
GCBits b2;
b2.alloc(786);
b2.set(38);
b.copy(&b2);
assert(b.test(38));
b2.Dtor();
b.Dtor();
}