mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
FSO functions that have high potential but does not have caller inside current module. The thunk can then be inlined into the module calling the function and the function would get the benefit of FSO. The heuristic for selecting such function is 1. Have no indirect caller. This would introduce a thunk. 2. Have potential to give better performance. i.e. function argument can be O2G. Regression TEST | OLD_MIN | NEW_MIN | DELTA (%) | SPEEDUP --- | --- | --- | --- | --- BenchLangCallingCFunction | 184 | 211 | +14.7% | **0.87x** Calculator | 55 | 59 | +7.3% | **0.93x** DeadArray | 687 | 741 | +7.9% | **0.93x** MonteCarloPi | 39275 | 41669 | +6.1% | **0.94x** Improvement TEST | OLD_MIN | NEW_MIN | DELTA (%) | SPEEDUP --- | --- | --- | --- | --- LuhnAlgoLazy | 2478 | 2327 | -6.1% | **1.06x** OpenClose | 54 | 51 | -5.6% | **1.06x** SortLettersInPlace | 1016 | 946 | -6.9% | **1.07x** ObjectiveCBridgeFromNSDictionaryAnyObjectToStringForced | 149993 | 139755 | -6.8% | **1.07x** Phonebook | 9666 | 8992 | -7.0% | **1.07x** ObjectiveCBridgeFromNSDictionaryAnyObjectToString | 222713 | 206538 | -7.3% | **1.08x** LuhnAlgoEager | 2393 | 2226 | -7.0% | **1.08x** Dictionary | 1307 | 1196 | -8.5% | **1.09x** JSONHelperDeserialize | 3808 | 3492 | -8.3% | **1.09x** StdlibSort | 7310 | 4084 | -44.1% | **1.79x** I see 0.15% increase in code size for Benchmark_O. Thanks @gottesmm for suggesting this opportunity. rdar://25345056
404 lines
14 KiB
C++
404 lines
14 KiB
C++
//===--- FunctionSignatureOptUtils.cpp ------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See http://swift.org/LICENSE.txt for license information
|
|
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/SIL/SILBasicBlock.h"
|
|
#include "swift/SIL/SILFunction.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
#include "swift/SIL/SILModule.h"
|
|
#include "swift/SIL/SILValue.h"
|
|
#include "swift/SIL/DebugUtils.h"
|
|
#include "swift/SILOptimizer/Utils/FunctionSignatureOptUtils.h"
|
|
#include "swift/SILOptimizer/Utils/Local.h"
|
|
#include "swift/SIL/Mangle.h"
|
|
|
|
using namespace swift;
|
|
|
|
static bool isSpecializableRepresentation(SILFunctionTypeRepresentation Rep) {
|
|
switch (Rep) {
|
|
case SILFunctionTypeRepresentation::Method:
|
|
case SILFunctionTypeRepresentation::Thin:
|
|
case SILFunctionTypeRepresentation::Thick:
|
|
case SILFunctionTypeRepresentation::CFunctionPointer:
|
|
return true;
|
|
case SILFunctionTypeRepresentation::WitnessMethod:
|
|
case SILFunctionTypeRepresentation::ObjCMethod:
|
|
case SILFunctionTypeRepresentation::Block:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Returns true if F is a function which the pass know show to specialize
|
|
/// function signatures for.
|
|
bool swift::canSpecializeFunction(SILFunction *F) {
|
|
// Do not specialize the signature of SILFunctions that are external
|
|
// declarations since there is no body to optimize.
|
|
if (F->isExternalDeclaration())
|
|
return false;
|
|
|
|
// Do not specialize functions that are available externally. If an external
|
|
// function was able to be specialized, it would have been specialized in its
|
|
// own module. We will inline the original function as a thunk. The thunk will
|
|
// call the specialized function.
|
|
if (F->isAvailableExternally())
|
|
return false;
|
|
|
|
// Do not specialize the signature of always inline functions. We
|
|
// will just inline them and specialize each one of the individual
|
|
// functions that these sorts of functions are inlined into.
|
|
if (F->getInlineStrategy() == Inline_t::AlwaysInline)
|
|
return false;
|
|
|
|
// For now ignore generic functions to keep things simple...
|
|
if (F->getLoweredFunctionType()->isPolymorphic())
|
|
return false;
|
|
|
|
// Make sure F has a linkage that we can optimize.
|
|
if (!isSpecializableRepresentation(F->getRepresentation()))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void swift::
|
|
addReleasesForConvertedOwnedParameter(SILBuilder &Builder,
|
|
SILLocation Loc,
|
|
ArrayRef<SILArgument*> Parameters,
|
|
ArrayRef<ArgumentDescriptor> &ArgDescs) {
|
|
// If we have any arguments that were consumed but are now guaranteed,
|
|
// insert a release_value.
|
|
for (auto &ArgDesc : ArgDescs) {
|
|
if (ArgDesc.CalleeRelease.empty())
|
|
continue;
|
|
Builder.createReleaseValue(Loc, Parameters[ArgDesc.Index]);
|
|
}
|
|
}
|
|
|
|
void swift::
|
|
addReleasesForConvertedOwnedParameter(SILBuilder &Builder,
|
|
SILLocation Loc,
|
|
OperandValueArrayRef Parameters,
|
|
ArrayRef<ArgumentDescriptor> &ArgDescs) {
|
|
// If we have any arguments that were consumed but are now guaranteed,
|
|
// insert a release_value.
|
|
for (auto &ArgDesc : ArgDescs) {
|
|
// The argument is dead. Make sure we have a release to balance out
|
|
// the retain for creating the @owned parameter.
|
|
if (ArgDesc.IsEntirelyDead &&
|
|
ArgDesc.Arg->getKnownParameterInfo().getConvention() ==
|
|
ParameterConvention::Direct_Owned) {
|
|
Builder.createReleaseValue(Loc, Parameters[ArgDesc.Index]);
|
|
continue;
|
|
}
|
|
if (ArgDesc.CalleeRelease.empty())
|
|
continue;
|
|
Builder.createReleaseValue(Loc, Parameters[ArgDesc.Index]);
|
|
}
|
|
}
|
|
|
|
void swift::
|
|
addRetainsForConvertedDirectResults(SILBuilder &Builder,
|
|
SILLocation Loc,
|
|
SILValue ReturnValue,
|
|
SILInstruction *AI,
|
|
ArrayRef<ResultDescriptor> DirectResults) {
|
|
for (auto I : indices(DirectResults)) {
|
|
auto &RV = DirectResults[I];
|
|
if (RV.CalleeRetain.empty()) continue;
|
|
|
|
bool IsSelfRecursionEpilogueRetain = false;
|
|
for (auto &X : RV.CalleeRetain) {
|
|
IsSelfRecursionEpilogueRetain |= (AI == X);
|
|
}
|
|
|
|
// We do not create a retain if this ApplyInst is a self-recursion.
|
|
if (IsSelfRecursionEpilogueRetain)
|
|
continue;
|
|
|
|
// Extract the return value if necessary.
|
|
SILValue SpecificResultValue = ReturnValue;
|
|
if (DirectResults.size() != 1)
|
|
SpecificResultValue = Builder.createTupleExtract(Loc, ReturnValue, I);
|
|
|
|
Builder.createRetainValue(Loc, SpecificResultValue);
|
|
}
|
|
}
|
|
|
|
using namespace swift;
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Utility
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Returns true if I is a release instruction.
|
|
static bool isRelease(SILInstruction *I) {
|
|
switch (I->getKind()) {
|
|
case ValueKind::StrongReleaseInst:
|
|
case ValueKind::ReleaseValueInst:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Returns true if LHS and RHS contain identical set of releases.
|
|
static bool hasIdenticalReleases(ReleaseList LHS, ReleaseList RHS) {
|
|
llvm::DenseSet<SILInstruction *> Releases;
|
|
if (LHS.size() != RHS.size())
|
|
return false;
|
|
for (auto &X : LHS)
|
|
Releases.insert(X);
|
|
for (auto &X : RHS)
|
|
if (Releases.find(X) == Releases.end())
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static ReleaseSet
|
|
collectEpilogueReleases(ConsumedArgToEpilogueReleaseMatcher &Return,
|
|
ConsumedArgToEpilogueReleaseMatcher &Throw,
|
|
SILArgument *Arg) {
|
|
ReleaseSet EpilogueReleases;
|
|
// Handle return block.
|
|
auto ReturnReleases = Return.getReleasesForArgument(Arg);
|
|
if (ReturnReleases.empty())
|
|
return EpilogueReleases;
|
|
for (auto &X : Return.getReleasesForArgument(Arg))
|
|
EpilogueReleases.insert(X);
|
|
|
|
// Handle throw block.
|
|
if (!Throw.hasBlock())
|
|
return EpilogueReleases;
|
|
auto ThrowReleases = Throw.getReleasesForArgument(Arg);
|
|
if (ThrowReleases.empty()) {
|
|
EpilogueReleases.clear();
|
|
return EpilogueReleases;
|
|
}
|
|
for (auto &X : ThrowReleases)
|
|
EpilogueReleases.insert(X);
|
|
|
|
return EpilogueReleases;
|
|
}
|
|
|
|
/// Returns .Some(I) if I is a release that is the only non-debug instruction
|
|
/// with side-effects in the use-def graph originating from Arg. Returns
|
|
/// .Some(nullptr), if all uses from the arg were either debug insts or do not
|
|
/// have side-effects. Returns .None if there were any non-release instructions
|
|
/// with side-effects in the use-def graph from Arg or if there were multiple
|
|
/// release instructions with side-effects in the use-def graph from Arg.
|
|
static llvm::Optional<ReleaseList>
|
|
getNonTrivialNonDebugReleaseUse(SILArgument *Arg) {
|
|
llvm::SmallVector<SILInstruction *, 8> Worklist;
|
|
llvm::SmallPtrSet<SILInstruction *, 8> SeenInsts;
|
|
ReleaseList Result;
|
|
|
|
for (Operand *I : getNonDebugUses(SILValue(Arg)))
|
|
Worklist.push_back(I->getUser());
|
|
|
|
while (!Worklist.empty()) {
|
|
SILInstruction *U = Worklist.pop_back_val();
|
|
if (!SeenInsts.insert(U).second)
|
|
continue;
|
|
|
|
// If U is a terminator inst, return false.
|
|
if (isa<TermInst>(U))
|
|
return None;
|
|
|
|
// If U has side effects...
|
|
if (U->mayHaveSideEffects()) {
|
|
// And is not a release_value, return None.
|
|
if (!isRelease(U))
|
|
return None;
|
|
|
|
// Otherwise, set result to that value.
|
|
Result.push_back(U);
|
|
continue;
|
|
}
|
|
|
|
// Otherwise add all non-debug uses of I to the worklist.
|
|
for (Operand *I : getNonDebugUses(U))
|
|
Worklist.push_back(I->getUser());
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
bool FunctionSignatureInfo::analyzeParameters() {
|
|
// For now ignore functions with indirect results.
|
|
if (F->getLoweredFunctionType()->hasIndirectResults())
|
|
return false;
|
|
|
|
ArrayRef<SILArgument *> Args = F->begin()->getBBArgs();
|
|
|
|
// A map from consumed SILArguments to the release associated with an
|
|
// argument.
|
|
ConsumedArgToEpilogueReleaseMatcher ArgToReturnReleaseMap(RCFI, F);
|
|
ConsumedArgToEpilogueReleaseMatcher ArgToThrowReleaseMap(
|
|
RCFI, F, ConsumedArgToEpilogueReleaseMatcher::ExitKind::Throw);
|
|
|
|
// Did we decide we should optimize any parameter?
|
|
bool SignatureOptimize = false;
|
|
|
|
// Analyze the argument information.
|
|
for (unsigned i = 0, e = Args.size(); i != e; ++i) {
|
|
// Find all the epilogue releases for this argument.
|
|
auto EpilogueReleases = collectEpilogueReleases(ArgToReturnReleaseMap,
|
|
ArgToThrowReleaseMap,
|
|
Args[i]);
|
|
ArgumentDescriptor A(Allocator, Args[i], EpilogueReleases);
|
|
bool HaveOptimizedArg = false;
|
|
|
|
// Whether we will explode the argument or not.
|
|
A.Explode = A.shouldExplode();
|
|
|
|
bool isABIRequired = isArgumentABIRequired(Args[i]);
|
|
auto OnlyRelease = getNonTrivialNonDebugReleaseUse(Args[i]);
|
|
|
|
// If this argument is not ABI required and has no uses except for debug
|
|
// instructions, remove it.
|
|
if (!isABIRequired && OnlyRelease && OnlyRelease.getValue().empty()) {
|
|
A.IsEntirelyDead = true;
|
|
HaveOptimizedArg = true;
|
|
}
|
|
|
|
// See if we can find a ref count equivalent strong_release or release_value
|
|
// at the end of this function if our argument is an @owned parameter.
|
|
if (A.hasConvention(SILArgumentConvention::Direct_Owned)) {
|
|
auto Releases = ArgToReturnReleaseMap.getReleasesForArgument(A.Arg);
|
|
if (!Releases.empty()) {
|
|
|
|
// If the function has a throw block we must also find a matching
|
|
// release in the throw block.
|
|
auto ReleasesInThrow = ArgToThrowReleaseMap.getReleasesForArgument(A.Arg);
|
|
if (!ArgToThrowReleaseMap.hasBlock() || !ReleasesInThrow.empty()) {
|
|
|
|
// TODO: accept a second release in the throw block to let the
|
|
// argument be dead.
|
|
if (OnlyRelease && hasIdenticalReleases(OnlyRelease.getValue(), Releases)) {
|
|
A.IsEntirelyDead = true;
|
|
}
|
|
|
|
A.CalleeRelease = Releases;
|
|
A.CalleeReleaseInThrowBlock = ReleasesInThrow;
|
|
HaveOptimizedArg = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (A.Explode) {
|
|
HaveOptimizedArg = true;
|
|
}
|
|
|
|
if (HaveOptimizedArg) {
|
|
SignatureOptimize = true;
|
|
// Store that we have modified the self argument. We need to change the
|
|
// calling convention later.
|
|
if (Args[i]->isSelf())
|
|
ShouldModifySelfArgument = true;
|
|
}
|
|
|
|
// Add the argument to our list.
|
|
ArgDescList.push_back(std::move(A));
|
|
}
|
|
|
|
return SignatureOptimize;
|
|
}
|
|
|
|
bool FunctionSignatureInfo::analyzeResult() {
|
|
// For now ignore functions with indirect results.
|
|
if (F->getLoweredFunctionType()->hasIndirectResults())
|
|
return false;
|
|
|
|
// Did we decide we should optimize any parameter?
|
|
bool SignatureOptimize = false;
|
|
|
|
// Analyze return result information.
|
|
auto DirectResults = F->getLoweredFunctionType()->getDirectResults();
|
|
for (SILResultInfo DirectResult : DirectResults) {
|
|
ResultDescList.emplace_back(DirectResult);
|
|
}
|
|
// For now, only do anything if there's a single direct result.
|
|
if (DirectResults.size() == 1 &&
|
|
ResultDescList[0].hasConvention(ResultConvention::Owned)) {
|
|
auto &RI = ResultDescList[0];
|
|
// We have an @owned return value, find the epilogue retains now.
|
|
ConsumedResultToEpilogueRetainMatcher RVToReturnRetainMap(RCFI, AA, F);
|
|
auto Retains = RVToReturnRetainMap.getEpilogueRetains();
|
|
// We do not need to worry about the throw block, as the return value is only
|
|
// going to be used in the return block/normal block of the try_apply instruction.
|
|
if (!Retains.empty()) {
|
|
RI.CalleeRetain = Retains;
|
|
SignatureOptimize = true;
|
|
}
|
|
}
|
|
return SignatureOptimize;
|
|
}
|
|
|
|
/// This function goes through the arguments of F and sees if we have anything
|
|
/// to optimize in which case it returns true. If we have nothing to optimize,
|
|
/// it returns false.
|
|
void FunctionSignatureInfo::analyze() {
|
|
// Compute the signature optimization.
|
|
bool OptimizedParams = analyzeParameters();
|
|
bool OptimizedResult = analyzeResult();
|
|
ShouldOptimize = OptimizedParams || OptimizedResult;
|
|
// We set this function to highly profitable if we have a O2G on one of its
|
|
// parameters or results.
|
|
for (auto &X : ArgDescList) {
|
|
HighlyProfitable |= !X.CalleeRelease.empty();
|
|
}
|
|
for (auto &X : ResultDescList) {
|
|
HighlyProfitable |= !X.CalleeRetain.empty();
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Mangling
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
std::string FunctionSignatureInfo::getOptimizedName() const {
|
|
Mangle::Mangler M;
|
|
auto P = SpecializationPass::FunctionSignatureOpts;
|
|
FunctionSignatureSpecializationMangler FSSM(P, M, F);
|
|
|
|
// Handle arguments' changes.
|
|
for (unsigned i : indices(ArgDescList)) {
|
|
const ArgumentDescriptor &Arg = ArgDescList[i];
|
|
if (Arg.IsEntirelyDead) {
|
|
FSSM.setArgumentDead(i);
|
|
}
|
|
|
|
// If we have an @owned argument and found a callee release for it,
|
|
// convert the argument to guaranteed.
|
|
if (!Arg.CalleeRelease.empty()) {
|
|
FSSM.setArgumentOwnedToGuaranteed(i);
|
|
}
|
|
|
|
// If this argument is not dead and we can explode it, add 's' to the
|
|
// mangling.
|
|
if (Arg.Explode && !Arg.IsEntirelyDead) {
|
|
FSSM.setArgumentSROA(i);
|
|
}
|
|
}
|
|
|
|
// Handle return value's change.
|
|
// FIXME: handle multiple direct results here
|
|
if (ResultDescList.size() == 1 &&
|
|
!ResultDescList[0].CalleeRetain.empty())
|
|
FSSM.setReturnValueOwnedToUnowned();
|
|
|
|
FSSM.mangle();
|
|
|
|
return M.finalize();
|
|
}
|
|
|
|
|