diff --git a/std/algorithm/sorting.d b/std/algorithm/sorting.d index dbfe37f2fb0..1b1804e5304 100644 --- a/std/algorithm/sorting.d +++ b/std/algorithm/sorting.d @@ -3098,6 +3098,7 @@ if (isRandomAccessRange!R && hasLength!R && hasSwappableElements!R && import core.lifetime : emplace; import std.range : zip, SortedRange; import std.string : representation; + import std.typecons : shouldGCScan; static if (is(typeof(unaryFun!transform(r.front)))) { @@ -3129,7 +3130,7 @@ if (isRandomAccessRange!R && hasLength!R && hasSwappableElements!R && const nbytes = mulu(len, T.sizeof, overflow); if (overflow) assert(false, "multiplication overflowed"); T[] result = (cast(T*) malloc(nbytes))[0 .. len]; - static if (hasIndirections!T) + static if (shouldGCScan!T) { import core.memory : GC; GC.addRange(result.ptr, nbytes); @@ -3148,7 +3149,7 @@ if (isRandomAccessRange!R && hasLength!R && hasSwappableElements!R && static void trustedFree(T[] p) @trusted { import core.stdc.stdlib : free; - static if (hasIndirections!T) + static if (shouldGCScan!T) { import core.memory : GC; GC.removeRange(p.ptr); diff --git a/std/array.d b/std/array.d index bca137dbf58..78b5f6f0079 100644 --- a/std/array.d +++ b/std/array.d @@ -764,13 +764,14 @@ if (isAssociativeArray!AA) private template blockAttribute(T) { - import core.memory; - static if (hasIndirections!(T) || is(T == void)) + import std.typecons : shouldGCScan; + static if (shouldGCScan!T || is(T == void)) { enum blockAttribute = 0; } else { + import core.memory : GC; enum blockAttribute = GC.BlkAttr.NO_SCAN; } } diff --git a/std/container/array.d b/std/container/array.d index 68718bd4364..4285cc74048 100644 --- a/std/container/array.d +++ b/std/container/array.d @@ -273,11 +273,22 @@ if (!is(immutable T == immutable bool)) import core.memory : GC; import std.exception : enforce; - import std.typecons : RefCounted, RefCountedAutoInitialize; + import std.typecons : RefCounted, RefCountedAutoInitialize, + shouldGCScan; // This structure is not copyable. private struct Payload { + static if (!shouldGCScan!T) + { + // If no pointers to GC-allocated memory are reachable through T + // then no pointers to GC-allocated memory are reachable from this + // struct even though it contains a pointer as the _payload array is + // not GC-allocated. This annotation is so RefCounted!Payload + // doesn't call GC.addRange unnecessarily. + private enum bool __GC_NO_SCAN = true; + } + size_t _capacity; T[] _payload; @@ -292,7 +303,7 @@ if (!is(immutable T == immutable bool)) foreach (ref e; _payload) .destroy(e); - static if (hasIndirections!T) + static if (shouldGCScan!T) GC.removeRange(_payload.ptr); free(_payload.ptr); @@ -339,7 +350,7 @@ if (!is(immutable T == immutable bool)) assert(false, "Overflow"); } - static if (hasIndirections!T) + static if (shouldGCScan!T) { auto newPayloadPtr = cast(T*) enforceMalloc(nbytes); auto newPayload = newPayloadPtr[0 .. newLength]; @@ -385,7 +396,7 @@ if (!is(immutable T == immutable bool)) if (overflow) assert(false, "Overflow"); } - static if (hasIndirections!T) + static if (shouldGCScan!T) { /* Because of the transactional nature of this * relative to the garbage collector, ensure no @@ -486,7 +497,7 @@ if (!is(immutable T == immutable bool)) { emplace(p + i, e); } - static if (hasIndirections!T) + static if (shouldGCScan!T) { if (p) GC.addRange(p, T.sizeof * values.length); @@ -611,7 +622,7 @@ if (!is(immutable T == immutable bool)) assert(false, "Overflow"); } auto p = enforceMalloc(sz); - static if (hasIndirections!T) + static if (shouldGCScan!T) { // Zero out unused capacity to prevent gc from seeing false pointers memset(p, 0, sz); @@ -1101,6 +1112,11 @@ if (!is(immutable T == immutable bool)) a.length = 10; assert(a.length == 10); assert(a.capacity >= a.length); + + import std.typecons : shouldGCScan; + static assert(!shouldGCScan!(typeof(a))); + static assert(!shouldGCScan!(typeof(a._data))); + static assert(!shouldGCScan!(typeof(a._data.refCountedStore))); } @system unittest diff --git a/std/functional.d b/std/functional.d index cec61fef575..b89098c7d01 100644 --- a/std/functional.d +++ b/std/functional.d @@ -1309,8 +1309,8 @@ template memoize(alias fun, uint maxSize) ReturnType!fun memoize(Parameters!fun args) { import std.meta : staticMap; - import std.traits : hasIndirections, Unqual; - import std.typecons : tuple; + import std.traits : Unqual; + import std.typecons : shouldGCScan, tuple; static struct Value { staticMap!(Unqual, Parameters!fun) args; Unqual!(ReturnType!fun) res; } static Value[] memo; static size_t[] initialized; @@ -1323,7 +1323,7 @@ template memoize(alias fun, uint maxSize) static assert(maxSize < size_t.max / Value.sizeof); static assert(maxSize < size_t.max - (8 * size_t.sizeof - 1)); - enum attr = GC.BlkAttr.NO_INTERIOR | (hasIndirections!Value ? 0 : GC.BlkAttr.NO_SCAN); + enum attr = GC.BlkAttr.NO_INTERIOR | (shouldGCScan!Value ? 0 : GC.BlkAttr.NO_SCAN); memo = (cast(Value*) GC.malloc(Value.sizeof * maxSize, attr))[0 .. maxSize]; enum nwords = (maxSize + 8 * size_t.sizeof - 1) / (8 * size_t.sizeof); initialized = (cast(size_t*) GC.calloc(nwords * size_t.sizeof, attr | GC.BlkAttr.NO_SCAN))[0 .. nwords]; diff --git a/std/random.d b/std/random.d index 887fc66c4f6..2c6fe307a0c 100644 --- a/std/random.d +++ b/std/random.d @@ -3098,6 +3098,11 @@ private struct RandomCoverChoices private immutable size_t _length; private immutable bool hasPackedBits; private enum BITS_PER_WORD = typeof(buffer[0]).sizeof * 8; + // No pointers to GC-allocated memory are reachable from any of this + // struct's fields so despite containing a pointer this struct does + // not need to be scanned by the GC. This explicit property is for use by + // std.typecons.shouldGCScan. + private enum bool __GC_NO_SCAN = true; void opAssign(T)(T) @disable; diff --git a/std/typecons.d b/std/typecons.d index e54622d8c30..45ecf61235e 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -6378,14 +6378,8 @@ struct RefCounted(T, RefCountedAutoInitialize autoInit = RefCountedAutoInitialize.yes) if (!is(T == class) && !(is(T == interface))) { - version (D_BetterC) - { - private enum enableGCScan = false; - } - else - { - private enum enableGCScan = hasIndirections!T; - } + + private enum enableGCScan = shouldGCScan!T; // TODO remove pure when https://issues.dlang.org/show_bug.cgi?id=15862 has been fixed extern(C) private pure nothrow @nogc static @@ -6409,6 +6403,15 @@ if (!is(T == class) && !(is(T == interface))) private Impl* _store; + static if (!enableGCScan) + { + // If no pointers to GC-allocated memory are reachable through the + // payload then the same applies to this RefCountedStore since + // _store is not GC-allocated. This explicit annotation is for use + // by std.typecons.shouldGCScan. + private enum bool __GC_NO_SCAN = true; + } + private void initialize(A...)(auto ref A args) { import core.lifetime : emplace, forward; @@ -6655,6 +6658,56 @@ assert(refCountedStore.isInitialized)). // the pair will be freed when rc1 and rc2 go out of scope } +version (D_BetterC) + package(std) enum bool shouldGCScan(T) = false; +else +/+ +Code that manually allocates memory without using the GC is responsible +for calling GC.addRange if there is the possibility the memory might contain a +pointer through which GC-allocated memory is reachable. Conservatively +determining this via std.traits.hasIndirections can lead to false positives when +nesting multiple types that manage their own memory (for instance a refcounted +array of refcounted items). This template addresses that problem by acting like +std.traits.hasIndirections but adding an escape hatch: any struct or union that +defines `enum bool __GC_NO_SCAN = true` is regarded as having no pointers that +can be used to reach GC-allocated memory. Names beginning with two underscores +are reserved for use by the language so there is not a danger of conflicting +with an enum that is identically-named by coincidence. ++/ +package(std) template shouldGCScan(T) +{ + static if (is(T == struct) || is(T == union)) + { + static if (__traits(hasMember, T, "__GC_NO_SCAN") && + // Verify T is the parent to make sure this is not a false positive + // from `alias this`. + __traits(isSame, T, __traits(parent, __traits(getMember, T, "__GC_NO_SCAN")))) + { + // Trying to evaluate the truth value of T.__GC_NO_SCAN is intended + // to cause compilation failure if it is an instance member or a + // mutable static field. + static if (__traits(getMember, T, "__GC_NO_SCAN")) + enum bool shouldGCScan = false; + else + static assert(0, "The truth value of __GC_NO_SCAN is not used." ~ + " Rather than define it as false do not define it."); + } + else + { + import core.internal.traits : anySatisfy; + enum bool shouldGCScan = anySatisfy!(.shouldGCScan, + typeof(T.tupleof)); + } + } + else static if (__traits(isStaticArray, T)) + { + enum bool shouldGCScan = T.sizeof > 0 && + shouldGCScan!(typeof(T.init.ptr[0])); + } + else + enum bool shouldGCScan = hasIndirections!T; +} + pure @system unittest { RefCounted!int* p; @@ -6783,6 +6836,10 @@ pure @system unittest struct B { int b; alias b this; } struct C { B b; alias b this; } assert(to!string(refCounted(C(B(123)))) == to!string(C(B(123)))); + + // Test shouldGCScan. + static assert(shouldGCScan!(typeof(a))); + static assert(!shouldGCScan!(typeof(refCounted(C(B(123)))))); } /**