-
-
Notifications
You must be signed in to change notification settings - Fork 606
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
Lower CmpExp between classes to __cmp call #9629
Conversation
Thanks for your pull request and interest in making D better, @edi33416! 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 "master + dmd#9629" |
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.
Can we test this with a fake object.d directly here?
src/dmd/expressionsem.d
Outdated
// Lower to object.__cmp(e1, e2) | ||
Expression cl = new IdentifierExp(exp.loc, Id.empty); | ||
cl = new DotIdExp(exp.loc, cl, Id.object); | ||
cl = new DotIdExp(exp.loc, cl, Id.__cmp); |
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.
As discussed on other PRs: don't use __ as it can lead to weird side effects - use _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.
__cmp
is already defined in druntime and it’s already used in the lowering of array comparisons. This PR just uses an overload of it.
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.
@wilzbach In this case the Id.__cmp
is unavoidable. It's the same for the __ArrayCast
identifier that I refactored in #9572. We'd have to refactor id.d
and object.d to correct the issue. See how they are mixed in here: https://github.com/JinShil/dmd/blob/master/src/dmd/id.d#L26-L42
If this were a new symbol, it would be prudent to have it changed, but since this already exists, I think it would be too disruptive to include it in this PR. It could be remedied in a separate DMD and druntime PR, however.
How can I do that? |
What I've been advocating (actually an idea from @ibuclaw) is to focus on the druntime PR first.
So, I suggest focusing on the druntime PR first: Here's the workflow we followed for changing
There were a couple of patches along the way, but I think that's a good pattern to follow for this work. Of course it would be wise for the author to test locally before submitting any PRs to reduce additional PRs being needed to patch things. |
LGTM. |
src/dmd/expressionsem.d
Outdated
if (t1.ty == Tclass && t2.ty == Tclass) | ||
{ | ||
// Lower to object.__cmp(e1, e2) | ||
Expression cl = new IdentifierExp(exp.loc, Id.empty); |
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.
Will this lower to object.__cmp(e1, e2)
or .object.__cmp(e1, e2)
? Note the leading dot.
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.
Is there a .object.__cmp(e1, e2)
? I assumed that object.__cmp(e1, e2)
gives the full path, so the dot(.
) for "take the global __cmp(e1, e2)
" wouldn't change who gets called. Am I wrong?
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.
object.__cmp(e1, e2)
could find a local class or instance object
and search it for a member __cmp
, but Id.empty
takes care of starting the search at module level. So .object.__cmp(e1, e2)
would be a more accurate comment, but that is not done anywhere 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.
but that is not done anywhere else
It doesn't hurt to improve. I had to ask so it was not clear enough.
src/dmd/expressionsem.d
Outdated
@@ -10225,6 +10225,24 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor | |||
return setError(); | |||
} | |||
|
|||
if (t1.ty == Tclass && t2.ty == Tclass) |
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 suspect this is handled too early here. What about operator overloads, e.g. opBinary and opBinaryRight?
IMO lowerings should happen after all possible semantic errors have been reported to the user, so the user doesn't see cryptic messages referring to the lowered expressions in druntime (unless the implementation there is broken).
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 suspect this is handled too early here. IMO lowerings should happen after all possible semantic errors have been reported to the user, so the user doesn't see cryptic messages referring to the lowered expressions in druntime (unless the implementation there is broken).
Where, specifically, do you recommend this be done? How does one know that semantic is finished and it's the proper time for lowering?
What about operator overloads, e.g. opBinary and opBinaryRight?
Can you provide a test case?
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.
Sorry for the delay.
Where, specifically, do you recommend this be done?
I would have expected it to be close to where the array lowering is happening, i.e. after operator overload.
How does one know that semantic is finished and it's the proper time for lowering?
Yeah, hard to tell due to possibly deferred analysis. I imagine/dream that no lowerings should happen in the first 3 semantic passes (so user messages never get polluted with compiler generated identifiers and the AST can be mapped back to the source correctly). An additional pass would lower the code to library constructs and continue semantic analysis on the result, but should never generate messages if the library code isn't screwed up.
What about operator overloads, e.g. opBinary and opBinaryRight?
Can you provide a test case?
I was wrong about opBinary that cannot replace opCmp, but here's an example that gets lowered too early:
import core.stdc.stdio;
class A
{
int a = 3;
override int opCmp(Object o)
{
auto b = cast(B)o;
if (!b)
return super.opCmp(o);
printf("comparing a=%d with b=%d\n", this.a, b.b);
return this.a - b.b;
}
}
class B
{
int b = 4;
}
void main()
{
A a = new A;
B b = new B;
assert(b > a);
}
I don't think it is possible for the library code to detect "rewrite 2" in https://dlang.org/spec/operatoroverloading.html#compare
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.
If I understand correctly, the purpose of this PR is so that comparison will work with |
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.
@rainers or @WalterBright when you're good I'm good. Please request changes/approve appropriately.
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.
comment https://github.com/dlang/dmd/pull/9629/files#r277529334 not addressed yet.
d1786b6
to
4fbcb6c
Compare
4fbcb6c
to
281b49e
Compare
5fb6afc
to
ff37800
Compare
ff37800
to
afcbe15
Compare
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.
cc @rainers I think this is good to go
} | ||
// When reversing operands of comparison operators, |
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.
Is this change necessary? Why?
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 belive it is: you only need to reverse the op
only when we decide to write the call exp as e2.opFunc(e1)
.
As it was, the op was always reversed. Imho this was a bug that just wasn’t manifesting
The global symbol lookup seems fine now, but none of the changes seem to be covered by the test suite. As the druntime PR is merged, adding tests should be possible now. |
@rainers Hmm, I was going to write that since this implementation is now used for class comparison, the fact that the tests are passing shows that it should be correct. My expectation wass that class comparisons are thoroughly tested in the test suite, but upon a transitory look, I was surprised to see how little such tests are employed. @edi33416 could you please add tests as to cover the added code? |
@@ -11343,13 +11344,48 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor | |||
} | |||
if (e.op == EXP.call) | |||
{ | |||
|
|||
if (t1.ty == Tclass && t2.ty == Tclass) |
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 you also have to verify that the call is actually to Object.opCmp, as it can be redirected to other functions aswell, e.g.
class A
{
int x;
this(int a) { x = a; }
alias opCmp = Object.opCmp;
alias opCmp = my_cmp;
final int my_cmp(A a)
{
return x - a.x;
}
}
int main()
{
auto a = new A(1);
return a < a;
}
The comparison currently results in a non-virtual call to my_cmp. Does the lowered template do the same?
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.
The lowered template doest the same. I don't think the lowering should change the current behaviour.
Why do you want the behaviour to be different?
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.
Why do you want the behaviour to be different?
I don't want observable changes, and AFAICT the actual call stays the same - that's good. But the template adds some additional checks that are not there so far. This might duplicate null
checks in existing custom implementations of opCmp
, or it might work differently. The spec doesn't seem to cover the comparison to null
. I'm not against adding the checks, just saying they should be added to the spec (similar to opEquals
) if the change is deliberate.
BTW: dmd is unable to inline the __cmp
template, it can if it is written as return lhs is rhs ? 0 : lhs is null ? -1 : rhs is null ? 1 : lhs.opCmp(rhs);
. dmd doesn't eliminate unnecessary checks, though.
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.
Why do you want the behaviour to be different?
I don't want observable changes, and AFAICT the actual call stays the same - that's good. But the template adds some additional checks that are not there so far. This might duplicate
null
checks in existing custom implementations ofopCmp
, or it might work differently. The spec doesn't seem to cover the comparison tonull
. I'm not against adding the checks, just saying they should be added to the spec (similar toopEquals
) if the change is deliberate.
The change is deliberate. I’ll update the spec to mention the changes.
BTW: dmd is unable to inline the
__cmp
template, it can if it is written asreturn lhs is rhs ? 0 : lhs is null ? -1 : rhs is null ? 1 : lhs.opCmp(rhs);
. dmd doesn't eliminate unnecessary checks, though.
Hmm, rewritting it this way would make the code much harder to understand. Imho, we shouldn’t make the code harder to understand just so we improve dmd’s inlining. If this also affects ldc and gdc (haven’t checked yet) then we should modify the call.
I think the issue at hand is related to how dmd is able to inline such constructs. I think that should be a separate improvement done in dmd, as it will benefit other existing user code bases.
afcbe15
to
6ed5af8
Compare
6ed5af8
to
c68758e
Compare
@rainers Is this good to go? |
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.
Still not very happy with replacing any call with the template (see ExpressionSemanticVisitor.visit(CallExp e)
for what might happen to the expression), but the existing code assumes the same. So probably good enough.
BTW: it's a bit strange that comparison to null
is supported by the template, but forbidden when explicit null
is used (see error message a couple of lines above). Will this restriction be removed, too?
I hope so, as the current restriction doesn't make any sense. |
This requires druntime's PR