Skip to content

Perform ExistentialSpecializer when SoleConformingType is known #20977

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
Jan 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "swift/SIL/SILArgument.h"
#include "swift/SIL/SILValue.h"
#include "swift/SILOptimizer/Analysis/Analysis.h"
#include "swift/SILOptimizer/Analysis/ClassHierarchyAnalysis.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallVector.h"
Expand Down Expand Up @@ -78,6 +79,10 @@ class ProtocolConformanceAnalysis : public SILAnalysis {
/// conforming concrete type.
NominalTypeDecl *findSoleConformingType(ProtocolDecl *Protocol);

// Wrapper function to findSoleConformingType that checks for additional
// constraints for classes using ClassHierarchyAnalysis.
bool getSoleConformingType(ProtocolDecl *Protocol, ClassHierarchyAnalysis *CHA, CanType &ConcreteType);

private:
/// Compute inheritance properties.
void init();
Expand Down
22 changes: 22 additions & 0 deletions lib/SILOptimizer/Analysis/ProtocolConformanceAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,26 @@ ProtocolConformanceAnalysis::findSoleConformingType(ProtocolDecl *Protocol) {
return SoleConformingNTD;
}

// Wrapper function to findSoleConformingType that checks for additional
// constraints for classes using ClassHierarchyAnalysis.
bool ProtocolConformanceAnalysis::getSoleConformingType(
ProtocolDecl *Protocol, ClassHierarchyAnalysis *CHA, CanType &ConcreteType) {
// Determine the sole conforming type.
auto *NTD = findSoleConformingType(Protocol);
if (!NTD)
return false;

// Sole conforming class should not be open access or have any derived class.
ClassDecl *CD;
if ((CD = dyn_cast<ClassDecl>(NTD)) &&
(CD->getEffectiveAccess() == AccessLevel::Open ||
CHA->hasKnownDirectSubclasses(CD))) {
return false;
}

// Save the concrete type.
ConcreteType = NTD->getDeclaredType()->getCanonicalType();
return true;
}

ProtocolConformanceAnalysis::~ProtocolConformanceAnalysis() {}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "ExistentialTransform.h"
#include "swift/SIL/SILFunction.h"
#include "swift/SIL/SILInstruction.h"
#include "swift/SILOptimizer/Analysis/ProtocolConformanceAnalysis.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/Existential.h"
#include "swift/SILOptimizer/Utils/Local.h"
Expand Down Expand Up @@ -48,9 +49,18 @@ class ExistentialSpecializer : public SILFunctionTransform {
/// Specialize existential args in function F.
void specializeExistentialArgsInAppliesWithinFunction(SILFunction &F);

/// Find concrete type using protocolconformance analysis.
bool findConcreteTypeFromSoleConformingType(
SILFunctionArgument *Arg, CanType &ConcreteType);

/// CallerAnalysis information.
CallerAnalysis *CA;

// Determine the set of types a protocol conforms to in whole-module
// compilation mode.
ProtocolConformanceAnalysis *PCA;

ClassHierarchyAnalysis *CHA;
public:
void run() override {

Expand All @@ -64,12 +74,39 @@ class ExistentialSpecializer : public SILFunctionTransform {
/// Get CallerAnalysis information handy.
CA = PM->getAnalysis<CallerAnalysis>();

/// Get ProtocolConformanceAnalysis.
PCA = PM->getAnalysis<ProtocolConformanceAnalysis>();

/// Get ClassHierarchyAnalysis.
CHA = PM->getAnalysis<ClassHierarchyAnalysis>();
/// Perform specialization.
specializeExistentialArgsInAppliesWithinFunction(*F);
}
};
} // namespace

/// Find concrete type from Sole Conforming Type.
bool ExistentialSpecializer::findConcreteTypeFromSoleConformingType(
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 method checks if a SoleConformingType can be determined for the argument. Some parts of code (5-6 lines) overlap with what was done in SILCombiner, but there are lot of disparities to make it a new APi in Existential.h altogether.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@atrick You had a similar concern from SILCombiner, but the overlap is minor to warrant a new API?

SILFunctionArgument *Arg, CanType &ConcreteType) {
auto ArgType = Arg->getType();
auto SwiftArgType = ArgType.getASTType();

/// Do not handle composition types yet.
if (isa<ProtocolCompositionType>(SwiftArgType))
return false;
assert(ArgType.isExistentialType());
/// Find the protocol decl.
auto *PD = dyn_cast<ProtocolDecl>(SwiftArgType->getAnyNominal());
if (!PD)
return false;

// Get SoleConformingType in ConcreteType using ProtocolConformanceAnalysis
// and ClassHierarchyAnalysis.
if (!PCA->getSoleConformingType(PD, CHA, ConcreteType))
return false;
return true;
}

/// Check if the argument Arg is used in a destroy_use instruction.
static void
findIfCalleeUsesArgInDestroyUse(SILValue Arg,
Expand All @@ -83,6 +120,14 @@ findIfCalleeUsesArgInDestroyUse(SILValue Arg,
}
}

/// Helper function to ensure that the argument is not InOut or InOut_Aliasable
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added to filter out inout args, which otherwise would be fed to openExistentialRef as non-objects!

static bool isNonInoutIndirectArgument(SILValue Arg,
SILArgumentConvention ArgConvention) {
return !Arg->getType().isObject() && ArgConvention.isIndirectConvention() &&
ArgConvention != SILArgumentConvention::Indirect_Inout &&
ArgConvention != SILArgumentConvention::Indirect_InoutAliasable;
}

/// Check if any apply argument meets the criteria for existential
/// specialization.
bool ExistentialSpecializer::canSpecializeExistentialArgsInFunction(
Expand Down Expand Up @@ -122,7 +167,14 @@ bool ExistentialSpecializer::canSpecializeExistentialArgsInFunction(
Operand &ArgOper = Apply.getArgumentRef(Idx);
CanType ConcreteType =
ConcreteExistentialInfo(ArgOper.get(), ArgOper.getUser()).ConcreteType;
if (!ConcreteType) {
auto ArgConvention = F->getConventions().getSILArgumentConvention(Idx);
/// Find the concrete type, either via sole type or via
/// findInitExistential..
CanType SoleConcreteType;
if (!((F->getModule().isWholeModule() &&
Copy link
Contributor Author

Choose a reason for hiding this comment

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

First tries to check if there is a SoleConformingType, if not, then tries findConcreteTypeUsingInitExistential.

isNonInoutIndirectArgument(CalleeArg, ArgConvention) &&
findConcreteTypeFromSoleConformingType(CalleeArg, SoleConcreteType)) ||
ConcreteType)) {
LLVM_DEBUG(
llvm::dbgs()
<< "ExistentialSpecializer Pass: Bail! cannot find ConcreteType "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,8 @@ void ExistentialTransform::populateThunkBody() {
break;
}
case ExistentialRepresentation::Class: {
/// If the operand is not object type, we would need an explicit load.
assert(OrigOperand->getType().isObject());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Extra check for accidental non-object operand type being passed to OEI.

archetypeValue =
Builder.createOpenExistentialRef(Loc, OrigOperand, OpenedSILType);
ApplyArgs.push_back(archetypeValue);
Expand Down
16 changes: 2 additions & 14 deletions lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -654,22 +654,10 @@ SILCombiner::buildConcreteOpenedExistentialInfoFromSoleConformingType(
return None;

// Determine the sole conforming type.
auto *NTD = PCA->findSoleConformingType(PD);
if (!NTD)
CanType ConcreteType;
if (!PCA->getSoleConformingType(PD, CHA, ConcreteType))
return None;

// Sole conforming class should not be open access or have any derived class.
ClassDecl *CD;
if ((CD = dyn_cast<ClassDecl>(NTD)) &&
(CD->getEffectiveAccess() == AccessLevel::Open ||
CHA->hasKnownDirectSubclasses(CD))) {
return None;
}

// Create SIL type for the concrete type.
auto ElementType = NTD->getDeclaredType();
auto ConcreteType = ElementType->getCanonicalType();

// Determine OpenedArchetypeDef and SubstituionMap.
ConcreteOpenedExistentialInfo COAI(ArgOperand, ConcreteType, PD);
if (!COAI.CEI)
Expand Down
52 changes: 52 additions & 0 deletions test/SILOptimizer/existential_transform_soletype.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// RUN: %target-swift-frontend -O -wmo -sil-existential-specializer -Xllvm -sil-disable-pass=GenericSpecializer -Xllvm -sil-disable-pass=FunctionSignatureOpts -Xllvm -sil-disable-pass=SILCombine -emit-sil -sil-verify-all %s | %FileCheck %s

internal protocol SPP {
func bar() -> Int32
}
internal class SCC: SPP {
@inline(never) func bar() -> Int32 {
return 5
}
}

@inline(never) internal func opt2(b:SPP) -> Int32{
return b.bar()
}

@inline(never) internal func opt1(b:SPP) -> Int32{
return opt2(b:b)
}

// CHECK-LABEL: sil hidden [noinline] @$s30existential_transform_soletype4opt11bs5Int32VAA3SPP_p_tF : $@convention(thin) (@in_guaranteed SPP) -> Int32 {
// CHECK: bb0(%0 : $*SPP):
// CHECK: debug_value_addr
// CHECK: function_ref @$s30existential_transform_soletype4opt21bs5Int32VAA3SPP_p_tFTf4e_n : $@convention(thin) <τ_0_0 where τ_0_0 : SPP> (@in_guaranteed τ_0_0) -> Int32 // user: %4
// CHECK: open_existential_addr
// CHECK: apply
// CHECK: return
// CHECK-LABEL: } // end sil function '$s30existential_transform_soletype4opt11bs5Int32VAA3SPP_p_tF'

// CHECK-LABEL: sil shared [noinline] @$s30existential_transform_soletype4opt21bs5Int32VAA3SPP_p_tFTf4e_n : $@convention(thin) <τ_0_0 where τ_0_0 : SPP> (@in_guaranteed τ_0_0) -> Int32 {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

opt1 does not get optimized since the caller foo is under @_optimize(none).

Copy link
Contributor

Choose a reason for hiding this comment

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

It's generally better to use .sil test cases for individual passes, but not an absolute requirement. The source is definitely helpful, but that can be included as a comment.

// CHECK: bb0(%0 : $*τ_0_0):
// CHECK: alloc_stack
// CHECK: init_existential_addr
// CHECK: copy_addr
// CHECK: debug_value_addr
// CHECK: open_existential_addr
// CHECK: witness_method
// CHECK: apply
// CHECK: dealloc_stack
// CHECK: return
// CHECK-LABEL: } // end sil function '$s30existential_transform_soletype4opt21bs5Int32VAA3SPP_p_tFTf4e_n'


@_optimize(none) func foo(number:Int32)->Int32 {
var b:SPP
if number < 5 {
b = SCC()
} else {
b = SCC()
}
let x = opt1(b:b)
return x
}