| /** |
| * 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(); |
| } |