Skip to content
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

Fix Issue 20353 - -checkaction=context does not work well with const … #10535

Closed

Conversation

MoonlightSentinel
Copy link
Contributor

…numbers

@dlang-bot
Copy link
Contributor

dlang-bot commented Nov 5, 2019

Thanks for your pull request and interest in making D better, @MoonlightSentinel! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.
Please verify that your PR follows this checklist:

  • My PR is fully covered with tests (you can see the coverage diff by visiting the details link of the codecov check)
  • My PR is as minimal as possible (smaller, focused PRs are easier to review than big ones)
  • I have provided a detailed rationale explaining my changes
  • New or modified functions have Ddoc comments (with Params: and Returns:)

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 references

Auto-close Bugzilla Severity Description
20353 normal -checkaction=context does not work well with const numbers

Testing this PR locally

If you don't have a local development environment setup, you can use Digger to test this PR:

dub run digger -- build "master + dmd#10535"

src/dmd/expressionsem.d Outdated Show resolved Hide resolved
src/dmd/expressionsem.d Outdated Show resolved Hide resolved
@MoonlightSentinel MoonlightSentinel force-pushed the checkaction-const branch 2 times, most recently from 8b6cc65 to 6a2f6fc Compare November 6, 2019 11:22
@MoonlightSentinel
Copy link
Contributor Author

MoonlightSentinel commented Nov 6, 2019

The test fails for release builds but succeeds when the optimize call is disabled (see last commit).
The failure is caused by an ud2 in the function prolog of testFolded(): .....
Is this a backend bug or caused by an malformed AST (which should probably abort compilation)?

@MoonlightSentinel
Copy link
Contributor Author

MoonlightSentinel commented Dec 4, 2019

The delayed optimize const-folds x < y into false in release mode, resulting in an assert(false) which aborted the test, hence the version(assert).
I'm not sure wether this is comformant to the spec:

If the first AssignExpression consists entirely of compile time constants, and evaluates to false, it is a special case; it signifies that it is unreachable code. Compile Time Function Execution (CTFE) is not attempted.

CC @RazvanN7

EDIT: The spec refers to executing the assert(false), not the condition

@MoonlightSentinel
Copy link
Contributor Author

MoonlightSentinel commented Dec 8, 2019

This should be good now

(The failures are network timeouts)

@Geod24
Copy link
Member

Geod24 commented Dec 8, 2019

I'm not really convinced by this diff TBH. From what I see, this changes behavior so that constfolded expressions do not result in assert(false) anymore, which is kept even in -release builds.

And the benefit of this is just a slightly better output. Now it seems that every asserts that would be affected by this bug could be turned into a static assert, for which we have been generating user-friendly message for a while now:

void main() {
    const x = -1L;
    const y = -2L;
    static assert(x == y);
}
pr10535.d(4): Error: static assert:  -1L == -2L is false

So, why not re-use this mechanism instead and provide the string directly from the compiler ?

@MoonlightSentinel
Copy link
Contributor Author

MoonlightSentinel commented Dec 9, 2019

I'm not really convinced by this diff TBH. From what I see, this changes behavior so that constfolded expressions do not result in assert(false) anymore, which is kept even in -release builds.

The example in the test is constfolded and treated as assert(false) (hence its excluded if testassert.d is compiled with -release). Do you have a concrete example where the behavoir differs from master?

And the benefit of this is just a slightly better output. Now it seems that every asserts that would be affected by this bug could be turned into a static assert, for which we have been generating user-friendly message for a while now:

void main() {
    const x = -1L;
    const y = -2L;
    static assert(x == y);
}
pr10535.d(4): Error: static assert:  -1L == -2L is false

So, why not re-use this mechanism instead and provide the string directly from the compiler ?

Are you suggesting to implicitly promote assert(cond) to static assert(cond) if cond is non-trivial (0, false) but can be evaluated at compile time?

@Geod24
Copy link
Member

Geod24 commented Dec 9, 2019

hence its excluded if testassert.d is compiled with -release

assert(false) are never removed, even in -release mode.
They are used to mark unreachable code.

% echo "void main () { const x = 1; const y = 42; assert(x == y); }" > pr10535.d
% dmd -release -run pr10535.d
Error: program killed by signal 4

That being said, I just tested this PR and the assert is still there for -release builds, with or without -checkaction=context, so that's good.

Are you suggesting to implicitly promote assert(cond) to static assert(cond) if cond is non-trivial (0, false) but can be evaluated at compile time?

No, as that would break the usage of "assert(0) for unreachable code". What I was suggesting is to just generate the message upfront, in DMD, when the assert is const-folded, instead of relying on druntime.

@MoonlightSentinel
Copy link
Contributor Author

hence its excluded if testassert.d is compiled with -release

assert(false) are never removed, even in -release mode.
They are used to mark unreachable code.

% echo "void main () { const x = 1; const y = 42; assert(x == y); }" > pr10535.d
% dmd -release -run pr10535.d
Error: program killed by signal 4

That being said, I just tested this PR and the assert is still there for -release builds, with or without -checkaction=context, so that's good.

I think my previous reply was worded poorly- the test case (not the assert) is excluded when compiling with -release.

No, as that would break the usage of "assert(0) for unreachable code". What I was suggesting is to just generate the message upfront, in DMD, when the assert is const-folded, instead of relying on druntime.

Thats could be an improvement but would require special-casing depending on the operands as __d_assert_fail can format more expression than toChars used by static assert. (unless we can assume that these expressions cannot be constfolded by the fronted)

I'm not entirely sure why your example even works but dmd seems to promote x, y to enums.

@Geod24
Copy link
Member

Geod24 commented Dec 9, 2019

Thats could be an improvement but would require special-casing depending on [...]

I was thinking of just providing a template overload that accepts a string.

Can you add the following test case to your PR:

struct Foo { int a; float b; }
static immutable Foo x, y;
void main ()
{
    assert(x == y);
}

For me this still print core.exception.AssertError@pr10535.d(5): assert(x == y) failed with your branch.
Interestingly, when the struct only have a single float member, it properly prints "nan != nan" (even without your PR).

@MoonlightSentinel
Copy link
Contributor Author

MoonlightSentinel commented Dec 9, 2019

Thats could be an improvement but would require special-casing depending on [...]

I was thinking of just providing a template overload that accepts a string. Do you h

There is something missing here?

Can you add the following test case to your PR:

Done

For me this still print core.exception.AssertError@pr10535.d(5): assert(x == y) failed with your branch.

unaSemantic rewrites that assert to assert(x.a == y.a && (x.b == y.b))

Interestingly, when the struct only have a single float member, it properly prints "nan != nan" (even without your PR).

unaSemantic rewrites that assert to assert(x.b == y.b)

Shouldn't these assert generate an implicit opEquals and be rewritten into assert(x.opEquals(y))?

NVM, the spec allows this rewrite

@MoonlightSentinel
Copy link
Contributor Author

For me this still print core.exception.AssertError@pr10535.d(5): assert(x == y) failed with your branch.

unaSemantic rewrites that assert to assert(x.a == y.a && (x.b == y.b))

Essentially this is a problem of the current implementation of checkaction=context not my PR as it is unable to handle complex expressions.

The correct approach IMHO would be to apply checkaction=context before rewriting to format the original expression instead of the lowering. This would require less special casing and produce better messages (S(0, 1) != S(1, 1) instead of maybe 0 != 1 || 1 != 1).

Handling complex expressions would also suffice to handle such == lowerings at the cost of slightly worse messages.

I tried to implement the first approach but was not successfull yet

Copy link
Contributor

@RazvanN7 RazvanN7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the fix is fine. I have hit this problem in the past also where the optimizer kicks in way too early, before actual error checking is done, and by the time you want to print the error the AST has already been irreversibly altered.

@MoonlightSentinel
Copy link
Contributor Author

This issue will probably be fixed by #11005

@MoonlightSentinel MoonlightSentinel deleted the checkaction-const branch May 23, 2020 09:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants