Skip to content

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

Closed
wants to merge 3 commits into from

Conversation

rajbarik
Copy link
Contributor

@rajbarik rajbarik commented May 19, 2018

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:

internal protocol SomeProtocol : class {
  func foo(x:SomeProtocol)  -> Int
  func foo_internal()  -> Int
}
internal class SomeClass: SomeProtocol {
  func foo_internal() ->Int {
    return 10
  }
  func foo(x:SomeProtocol) -> Int {
    return x.foo_internal()
  }
}
internal protocol SomeNonClassProtocol {
  func bar(x:SomeNonClassProtocol)  -> Int
  func bar_internal()  -> Int
}
internal class SomeNonClass: SomeNonClassProtocol {
  func bar_internal() -> Int {
    return 20
  }
  func bar(x:SomeNonClassProtocol) -> Int {
    return x.bar_internal()
  }
}
internal protocol UnrelatedProtocol {
  func foo()  -> Int
}
internal class SomeUnrelatedClass: UnrelatedProtocol {
  func foo() -> Int {
    return 20
  }
}
internal class SomeUnrelatedClassDerived: SomeUnrelatedClass {
}
internal class Other {
   let x:SomeProtocol
   let y:SomeNonClassProtocol
   let z:UnrelatedProtocol
   init(x:SomeProtocol, y:SomeNonClassProtocol, z:UnrelatedProtocol) {
     self.x = x; self.y = y; self.z = z;
   }
   **@inline(never) func doWork () ->Int {
      return self.x.foo(x:self.x) + self.y.bar(x:self.y) + self.z.foo()
   }**
}

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.

@rajbarik
Copy link
Contributor Author

@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.

@slavapestov
Copy link
Contributor

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?

@gottesmm
Copy link
Contributor

@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.

@rajbarik
Copy link
Contributor Author

@slavapestov I have some ideas -- will update the PR and resubmit.
Thanks a lot for formatting the code! @gottesmm.

@gottesmm
Copy link
Contributor

@rajbarik Happy to help = ).

@rajbarik
Copy link
Contributor Author

rajbarik commented Jun 1, 2018

@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.

@rajbarik
Copy link
Contributor Author

rajbarik commented Jun 7, 2018

@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()))) {
Copy link
Contributor

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();
Copy link
Contributor

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)) ||
Copy link
Contributor

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 ---------===//
Copy link
Contributor

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()))
Copy link
Contributor

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(
Copy link
Contributor

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);
Copy link
Contributor

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);
Copy link
Contributor

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
Copy link
Contributor

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 {
Copy link
Contributor

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 { ... }
}

Copy link
Contributor Author

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..

Copy link
Contributor Author

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()".

@slavapestov
Copy link
Contributor

slavapestov commented Jun 8, 2018

Couple of other comments:

  • How about a separate PR with just the analysis, and then a second PR for the SIL combiner change?
  • Test nested types, local types (types inside functions)
  • What about existentials like P & Q?

@slavapestov slavapestov self-assigned this Jun 8, 2018
Copy link
Contributor

@gottesmm gottesmm left a 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
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks!

@rajbarik
Copy link
Contributor Author

rajbarik commented Jun 13, 2018

@slavapestov Smaller PR with just the analysis is here: #17177

@slavapestov
Copy link
Contributor

Closing as discussed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants