|
1 | 1 | /** |
2 | | - * This module contains compiler support determining equality of dynamic arrays. |
| 2 | + * This module contains compiler support determining equality of arrays. |
3 | 3 | * |
4 | | - * Copyright: Copyright Digital Mars 2000 - 2019. |
| 4 | + * Copyright: Copyright Digital Mars 2000 - 2020. |
5 | 5 | * License: Distributed under the |
6 | 6 | * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). |
7 | 7 | * (See accompanying file LICENSE) |
|
10 | 10 |
|
11 | 11 | module core.internal.array.equality; |
12 | 12 |
|
13 | | - // `lhs == rhs` lowers to `__equals(lhs, rhs)` for dynamic arrays |
| 13 | +// The compiler lowers `lhs == rhs` to `__equals(lhs, rhs)` for |
| 14 | +// * dynamic arrays, |
| 15 | +// * (most) arrays of different (unqualified) element types, and |
| 16 | +// * arrays of structs with custom opEquals. |
14 | 17 | bool __equals(T1, T2)(scope T1[] lhs, scope T2[] rhs) |
15 | 18 | { |
16 | | - import core.internal.traits : Unqual; |
17 | | - alias U1 = Unqual!T1; |
18 | | - alias U2 = Unqual!T2; |
19 | | - |
20 | | - static @trusted ref R at(R)(R[] r, size_t i) { return r.ptr[i]; } |
21 | | - static @trusted R trustedCast(R, S)(S[] r) { return cast(R) r; } |
22 | | - |
23 | 19 | if (lhs.length != rhs.length) |
24 | 20 | return false; |
25 | 21 |
|
26 | | - if (lhs.length == 0 && rhs.length == 0) |
| 22 | + if (lhs.length == 0) |
27 | 23 | return true; |
28 | 24 |
|
29 | | - static if (is(U1 == void) && is(U2 == void)) |
30 | | - { |
31 | | - return __equals(trustedCast!(ubyte[])(lhs), trustedCast!(ubyte[])(rhs)); |
32 | | - } |
33 | | - else static if (is(U1 == void)) |
34 | | - { |
35 | | - return __equals(trustedCast!(ubyte[])(lhs), rhs); |
36 | | - } |
37 | | - else static if (is(U2 == void)) |
38 | | - { |
39 | | - return __equals(lhs, trustedCast!(ubyte[])(rhs)); |
40 | | - } |
41 | | - else static if (!is(U1 == U2)) |
| 25 | + static if (useMemcmp!(T1, T2)) |
42 | 26 | { |
43 | | - foreach (const u; 0 .. lhs.length) |
44 | | - { |
45 | | - if (at(lhs, u) != at(rhs, u)) |
46 | | - return false; |
47 | | - } |
48 | | - return true; |
49 | | - } |
50 | | - else static if (__traits(isIntegral, U1)) |
51 | | - { |
52 | | - |
53 | 27 | if (!__ctfe) |
54 | 28 | { |
55 | | - import core.stdc.string : memcmp; |
56 | | - return () @trusted { return memcmp(cast(void*)lhs.ptr, cast(void*)rhs.ptr, lhs.length * U1.sizeof) == 0; }(); |
| 29 | + static bool trustedMemcmp(T1[] lhs, T2[] rhs) @trusted @nogc nothrow pure |
| 30 | + { |
| 31 | + pragma(inline, true); |
| 32 | + import core.stdc.string : memcmp; |
| 33 | + return memcmp(cast(void*) lhs.ptr, cast(void*) rhs.ptr, lhs.length * T1.sizeof) == 0; |
| 34 | + } |
| 35 | + return trustedMemcmp(lhs, rhs); |
57 | 36 | } |
58 | 37 | else |
59 | 38 | { |
60 | | - foreach (const u; 0 .. lhs.length) |
| 39 | + foreach (const i; 0 .. lhs.length) |
61 | 40 | { |
62 | | - if (at(lhs, u) != at(rhs, u)) |
| 41 | + if (at(lhs, i) != at(rhs, i)) |
63 | 42 | return false; |
64 | 43 | } |
65 | 44 | return true; |
66 | 45 | } |
67 | 46 | } |
68 | 47 | else |
69 | 48 | { |
70 | | - foreach (const u; 0 .. lhs.length) |
| 49 | + foreach (const i; 0 .. lhs.length) |
71 | 50 | { |
72 | | - static if (__traits(compiles, __equals(at(lhs, u), at(rhs, u)))) |
73 | | - { |
74 | | - if (!__equals(at(lhs, u), at(rhs, u))) |
75 | | - return false; |
76 | | - } |
77 | | - else static if (__traits(isFloating, U1)) |
78 | | - { |
79 | | - if (at(lhs, u) != at(rhs, u)) |
80 | | - return false; |
81 | | - } |
82 | | - else static if (is(U1 : Object) && is(U2 : Object)) |
83 | | - { |
84 | | - if (!(cast(Object)at(lhs, u) is cast(Object)at(rhs, u) |
85 | | - || at(lhs, u) && (cast(Object)at(lhs, u)).opEquals(cast(Object)at(rhs, u)))) |
86 | | - return false; |
87 | | - } |
88 | | - else static if (__traits(hasMember, U1, "opEquals")) |
89 | | - { |
90 | | - if (!at(lhs, u).opEquals(at(rhs, u))) |
91 | | - return false; |
92 | | - } |
93 | | - else static if (is(U1 == delegate)) |
94 | | - { |
95 | | - if (at(lhs, u) != at(rhs, u)) |
96 | | - return false; |
97 | | - } |
98 | | - else static if (is(U1 == U11*, U11)) |
99 | | - { |
100 | | - if (at(lhs, u) != at(rhs, u)) |
101 | | - return false; |
102 | | - } |
103 | | - else static if (__traits(isAssociativeArray, U1)) |
104 | | - { |
105 | | - if (at(lhs, u) != at(rhs, u)) |
106 | | - return false; |
107 | | - } |
108 | | - else |
109 | | - { |
110 | | - if (at(lhs, u).tupleof != at(rhs, u).tupleof) |
111 | | - return false; |
112 | | - } |
| 51 | + if (at(lhs, i) != at(rhs, i)) |
| 52 | + return false; |
113 | 53 | } |
114 | | - |
115 | 54 | return true; |
116 | 55 | } |
117 | 56 | } |
@@ -186,3 +125,81 @@ bool __equals(T1, T2)(scope T1[] lhs, scope T2[] rhs) |
186 | 125 | assert(!__equals(a1, a2)); |
187 | 126 | assert(a1 != a2); |
188 | 127 | } |
| 128 | + |
| 129 | + |
| 130 | +private: |
| 131 | + |
| 132 | +enum isVoid(T) = is(immutable T == immutable void); |
| 133 | + |
| 134 | +// - Recursively folds static array types to their element type, |
| 135 | +// - maps void to ubyte, and |
| 136 | +// - pointers to size_t. |
| 137 | +template BaseType(T) |
| 138 | +{ |
| 139 | + static if (__traits(isStaticArray, T)) |
| 140 | + alias BaseType = BaseType!(typeof(T.init[0])); |
| 141 | + else static if (isVoid!T) |
| 142 | + alias BaseType = ubyte; |
| 143 | + else static if (is(T == E*, E)) |
| 144 | + alias BaseType = size_t; |
| 145 | + else |
| 146 | + alias BaseType = T; |
| 147 | +} |
| 148 | + |
| 149 | +// Use memcmp if the element sizes match and both base element types are integral. |
| 150 | +// Due to int promotion, disallow small integers of diverging signed-ness though. |
| 151 | +template useMemcmp(T1, T2) |
| 152 | +{ |
| 153 | + static if (T1.sizeof != T2.sizeof) |
| 154 | + enum useMemcmp = false; |
| 155 | + else |
| 156 | + { |
| 157 | + alias B1 = BaseType!T1; |
| 158 | + alias B2 = BaseType!T2; |
| 159 | + enum useMemcmp = __traits(isIntegral, B1) && __traits(isIntegral, B2) |
| 160 | + && !( (B1.sizeof < 4 || B2.sizeof < 4) && __traits(isUnsigned, B1) != __traits(isUnsigned, B2) ); |
| 161 | + } |
| 162 | +} |
| 163 | + |
| 164 | +unittest |
| 165 | +{ |
| 166 | + enum E { foo, bar } |
| 167 | + |
| 168 | + static assert(useMemcmp!(byte, byte)); |
| 169 | + static assert(useMemcmp!(ubyte, ubyte)); |
| 170 | + static assert(useMemcmp!(void, const void)); |
| 171 | + static assert(useMemcmp!(void, immutable bool)); |
| 172 | + static assert(useMemcmp!(void, inout char)); |
| 173 | + static assert(useMemcmp!(void, shared ubyte)); |
| 174 | + static assert(!useMemcmp!(void, byte)); // differing signed-ness |
| 175 | + static assert(!useMemcmp!(char[8], byte[8])); // ditto |
| 176 | + |
| 177 | + static assert(useMemcmp!(short, short)); |
| 178 | + static assert(useMemcmp!(wchar, ushort)); |
| 179 | + static assert(!useMemcmp!(wchar, short)); // differing signed-ness |
| 180 | + |
| 181 | + static assert(useMemcmp!(int, uint)); // no promotion, ignoring signed-ness |
| 182 | + static assert(useMemcmp!(dchar, E)); |
| 183 | + |
| 184 | + static assert(useMemcmp!(immutable void*, size_t)); |
| 185 | + static assert(useMemcmp!(double*, ptrdiff_t)); |
| 186 | + static assert(useMemcmp!(long[2][3], const(ulong)[2][3])); |
| 187 | + |
| 188 | + static assert(!useMemcmp!(float, float)); |
| 189 | + static assert(!useMemcmp!(double[2], double[2])); |
| 190 | + static assert(!useMemcmp!(Object, Object)); |
| 191 | + static assert(!useMemcmp!(int[], int[])); |
| 192 | +} |
| 193 | + |
| 194 | +// Returns a reference to an array element, eliding bounds check and |
| 195 | +// casting void to ubyte. |
| 196 | +pragma(inline, true) |
| 197 | +ref at(T)(T[] r, size_t i) @trusted |
| 198 | + // exclude opaque structs due to https://issues.dlang.org/show_bug.cgi?id=20959 |
| 199 | + if (!(is(T == struct) && !is(typeof(T.sizeof)))) |
| 200 | +{ |
| 201 | + static if (isVoid!T) |
| 202 | + return (cast(ubyte*) r.ptr)[i]; |
| 203 | + else |
| 204 | + return r.ptr[i]; |
| 205 | +} |
0 commit comments