Skip to content

Commit 3fa1978

Browse files
authored
Merge pull request #9331 from WalterBright/no-cmpsb
don't use memcmp() for struct equality, do memberwise comparisons per the spec
2 parents b58f36d + 4accb77 commit 3fa1978

8 files changed

Lines changed: 61 additions & 22 deletions

File tree

changelog/no-cmpsb.dd

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
`memcmp()` compares are no longer performed for struct equality tests, memberwise comparisons are done instead, per the spec
2+
3+
The compiler would sometimes generate code for struct equality tests using
4+
`memcmp()` across the whole struct object. This assumed that:
5+
6+
$(OL
7+
$(LI alignment holes were filled with 0, which is not always the case)
8+
$(LI there were no floating point NaN values, which should compare as not equal
9+
even if the bit patterns match)
10+
)
11+
12+
The spec requires that the comparison be done in a memberwise manner.
13+
This brings the implementation in line with this.
14+
15+
$(LINK2 https://dlang.org/spec/expression.html#equality_expressions, Equality Expressions)
16+
17+
This can break existing code that relied on the former erroneous behavior.
18+
To correct such code, use one of the following:
19+
20+
$(OL
21+
$(LI define an `opEquals()` operator overload to achieve the desired behavior)
22+
$(LI use `is` instead of `==`, as `is` will do a bit compare of the struct object.)
23+
)
24+
25+
This new behavior is enabled with the `-preview=fieldwise` compiler switch. It
26+
will eventually become the default behavior.

src/dmd/cli.d

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,7 @@ dmd -cov -unittest myprog.d
691691
"implement https://github.com/dlang/DIPs/blob/master/DIPs/DIP1000.md (Scoped Pointers)"),
692692
Feature("dip1008", "ehnogc",
693693
"implement https://github.com/dlang/DIPs/blob/master/DIPs/DIP1008.md (@nogc Throwable)"),
694+
Feature("fieldwise", "fieldwise", "use fieldwise comparisons for struct equality"),
694695
Feature("markdown", "markdown", "enable Markdown replacements in Ddoc"),
695696
Feature("fixAliasThis", "fixAliasThis",
696697
"when a symbol is resolved, check alias this scope before going to upper scopes"),

src/dmd/globals.d

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ struct Param
167167
bool ehnogc; // use @nogc exception handling
168168
bool dtorFields; // destruct fields of partially constructed objects
169169
// https://issues.dlang.org/show_bug.cgi?id=14246
170+
bool fieldwise; // do struct equality testing field-wise rather than by memcmp()
170171

171172
CppStdRevision cplusplus = CppStdRevision.cpp98; // version of C++ standard to support
172173

src/dmd/globals.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ struct Param
137137
bool ehnogc; // use @nogc exception handling
138138
bool dtorFields; // destruct fields of partially constructed objects
139139
// https://issues.dlang.org/show_bug.cgi?id=14246
140+
bool fieldwise; // do struct equality testing field-wise rather than by memcmp()
140141
unsigned cplusplus; // version of C++ name mangling to support
141142
bool markdown; // enable Markdown replacements in Ddoc
142143
bool vmarkdown; // list instances of Markdown replacements in Ddoc

src/dmd/opover.d

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1241,7 +1241,7 @@ Expression op_overload(Expression e, Scope* sc)
12411241
return;
12421242

12431243
import dmd.clone : needOpEquals;
1244-
if (!needOpEquals(sd))
1244+
if (!global.params.fieldwise && !needOpEquals(sd))
12451245
{
12461246
// Use bitwise equality.
12471247
auto op2 = e.op == TOK.equal ? TOK.identity : TOK.notIdentity;
@@ -1251,6 +1251,7 @@ Expression op_overload(Expression e, Scope* sc)
12511251
}
12521252

12531253
/* Do memberwise equality.
1254+
* https://dlang.org/spec/expression.html#equality_expressions
12541255
* Rewrite:
12551256
* e1 == e2
12561257
* as:

test/compilable/test16284.d

Lines changed: 0 additions & 18 deletions
This file was deleted.

test/fail_compilation/test16284.d

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/* REQUIRED_ARGS: -preview=fieldwise
2+
TEST_OUTPUT:
3+
---
4+
fail_compilation/test16284.d(24): Error: reinterpretation through overlapped field `s` is not allowed in CTFE
5+
fail_compilation/test16284.d(27): called from here: `test()`
6+
fail_compilation/test16284.d(27): while evaluating: `static assert(test())`
7+
---
8+
*/
9+
10+
// https://issues.dlang.org/show_bug.cgi?id=16284
11+
12+
struct S {}
13+
14+
struct T
15+
{
16+
union {int i; S s;}
17+
this(uint dummy) { s = S.init; }
18+
}
19+
20+
bool test()
21+
{
22+
auto t1 = T(0);
23+
auto t2 = T(0);
24+
return t1 == t2;
25+
}
26+
27+
static assert(test());

test/runnable/opover2.d

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// PERMUTE_ARGS: -inline -O
2-
// REQUIRED_ARGS: -preview=dip25
2+
// REQUIRED_ARGS: -preview=dip25 -preview=fieldwise
33

44
// Test operator overloading
55

@@ -728,7 +728,7 @@ bool test3789()
728728
auto ua2 = UnionA([1,2,3]);
729729
assert(ua1.u.x is ua2.u.x);
730730
assert(ua1.u.x != ua2.u.x);
731-
assert(ua1 == ua2);
731+
assert(ua1 != ua2);
732732
ua1.u.x = 1.0;
733733
ua2.u.x = 1.0;
734734
assert(ua1.u.x is ua2.u.x);
@@ -755,7 +755,7 @@ bool test3789()
755755
ub2.u.a = [1,2,3].dup;
756756
assert(ub1.u.a !is ub2.u.a);
757757
assert(ub1.u.a == ub2.u.a);
758-
assert(ub1 != ub2);
758+
assert(ub1 == ub2);
759759
ub2.u.a = ub1.u.a;
760760
assert(ub1.u.a is ub2.u.a);
761761
assert(ub1.u.a == ub2.u.a);

0 commit comments

Comments
 (0)