Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions std/algorithm/sorting.d
Original file line number Diff line number Diff line change
Expand Up @@ -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))))
{
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
5 changes: 3 additions & 2 deletions std/array.d
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
28 changes: 22 additions & 6 deletions std/container/array.d
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions std/functional.d
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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];
Expand Down
5 changes: 5 additions & 0 deletions std/random.d
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
73 changes: 65 additions & 8 deletions std/typecons.d
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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))))));
}

/**
Expand Down