| /** |
| BlkInfo thread-local cache. Used for array appending in the conservative GC to avoid the lock when possible. |
| |
| Note: this used to be in rt.lifetime, but was moved here to allow GCs to take over array operations. |
| */ |
| module core.internal.gc.blkcache; |
| |
| import core.memory; |
| import core.attribute; |
| |
| debug (PRINTF) import core.stdc.stdio : printf; |
| |
| alias BlkInfo = GC.BlkInfo; |
| alias BlkAttr = GC.BlkAttr; |
| |
| /** |
| cache for the lookup of the block info |
| */ |
| private enum N_CACHE_BLOCKS = 8; |
| |
| // note this is TLS, so no need to sync. |
| BlkInfo *__blkcache_storage; |
| |
| static if (N_CACHE_BLOCKS == 1) |
| { |
| version=single_cache; |
| } |
| else |
| { |
| //version=simple_cache; // uncomment to test simple cache strategy |
| //version=random_cache; // uncomment to test random cache strategy |
| |
| // ensure N_CACHE_BLOCKS is power of 2. |
| static assert(!((N_CACHE_BLOCKS - 1) & N_CACHE_BLOCKS)); |
| |
| version (random_cache) |
| { |
| int __nextRndNum = 0; |
| } |
| int __nextBlkIdx; |
| } |
| |
| @property BlkInfo *__blkcache() nothrow @nogc |
| { |
| if (!__blkcache_storage) |
| { |
| import core.stdc.stdlib : calloc; |
| import core.thread.threadbase; |
| auto tBase = ThreadBase.getThis(); |
| if (tBase is null) |
| // if we don't have a thread object, this is a detached thread, and |
| // this won't be properly maintained by the GC. |
| return null; |
| |
| // allocate the block cache for the first time |
| immutable size = BlkInfo.sizeof * N_CACHE_BLOCKS; |
| // use C alloc, because this may become a detached thread, and the GC |
| // would then clean up the cache without zeroing this pointer. |
| __blkcache_storage = cast(BlkInfo*) calloc(size, 1); |
| tBase.tlsGCData = __blkcache_storage; |
| } |
| return __blkcache_storage; |
| } |
| |
| // free the allocation on thread exit. |
| @standalone static ~this() |
| { |
| if (__blkcache_storage) |
| { |
| import core.stdc.stdlib : free; |
| import core.thread.threadbase; |
| auto tBase = ThreadBase.getThis(); |
| if (tBase !is null) |
| tBase.tlsGCData = null; |
| free(__blkcache_storage); |
| __blkcache_storage = null; |
| } |
| } |
| |
| /** |
| * Indicates whether an address has been marked by the GC. |
| */ |
| enum IsMarked : int |
| { |
| no, /// Address is not marked. |
| yes, /// Address is marked. |
| unknown, /// Address is not managed by the GC. |
| } |
| |
| alias IsMarkedDg = IsMarked delegate(void* addr) nothrow; /// The isMarked callback function. |
| |
| // we expect this to be called with the lock in place |
| void processGCMarks(void* data, scope IsMarkedDg isMarked) nothrow |
| { |
| if (!data) |
| return; |
| |
| auto cache = cast(BlkInfo*) data; |
| // called after the mark routine to eliminate block cache data when it |
| // might be ready to sweep |
| |
| debug(PRINTF) printf("processing GC Marks, %p\n", cache); |
| debug(PRINTF) foreach (i; 0 .. N_CACHE_BLOCKS) |
| { |
| printf("cache entry %d has base ptr %p\tsize %zd\tflags %x\n", i, cache[i].base, cache[i].size, cache[i].attr); |
| } |
| auto cache_end = cache + N_CACHE_BLOCKS; |
| for (;cache < cache_end; ++cache) |
| { |
| if (cache.base != null && isMarked(cache.base) == IsMarked.no) |
| { |
| debug(PRINTF) printf("clearing cache entry at %p\n", cache.base); |
| cache.base = null; // clear that data. |
| } |
| } |
| } |
| |
| unittest |
| { |
| // Bugzilla 10701 - segfault in GC |
| ubyte[] result; result.length = 4096; |
| GC.free(result.ptr); |
| GC.collect(); |
| } |
| |
| /** |
| Get the cached block info of an interior pointer. Returns null if the |
| interior pointer's block is not cached. |
| |
| NOTE: The following note was not valid, but is retained for historical |
| purposes. The data cannot be cleared because the stack contains a |
| reference to the affected block (e.g. through `interior`). Therefore, |
| the element will not be collected, and the data will remain valid. |
| |
| ORIGINAL: The base ptr in this struct can be cleared asynchronously by the GC, |
| so any use of the returned BlkInfo should copy it and then check the |
| base ptr of the copy before actually using it. |
| */ |
| BlkInfo *__getBlkInfo(void *interior) nothrow @nogc |
| { |
| BlkInfo *ptr = __blkcache; |
| if (ptr is null) |
| // if for some reason we don't have a cache, return null. |
| return null; |
| version (single_cache) |
| { |
| if (ptr.base && ptr.base <= interior && (interior - ptr.base) < ptr.size) |
| return ptr; |
| return null; // not in cache. |
| } |
| else version (simple_cache) |
| { |
| foreach (i; 0..N_CACHE_BLOCKS) |
| { |
| if (ptr.base && ptr.base <= interior && (interior - ptr.base) < ptr.size) |
| return ptr; |
| ptr++; |
| } |
| } |
| else |
| { |
| // try to do a smart lookup, using __nextBlkIdx as the "head" |
| auto curi = ptr + __nextBlkIdx; |
| for (auto i = curi; i >= ptr; --i) |
| { |
| if (i.base && i.base <= interior && cast(size_t)(interior - i.base) < i.size) |
| return i; |
| } |
| |
| for (auto i = ptr + N_CACHE_BLOCKS - 1; i > curi; --i) |
| { |
| if (i.base && i.base <= interior && cast(size_t)(interior - i.base) < i.size) |
| return i; |
| } |
| } |
| return null; // not in cache. |
| } |
| |
| void __insertBlkInfoCache(BlkInfo bi, BlkInfo *curpos) nothrow @nogc |
| { |
| auto cache = __blkcache; |
| if (cache is null) |
| // no cache to use. |
| return; |
| |
| version (single_cache) |
| { |
| *cache = bi; |
| return; |
| } |
| else |
| { |
| version (simple_cache) |
| { |
| if (curpos) |
| *curpos = bi; |
| else |
| { |
| // note, this is a super-simple algorithm that does not care about |
| // most recently used. It simply uses a round-robin technique to |
| // cache block info. This means that the ordering of the cache |
| // doesn't mean anything. Certain patterns of allocation may |
| // render the cache near-useless. |
| cache[__nextBlkIdx] = bi; |
| __nextBlkIdx = (__nextBlkIdx+1) & (N_CACHE_BLOCKS - 1); |
| } |
| } |
| else version (random_cache) |
| { |
| // strategy: if the block currently is in the cache, move the |
| // current block index to the a random element and evict that |
| // element. |
| if (!curpos) |
| { |
| __nextBlkIdx = (__nextRndNum = 1664525 * __nextRndNum + 1013904223) & (N_CACHE_BLOCKS - 1); |
| curpos = cache + __nextBlkIdx; |
| } |
| else |
| { |
| __nextBlkIdx = curpos - cache; |
| } |
| *curpos = bi; |
| } |
| else |
| { |
| // |
| // strategy: If the block currently is in the cache, swap it with |
| // the head element. Otherwise, move the head element up by one, |
| // and insert it there. |
| // |
| if (!curpos) |
| { |
| __nextBlkIdx = (__nextBlkIdx+1) & (N_CACHE_BLOCKS - 1); |
| curpos = cache + __nextBlkIdx; |
| } |
| else if (curpos !is cache + __nextBlkIdx) |
| { |
| *curpos = cache[__nextBlkIdx]; |
| curpos = cache + __nextBlkIdx; |
| } |
| *curpos = bi; |
| } |
| } |
| } |
| |
| debug(PRINTF) |
| { |
| extern(C) void printArrayCache() |
| { |
| auto ptr = __blkcache; |
| printf("CACHE: \n"); |
| foreach (i; 0 .. N_CACHE_BLOCKS) |
| { |
| printf(" %d\taddr:% .8p\tsize:% .10zd\tflags:% .8x\n", i, ptr[i].base, ptr[i].size, ptr[i].attr); |
| } |
| } |
| } |