Skip to content

ExistentialSpecializer Pass (without SILCombine/ConcreteType Propagation) #17366

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

Merged
merged 1 commit into from
Sep 26, 2018

Conversation

rajbarik
Copy link
Contributor

ExistentialSpecializer transforms functions with existential arguments to generic parameter types.
That is, it transforms from

func foo(a:P) -> Int { // P is an existential that is a protocol or a protocol composition type
 return a.bar()
}

to

@inline(always) func foo(a: P) -> Int {
  let _a = a open as T
  return _wrap_inc(_a)
}
func _foo<T : P>(_a:P) -> Int{
 let a: P = _a
 return a.bar()
}

With subsequent passes such as Inlining, Generic Specialization and SILCombine, the concrete type of P can be determined, which most likely will devirtualize a.bar() method call.

This is part of the large PR #13991
test cases will be added after the final code for SILCombine/ConcreteType Propagation lands.

@rajbarik
Copy link
Contributor Author

@slavapestov Breaking the large PR from #13991 to this one as Part-1. Part-2 will be the changes to SILCombiner.

@slavapestov slavapestov self-assigned this Jun 27, 2018
@rajbarik rajbarik force-pushed the raj-existential2generic branch from aec0db1 to 23c6dd3 Compare July 10, 2018 22:52
@rajbarik
Copy link
Contributor Author

@slavapestov @atrick This is the transformation pass for existential specializer. Added a new test case and cleaned up the code as suggested by Slava.

@rajbarik rajbarik force-pushed the raj-existential2generic branch from 23c6dd3 to a8a1fab Compare July 11, 2018 21:26
} // namespace

/// Find concrete type from init_existential refs/addrs.
static bool findConcreteTypeFromIE(SILValue Arg, CanType &ConcreteType) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Rename to findConcreteTypeFromInitExistential?

auto ArgType = Arg->getType();
auto SwiftArgType = ArgType.getASTType();
if (!ArgType || !SwiftArgType || !(ArgType.isExistentialType()) ||
ArgType.isAnyObject() || SwiftArgType->isAny())
Copy link
Contributor

Choose a reason for hiding this comment

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

Please figure out why this doesn't work

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This will cause code blowup due to specialization -- I will add a comment. We do not want to do this for now.

auto Arg = Args[Idx];
auto ArgType = Arg->getType();
auto SwiftArgType = ArgType.getASTType();
if (!ArgType || !SwiftArgType || !(ArgType.isExistentialType()) ||
Copy link
Contributor

Choose a reason for hiding this comment

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

These will never be null


/// Find concrete type.
CanType ConcreteType;
int ApplyArgIdx = PAI ? Idx - Num + minPartialAppliedArgs : Idx;
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe don't bother with PartialApplyInst

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure.

return false;

/// External function definitions.
if ((!Callee->isDefinition()) || Callee->isExternalDeclaration())
Copy link
Contributor

Choose a reason for hiding this comment

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

isExternalDeclaration is checking the same thing as isDefinition

SILType LoweredType = NewF->getLoweredType();
SILFunctionConventions Conv(SubstCalleeType, M);
SILType ResultType = Conv.getSILResultType();
auto FunctionTy = LoweredType.castTo<SILFunctionType>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Unnecessary cast

NormalBlock->createPHIArgument(ResultType, ValueOwnershipKind::Owned);
SILBasicBlock *ErrorBlock = Thunk->createBasicBlock();
SILType Error =
SILType::getPrimitiveObjectType(FunctionTy->getErrorResult().getType());
Copy link
Contributor

Choose a reason for hiding this comment

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

getSILType()

@@ -238,6 +238,9 @@ void addSSAPasses(SILPassPipelinePlan &P, OptimizationLevelKind OpLevel) {
// Promote stack allocations to values.
P.addMem2Reg();

// Run the existential pecializer Pass.
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo "specializer"


/// Return the concrete type from init_existential returned from
/// findInitExistential.
if (auto *IER = dyn_cast<InitExistentialRefInst>(InitExistential)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Use findConcreteTypeFromInitExistential() here?

auto Arg = Args[Idx];
auto ArgType = Arg->getType();
auto SwiftArgType = ArgType.getASTType();
if (!ArgType || !SwiftArgType || !(ArgType.isExistentialType()) ||
Copy link
Contributor

Choose a reason for hiding this comment

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

Unnecessary parentheses around ArgType.isExistentialType() check

/// Then we splice the body of F to NewF after the first basic block.
auto It = NewF->begin();
It++;
NewF->getBlocks().splice(It, F->getBlocks());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Instead of splice: use a TypeSubstCloner with a Builder and SubstitutionMap and then call the API visitSILFunction(original function F);


unsigned int OrigDepth = 0;
if (F->getLoweredFunctionType()->isPolymorphic()) {
OrigDepth = OrigCalleeGenericSig->getGenericParams().back()->getDepth();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Make this OrigDepth = OrigCalleeGenericSig->getGenericParams().back()->getDepth()+1;
and then remove the check for polymorphic on line 457 to F->getLoweredFunctionType()->isPolymorphic() and then the 458 is now < not <=


unsigned int OrigDepth = 0;
if (F->getLoweredFunctionType()->isPolymorphic()) {
OrigDepth = OrigCalleeGenericSig->getGenericParams().back()->getDepth();
Copy link
Contributor

Choose a reason for hiding this comment

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

Do +1 here

GenericTypeParamType *GP = nullptr;
if ((GP = dyn_cast<GenericTypeParamType>(type))) {
if (F->getLoweredFunctionType()->isPolymorphic() &&
GP->getDepth() <= OrigDepth) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove the isPolymorphic check and use < instead of <=

if (F->getLoweredFunctionType()->isPolymorphic() &&
GP->getDepth() <= OrigDepth) {
return QuerySubstitutionMap{OrigSubMap}(
cast<SubstitutableType>(GP->getCanonicalType()));
Copy link
Contributor

Choose a reason for hiding this comment

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

Type(GP).subst(OrigSubMap)

CalleeGenericSig,
[&](SubstitutableType *type) -> Type {
GenericTypeParamType *GP = nullptr;
if ((GP = dyn_cast<GenericTypeParamType>(type))) {
Copy link
Contributor

Choose a reason for hiding this comment

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

if (auto *GP = dyn_cast<GenericTypeParamType>(type))

@rajbarik rajbarik force-pushed the raj-existential2generic branch from a8a1fab to cc60047 Compare July 27, 2018 20:56
@rajbarik
Copy link
Contributor Author

@slavapestov Could you also review this one? All your comments are taken care of, except generic methods.

@rajbarik
Copy link
Contributor Author

@slavapestov can you check this now?

@rajbarik rajbarik force-pushed the raj-existential2generic branch 5 times, most recently from 2642368 to 700d019 Compare September 5, 2018 19:15
@rajbarik rajbarik force-pushed the raj-existential2generic branch from 700d019 to 9592802 Compare September 6, 2018 19:42
@rajbarik
Copy link
Contributor Author

rajbarik commented Sep 7, 2018

@slavapestov should be ready now?

}

/// Determine attributes of the existential addr arguments such as
/// destroy_use, immutable_access. We should probably also disable InOut
Copy link
Contributor

Choose a reason for hiding this comment

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

What does "probably" mean here? Either they should be disabled if they don't work, or they should be enabled


auto ExistentialRepr =
Arg->getType().getPreferredExistentialRepresentation(F->getModule());
if (!(ExistentialRepr == ExistentialRepresentation::Opaque ||
Copy link
Contributor

Choose a reason for hiding this comment

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

I would find if (ExistentialRepr != ExistentialRepresentation::Opaque && ExistentialRepr != ExistentialRepresentation::Class more readable

bool ExistentialSpecializer::canSpecializeCalleeFunction(ApplySite &Apply) {

/// Do not handle partial applies.
if (auto *PAI = dyn_cast<PartialApplyInst>(Apply.getInstruction())) {
Copy link
Contributor

Choose a reason for hiding this comment

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

You can use isa instead since you don't care about the value

if (!Callee->isDefinition())
return false;

/// Ignore functions with indirect results.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Next PR will lift this!

if (Callee->getConventions().hasIndirectSILResults())
return false;

/// Ignore error returning functions.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Next PR will life this.

/// Now add the yields.
llvm::SmallVector<SILYieldInfo, 8> InterfaceYields;
for (SILYieldInfo InterfaceYield : FTy->getYields()) {
InterfaceYields.push_back(InterfaceYield);
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't you need to map this type out of context too?

auto SwiftType = ArgDesc.Arg->getType().getASTType();
auto OpenedType =
SwiftType->openAnyExistentialType(Opened)->getCanonicalType();
auto OpenedSILType = NewF->getModule().Types.getLoweredType(OpenedType);
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe just F.getLoweredType(OpenedType)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This does not work!

Copy link
Contributor

Choose a reason for hiding this comment

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

Why not?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is no API in F that allows you to take a Type. Its an empty one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is F.getLoweredType does not take an argument.

break;
}
};
Generic2OpenedTypeMap.insert(
Copy link
Contributor

Choose a reason for hiding this comment

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

Assert that the insert succeeded

}
}

/// Strategy to specialize existentail arguments:
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: "existential"

[&](SubstitutableType *type) -> Type {
return NewFGenericEnv->mapTypeIntoContext(type);
},
LookUpConformanceInSignature(*NewFGenericSig));
Copy link
Contributor

Choose a reason for hiding this comment

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

Please just use LookUpConformanceInModule instead. LookUpConformanceInSignature is going away

@rajbarik rajbarik force-pushed the raj-existential2generic branch from 9592802 to b809ac7 Compare September 17, 2018 18:28
@rajbarik
Copy link
Contributor Author

@slavapestov please have a look.

} else {
auto Ty = param.getType();
InterfaceParams.push_back(
SILParameterInfo(!Ty->hasTypeParameter()
Copy link
Contributor

Choose a reason for hiding this comment

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

if hasTypeParameter() returns false, mapTypeOutOfContext() does nothing. Perhaps remove this whole ?: operation and just use 'Ty'?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately, mapTypeOutOfContext() in GenericEnvironment puts an assertion like this:
"assert(!hasTypeParameter() && "already have an interface type");"
So, I can not find an easy way to go around it.

Copy link
Contributor

Choose a reason for hiding this comment

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

If the type has type parameters it's already been "mapped out of context". You don't have to do anything here.

Copy link
Contributor

@slavapestov slavapestov 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 if you just fix the last mapTypeOutOfContext() nitpick and use SmallVector instead of SmallDenseMap<int, ...> (or explain why you can't) I will merge this.

/// Arguments that are not rewritten.
auto Ty = params[ArgDesc.Index].getType();
auto MappedTy = ArgDesc.Arg->getType();
if (Ty->hasTypeParameter()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

You don't need the 'if'. It should either be fine to map it in unconditionally, or if that always fails, just don't map it in at all.

param.getConvention()));
} else {
auto Ty = param.getType();
InterfaceParams.push_back(
Copy link
Contributor

Choose a reason for hiding this comment

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

You can replace this whole thing with InterfaceParams.push_back(param). You don't need to do anything with Ty.

llvm::SmallVector<SILResultInfo, 8> InterfaceResults;
for (auto &result : FTy->getResults()) {
auto Ty = result.getType();
InterfaceResults.push_back(result.getWithType(
Copy link
Contributor

Choose a reason for hiding this comment

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

You can replace this whole thing with InterfaceResults.push_back(result). You don't need to do anything with Ty.

Copy link
Contributor

Choose a reason for hiding this comment

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

Or why bother with InterfaceResults at all? You can remove that SmallVector altogether and use FTy->getResults() when you build the new function type instead. The only thing that's changing here is the parameter types, everything else comes directly from the old function type and doesn't need any remapping.

if (FTy->hasErrorResult()) {
auto errorResult = FTy->getErrorResult();
auto Ty = errorResult.getType();
InterfaceErrorResult = SILResultInfo(
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 just InterfaceErrorResult = errorResult.

llvm::SmallVector<SILYieldInfo, 8> InterfaceYields;
for (SILYieldInfo yield : FTy->getYields()) {
auto Ty = yield.getType();
InterfaceYields.push_back(yield.getWithType(
Copy link
Contributor

Choose a reason for hiding this comment

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

You can replace this whole thing with InterfaceYields.push_back(yield). You don't need to do anything with Ty.

@slavapestov
Copy link
Contributor

@rajbarik What do you think of using a SmallVector instead of SmallDenseMap<int, ...>?

@rajbarik
Copy link
Contributor Author

@rajbarik What do you think of using a SmallVector instead of SmallDenseMap<int, ...>?

An ExistentialTransformArgumentDescriptor is only built for an existential argument, not other arguments. So,SmallVector is not the right option? Am I missing anything?

@rajbarik rajbarik force-pushed the raj-existential2generic branch from b809ac7 to 23a6ef7 Compare September 25, 2018 20:08
/// Now add the errors.
Optional<SILResultInfo> InterfaceErrorResult;
if (FTy->hasErrorResult()) {
InterfaceErrorResult = FTy->getErrorResult();
Copy link
Contributor

Choose a reason for hiding this comment

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

In a follow-up PR, please remove this whole block. Instead, just pass FTy->getErrorResult() to SILFunctionType::get() below.

@slavapestov
Copy link
Contributor

@swift-ci Please test

@slavapestov
Copy link
Contributor

@swift-ci Please test source compatibility

@slavapestov
Copy link
Contributor

@rajbarik I think this is good to merge now! Thank you for all your hard work. I'm going to merge it if the tests pass.

Next, can you make a single commit which enables it by default, and we'll run the test suite and benchmarks to see if there are any regressions?

@slavapestov
Copy link
Contributor

@swift-ci Please test

@slavapestov
Copy link
Contributor

@swift-ci Please test source compatibility

@rajbarik
Copy link
Contributor Author

To enable this transformation by default, please change the following line in ExistentialSpecializer.cpp

if (!F->shouldOptimize() || !F->getModule().getOptions().ExistentialSpecializer)

to

if (!F->shouldOptimize())

@slavapestov
Copy link
Contributor

@rajbarik Please make a PR that does changes the default flag value in SILOptions. Then we'll run PR testing on it.

@rajbarik rajbarik force-pushed the raj-existential2generic branch from 23a6ef7 to cabaf2f Compare September 25, 2018 21:28
@slavapestov
Copy link
Contributor

@swift-ci Please test

@slavapestov
Copy link
Contributor

@swift-ci Please test source compatibility

@slavapestov
Copy link
Contributor

@swift-ci Please test

1 similar comment
@slavapestov
Copy link
Contributor

@swift-ci Please test

@rajbarik rajbarik force-pushed the raj-existential2generic branch from cabaf2f to 89d31be Compare September 25, 2018 21:52
@slavapestov
Copy link
Contributor

@swift-ci Please test

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - cabaf2fb41ca2aef104e26bef763c4121a548b6f

@slavapestov
Copy link
Contributor

@swift-ci Please test

@rajbarik rajbarik merged commit a410312 into swiftlang:master Sep 26, 2018
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