New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[stable] Fix misc. issues wrt. temporaries for -checkaction=context lowering #11005
Conversation
const generateMsg = !exp.msg && global.params.checkAction == CHECKACTION.context; | ||
auto assertExpMsg = generateMsg ? exp.toChars() : null; | ||
|
||
if (Expression ex = unaSemantic(exp, sc)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This early expression sema of exp.e1
, the assert condition, was one of the problems. E.g.:
struct S
{
int a;
this(this) {}
bool opEquals(S) @safe
{
return true;
}
}
void foo()
{
S a;
assert(S() == a); // S().opEquals(a)
}
Here, an lvalue a
is passed to S.opEquals()
, which takes the arg by value. The early CallExp
sema already lowers the argument expression a
to a temporary, to something like S __copytmp = (__copytmp = a).__postblit()
. That expression (instead of a
) was previously stored as a further __assertOp
temporary, and the original argument replaced by that temporary (without postblit).
Previous lowering:
assert(S(0).opEquals(((S __assertOp3 = (S __copytmp2 = (__copytmp2 = a).__postblit();) , __assertOp3 = __copytmp2;) , __assertOp3)), _d_assert_fail(S(0), __assertOp3));
New:
assert(S(0).opEquals(((S __copytmp2 = (__copytmp2 = a).__postblit();) , __copytmp2)), _d_assert_fail(S(0), a));
A related 2nd issue concerns rvalue arguments - the early CallExp
sema detects that no temporary is needed (move; elide postblit and dtor in the caller); the rvalue was previously correctly stored as __assertOp
temporary, but as it is now an lvalue (possibly accessed a 2nd time for the _d_assert_fail()
call if the assertion fails), it must be copied + postblitted. This can be fixed by deferring the CallExp
sema until the arguments have been replaced by required temporaries (and hence possibly promoted from rvalues to lvalues).
void rval()
{
static S nonPure()
{
__gshared int counter;
++counter;
return S(123);
}
assert(S() == nonPure());
}
Previous lowering:
assert(S(0).opEquals(((S __assertOp12 = nonPure();) , __assertOp12)), _d_assert_fail(S(0), __assertOp12));
New:
(S __assertOp11 = nonPure();) , assert(S(0).opEquals(((S __copytmp12 = (__copytmp12 = __assertOp11).__postblit();) , __copytmp12)), _d_assert_fail(S(0), __assertOp11));
…ields This is blocking dlang/dmd#11005.
Please do ONE issue per PR. |
62d7d84
to
ed05c88
Compare
Deferring the CallExp sema fixes multiple issues at once. - I've been testing this with LDC, as I don't have a DMD dev environment set up, and I can't reproduce (on Windows) the remaining failure in dmd/test/runnable/testassert.d Line 126 in ed05c88
|
The mentioned DMD failure can be worked around by making Edit: Still not enough for Win64. |
druntime would need this: diff --git a/test/exceptions/src/assert_fail.d b/test/exceptions/src/assert_fail.d
index 3e21ef64..c31ad4ee 100644
--- a/test/exceptions/src/assert_fail.d
+++ b/test/exceptions/src/assert_fail.d
@@ -109,7 +109,7 @@ void testToString()()
}
}
- test!"!="(Overloaded(), Overloaded(), "Const is Const");
+ test!"!="(Overloaded(), Overloaded(), "Const == Const");
}
@@ -129,8 +129,8 @@ void testStruct()()
{
struct S { int s; }
struct T { T[] t; }
- test(S(0), S(1), "S(0) !is S(1)");
- test(T([T(null)]), T(null), "[T([])] != []");
+ test(S(0), S(1), "S(0) != S(1)");
+ test(T([T(null)]), T(null), "T([T([])]) != T([])");
// https://issues.dlang.org/show_bug.cgi?id=20323
static struct NoCopy
@@ -139,7 +139,7 @@ void testStruct()()
}
NoCopy n;
- test(assert(n != n), "NoCopy() is NoCopy()");
+ test(assert(n != n), "NoCopy() == NoCopy()");
}
void testAA()()
@@ -161,13 +161,13 @@ void testVoidArray()()
{
assert([] is null);
assert(null is null);
- test([1], null, "[1] != []");
- test("s", null, `"s" != ""`);
- test(['c'], null, `"c" != ""`);
+ test([1], null, "[1] != `null`");
+ test("s", null, "\"s\" != `null`");
+ test(['c'], null, "\"c\" != `null`");
test!"!="(null, null, "`null` == `null`");
const void[] chunk = [byte(1), byte(2), byte(3)];
- test(chunk, null, "[1, 2, 3] != []");
+ test(chunk, null, "[1, 2, 3] != `null`");
}
void testTemporary()
@@ -177,7 +177,7 @@ void testTemporary()
~this() @system {}
}
- test(assert(Bad() != Bad()), "Bad() is Bad()");
+ test(assert(Bad() != Bad()), "Bad() == Bad()");
}
void testEnum() |
The first part looks nice because it retains the original types and operators. But the second part looks kindof bad now, especially when comparing strings and |
I don't care how null could be handled more elegantly (i.e., the druntime side of this), but as most of the time, it's a pain to get things done here due to the druntime/compiler entanglement. |
That can be handled in a later PR, was just thinking about different approaches to retain some of the current behaviour. The current test situation for
What do you think? |
Yes, that would be exactly it. I was planning to finish this for 2.091.1 / LDC 1.21.0 final, but that's no longer an option. ;) |
Guess i'll start to apply the aforementioned changes. Whats the schedule for LDC 1.21.0 final? |
Thx, help is greatly appreciated. - The usual schedule would be something like max 1 week after the 2.091.1 release, i.e., soon, but it's not set in stone (and if I really wanted to, I could of course make the LDC tests pass and handle it here upstream in parallel). |
This test should only check the Druntime component of `-checkaction=context`, i.e. that _d_assert_fail yields an appropriate assert message. Using `assert` directly linked this test to the lowering details and made changes changes quite difficult because one cannot make changes for DMD + Druntime in a single PR (see e.g. dlang/dmd#11005).
This test should only check the druntime component of `-checkaction=context`, i.e. that `_d_assert_fail` yields an appropriate assert message. Using `assert` directly linked this test to the lowering details and made changes changes quite difficult because one cannot make changes to dmd and druntime in a single PR (see e.g. dlang/dmd#11005).
This test should only check the druntime component of `-checkaction=context`, i.e. that `_d_assert_fail` yields an appropriate assert message. Using `assert` directly linked this test to the lowering details and made changes changes quite difficult because one cannot make changes to dmd and druntime in a single PR (see e.g. dlang/dmd#11005).
This test should only check the druntime component of `-checkaction=context`, i.e. that `_d_assert_fail` yields an appropriate assert message. Using `assert` directly linked this test to the lowering details and made changes changes quite difficult because one cannot make changes to dmd and druntime in a single PR (see e.g. dlang/dmd#11005).
818447c
to
3a62577
Compare
6272c84
to
a25884a
Compare
Thanks for your pull request and interest in making D better, @kinke! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.
Please see CONTRIBUTING.md for more information. If you have addressed all reviews or aren't sure how to proceed, don't hesitate to ping us with a simple comment. Bugzilla referencesYour PR doesn't reference any Bugzilla issue. If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog. Testing this PR locallyIf you don't have a local development environment setup, you can use Digger to test this PR: dub run digger -- build "stable + dmd#11005" |
The early expression sema of `exp.e1`, the assert condition, was a major problem. E.g.: ``` struct S { int a; this(this) {} bool opEquals(S) @safe { return true; } } void foo() { S a; assert(S() == a); // S().opEquals(a) } ``` Here, an lvalue `a` is passed to `S.opEquals()`, which takes the arg by value. The early `CallExp` sema already lowers the argument expression `a` to a temporary, to something like `S __copytmp = (__copytmp = a).__postblit()`. *That* expression (instead of `a`) was previously stored as a further `__assertOp` temporary, and the original argument replaced by that temporary (without postblit). Previous lowering: assert(S(0).opEquals(((S __assertOp3 = (S __copytmp2 = (__copytmp2 = a).__postblit();) , __assertOp3 = __copytmp2;) , __assertOp3)), _d_assert_fail(S(0), __assertOp3)); New lowering: assert(S(0).opEquals(((S __copytmp2 = (__copytmp2 = a).__postblit();) , __copytmp2)), _d_assert_fail(S(0), a)); A related 2nd issue concerns rvalue arguments - the early `CallExp` sema detects that no temporary is needed (move; elide postblit and dtor in the caller); the rvalue was previously correctly stored as `__assertOp` temporary, but as it is now an lvalue (possibly accessed a 2nd time for the `_d_assert_fail()` call if the assertion fails), it must be copied + postblitted. This can be fixed by deferring the `CallExp` sema until the arguments have been replaced by required temporaries (and hence possibly promoted from rvalues to lvalues). void rval() { static S nonPure() { __gshared int counter; ++counter; return S(123); } assert(S() == nonPure()); } Previous lowering: assert(S(0).opEquals(((S __assertOp12 = nonPure();) , __assertOp12)), _d_assert_fail(S(0), __assertOp12)); New lowering: (S __assertOp11 = nonPure();) , assert(S(0).opEquals(((S __copytmp12 = (__copytmp12 = __assertOp11).__postblit();) , __copytmp12)), _d_assert_fail(S(0), __assertOp11));
For druntime's test/exceptions/src/assert_fail.d.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise looks fine.
assert(msg == "RefCounted() == RefCounted()"); | ||
} | ||
|
||
version (DMD_Win64) // FIXME: temporaries apparently not destructed when unwinding via AssertError |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm mildly worried about a FIXME targeting stable, is tis problem EH, ABI, Codegen, something else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think many of these tests here are actually testing undefined behavior, as the lifetime of temporaries in statements throwing an AssertError is tracked, continuing in the tests after catching the AssertError
, checking its message and checking that the temporaries have been properly destructed.
I'm unsure about the spec. Win64 with DMD seems to be the only platform eliding the cleanups, so I found it mildly worrying too, hence the comment, but at the same time I don't care about the potential DMD-specific codegen or druntime EH issue, as it's definitely unrelated to this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough.
This test should only check the druntime component of `-checkaction=context`, i.e. that `_d_assert_fail` yields an appropriate assert message. Using `assert` directly linked this test to the lowering details and made changes changes quite difficult because one cannot make changes to dmd and druntime in a single PR (see e.g. dlang/dmd#11005).
No description provided.