Skip to content

Commit

Permalink
Merge pull request #6024 from n8sh/uninitializedFillDefault-memset
Browse files Browse the repository at this point in the history
Fix Issue 18223: use memset in uninitializedFillDefault(T[])
merged-on-behalf-of: unknown
  • Loading branch information
dlang-bot committed Mar 31, 2018
2 parents 2a9d890 + 8777cbf commit 6797b52
Show file tree
Hide file tree
Showing 2 changed files with 363 additions and 3 deletions.
324 changes: 324 additions & 0 deletions std/experimental/allocator/common.d
Expand Up @@ -743,3 +743,327 @@ version(unittest)
}
}
}

/+
Can the representation be determined at compile time to consist of nothing but
zero bits? Padding between a struct's fields is not considered.
+/
private template isAllZeroBits(T, T value)
{
static if (isDynamicArray!(typeof(value)))
enum isAllZeroBits = value is null && value.length == 0;
else static if (is(typeof(value is null)))
enum isAllZeroBits = value is null;
else static if (is(typeof(value is 0)))
enum isAllZeroBits = value is 0;
else static if (isStaticArray!(typeof(value)))
enum isAllZeroBits = ()
{
bool b = true;
// Use index so this works when T.length is 0.
static foreach (i; 0 .. T.length)
{
b &= isAllZeroBits!(typeof(value[i]), value[i]);
if (b == false) return b;
}

return b;
}();
else static if (is(typeof(value) == struct) || is(typeof(value) == union))
enum isAllZeroBits = ()
{
bool b = true;
static foreach (e; value.tupleof)
{
b &= isAllZeroBits!(typeof(e), e);
if (b == false) return b;
}

return b;
}();
else
enum isAllZeroBits = false;
}

@nogc nothrow pure @safe unittest
{
import std.meta : AliasSeq;
static foreach (Int; AliasSeq!(bool, char, wchar, dchar, byte, ubyte,
short, ushort, int, uint, long, ulong))
{
static assert(isAllZeroBits!(Int, Int(0)));
static assert(!isAllZeroBits!(Int, Int(1)));
}

foreach (Float; AliasSeq!(float, double, real))
{
assert(isAllZeroBits!(Float, 0.0));
assert(!isAllZeroBits!(Float, -0.0));
assert(!isAllZeroBits!(Float, Float.nan));
}

static assert(isAllZeroBits!(void*, null));
static assert(isAllZeroBits!(int*, null));
static assert(isAllZeroBits!(Object, null));
}

/+
Is the representation of T.init known at compile time to consist of nothing but
zero bits? Padding between a struct's fields is not considered.
+/
package template isInitAllZeroBits(T)
{
static if (isStaticArray!T && __traits(compiles, T.init[0]))
enum isInitAllZeroBits = __traits(compiles, {
static assert(isAllZeroBits!(typeof(T.init[0]), T.init[0]));
});
else
enum isInitAllZeroBits = __traits(compiles, {
static assert(isAllZeroBits!(T, T.init));
});
}

@nogc nothrow pure @safe unittest
{
static assert(isInitAllZeroBits!(Object));
static assert(isInitAllZeroBits!(void*));
static assert(isInitAllZeroBits!uint);
static assert(isInitAllZeroBits!(uint[2]));

static assert(!isInitAllZeroBits!float);
static assert(isInitAllZeroBits!(float[0]));
static assert(!isInitAllZeroBits!(float[2]));

static struct S1
{
int a;
}
static assert(isInitAllZeroBits!S1);

static struct S2
{
int a = 1;
}
static assert(!isInitAllZeroBits!S2);

static struct S3
{
S1 a;
int b;
}
static assert(isInitAllZeroBits!S3);
static assert(isInitAllZeroBits!(S3[2]));

static struct S4
{
S1 a;
S2 b;
}
static assert(!isInitAllZeroBits!S4);

static struct S5
{
real r = 0;
}
static assert(isInitAllZeroBits!S5);

static struct S6
{

}
static assert(isInitAllZeroBits!S6);

static struct S7
{
float[0] a;
}
static assert(isInitAllZeroBits!S7);

static class C1
{
int a = 1;
}
static assert(isInitAllZeroBits!C1);

// Ensure Tuple can be read.
import std.typecons : Tuple;
static assert(isInitAllZeroBits!(Tuple!(int, int)));
static assert(!isInitAllZeroBits!(Tuple!(float, float)));

// Ensure private fields of structs from other modules
// are taken into account.
import std.random : Mt19937;
static assert(!isInitAllZeroBits!Mt19937);
// Check that it works with const.
static assert(isInitAllZeroBits!(const(Mt19937)) == isInitAllZeroBits!Mt19937);
static assert(isInitAllZeroBits!(const(S5)) == isInitAllZeroBits!S5);
}

/+
Can the representation be determined at compile time to consist of nothing but
1 bits? This is reported as $(B false) for structs with padding between
their fields because `opEquals` and hashing may rely on those bits being
zero.
Note:
A bool occupies 8 bits so `isAllOneBits!(bool, true) == false`
See_Also:
https://forum.dlang.org/post/hn11oh$1usk$1@digitalmars.com
+/
private template isAllOneBits(T, T value)
{
static if (isIntegral!T || isSomeChar!T)
{
import core.bitop : popcnt;
static if (T.min < T(0))
enum isAllOneBits = popcnt(cast(Unsigned!T) value) == T.sizeof * 8;
else
enum isAllOneBits = popcnt(value) == T.sizeof * 8;
}
else static if (isStaticArray!(typeof(value)))
{
enum isAllOneBits = ()
{
bool b = true;
// Use index so this works when T.length is 0.
static foreach (i; 0 .. T.length)
{
b &= isAllOneBits!(typeof(value[i]), value[i]);
if (b == false) return b;
}

return b;
}();
}
else static if (is(typeof(value) == struct))
{
enum isAllOneBits = ()
{
bool b = true;
size_t fieldSizeSum = 0;
static foreach (e; value.tupleof)
{
b &= isAllOneBits!(typeof(e), e);
if (b == false) return b;
fieldSizeSum += typeof(e).sizeof;
}
// If fieldSizeSum == T.sizeof then there can be no gaps
// between fields.
return b && fieldSizeSum == T.sizeof;
}();
}
else
{
enum isAllOneBits = false;
}
}

// If `isAllOneBits` becomes public document this unittest.
@nogc nothrow pure @safe unittest
{
static assert(isAllOneBits!(char, 0xff));
static assert(isAllOneBits!(wchar, 0xffff));
static assert(isAllOneBits!(byte, cast(byte) 0xff));
static assert(isAllOneBits!(int, 0xffff_ffff));
static assert(isAllOneBits!(char[4], [0xff, 0xff, 0xff, 0xff]));

static assert(!isAllOneBits!(bool, true));
static assert(!isAllOneBits!(wchar, 0xff));
static assert(!isAllOneBits!(Object, Object.init));
}

// Don't document this unittest.
@nogc nothrow pure @safe unittest
{
import std.meta : AliasSeq;
foreach (Int; AliasSeq!(char, wchar, byte, ubyte, short, ushort, int, uint,
long, ulong))
{
static assert(isAllOneBits!(Int, cast(Int) 0xffff_ffff_ffff_ffffUL));
static assert(!isAllOneBits!(Int, Int(1)));
static if (Int.sizeof > 1)
static assert(!isAllOneBits!(Int, cast(Int) 0xff));
}
static assert(!isAllOneBits!(dchar, 0xffff));
}

/+
Can the representation be determined at compile time to consist of nothing but
1 bits? This is reported as $(B false) for structs with padding between
their fields because `opEquals` and hashing may rely on those bits being
zero.
See_Also:
https://forum.dlang.org/post/hn11oh$1usk$1@digitalmars.com
+/
package template isInitAllOneBits(T)
{
static if (isStaticArray!T && __traits(compiles, T.init[0]))
enum isInitAllOneBits = __traits(compiles, {
static assert(isAllOneBits!(typeof(T.init[0]), T.init[0]));
});
else
enum isInitAllOneBits = __traits(compiles, {
static assert(isAllOneBits!(T, T.init));
});
}

@nogc nothrow pure @safe unittest
{
static assert(isInitAllOneBits!char);
static assert(isInitAllOneBits!wchar);
static assert(!isInitAllOneBits!dchar);

static assert(isInitAllOneBits!(char[4]));
static assert(!isInitAllOneBits!(int[4]));
static assert(!isInitAllOneBits!Object);

static struct S1
{
char a;
char b;
}
static assert(isInitAllOneBits!S1);

static struct S2
{
char a = 1;
}
static assert(!isInitAllOneBits!S2);

static struct S3
{
S1 a;
char b;
}
static assert(isInitAllOneBits!S3);
static assert(isInitAllOneBits!(S3[2]));

static struct S4
{
S1 a;
S2 b;
}
static assert(!isInitAllOneBits!S4);

static struct S5
{
int r = 0xffff_ffff;
}
static assert(isInitAllOneBits!S5);

// Verify that when there is padding between fields isInitAllOneBits is false.
static struct S6
{
align(4) char a;
align(4) char b;
}
static assert(!isInitAllOneBits!S6);

static class C1
{
char c;
}
static assert(!isInitAllOneBits!C1);
}
42 changes: 39 additions & 3 deletions std/experimental/allocator/package.d
Expand Up @@ -1403,9 +1403,27 @@ if (T.sizeof != 1)

private T[] uninitializedFillDefault(T)(T[] array) nothrow
{
T t = T.init;
fillWithMemcpy(array, t);
return array;
static if (isInitAllZeroBits!T)
{
import core.stdc.string : memset;
if (array !is null)
memset(array.ptr, 0, T.sizeof * array.length);
return array;
}
else static if (isInitAllOneBits!T)
{
// Mostly for char and wchar.
import core.stdc.string : memset;
if (array !is null)
memset(array.ptr, 0xff, T.sizeof * array.length);
return array;
}
else
{
T t = T.init;
fillWithMemcpy(array, t);
return array;
}
}

pure nothrow @nogc
Expand All @@ -1424,6 +1442,24 @@ pure nothrow @nogc
int[] a = [1, 2, 4];
uninitializedFillDefault(a);
assert(a == [0, 0, 0]);

char[] b = [1, 2, 4];
uninitializedFillDefault(b);
assert(b == [0xff, 0xff, 0xff]);

wchar[] c = [1, 2, 4];
uninitializedFillDefault(c);
assert(c == [0xffff, 0xffff, 0xffff]);
}

@system unittest
{
static struct P { float x = 0; float y = 0; }

static assert(isInitAllZeroBits!P);
P[] a = [P(10, 11), P(20, 21), P(40, 41)];
uninitializedFillDefault(a);
assert(a == [P.init, P.init, P.init]);
}

/**
Expand Down

0 comments on commit 6797b52

Please sign in to comment.