Skip to content

Existential Specializer Pass #13991

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 1 commit into from
Closed

Conversation

rajbarik
Copy link
Contributor

@rajbarik rajbarik commented Jan 17, 2018

Existential Specializer Pass that specializes functions that take protocols (aka existentials) as arguments to transform them with concrete types instead of protocol types when the concrete types can be determined statically.

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 the name is slightly confusing. Elsewhere in the compiler devirtualization refers to replacing virtual method calls with concrete method calls. What you're doing here is not devirtualization, it is specialization.

How about ExistentialSpecializer?

Also I think specializing existential parameters with their sole conforming class is too limited. You should replace them with a generic parameter instead.

[&](SubstitutableType *type) -> Type {
if (GenericType2ConcreteTypeMap[type])
return GenericType2ConcreteTypeMap[type];
return type;
Copy link
Contributor

Choose a reason for hiding this comment

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

When does the map not contain the type parameter? That sounds like a bug.

SILType NewSubstCalleeType;
auto FnSubsMap =
FnTy->getGenericSignature()->getSubstitutionMap(AI.getSubstitutions());
auto FinalSubsMap = FnSubsMap.subst(
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 guess that FinalSubsMap would be identical to FnSubMap.

return %2 : $Int
}

// CHECK-LABEL: sil shared [noinline] @$S25something_to_devirtualizeTf5n_n4main9SomeClassC_Tg5 : $@convention(thin) (@guaranteed SomeClass) -> Int {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think instead of replacing the protocol parameter with the sole conforming class, it should remain a generic parameter. The generic specializer can specialize it to the class later.

As a bonus you should be able to get rid of the "sole conforming class" analysis. It makes the optimization only apply in very narrow cases, when it could be more general.

Copy link
Contributor

Choose a reason for hiding this comment

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

related ticket, from the the ABI dashboard: https://bugs.swift.org/browse/SR-4331

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just a heads up: the generic method and thunk creation now works for both protocol compositions and class-constrained compositions. I was able to eliminate the analysis altogether. I am working on the peephole optimizer right now. I will see if I can combine mine with the existing self peephole optimizer.

auto ArgType = Args[i]->getType();
/// Keep it simple for now. Do not handle Protocol constrained methods or Optionals.
auto SwiftArgType = ArgType.getSwiftRValueType();
if (ArgType && ArgType.isAnyExistentialType()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

isAnyExistentialType() is also true for existential metatypes. If you don't care about those, you can use isExistentialType() instead.

/// Keep it simple for now. Do not handle Protocol constrained methods or Optionals.
auto SwiftArgType = ArgType.getSwiftRValueType();
if (ArgType && ArgType.isAnyExistentialType()) {
auto SwiftProtoDecl = SwiftArgType.getAnyNominal();
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 be null for protocol compositions, including class-constrained compositions. You should handle this more generally so that

func foo(p: P & Q & C)

Is specialized as

func foo<T>(p: T) where T : P, T : Q, T : C

@slavapestov slavapestov self-assigned this Jan 18, 2018
@rajbarik
Copy link
Contributor Author

Thanks a lot for the suggestions Slava. Let me fix them and resubmit the patch later today.

@rajbarik
Copy link
Contributor Author

rajbarik commented Apr 3, 2018

@slavapestov @karwa, I committed a new version. Please review it.

@rajbarik rajbarik changed the title Protocol Devirtualizer Pass Existential Specializer Pass Apr 4, 2018
@slavapestov
Copy link
Contributor

slavapestov commented Apr 18, 2018

@rajbarik it looks when you rebased, you ended up re-applying a bunch of patches already in the repo. Please do this,

git reset --hard remote/branch
git fetch origin
git rebase -i origin/master
# remove any irrelevant lines
git push -f remote HEAD:refs/heads/branch

Where remote is the name of your remote, and branch is a new branch name (it looks like in this PR it's master, you can either push to that or create a new PR since it's not good practice to name your PRs master).

@slavapestov
Copy link
Contributor

Thank you! I will review this today.

@@ -299,6 +301,75 @@ class GenericFuncSpecializer {
}
};

/// A descriptor to carry information from ExistentialTransform analysis
/// to transformation.
struct ExistentialTransformArgumentDescriptor {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe these structs belong in its own header file?

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. Did you mean just the struct or the class ExistentialSpecializerTransform as well?

@@ -231,14 +231,17 @@ void addSSAPasses(SILPassPipelinePlan &P, OptimizationLevelKind OpLevel) {
// Promote stack allocations to values.
P.addMem2Reg();

// Run the existentail 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.

Typos in this line

@@ -231,14 +231,17 @@ void addSSAPasses(SILPassPipelinePlan &P, OptimizationLevelKind OpLevel) {
// Promote stack allocations to values.
P.addMem2Reg();

// Run the existentail pecializer Pass.
P.addExistentialSpecializer();
Copy link
Contributor

Choose a reason for hiding this comment

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

SSA passes run multiple times -- is that what you want? Perhaps this should be added at the same stage of the pipeline as the devirtualizer or function signature optimizations

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I do not want the pass to run multiple times, but I do depend on GenericSpecialization and SILCombiner (Concrete Type propagation) to run multiple times to see the benefit of my pass. I experimented with a few places. Do you have any suggestions (to run my pass once and make sure GenericSpecializer and SIlCombiner run more than once to get the desired result)?

ArrayRef<Optional<ProtocolConformanceRef>> Conformance;
ArchetypeType *OpenedArchetype;
ApplyArgumentDescriptor() {}
ApplyArgumentDescriptor(SILValue NewArg, CanType ConcreteType, ArrayRef<Optional<ProtocolConformanceRef>> Conformance, ArchetypeType *OpenedArchetype) : NewArg(NewArg), ConcreteType(ConcreteType), Conformance(Conformance), OpenedArchetype(OpenedArchetype) {}
Copy link
Contributor

Choose a reason for hiding this comment

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

Lines should be < 80 chars

struct ApplyArgumentDescriptor {
SILValue NewArg;
CanType ConcreteType;
ArrayRef<Optional<ProtocolConformanceRef>> Conformance;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is it optional?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am storing the result of "SubMap-> lookupConformance(CanType type, ProtocolDecl *proto)" which returns an Optional as is. I do not understand why this is a problem.. Please explain.

Copy link
Contributor

Choose a reason for hiding this comment

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

In the optimizer all conformances already exist, so you can assume the Optional is inhabited and dereference it right after the call to lookupConformance. Then you don't need to store Optional here.


/// If the original function is generic, then maintain the same.
/// Does it have generic structure already ?
auto HasGenericSignature = FTy->getGenericSignature() != nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can it ever not be generic? I thought that was the whole point of this pass.

NewF->setUnqualifiedOwnership();
}

// Transfer array attributes, if any.
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.

Note the "UnqualifiedOwnership()": If the original function was Unqualified, NewF is made Unqualified too.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm wondering about the part that transfers array attributes specifically

mergeBasicBlockWithSuccessor(&(*(NewF->begin())), nullptr, nullptr);

/// Walk over destroy_addr instructions and perform fixups
for (const auto& mapPair : Arg2AllocStackMap) {
Copy link
Contributor

Choose a reason for hiding this comment

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

"const auto &mapPair" here and elsewhere

NewIt++;
SILBasicBlock *OldEntryBB = &(*NewIt);

/// Create a fake branch instruction, which will be eliminated by the merge function below.
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.

There might be a better way of doing this. I ran into a lot of SILCloner issues trying to append code from the old F to the NewF. At some point I wrote my own SILCloner too. Given that, the mergeBasicBlock eliminates everything, it should not matter. Should it?

Copy link
Contributor

Choose a reason for hiding this comment

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

What issues did you run into?

/// Get the substitutions.
CalleeGenericSig->getSubstitutions(SubMap, Substitutions);

/// Combine the two substitutions (original F's substitutions and now NewF).
Copy link
Contributor

Choose a reason for hiding this comment

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

Please find a way of doing this in terms of SubstitutionMaps. The representation and contents of a SubstitutionList are effectively opaque and your code will break if the internal representation's invariants change

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I understand you correctly, you are pointing at line 2982 -- LookUpConformanceInSignature(*CalleeGenericSig));
if so, I do not quite sure how to write a lambda that contains more than one ProtocolConformanceRef, which is my case with protocol composition types. Are there any code snippets for me to look at here?

Copy link
Contributor

Choose a reason for hiding this comment

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

What you want to do here is take the original apply substitutions and add substitutions for your opened existential parameters. Look at SubstitutionMap::combineSubstitutionMaps().

I'm not sure what you're suggesting about a lambda taking multiple conformances, that's not the issue here.


int GPIdx = 0;
/// Convert the protocol arguments of F to generic ones.
for (auto const& IdxIt: ExistentialArgDescriptor) {
Copy link
Contributor

Choose a reason for hiding this comment

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

auto const &IdxIt

Requirements.push_back(NewRequirement);
} else {
auto CompType = PType->castTo<ProtocolCompositionType>();
recursivelyAddCompositeTypes(CompType, NewGenericParam, Requirements);
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 the case you don't have to handle -- both ProtocolType and ProtocolCompositionType can be passed to the constructor for Requirement

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Question: if you did not add requirements explicitly one by one like above, how do you lookup conformance for them and build a conformance vector, like the one I did in 2812-2818? Is there an API for this already?

SmallVector<Requirement, 4> Requirements;
if (PType->is<ProtocolType>()) {
auto DeclType = PType->castTo<ProtocolType>()->getDecl();
Requirement NewRequirement(RequirementKind::Conformance, NewGenericParam, DeclType->getDeclaredType());
Copy link
Contributor

Choose a reason for hiding this comment

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

DeclType->getDeclaredType() is going to equal PType

}
auto Source = GenericSignatureBuilder::FloatingRequirementSource::forAbstract();
for (auto &Req : Requirements) {
Builder.addRequirement(Req, Source, Mod);
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of adding requirements to a vector and then adding them to the builder, you can just add them directly to the builder

InterfaceParams.reserve(params.size());
for (auto &param : params) {
if (Arg2GenericTypeMap[Idx]) {
auto GenericParam = Arg2GenericTypeMap[Idx];
Copy link
Contributor

Choose a reason for hiding this comment

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

You should just store CanTypes in this DenseMap then you won't need to call getCanonicalType()

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 are a number os usage scenario. For now, I am keeping it as is. Let me know if you see a major issue.

llvm::SmallDenseMap<int, SILInstruction *> Arg2AllocStackMap;
for (auto &ArgDesc : ArgumentDescList) {
/// For all Generic Arguments.
if(Arg2GenericTypeMap[ArgDesc.Index]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Space before (

@rajbarik rajbarik force-pushed the master branch 4 times, most recently from 12f210e to 5d9e366 Compare May 4, 2018 20:48
@rajbarik
Copy link
Contributor Author

rajbarik commented May 4, 2018

@slavapestov Please have a look. Thanks.

@slavapestov
Copy link
Contributor

@rajbarik I think the next part of this PR I'd like to peel off and check in is the mangling for the existential->generic specialization. Can you split that part off into its own PR and we can review it and check it in?

@slavapestov
Copy link
Contributor

@rajbarik also my apologies for not looking at this until now, I was on vacation for the last week.

@rajbarik
Copy link
Contributor Author

rajbarik commented May 12, 2018 via email

@rajbarik
Copy link
Contributor Author

@slavapestov #16603

@rajbarik
Copy link
Contributor Author

@slavapestov Please have a look!

@rajbarik
Copy link
Contributor Author

@slavapestov When you get free time, please let me know the next steps for this PR. Thanks.

/// %8 = alloc_stack $SomeP
/// copy_addr %3 to [initialization] %8 : $*SomeP
/// %10 = apply %9(%3) : $@convention(thin) (@in_guaranteed SomeP)
SILValue findInitExistentialFromGlobalAddr(GlobalAddrInst *GAI,
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like this got moved from the SILCombiner to Local.cpp. Can you split these kinds of refactorings into separate PRs, with justification?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason I moved this is because Existential Specializer pass needs this apart from SILCombiner. I will create a PR for this. Should I just go ahead break this large PR into two more PRs, one with just the ExistentialTransform Pass and the other PR focusing on concrete type propagation? Ideas?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

@rajbarik rajbarik Jun 1, 2018

Choose a reason for hiding this comment

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

@slavapestov and others, I see a conflict on this PR in lib/SILOptimizer/Transforms/FunctionSignatureOpts.h because of the recent move from include to lib. Since I use some of the functionalities defined in FunctionSignatureOpts.h, is it ok to move it back to include?

@rajbarik
Copy link
Contributor Author

rajbarik commented Jun 7, 2018

@slavapestov things done in today's commit as per our conversation at WWDC: (1) squashed all commits. (2) rearranged code for ExistentialSpecializer so that they are in FunctionSignatureTransform directory with ExistentailSpecializer.cpp/h instead of sprinkled in GenericSpecializer.cpp and Generics.cpp (This is because I use FunctionSignatureOpts.h and ExistentialSpecializer is a kind of FunctionSignatureTransform as it rewrites the arguments).

One thing I am still working on is to extend ExistentialTransform to generic functions. I will land it as a separate PR.

Let me know how we go about breaking this PR into small pieces. Thanks.

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.

Will take a closer look later!

break;
}
default: {
assert(true);
Copy link
Contributor

Choose a reason for hiding this comment

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

Use llvm_unreachable instead

switch (ExistentialRepr) {
case ExistentialRepresentation::Opaque: {
archetypeValue = Builder.createOpenExistentialAddr(
Loc, OrigOperand, OpenedSILType, OpenedExistentialAccess::Mutable);
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it have to be mutable? You're not specializing 'inout ExistentialType' to 'inout T' are you? For CoW existentials, mutable existentials incur a copy of the box so you don't want to do it unless you need to

llvm::SmallVector<SILValue, 8> ApplyArgs;

/// Ideally this should be a SubstitutionMap. TODO.
llvm::DenseMap<SubstitutableType *, Type> Generic2OpenedTypeMap;
Copy link
Contributor

Choose a reason for hiding this comment

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

Are the keys here always GenericTypeParamTypes?

break;
}
};
auto GenericParam = Arg2GenericTypeMap[ArgDesc.Index];
Copy link
Contributor

Choose a reason for hiding this comment

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

Use this pattern:

auto iter = map.find(key);
assert(iter != map.end());
... iter->second

Generic2OpenedTypeMap[GenericParam] = OpenedType;
} else {
auto CallerArg = ThunkBody->getArgument(ArgDesc.Index);
auto SwiftType = CallerArg->getType().getASTType();
Copy link
Contributor

Choose a reason for hiding this comment

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

This won't work if the caller has its own generic parameters. You need a way to track the generic parameters you added, only

ArchetypeType *Opened;
auto OpenedType =
SwiftType->openAnyExistentialType(Opened)->getCanonicalType();
Generic2OpenedTypeMap[GP] = OpenedType;
Copy link
Contributor

Choose a reason for hiding this comment

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

This OpenedType is never used?

auto SubMap =
SubstitutionMap::get(CalleeGenericSig,
[&](SubstitutableType *type) -> Type {
return Generic2OpenedTypeMap[type];
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 where you check: is 'type' a GenericTypeParamType that you added, in which case you look up into Generic2OpenedTypeMap, otherwise, you F->mapTypeIntoContext(type).

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

Choose a reason for hiding this comment

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

use MakeAbstractConformanceForGenericType() instead


/// Perform the substitutions.
auto SubstCalleeType = GenCalleeType->substGenericArgs(M, SubMap);
SILType::getPrimitiveObjectType(SubstCalleeType);
Copy link
Contributor

Choose a reason for hiding this comment

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

The return value of this function is not used

@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