-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Improve sil_combiner using subtyping information from CHA #16739
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
Conversation
@slavapestov when you get some free time, please have a look at this PR as well. I plan on combining this PR with my old PR from #13991. |
This PR brings back the protocol implementation analysis that I removed in 1a66c89. The problem was that this analysis was performing a walk over all declarations in the module, once per source file. This meant quadratic complexity in non-WMO mode. Can you think of another way of achieving what you're trying to do here without the compile time impact? |
@rajbarik just a friendly FYI. You can use 3 '`' before/after a block to make it render in fixed point. It makes it much easier to read code and demarcates it cleanly. I edited your post above to add that. If you hit the edit button you will see the change I made. |
@slavapestov I have some ideas -- will update the PR and resubmit. |
@rajbarik Happy to help = ). |
@slavapestov I have moved the expensive DeclWalk from ClassHierarchyAnalysis to ConcreteTypeAnalysis and guarded it with WMO. That is, ConcreteTypeAnalysis is performed only in WMO. I did not find any API that just builds conformance table for a single source file for non-WMO mode. If you have any pointers, let me know -- I can easily re-enable this optimization for fileprivate declarations. |
@slavapestov As per our discussion, I have added support for enums and structs. Moreover, ClassHierarchyAnalysis is moved out of ConcreteTypeAnalysis. It also handles the bug mentioned in https://bugs.swift.org/browse/SR-7891. Let me know the next steps. |
} | ||
|
||
auto ElementType = NTD->getDeclaredType()->getCanonicalType(); | ||
if ((isa<ClassDecl>(NTD)) && (!(ElementType->getClassOrBoundGenericClass()))) { |
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 need for the extra parentheses. Not clear why this condition occurs, add a comment
return false; | ||
} | ||
|
||
auto ElementType = NTD->getDeclaredType()->getCanonicalType(); |
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.
Unnecessary call to getCanonicalType()
ClassDecl *CD = nullptr; | ||
/// Check if the class has no subclasses: direct or indirect. | ||
if ((CD = dyn_cast<ClassDecl>(NTD)) && | ||
((CHA->hasKnownDirectSubclasses(CD)) || |
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.
Indent so it lines up with the if
and remove redundant parens
@@ -0,0 +1,98 @@ | |||
//===--- ConcreteTypeAnalysis.cpp - Protocol to Class inheritance ---------===// |
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.
Word "Class" should not appear here, and maybe rename this to "ProtocolConfomanceAnalysis"
in general we use "X conforms to Y" instead of "X implements Y"
void ConcreteTypeAnalysis::init() { | ||
|
||
// We only do this in Whole-Module compilation mode. | ||
if (!(M->isWholeModule())) |
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.
Extra parens
SILType ConcreteSILType = SILType::getPrimitiveAddressType(ConcreteType); | ||
auto *UACI = | ||
Builder.createUncheckedAddrCast(OEA->getLoc(), OEA, ConcreteSILType); | ||
ConformanceRef = M.getSwiftModule()->lookupConformance( |
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.
Conformance should already be stored inside the OpenExistentialAddrInst
NewArg = URCI; | ||
} else if (auto *OEA = dyn_cast<OpenExistentialAddrInst>(Arg)) { | ||
ConcreteType = ElementType; | ||
SILType ConcreteSILType = SILType::getPrimitiveAddressType(ConcreteType); |
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 will produce an invalid SIL type if its an Optional. You need to lower the SIL type; SILModule.Types.getLoweredType(ConcreteType)
} | ||
if (auto *OER = dyn_cast<OpenExistentialRefInst>(Arg)) { | ||
ConcreteType = ElementType; | ||
SILType ConcreteSILType = M.Types.getLoweredType(ConcreteType); |
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 is correct
return true; | ||
} | ||
|
||
// If class C is the only class conforming to a protocol P, then |
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 is no longer specific to classes
@@ -0,0 +1,139 @@ | |||
// RUN: %target-swift-frontend %s -O -wmo -emit-sil -Xllvm -sil-disable-pass=DeadFunctionElimination | %FileCheck %s | |||
|
|||
internal protocol SomeProtocol : class { |
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.
Please test:
- structs
- public protocol (make sure its not optimized)
- class conforming to non-class-bound protocol
- generic class/struct/enum cannot be optimized
- also check this case:
struct Outer<T> {
struct Inner : MyProto { ... }
}
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.
@slavapestov I have added tests for all of the above except two. Can you help?
(1) Can you remind me what you meant by structs? I already have struct test.
(2) For this case:
struct Outer {
struct Inner : MyProto { ... }
}
The M.Types.getLoweredType call crashes with "Bad base type". Do you have any recommendations on how to obtain lowered SILType for generic nested types like above? I am thinking this case can be handled..
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.
FYI, I can also skip the optimization altogether by checking if the concrete type is an unbound generic type using "hasUnboundGenericType()".
Couple of other comments:
|
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.
Quick drive by comment.
@@ -0,0 +1,70 @@ | |||
#ifndef SWIFT_SILOPTIMIZER_ANALYSIS_CONCRETETYPE_H |
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 needs a file header.
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.
Thanks!
@slavapestov Smaller PR with just the analysis is here: #17177 |
Closing as discussed |
When sil_combiner can not determine the concrete type via "findInitExistential", this PR allows it to fall back to the sub-typing information available in the class hierarchy analysis to see if it can determine concrete type of the self argument. It then devirtualizes the apply instruction using this concrete type. This seems to be a generally useful optimization for apps that use protocols heavily -- including protocols used for test-mocking.
Example:
If you look at method doWork(), the calls to self.x.foo, self.y.bar and self.z.foo are all witness methods. With subtyping information in class hierarchy, you can determine that self.x.foo and self.y.bar can be devirtualized. Note that self.z.foo can not be devirtualized since self.z can either be the base class SomeUnrelatedClass or the derived class SomeUnrelatedClassDerived.