-
Notifications
You must be signed in to change notification settings - Fork 1.7k
C#: Extract and use ambiguous type information for call target resolution #14891
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
base: main
Are you sure you want to change the base?
Conversation
f7cac13
to
f21f16a
Compare
@hvitved, @michaelnebel Could you have a look at this PR? It's not exactly ready for review. DCA is showing significant slowdown. But I'd like to get some early feedback on the approach, which significantly changes how we come up with call targets. |
{ | ||
if (memberName is not null) | ||
{ | ||
trapFile.invocation_member_name(this, memberName); |
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 are all dynamic
calls excluded?
A far as I understand, a call can be considered dynamic, if just one of the arguments are.
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.
That's a good point. We could include dynamic_member_name
in MethodCall::getACandidateTarget
, where we use invocation_member_name
. But there's no need to store dynamic member names duplicately in the DB.
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've done this in 82c675a.
not this.hasMultipleParamsArguments() | ||
not this.hasMultipleParamsArguments(c) | ||
or | ||
result.getType().isImplicitlyConvertibleTo(p.getType().(ArrayType).getElementType()) |
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 is this needed?
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 getArgumentForParameter
was not handling correctly params
before. The corresponding tests also report more argument-parameter pairs now. See here.
In case of params
, we were already checking the types with isValidExplicitParamsType
, which I think checks whether an array type is convertible to another array type, such as string[]
to object[]
. For the expanded form, we'd need to check the types agains the element type of the array.
void M1(string s, params int[] a) {}
void M2(string s, params double[] a) {}
M1("", 1, 2, 3); // Viable
M1("", 1.1, 2.2, 3.3); // This is not a viable call to M due to `1.1` having `double` as a type, which is not convertible to the element type `int` of array type `int[]`.
M2("", 1, 2, 3); // Viable, `int` is implicitly convertible to `double`.
M2("", 1.1, 2.2, 3.3); // Viable
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.
@hvitved raised the point below that isImplicitlyConvertibleTo
might not work for generics. This is something that we should check and fix here. final override Expr getArgumentForParameter
should work for traced DBs.
this.getQualifier() | ||
.getType() | ||
.(ValueOrRefType) | ||
.getABaseType*() |
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.
Including base types here somehow seems related to dispatching. Could this be a potential performance killer? Would it make sense to initially only look at type alternatives without taking base types into account?
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 test cases show that including the base classes is needed. At the same time, it can easily be a performance killer.
.getType() | ||
.(ValueOrRefType) | ||
.getABaseType*() | ||
.getAnAmbiguousAlternativeType*() and |
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.
What happens, if we only look at "one" level of alternatives? Does that cover most cases?
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've done this in a4eb47f
I think this looks really interesting. |
@hvitved Have you got a chance to do an early review of 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.
Some initial comments.
@@ -45,6 +45,23 @@ class Type extends DotNet::Type, Member, TypeContainer, @type { | |||
|
|||
/** Holds if this type is a value type, or a type parameter that is a value type. */ | |||
predicate isValueType() { none() } | |||
|
|||
/** Gets an alternative type that is reported by the compiler as being ambiguous with this type. */ | |||
Type getAnAmbiguousAlternativeType() { |
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.
Modeling it in this way means that we cannot distinguish when a type is used unambiguously and when it is used ambiguously, that may be too simplistic.
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.
Wouldn't not exists(this.getAnAmbiguousAlternativeType())
mean that the type is used unambiguously?
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 may have misunderstood how it works, but consider this example:
namespace N1 {
class C {}
}
namespace N2 {
class C {}
}
namespace N3 {
class C {}
}
namespace N4 {
using N1;
using N2;
class C3 {
C Field; // either `N1.C` or `N2.C`, not `N3.C`
}
}
namespace N5 {
using N1;
using N3;
class C3 {
C Field; // either `N1.C` or `N3.C`, not `N2.C`
}
}
AFAICT, N1.C
, N2.C
, and N3.C
will be considered equal modulo getAnAmbiguousAlternativeType
, but that is an over approximation
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.
No, your understanding is correct, we over approximate. What would be your suggestion? (Let's continue this discussion on Slack)
not p.isParams() and | ||
arg.getType().isImplicitlyConvertibleTo(p.getType()) | ||
or | ||
p.isParams() and | ||
arg.getType().isImplicitlyConvertibleTo(p.getType().(ArrayType).getElementType()) |
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 don't think this works if either the parameter type or the argument type contains type parameters.
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.
We can definitely improve this further. At the same time, I don't think it's critical. It means that there are cases when we don't find a candidate call target, doesn't it? The opposite can also happen: we may find too many candidate call targets. These are expected as we haven't implemented the entire resolution logic.
527ce26
to
815c2ec
Compare
… them to compute call targets
2c65eba
to
b67f8f3
Compare
d35fc33
to
52aa355
Compare
DCA performance looks good. Removed/added alert count doesn't seem as good as I expected. We do find 9 new |
This PR
MethodCall::getTarget
to look up candidate call targets when the qualifier of the call has an ambiguous type.