Skip to content

Commit bfcaa35

Browse files
kinkeJohanEngelen
authored andcommitted
Revise core.internal.array.equality
* Use memcmp for more types (incl. pointers and some static arrays), and don't require both element types to be equivalent (e.g., use it for comparing wchar[] and ushort[] too). * Simplify the non-memcmp branch - the previous code seemed to try to do what the compiler already does. * The previous code for structs without custom opEquals basically enforced -preview=fieldwise via .tupleof comparison. This breaking change with 2.078 almost certainly wasn't intended (and not mentioned in the changelog) and led to undesirable inconsistent behavior, see dlang#3142 (comment). This reverts that change in semantics. * Avoid superfluously nested templates and convert the remaining `at()` helper to a module-level template to reduce the number of redundant instantiations. See dlang#3142
1 parent 9a0b6ce commit bfcaa35

2 files changed

Lines changed: 120 additions & 86 deletions

File tree

changelog/array_equality.dd

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Equality of arrays of structs is consistent again, as before v2.078
2+
3+
Since v2.078, some array equality comparisons (e.g., if both arrays are
4+
dynamic arrays) have been wrongly using a `.tupleof` comparison for
5+
structs without custom `opEquals`, basically enforcing
6+
`-preview=fieldwise` for these array comparisons.
7+
8+
---
9+
union U
10+
{
11+
string s;
12+
}
13+
14+
void main()
15+
{
16+
static immutable from = "from", from2 = "from2";
17+
U[] a = [{ s : from }];
18+
U[1] b = [{ s : from2[0..4] }];
19+
assert(a[0] != b[0]);
20+
assert(a != b);
21+
assert(a != b[]); // worked before v2.078, been failing since, now working again
22+
}
23+
---

src/core/internal/array/equality.d

Lines changed: 97 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
2-
* This module contains compiler support determining equality of dynamic arrays.
2+
* This module contains compiler support determining equality of arrays.
33
*
4-
* Copyright: Copyright Digital Mars 2000 - 2019.
4+
* Copyright: Copyright Digital Mars 2000 - 2020.
55
* License: Distributed under the
66
* $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
77
* (See accompanying file LICENSE)
@@ -23,112 +23,47 @@ bool __ArrayEq(T1, T2)(T1[] a, T2[] b)
2323
return true;
2424
}
2525

26-
// `lhs == rhs` lowers to `__equals(lhs, rhs)` for dynamic arrays
27-
bool __equals(T1, T2)(T1[] lhs, T2[] rhs)
26+
// The compiler lowers `lhs == rhs` to `__equals(lhs, rhs)` for
27+
// * dynamic arrays,
28+
// * (most) arrays of different (unqualified) element types, and
29+
// * arrays of structs with custom opEquals.
30+
bool __equals(T1, T2)(scope T1[] lhs, scope T2[] rhs)
2831
{
29-
import core.internal.traits : Unqual;
30-
alias U1 = Unqual!T1;
31-
alias U2 = Unqual!T2;
32-
33-
static @trusted ref R at(R)(R[] r, size_t i) { return r.ptr[i]; }
34-
static @trusted R trustedCast(R, S)(S[] r) { return cast(R) r; }
35-
3632
if (lhs.length != rhs.length)
3733
return false;
3834

39-
if (lhs.length == 0 && rhs.length == 0)
35+
if (lhs.length == 0)
4036
return true;
4137

42-
static if (is(U1 == void) && is(U2 == void))
43-
{
44-
return __equals(trustedCast!(ubyte[])(lhs), trustedCast!(ubyte[])(rhs));
45-
}
46-
else static if (is(U1 == void))
47-
{
48-
return __equals(trustedCast!(ubyte[])(lhs), rhs);
49-
}
50-
else static if (is(U2 == void))
51-
{
52-
return __equals(lhs, trustedCast!(ubyte[])(rhs));
53-
}
54-
else static if (!is(U1 == U2))
38+
static if (useMemcmp!(T1, T2))
5539
{
56-
// This should replace src/object.d _ArrayEq which
57-
// compares arrays of different types such as long & int,
58-
// char & wchar.
59-
// Compiler lowers to __ArrayEq in dmd/src/opover.d
60-
foreach (const u; 0 .. lhs.length)
61-
{
62-
if (at(lhs, u) != at(rhs, u))
63-
return false;
64-
}
65-
return true;
66-
}
67-
else static if (__traits(isIntegral, U1))
68-
{
69-
7040
if (!__ctfe)
7141
{
72-
import core.stdc.string : memcmp;
73-
return () @trusted { return memcmp(cast(void*)lhs.ptr, cast(void*)rhs.ptr, lhs.length * U1.sizeof) == 0; }();
42+
static bool trustedMemcmp(scope T1[] lhs, scope T2[] rhs) @trusted @nogc nothrow pure
43+
{
44+
pragma(inline, true);
45+
import core.stdc.string : memcmp;
46+
return memcmp(cast(void*) lhs.ptr, cast(void*) rhs.ptr, lhs.length * T1.sizeof) == 0;
47+
}
48+
return trustedMemcmp(lhs, rhs);
7449
}
7550
else
7651
{
77-
foreach (const u; 0 .. lhs.length)
52+
foreach (const i; 0 .. lhs.length)
7853
{
79-
if (at(lhs, u) != at(rhs, u))
54+
if (at(lhs, i) != at(rhs, i))
8055
return false;
8156
}
8257
return true;
8358
}
8459
}
8560
else
8661
{
87-
foreach (const u; 0 .. lhs.length)
62+
foreach (const i; 0 .. lhs.length)
8863
{
89-
static if (__traits(compiles, __equals(at(lhs, u), at(rhs, u))))
90-
{
91-
if (!__equals(at(lhs, u), at(rhs, u)))
92-
return false;
93-
}
94-
else static if (__traits(isFloating, U1))
95-
{
96-
if (at(lhs, u) != at(rhs, u))
97-
return false;
98-
}
99-
else static if (is(U1 : Object) && is(U2 : Object))
100-
{
101-
if (!(cast(Object)at(lhs, u) is cast(Object)at(rhs, u)
102-
|| at(lhs, u) && (cast(Object)at(lhs, u)).opEquals(cast(Object)at(rhs, u))))
103-
return false;
104-
}
105-
else static if (__traits(hasMember, U1, "opEquals"))
106-
{
107-
if (!at(lhs, u).opEquals(at(rhs, u)))
108-
return false;
109-
}
110-
else static if (is(U1 == delegate))
111-
{
112-
if (at(lhs, u) != at(rhs, u))
113-
return false;
114-
}
115-
else static if (is(U1 == U11*, U11))
116-
{
117-
if (at(lhs, u) != at(rhs, u))
118-
return false;
119-
}
120-
else static if (__traits(isAssociativeArray, U1))
121-
{
122-
if (at(lhs, u) != at(rhs, u))
123-
return false;
124-
}
125-
else
126-
{
127-
if (at(lhs, u).tupleof != at(rhs, u).tupleof)
128-
return false;
129-
}
64+
if (at(lhs, i) != at(rhs, i))
65+
return false;
13066
}
131-
13267
return true;
13368
}
13469
}
@@ -203,3 +138,79 @@ bool __equals(T1, T2)(T1[] lhs, T2[] rhs)
203138
assert(!__equals(a1, a2));
204139
assert(a1 != a2);
205140
}
141+
142+
143+
private:
144+
145+
// - Recursively folds static array types to their element type,
146+
// - maps void to ubyte, and
147+
// - pointers to size_t.
148+
template BaseType(T)
149+
{
150+
static if (__traits(isStaticArray, T))
151+
alias BaseType = BaseType!(typeof(T.init[0]));
152+
else static if (is(immutable T == immutable void))
153+
alias BaseType = ubyte;
154+
else static if (is(T == E*, E))
155+
alias BaseType = size_t;
156+
else
157+
alias BaseType = T;
158+
}
159+
160+
// Use memcmp if the element sizes match and both base element types are integral.
161+
// Due to int promotion, disallow small integers of diverging signed-ness though.
162+
template useMemcmp(T1, T2)
163+
{
164+
static if (T1.sizeof != T2.sizeof)
165+
enum useMemcmp = false;
166+
else
167+
{
168+
alias B1 = BaseType!T1;
169+
alias B2 = BaseType!T2;
170+
enum useMemcmp = __traits(isIntegral, B1) && __traits(isIntegral, B2)
171+
&& !( (B1.sizeof < 4 || B2.sizeof < 4) && __traits(isUnsigned, B1) != __traits(isUnsigned, B2) );
172+
}
173+
}
174+
175+
unittest
176+
{
177+
enum E { foo, bar }
178+
179+
static assert(useMemcmp!(byte, byte));
180+
static assert(useMemcmp!(ubyte, ubyte));
181+
static assert(useMemcmp!(void, const void));
182+
static assert(useMemcmp!(void, immutable bool));
183+
static assert(useMemcmp!(void, inout char));
184+
static assert(useMemcmp!(void, shared ubyte));
185+
static assert(!useMemcmp!(void, byte)); // differing signed-ness
186+
static assert(!useMemcmp!(char[8], byte[8])); // ditto
187+
188+
static assert(useMemcmp!(short, short));
189+
static assert(useMemcmp!(wchar, ushort));
190+
static assert(!useMemcmp!(wchar, short)); // differing signed-ness
191+
192+
static assert(useMemcmp!(int, uint)); // no promotion, ignoring signed-ness
193+
static assert(useMemcmp!(dchar, E));
194+
195+
static assert(useMemcmp!(immutable void*, size_t));
196+
static assert(useMemcmp!(double*, ptrdiff_t));
197+
static assert(useMemcmp!(long[2][3], const(ulong)[2][3]));
198+
199+
static assert(!useMemcmp!(float, float));
200+
static assert(!useMemcmp!(double[2], double[2]));
201+
static assert(!useMemcmp!(Object, Object));
202+
static assert(!useMemcmp!(int[], int[]));
203+
}
204+
205+
// Returns a reference to an array element, eliding bounds check and
206+
// casting void to ubyte.
207+
pragma(inline, true)
208+
ref at(T)(T[] r, size_t i) @trusted
209+
// exclude opaque structs due to https://issues.dlang.org/show_bug.cgi?id=20959
210+
if (!(is(T == struct) && !is(typeof(T.sizeof))))
211+
{
212+
static if (is(immutable T == immutable void))
213+
return (cast(ubyte*) r.ptr)[i];
214+
else
215+
return r.ptr[i];
216+
}

0 commit comments

Comments
 (0)