Files
swift-mirror/lib/SILPasses/Utils/Local.cpp
Andrew Trick 250bb973bb Add an array optimization pass that hoists make_mutable calls.
This gives us a 10x speedup on -Ofast memset, which was the original
goal. We're now within 2x of C, but the C code produces movdqu instead
of movups for twice the throughput:
<rdar://problem/17722727> Memset at -Ofast is 2x slower than C

-O3 results:
| benchmark     | baserun0 |  optrun0 |   delta | speedup |
| Memset        | 39885.00 | 33978.00 | 5907.00 |   17.4% |
| NBody         |   459.00 |   440.00 |   19.00 |    4.3% |
| QuickSort     |   456.00 |   439.00 |   17.00 |    3.9% |
| StringWalk    |   625.00 |   647.00 |   22.00 |   -3.4% |
| SmallPT       |   557.00 |   575.00 |   18.00 |   -3.1% |
| Phonebook     |  1804.00 |  1862.00 |   58.00 |   -3.1% |

Memset, NBody, and Quicksort are the ones we expected to improve.

We don't get much gain on O3 because retains/release and bounds checks
are still there.
<rdar://problem/17719220> QuickSort -O3 has retains/releases in the inner loop.

Given the Ofast results, I think the small degradations at O3 are noise.

-Ofast results:
| benchmark     | baserun0 |  optrun0 |   delta | speedup |
| Memset        |  5453.00 |   452.00 | 5001.00 | 1106.4% |
| NBody         |   772.00 |   437.00 |  335.00 |   76.7% |
| Walsh         |  1530.00 |  1096.00 |  434.00 |   39.6% |
| QuickSort     |   682.00 |   524.00 |  158.00 |   30.2% |
| Phonebook     |  1453.00 |  1561.00 |  108.00 |   -6.9% |
| Hash          |   993.00 |   958.00 |   35.00 |    3.7% |
| StringWalk    |   458.00 |   446.00 |   12.00 |    2.7% |
| StringBuilder |  1603.00 |  1568.00 |   35.00 |    2.2% |

Swift SVN r20145
2014-07-18 06:52:17 +00:00

250 lines
8.4 KiB
C++

//===--- Local.cpp - Functions that perform local SIL transformations. ---===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2015 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/SILPasses/Utils/Local.h"
#include "swift/SILAnalysis/Analysis.h"
#include "swift/SILAnalysis/DominanceAnalysis.h"
#include "swift/SIL/CallGraph.h"
#include "swift/SIL/SILArgument.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SIL/SILModule.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/IR/Intrinsics.h"
#include <deque>
using namespace swift;
bool
swift::isSideEffectFree(BuiltinFunctionRefInst *FR) {
// First, check if we are dealing with a swift builtin.
const BuiltinInfo &BInfo = FR->getBuiltinInfo();
if (BInfo.ID != BuiltinValueKind::None) {
return BInfo.isReadNone();
}
// Second, specialcase llvm intrinsic.
const IntrinsicInfo & IInfo = FR->getIntrinsicInfo();
if (IInfo.ID != llvm::Intrinsic::not_intrinsic) {
return ( (IInfo.hasAttribute(llvm::Attribute::ReadNone) ||
IInfo.hasAttribute(llvm::Attribute::ReadOnly)) &&
IInfo.hasAttribute(llvm::Attribute::NoUnwind) );
}
llvm_unreachable("All cases are covered.");
}
bool swift::isReadNone(BuiltinFunctionRefInst *FR) {
// First, check if we are dealing with a swift builtin.
const BuiltinInfo &BInfo = FR->getBuiltinInfo();
if (BInfo.ID != BuiltinValueKind::None)
return BInfo.isReadNone();
// Second, specialcase llvm intrinsic.
const IntrinsicInfo & IInfo = FR->getIntrinsicInfo();
if (IInfo.ID != llvm::Intrinsic::not_intrinsic)
return IInfo.hasAttribute(llvm::Attribute::ReadNone) &&
IInfo.hasAttribute(llvm::Attribute::NoUnwind);
llvm_unreachable("All cases are covered.");
}
/// \brief Perform a fast local check to see if the instruction is dead.
///
/// This routine only examines the state of the instruction at hand.
bool
swift::isInstructionTriviallyDead(SILInstruction *I) {
if (!I->use_empty() || isa<TermInst>(I))
return false;
// We know that some calls do not have side effects.
if (const ApplyInst *AI = dyn_cast<ApplyInst>(I)) {
if (auto *BFRI = dyn_cast<BuiltinFunctionRefInst>(AI->getCallee())) {
return isSideEffectFree(BFRI);
}
if (auto *FRI = dyn_cast<FunctionRefInst>(AI->getCallee()))
// If we call an apply inst to a global initializer, but the value is not
// used it is safe to remove it.
if (FRI->getReferencedFunction()->isGlobalInit())
return true;
}
// condfail instructions that obviously can't fail are dead.
if (auto *CFI = dyn_cast<CondFailInst>(I))
if (auto *ILI = dyn_cast<IntegerLiteralInst>(CFI->getOperand()))
if (!ILI->getValue())
return true;
// mark_uninitialized is never dead.
if (isa<MarkUninitializedInst>(I))
return false;
// These invalidate enums so "write" memory, but that is not an essential
// operation so we can remove these if they are trivially dead.
if (isa<UncheckedTakeEnumDataAddrInst>(I))
return true;
if (!I->mayHaveSideEffects())
return true;
return false;
}
namespace {
using CallbackTy = std::function<void(SILInstruction *)>;
} // end anonymous namespace
bool swift::
recursivelyDeleteTriviallyDeadInstructions(ArrayRef<SILInstruction *> IA,
bool Force, CallbackTy Callback) {
// Delete these instruction and others that become dead after it's deleted.
llvm::SmallPtrSet<SILInstruction *, 8> DeadInsts;
for (auto I : IA) {
// If the instruction is not dead or force is false, there is nothing to do.
if (Force || isInstructionTriviallyDead(I))
DeadInsts.insert(I);
}
llvm::SmallPtrSet<SILInstruction *, 8> NextInsts;
while (!DeadInsts.empty()) {
for (auto I : DeadInsts) {
// Call the callback before we mutate the to be deleted instruction in any
// way.
Callback(I);
// Check if any of the operands will become dead as well.
MutableArrayRef<Operand> Ops = I->getAllOperands();
for (Operand &Op : Ops) {
SILValue OpVal = Op.get();
if (!OpVal)
continue;
// Remove the reference from the instruction being deleted to this
// operand.
Op.drop();
// If the operand is an instruction that is only used by the instruction
// being deleted, delete it.
if (SILInstruction *OpValInst = dyn_cast<SILInstruction>(OpVal))
if (!DeadInsts.count(OpValInst) &&
isInstructionTriviallyDead(OpValInst))
NextInsts.insert(OpValInst);
}
}
for (auto I : DeadInsts) {
// This will remove this instruction and all its uses.
I->eraseFromParent();
}
NextInsts.swap(DeadInsts);
NextInsts.clear();
}
return true;
}
/// \brief If the given instruction is dead, delete it along with its dead
/// operands.
///
/// \param I The instruction to be deleted.
/// \param Force If Force is set, don't check if the top level instruction is
/// considered dead - delete it regardless.
/// \return Returns true if any instructions were deleted.
bool swift::recursivelyDeleteTriviallyDeadInstructions(SILInstruction *I,
bool Force,
CallbackTy Callback) {
ArrayRef<SILInstruction *> AI = ArrayRef<SILInstruction *>(I);
return recursivelyDeleteTriviallyDeadInstructions(AI, Force, Callback);
}
void swift::eraseUsesOfInstruction(SILInstruction *Inst) {
for (auto UI : Inst->getUses()) {
auto *User = UI->getUser();
// If the instruction itself has any uses, recursively zap them so that
// nothing uses this instruction.
eraseUsesOfInstruction(User);
// Walk through the operand list and delete any random instructions that
// will become trivially dead when this instruction is removed.
for (auto &Op : User->getAllOperands()) {
if (auto *OpI = dyn_cast<SILInstruction>(Op.get())) {
// Don't recursively delete the pointer we're getting in.
if (OpI != Inst) {
Op.drop();
recursivelyDeleteTriviallyDeadInstructions(OpI);
}
}
}
User->eraseFromParent();
}
}
void swift::bottomUpCallGraphOrder(SILModule *M,
std::vector<SILFunction*> &order) {
CallGraphSorter<SILFunction*> sorter;
for (auto &Caller : *M)
for (auto &BB : Caller)
for (auto &I : BB)
if (FunctionRefInst *FRI = dyn_cast<FunctionRefInst>(&I)) {
SILFunction *Callee = FRI->getReferencedFunction();
sorter.addEdge(&Caller, Callee);
}
sorter.sort(order);
}
void swift::replaceWithSpecializedFunction(ApplyInst *AI, SILFunction *NewF) {
SILLocation Loc = AI->getLoc();
ArrayRef<Substitution> Subst;
SmallVector<SILValue, 4> Arguments;
for (auto &Op : AI->getArgumentOperands()) {
Arguments.push_back(Op.get());
}
SILBuilder Builder(AI);
FunctionRefInst *FRI = Builder.createFunctionRef(Loc, NewF);
ApplyInst *NAI =
Builder.createApply(Loc, FRI, Arguments, AI->isTransparent());
AI->replaceAllUsesWith(NAI);
recursivelyDeleteTriviallyDeadInstructions(AI, true);
}
bool swift::hasUnboundGenericTypes(TypeSubstitutionMap &SubsMap) {
// Check whether any of the substitutions are dependent.
for (auto &entry : SubsMap)
if (entry.second->getCanonicalType()->hasArchetype())
return true;
return false;
}
/// Find a new position for an ApplyInst's FuncRef so that it dominates its
/// use. Not that FuncionRefInsts may be shared by multiple ApplyInsts.
void swift::placeFuncRef(ApplyInst *AI, DominanceInfo *DT) {
FunctionRefInst *FuncRef = cast<FunctionRefInst>(AI->getCallee());
SILBasicBlock *DomBB =
DT->findNearestCommonDominator(AI->getParent(), FuncRef->getParent());
if (DomBB == AI->getParent() && DomBB != FuncRef->getParent())
// Prefer to place the FuncRef immediately before the call. Since we're
// moving FuncRef up, this must be the only call to it in the block.
FuncRef->moveBefore(AI);
else
// Otherwise, conservatively stick it at the beginning of the block.
FuncRef->moveBefore(DomBB->begin());
}