mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
1008 lines
35 KiB
C++
1008 lines
35 KiB
C++
//===-------------------------- SILCombine --------------------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// A port of LLVM's InstCombine pass to SIL. Its main purpose is for performing
|
|
// small combining operations/peepholes at the SIL level. It additionally
|
|
// performs dead code elimination when it initially adds instructions to the
|
|
// work queue in order to reduce compile time by not visiting trivially dead
|
|
// instructions.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#define DEBUG_TYPE "sil-combine"
|
|
#include "swift/SILPasses/Passes.h"
|
|
#include "swift/SILPasses/Transforms.h"
|
|
#include "swift/SIL/PatternMatch.h"
|
|
#include "swift/SIL/Projection.h"
|
|
#include "swift/SIL/SILBuilder.h"
|
|
#include "swift/SIL/SILVisitor.h"
|
|
#include "swift/SILPasses/Utils/Local.h"
|
|
#include "llvm/ADT/SmallPtrSet.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/ADT/Statistic.h"
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
using namespace swift;
|
|
using namespace swift::PatternMatch;
|
|
|
|
STATISTIC(NumSimplified, "Number of instructions simplified");
|
|
STATISTIC(NumCombined, "Number of instructions combined");
|
|
STATISTIC(NumDeadInst, "Number of dead insts eliminated");
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// SILCombineWorklist
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace swift {
|
|
|
|
/// This is the worklist management logic for SILCombine.
|
|
class SILCombineWorklist {
|
|
llvm::SmallVector<SILInstruction *, 256> Worklist;
|
|
llvm::DenseMap<SILInstruction *, unsigned> WorklistMap;
|
|
llvm::SmallVector<SILInstruction *, 8> TrackingList;
|
|
|
|
void operator=(const SILCombineWorklist &RHS) = delete;
|
|
SILCombineWorklist(const SILCombineWorklist &Worklist) = delete;
|
|
public:
|
|
SILCombineWorklist() {}
|
|
|
|
/// Returns true if the worklist is empty.
|
|
bool isEmpty() const { return Worklist.empty(); }
|
|
|
|
/// Add the specified instruction to the worklist if it isn't already in it.
|
|
void add(SILInstruction *I) {
|
|
if (WorklistMap.insert(std::make_pair(I, Worklist.size())).second) {
|
|
DEBUG(llvm::dbgs() << "SC: ADD: " << *I << '\n');
|
|
Worklist.push_back(I);
|
|
}
|
|
}
|
|
|
|
/// If the given ValueBase is a SILInstruction add it to the worklist.
|
|
void addValue(ValueBase *V) {
|
|
if (SILInstruction *I = llvm::dyn_cast<SILInstruction>(V))
|
|
add(I);
|
|
}
|
|
|
|
/// Add the given list of instructions in reverse order to the worklist. This
|
|
/// routine assumes that the worklist is empty and the given list has no
|
|
/// duplicates.
|
|
void addInitialGroup(ArrayRef<SILInstruction *> List) {
|
|
assert(Worklist.empty() && "Worklist must be empty to add initial group");
|
|
Worklist.reserve(List.size()+16);
|
|
WorklistMap.resize(List.size());
|
|
DEBUG(llvm::dbgs() << "SC: ADDING: " << List.size()
|
|
<< " instrs to worklist\n");
|
|
while (!List.empty()) {
|
|
SILInstruction *I = List.back();
|
|
List = List.slice(0, List.size()-1);
|
|
|
|
WorklistMap.insert(std::make_pair(I, Worklist.size()));
|
|
Worklist.push_back(I);
|
|
}
|
|
}
|
|
|
|
// If I is in the worklist, remove it.
|
|
void remove(SILInstruction *I) {
|
|
auto It = WorklistMap.find(I);
|
|
if (It == WorklistMap.end())
|
|
return; // Not in worklist.
|
|
|
|
// Don't bother moving everything down, just null out the slot. We will
|
|
// check before we process any instruction if it is null.
|
|
Worklist[It->second] = 0;
|
|
|
|
WorklistMap.erase(It);
|
|
}
|
|
|
|
/// Remove the top element from the worklist.
|
|
SILInstruction *removeOne() {
|
|
SILInstruction *I = Worklist.pop_back_val();
|
|
WorklistMap.erase(I);
|
|
return I;
|
|
}
|
|
|
|
/// When an instruction has been simplified, add all of its users to the
|
|
/// worklist since additional simplifications of its users may have been
|
|
/// exposed.
|
|
void addUsersToWorklist(ValueBase *I) {
|
|
for (auto UI : I->getUses())
|
|
add(UI->getUser());
|
|
}
|
|
|
|
/// If only one result of an instruction has been simplified, add all of the
|
|
/// users of that result to the worklist since additional simplifications of
|
|
/// its users may have been exposed.
|
|
void addUsersToWorklist(ValueBase *I, unsigned Index) {
|
|
for (auto UI : SILValue(I, Index).getUses())
|
|
add(UI->getUser());
|
|
}
|
|
|
|
/// Check that the worklist is empty and nuke the backing store for the map if
|
|
/// it is large.
|
|
void zap() {
|
|
assert(WorklistMap.empty() && "Worklist empty, but the map is not?");
|
|
|
|
// Do an explicit clear, this shrinks the map if needed.
|
|
WorklistMap.clear();
|
|
}
|
|
};
|
|
|
|
} // end namespace swift
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// SILCombiner
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace swift {
|
|
|
|
/// This is a class which maintains the state of the combiner and simplifies
|
|
/// many operations such as removing/adding instructions and syncing them with
|
|
/// the worklist.
|
|
class SILCombiner :
|
|
public SILInstructionVisitor<SILCombiner, SILInstruction *> {
|
|
public:
|
|
SILCombiner(bool removeCondFails) : Worklist(), MadeChange(false),
|
|
RemoveCondFails(removeCondFails), Iteration(0), Builder(0) { }
|
|
|
|
bool runOnFunction(SILFunction &F) {
|
|
clear();
|
|
|
|
// Create a SILBuilder for F and initialize the tracking list.
|
|
SILBuilder B(F);
|
|
B.setTrackingList(&TrackingList);
|
|
Builder = &B;
|
|
|
|
bool Changed = false;
|
|
// Perform iterations until we do not make any changes.
|
|
while (doOneIteration(F, Iteration)) {
|
|
Changed = true;
|
|
Iteration++;
|
|
}
|
|
|
|
// Cleanup the builder and return whether or not we made any changes.
|
|
Builder = 0;
|
|
return Changed;
|
|
}
|
|
|
|
void clear() {
|
|
Iteration = 0;
|
|
Worklist.zap();
|
|
MadeChange = false;
|
|
}
|
|
|
|
// Insert the instruction New before instruction Old in Old's parent BB. Add
|
|
// New to the worklist.
|
|
SILInstruction *insertNewInstBefore(SILInstruction *New,
|
|
SILInstruction &Old) {
|
|
assert(New && New->getParent() == 0 &&
|
|
"New instruction already inserted into a basic block!");
|
|
SILBasicBlock *BB = Old.getParent();
|
|
BB->getInstList().insert(&Old, New); // Insert inst
|
|
Worklist.add(New);
|
|
return New;
|
|
}
|
|
|
|
// This method is to be used when an instruction is found to be dead,
|
|
// replacable with another preexisting expression. Here we add all uses of I
|
|
// to the worklist, replace all uses of I with the new value, then return I,
|
|
// so that the combiner will know that I was modified.
|
|
SILInstruction *replaceInstUsesWith(SILInstruction &I, ValueBase *V) {
|
|
Worklist.addUsersToWorklist(&I); // Add all modified instrs to worklist.
|
|
|
|
DEBUG(llvm::dbgs() << "SC: Replacing " << I << "\n"
|
|
" with " << *V << '\n');
|
|
|
|
I.replaceAllUsesWith(V);
|
|
|
|
return &I;
|
|
}
|
|
|
|
/// This is meant to be used when one is attempting to replace only one of the
|
|
/// results of I with a result of V.
|
|
SILInstruction *replaceInstUsesWith(SILInstruction &I, ValueBase *V,
|
|
unsigned IIndex, unsigned VIndex=0) {
|
|
assert(IIndex < I.getNumTypes() && "Can not have more results than "
|
|
"types.");
|
|
assert(VIndex < V->getNumTypes() && "Can not have more results than "
|
|
"types.");
|
|
|
|
// Add all modified instrs to worklist.
|
|
Worklist.addUsersToWorklist(&I, IIndex);
|
|
|
|
DEBUG(llvm::dbgs() << "SC: Replacing " << I << "\n"
|
|
" with " << *V << '\n');
|
|
|
|
SILValue(&I, IIndex).replaceAllUsesWith(SILValue(V, VIndex));
|
|
|
|
return &I;
|
|
}
|
|
|
|
// Some instructions can never be "trivially dead" due to side effects or
|
|
// producing a void value. In those cases, since we can not rely on
|
|
// SILCombines trivially dead instruction DCE in order to delete the
|
|
// instruction, visit methods should use this method to delete the given
|
|
// instruction and upon completion of their peephole return the value returned
|
|
// by this method.
|
|
SILInstruction *eraseInstFromFunction(SILInstruction &I) {
|
|
DEBUG(llvm::dbgs() << "SC: ERASE " << I << '\n');
|
|
|
|
assert(I.use_empty() && "Cannot erase instruction that is used!");
|
|
// Make sure that we reprocess all operands now that we reduced their
|
|
// use counts.
|
|
if (I.getNumOperands() < 8)
|
|
for (auto &OpI : I.getAllOperands())
|
|
if (SILInstruction *Op = llvm::dyn_cast<SILInstruction>(&*OpI.get()))
|
|
Worklist.add(Op);
|
|
|
|
Worklist.remove(&I);
|
|
I.eraseFromParent();
|
|
MadeChange = true;
|
|
return 0; // Don't do anything with I
|
|
}
|
|
|
|
void addInitialGroup(ArrayRef<SILInstruction *> List) {
|
|
Worklist.addInitialGroup(List);
|
|
}
|
|
|
|
/// Base visitor that does not do anything.
|
|
SILInstruction *visitValueBase(ValueBase *V) { return nullptr; }
|
|
SILInstruction *visitReleaseValueInst(ReleaseValueInst *DI);
|
|
SILInstruction *visitRetainValueInst(RetainValueInst *CI);
|
|
SILInstruction *visitPartialApplyInst(PartialApplyInst *AI);
|
|
SILInstruction *visitApplyInst(ApplyInst *AI);
|
|
SILInstruction *visitCondFailInst(CondFailInst *CFI);
|
|
SILInstruction *visitStrongRetainInst(StrongRetainInst *SRI);
|
|
SILInstruction *visitRefToRawPointerInst(RefToRawPointerInst *RRPI);
|
|
SILInstruction *visitUpcastInst(UpcastInst *UCI);
|
|
SILInstruction *visitLoadInst(LoadInst *LI);
|
|
SILInstruction *visitAllocStackInst(AllocStackInst *AS);
|
|
SILInstruction *visitSwitchEnumAddrInst(SwitchEnumAddrInst *SEAI);
|
|
SILInstruction *visitInjectEnumAddrInst(InjectEnumAddrInst *IEAI);
|
|
SILInstruction *visitPointerToAddressInst(PointerToAddressInst *PTAI);
|
|
SILInstruction *visitUncheckedAddrCastInst(UncheckedAddrCastInst *UADCI);
|
|
SILInstruction *visitUncheckedRefCastInst(UncheckedRefCastInst *URCI);
|
|
SILInstruction *visitRawPointerToRefInst(RawPointerToRefInst *RPTR);
|
|
|
|
|
|
private:
|
|
/// Perform one SILCombine iteration.
|
|
bool doOneIteration(SILFunction &F, unsigned Iteration);
|
|
|
|
/// Worklist containing all of the instructions primed for simplification.
|
|
SILCombineWorklist Worklist;
|
|
/// Variable to track if the SILCombiner made any changes.
|
|
bool MadeChange;
|
|
/// If set to true then the optimizer is free to erase cond_fail instructions.
|
|
bool RemoveCondFails;
|
|
/// The current iteration of the SILCombine.
|
|
unsigned Iteration;
|
|
/// Builder used to insert instructions.
|
|
SILBuilder *Builder;
|
|
/// A list that the builder inserts newly created instructions into. Its
|
|
/// contents are added to the worklist after every iteration and then the list
|
|
/// is cleared.
|
|
llvm::SmallVector<SILInstruction *, 64> TrackingList;
|
|
};
|
|
|
|
} // end namespace swift
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// SILCombine Implementation
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// addReachableCodeToWorklist - Walk the function in depth-first order, adding
|
|
/// all reachable code to the worklist.
|
|
///
|
|
/// This has a couple of tricks to make the code faster and more powerful. In
|
|
/// particular, we DCE instructions as we go, to avoid adding them to the
|
|
/// worklist (this significantly speeds up SILCombine on code where many
|
|
/// instructions are dead or constant).
|
|
static void addReachableCodeToWorklist(SILBasicBlock *BB, SILCombiner &SC) {
|
|
llvm::SmallVector<SILBasicBlock*, 256> Worklist;
|
|
llvm::SmallVector<SILInstruction*, 128> InstrsForSILCombineWorklist;
|
|
llvm::SmallPtrSet<SILBasicBlock*, 64> Visited;
|
|
|
|
Worklist.push_back(BB);
|
|
do {
|
|
BB = Worklist.pop_back_val();
|
|
|
|
// We have now visited this block! If we've already been here, ignore it.
|
|
if (!Visited.insert(BB)) continue;
|
|
|
|
for (SILBasicBlock::iterator BBI = BB->begin(), E = BB->end(); BBI != E; ) {
|
|
SILInstruction *Inst = BBI++;
|
|
|
|
// DCE instruction if trivially dead.
|
|
if (isInstructionTriviallyDead(Inst)) {
|
|
++NumDeadInst;
|
|
DEBUG(llvm::dbgs() << "SC: DCE: " << *Inst << '\n');
|
|
Inst->eraseFromParent();
|
|
continue;
|
|
}
|
|
|
|
InstrsForSILCombineWorklist.push_back(Inst);
|
|
}
|
|
|
|
// Recursively visit successors.
|
|
for (auto SI = BB->succ_begin(), SE = BB->succ_end(); SI != SE; ++SI)
|
|
Worklist.push_back(*SI);
|
|
} while (!Worklist.empty());
|
|
|
|
// Once we've found all of the instructions to add to the worklist, add them
|
|
// in reverse order. This way SILCombine will visit from the top of the
|
|
// function down. This jives well with the way that it adds all uses of
|
|
// instructions to the worklist after doing a transformation, thus avoiding
|
|
// some N^2 behavior in pathological cases.
|
|
SC.addInitialGroup(InstrsForSILCombineWorklist);
|
|
}
|
|
|
|
bool SILCombiner::doOneIteration(SILFunction &F, unsigned Iteration) {
|
|
MadeChange = false;
|
|
|
|
DEBUG(llvm::dbgs() << "\n\nSILCOMBINE ITERATION #" << Iteration << " on "
|
|
<< F.getName() << "\n");
|
|
|
|
// Add reachable instructions to our worklist.
|
|
addReachableCodeToWorklist(F.begin(), *this);
|
|
|
|
// Process until we run out of items in our worklist.
|
|
while (!Worklist.isEmpty()) {
|
|
SILInstruction *I = Worklist.removeOne();
|
|
|
|
// When we erase an instruction, we use the map in the worklist to check if
|
|
// the instruction is in the worklist. If it is, we replace it with null
|
|
// instead of shifting all members of the worklist towards the front. This
|
|
// check makes sure that if we run into any such residual null pointers, we
|
|
// skip them.
|
|
if (I == 0)
|
|
continue;
|
|
|
|
// Check to see if we can DCE the instruction.
|
|
if (isInstructionTriviallyDead(I)) {
|
|
DEBUG(llvm::dbgs() << "SC: DCE: " << *I << '\n');
|
|
eraseInstFromFunction(*I);
|
|
++NumDeadInst;
|
|
MadeChange = true;
|
|
continue;
|
|
}
|
|
|
|
// Check to see if we can instsimplify the instruction.
|
|
if (SILValue Result = simplifyInstruction(I)) {
|
|
++NumSimplified;
|
|
|
|
DEBUG(llvm::dbgs() << "SC: Simplify Old = " << *I << '\n'
|
|
<< " New = " << *Result.getDef() << '\n');
|
|
|
|
// Everything uses the new instruction now.
|
|
replaceInstUsesWith(*I, Result.getDef(), 0, Result.getResultNumber());
|
|
|
|
// Push the new instruction and any users onto the worklist.
|
|
Worklist.addUsersToWorklist(Result.getDef());
|
|
|
|
eraseInstFromFunction(*I);
|
|
MadeChange = true;
|
|
continue;
|
|
}
|
|
|
|
// If we have reached this point, all attempts to do simple simplifications
|
|
// have failed. Prepare to SILCombine.
|
|
Builder->setInsertionPoint(I->getParent(), I);
|
|
|
|
#ifndef NDEBUG
|
|
std::string OrigI;
|
|
#endif
|
|
DEBUG(llvm::raw_string_ostream SS(OrigI); I->print(SS); OrigI = SS.str(););
|
|
DEBUG(llvm::dbgs() << "SC: Visiting: " << OrigI << '\n');
|
|
|
|
if (SILInstruction *Result = visit(I)) {
|
|
++NumCombined;
|
|
// Should we replace the old instruction with a new one?
|
|
if (Result != I) {
|
|
// Insert the new instruction into the basic block.
|
|
I->getParent()->getInstList().insert(I, Result);
|
|
|
|
DEBUG(llvm::dbgs() << "SC: Old = " << *I << '\n'
|
|
<< " New = " << *Result << '\n');
|
|
|
|
// Everything uses the new instruction now.
|
|
replaceInstUsesWith(*I, Result);
|
|
|
|
// Push the new instruction and any users onto the worklist.
|
|
Worklist.add(Result);
|
|
Worklist.addUsersToWorklist(Result);
|
|
|
|
|
|
eraseInstFromFunction(*I);
|
|
} else {
|
|
DEBUG(llvm::dbgs() << "SC: Mod = " << OrigI << '\n'
|
|
<< " New = " << *I << '\n');
|
|
|
|
// If the instruction was modified, it's possible that it is now dead.
|
|
// if so, remove it.
|
|
if (isInstructionTriviallyDead(I)) {
|
|
eraseInstFromFunction(*I);
|
|
} else {
|
|
Worklist.add(I);
|
|
Worklist.addUsersToWorklist(I);
|
|
}
|
|
}
|
|
MadeChange = true;
|
|
}
|
|
|
|
// Our tracking list has been accumulating instructions created by the
|
|
// SILBuilder during this iteration. Go through the tracking list and add
|
|
// its contents to the worklist and then clear said list in preparation for
|
|
// the next iteration.
|
|
for (SILInstruction *I : TrackingList)
|
|
Worklist.add(I);
|
|
TrackingList.clear();
|
|
}
|
|
|
|
Worklist.zap();
|
|
return MadeChange;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Visitors
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
SILInstruction *SILCombiner::visitSwitchEnumAddrInst(SwitchEnumAddrInst *SEAI) {
|
|
// Promote switch_enum_addr to switch_enum. Detect the pattern:
|
|
//store %X to %Y#1 : $*Optional<SomeClass>
|
|
//switch_enum_addr %Y#1 : $*Optional<SomeClass>, case ...
|
|
if (SEAI == SEAI->getParent()->begin())
|
|
return nullptr;
|
|
|
|
SILBasicBlock::iterator it = SEAI;
|
|
if (StoreInst *SI = dyn_cast<StoreInst>(--it)) {
|
|
SILValue EnumVal = SI->getSrc();
|
|
|
|
// Make sure that the store destination and the switch address is the same
|
|
// address.
|
|
if (SI->getDest() != SEAI->getOperand())
|
|
return nullptr;
|
|
|
|
SmallVector<std::pair<EnumElementDecl*, SILBasicBlock*>, 8> Cases;
|
|
for (int i = 0, e = SEAI->getNumCases(); i < e; ++i)
|
|
Cases.push_back(SEAI->getCase(i));
|
|
|
|
SILBasicBlock *Default = SEAI->hasDefault() ? SEAI->getDefaultBB() : 0;
|
|
Builder->createSwitchEnum(SEAI->getLoc(), EnumVal, Default, Cases);
|
|
eraseInstFromFunction(*SEAI);
|
|
return nullptr;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SILInstruction *SILCombiner::visitAllocStackInst(AllocStackInst *AS) {
|
|
// init_existential instructions behave like memory allocation within
|
|
// the allocated object. We can promote the init_existential allocation
|
|
// into a dedicated allocation.
|
|
|
|
// Detect this pattern
|
|
// %0 = alloc_stack $LogicValue
|
|
// %1 = init_existential %0#1 : $*LogicValue, $*Bool
|
|
// ...
|
|
// use of %1
|
|
// ...
|
|
// destroy_addr %0#1 : $*LogicValue
|
|
// dealloc_stack %0#0 : $*@local_storage LogicValue
|
|
bool LegalUsers = true;
|
|
InitExistentialInst *IEI = nullptr;
|
|
// Scan all of the uses of the AllocStack and check if it is not used for
|
|
// anything other than the init_existential container.
|
|
for (Operand *Op: AS->getUses()) {
|
|
// Destroy and dealloc are both fine.
|
|
if (isa<DestroyAddrInst>(Op->getUser()) ||
|
|
isa<DeallocStackInst>(Op->getUser()))
|
|
continue;
|
|
|
|
// Make sure there is exactly one init_existential.
|
|
if (auto *I = dyn_cast<InitExistentialInst>(Op->getUser())) {
|
|
if (IEI) {
|
|
LegalUsers = false;
|
|
break;
|
|
}
|
|
IEI = I;
|
|
continue;
|
|
}
|
|
|
|
// All other instructions are illegal.
|
|
LegalUsers = false;
|
|
break;
|
|
}
|
|
|
|
// Save the original insertion point.
|
|
auto OrigInsertionPoint = Builder->getInsertionPoint();
|
|
|
|
// If the only users of the alloc_stack are alloc, destroy and
|
|
// init_existential then we can promote the allocation of the init
|
|
// existential.
|
|
if (LegalUsers && IEI) {
|
|
auto *ConcAlloc = Builder->createAllocStack(AS->getLoc(),
|
|
IEI->getConcreteType());
|
|
SILValue(IEI, 0).replaceAllUsesWith(ConcAlloc->getAddressResult());
|
|
eraseInstFromFunction(*IEI);
|
|
|
|
|
|
for (Operand *Op: AS->getUses()) {
|
|
if (auto *DA = dyn_cast<DestroyAddrInst>(Op->getUser())) {
|
|
Builder->setInsertionPoint(DA);
|
|
Builder->createDestroyAddr(DA->getLoc(), ConcAlloc);
|
|
eraseInstFromFunction(*DA);
|
|
|
|
}
|
|
if (auto *DS = dyn_cast<DeallocStackInst>(Op->getUser())) {
|
|
Builder->setInsertionPoint(DS);
|
|
Builder->createDeallocStack(DS->getLoc(), ConcAlloc);
|
|
eraseInstFromFunction(*DS);
|
|
}
|
|
}
|
|
|
|
eraseInstFromFunction(*AS);
|
|
// Restore the insertion point.
|
|
Builder->setInsertionPoint(OrigInsertionPoint);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SILInstruction *SILCombiner::visitLoadInst(LoadInst *LI) {
|
|
// (load (upcast-ptr %x)) -> (upcast-ref (load %x))
|
|
if (auto *UI = dyn_cast<UpcastInst>(LI->getOperand())) {
|
|
SILValue NewLI = Builder->createLoad(LI->getLoc(), UI->getOperand());
|
|
return new (UI->getModule()) UpcastInst(LI->getLoc(), NewLI,
|
|
LI->getType());
|
|
}
|
|
|
|
// Given a load with multiple struct_extracts/tuple_extracts and no other
|
|
// uses, canonicalize the load into several (struct_element_addr (load))
|
|
// pairs.
|
|
using ProjInstPairTy = std::pair<Projection, SILInstruction *>;
|
|
|
|
// Go through the loads uses and add any users that are projections to the
|
|
// projection list.
|
|
llvm::SmallVector<ProjInstPairTy, 8> Projections;
|
|
for (auto *UI : LI->getUses()) {
|
|
if (auto *SEI = dyn_cast<StructExtractInst>(UI->getUser())) {
|
|
Projections.push_back({Projection(SEI), SEI});
|
|
continue;
|
|
}
|
|
|
|
if (auto *TEI = dyn_cast<TupleExtractInst>(UI->getUser())) {
|
|
Projections.push_back({Projection(TEI), TEI});
|
|
continue;
|
|
}
|
|
|
|
// If we have any non SEI, TEI instruction, don't do anything here.
|
|
return nullptr;
|
|
}
|
|
|
|
// Sort the list.
|
|
std::sort(Projections.begin(), Projections.end());
|
|
|
|
// Go through our sorted list creating new GEPs only when we need to.
|
|
Projection *LastProj = nullptr;
|
|
LoadInst *LastNewLoad = nullptr;
|
|
for (auto &Pair : Projections) {
|
|
auto &Proj = Pair.first;
|
|
auto *Inst = Pair.second;
|
|
|
|
// If this projection is the same as the last projection we processed, just
|
|
// replace all uses of the projection with the load we created previously.
|
|
if (LastProj && Proj == *LastProj) {
|
|
replaceInstUsesWith(*Inst, LastNewLoad, 0);
|
|
eraseInstFromFunction(*Inst);
|
|
continue;
|
|
}
|
|
|
|
// Ok, we have started to visit the range of instructions associated with
|
|
// a new projection. If we have a VarDecl, create a struct_element_addr +
|
|
// load. Make sure to update LastProj, LastNewLoad.
|
|
if (auto *V = Proj.getDecl()) {
|
|
assert(isa<StructExtractInst>(Inst) && "A projection with a VarDecl "
|
|
"should be associated with a struct_extract.");
|
|
|
|
LastProj = &Proj;
|
|
auto *SEA =
|
|
Builder->createStructElementAddr(LI->getLoc(), LI->getOperand(), V,
|
|
Inst->getType(0).getAddressType());
|
|
LastNewLoad = Builder->createLoad(LI->getLoc(), SEA);
|
|
replaceInstUsesWith(*Inst, LastNewLoad, 0);
|
|
eraseInstFromFunction(*Inst);
|
|
continue;
|
|
}
|
|
|
|
// If we have an index, then create a new tuple_element_addr + load.
|
|
assert(isa<TupleExtractInst>(Inst) && "A projection with an integer "
|
|
"should be associated with a tuple_extract.");
|
|
|
|
LastProj = &Proj;
|
|
auto *TEA =
|
|
Builder->createTupleElementAddr(LI->getLoc(), LI->getOperand(),
|
|
Proj.getIndex(),
|
|
Inst->getType(0).getAddressType());
|
|
LastNewLoad = Builder->createLoad(LI->getLoc(), TEA);
|
|
replaceInstUsesWith(*Inst, LastNewLoad, 0);
|
|
eraseInstFromFunction(*Inst);
|
|
}
|
|
|
|
// Erase the old load.
|
|
return eraseInstFromFunction(*LI);
|
|
}
|
|
|
|
SILInstruction *SILCombiner::visitReleaseValueInst(ReleaseValueInst *DI) {
|
|
SILValue Operand = DI->getOperand();
|
|
SILType OperandTy = Operand.getType();
|
|
|
|
// Destroy value of an enum with a trivial payload or no-payload is a no-op.
|
|
if (auto *EI = dyn_cast<EnumInst>(Operand))
|
|
if (!EI->hasOperand() ||
|
|
EI->getOperand().getType().isTrivial(EI->getModule()))
|
|
return eraseInstFromFunction(*DI);
|
|
|
|
// ReleaseValueInst of a reference type is a strong_release.
|
|
if (OperandTy.hasReferenceSemantics())
|
|
return new (DI->getModule()) StrongReleaseInst(DI->getLoc(), Operand);
|
|
|
|
// ReleaseValueInst of a trivial type is a no-op.
|
|
if (OperandTy.isTrivial(DI->getModule()))
|
|
return eraseInstFromFunction(*DI);
|
|
|
|
// Do nothing for non-trivial non-reference types.
|
|
return nullptr;
|
|
}
|
|
|
|
SILInstruction *SILCombiner::visitRetainValueInst(RetainValueInst *CI) {
|
|
SILValue Operand = CI->getOperand();
|
|
SILType OperandTy = Operand.getType();
|
|
|
|
// retain_value of an enum with a trivial payload or no-payload is a no-op +
|
|
// RAUW.
|
|
if (auto *EI = dyn_cast<EnumInst>(Operand))
|
|
if (!EI->hasOperand() ||
|
|
EI->getOperand().getType().isTrivial(CI->getModule())) {
|
|
return eraseInstFromFunction(*CI);
|
|
}
|
|
|
|
// RetainValueInst of a reference type is a strong_release.
|
|
if (OperandTy.hasReferenceSemantics()) {
|
|
Builder->createStrongRetain(CI->getLoc(), Operand);
|
|
return eraseInstFromFunction(*CI);
|
|
}
|
|
|
|
// RetainValueInst of a trivial type is a no-op + use propogation.
|
|
if (OperandTy.isTrivial(CI->getModule())) {
|
|
return eraseInstFromFunction(*CI);
|
|
}
|
|
|
|
// Do nothing for non-trivial non-reference types.
|
|
return nullptr;
|
|
}
|
|
|
|
SILInstruction *SILCombiner::visitPartialApplyInst(PartialApplyInst *PAI) {
|
|
// Delete dead closures of this form:
|
|
//
|
|
// %X = partial_apply %x(...) // has 1 use.
|
|
// strong_release %X;
|
|
|
|
// Only handle PartialApplyInst with one use.
|
|
if (!PAI->hasOneUse())
|
|
return nullptr;
|
|
|
|
SILLocation Loc = PAI->getLoc();
|
|
|
|
// The single user must be the StrongReleaseInst.
|
|
if (auto *SRI = dyn_cast<StrongReleaseInst>(PAI->use_begin()->getUser())) {
|
|
SILFunctionType *ClosureTy =
|
|
dyn_cast<SILFunctionType>(PAI->getCallee().getType().getSwiftType());
|
|
if (!ClosureTy)
|
|
return nullptr;
|
|
|
|
// Emit a destroy value for each captured closure argument.
|
|
auto Params = ClosureTy->getInterfaceParameters();
|
|
auto Args = PAI->getArguments();
|
|
unsigned Delta = Params.size() - Args.size();
|
|
assert(Delta <= Params.size() && "Error, more Args to partial apply than "
|
|
"params in its interface.");
|
|
|
|
// Set the insertion point of the release_value to be that of the release,
|
|
// which is the end of the lifetime of the partial_apply.
|
|
auto OrigInsertPoint = Builder->getInsertionPoint();
|
|
SILInstruction *SingleUser = PAI->use_begin()->getUser();
|
|
Builder->setInsertionPoint(SingleUser);
|
|
|
|
for (unsigned AI = 0, AE = Args.size(); AI != AE; ++AI) {
|
|
SILValue Arg = Args[AI];
|
|
auto Param = Params[AI + Delta];
|
|
|
|
if (!Param.isIndirect() && Param.isConsumed())
|
|
if (!Arg.getType().isAddress())
|
|
Builder->createReleaseValue(Loc, Arg);
|
|
}
|
|
|
|
Builder->setInsertionPoint(OrigInsertPoint);
|
|
|
|
// Delete the strong_release.
|
|
eraseInstFromFunction(*SRI);
|
|
// Delete the partial_apply.
|
|
return eraseInstFromFunction(*PAI);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SILInstruction *SILCombiner::visitApplyInst(ApplyInst *AI) {
|
|
// Optimize apply{partial_apply(x,y)}(z) -> apply(z,x,y).
|
|
if (auto *PAI = dyn_cast<PartialApplyInst>(AI->getCallee())) {
|
|
// Don't handle generic applys.
|
|
if (AI->hasSubstitutions() || PAI->hasSubstitutions())
|
|
return nullptr;
|
|
|
|
FunctionRefInst *FRI = dyn_cast<FunctionRefInst>(PAI->getCallee());
|
|
if (!FRI)
|
|
return nullptr;
|
|
|
|
// Prepare the args.
|
|
SmallVector<SILValue, 8> Args;
|
|
// First the ApplyInst args.
|
|
for (auto Op : AI->getArguments())
|
|
Args.push_back(Op);
|
|
// Next, the partial apply args.
|
|
for (auto Op : PAI->getArguments())
|
|
Args.push_back(Op);
|
|
|
|
// The thunk that implements the partial apply calls the closure function
|
|
// that expects all arguments to be consumed by the function. However, the
|
|
// captured arguments are not arguments of *this* apply, so they are not
|
|
// pre-incremented. When we combine the partial_apply and this apply into
|
|
// a new apply we need to retain all of the closure non-address type
|
|
// arguments.
|
|
for (auto Arg : PAI->getArguments())
|
|
if (!Arg.getType().isAddress())
|
|
Builder->emitRetainValueOperation(PAI->getLoc(), Arg);
|
|
|
|
ApplyInst *NAI = Builder->createApply(AI->getLoc(), FRI, Args,
|
|
AI->isTransparent());
|
|
|
|
// We also need to release the partial_apply instruction itself because it
|
|
// is consumed by the apply_instruction.
|
|
Builder->createStrongRelease(AI->getLoc(), PAI);
|
|
|
|
replaceInstUsesWith(*AI, NAI);
|
|
return eraseInstFromFunction(*AI);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SILInstruction *SILCombiner::visitCondFailInst(CondFailInst *CFI) {
|
|
// Remove runtime asserts such as overflow checks and bounds checks.
|
|
if (RemoveCondFails)
|
|
return eraseInstFromFunction(*CFI);
|
|
|
|
// Erase. (cond_fail 0)
|
|
if (auto *I = dyn_cast<IntegerLiteralInst>(CFI->getOperand()))
|
|
if (!I->getValue().getBoolValue())
|
|
return eraseInstFromFunction(*CFI);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SILInstruction *SILCombiner::visitStrongRetainInst(StrongRetainInst *SRI) {
|
|
// Sometimes in the stdlib due to hand offs, we will see code like:
|
|
//
|
|
// strong_release %0
|
|
// strong_retain %0
|
|
//
|
|
// with the matching strong_retain to the strong_release in a predecessor
|
|
// basic block and the matching strong_release for the strong_retain in a
|
|
// successor basic block.
|
|
//
|
|
// Due to the matching pairs being in different basic blocks, the ARC
|
|
// Optimizer (which is currently local to one basic block does not handle
|
|
// it). But that does not mean that we can not eliminate this pair with a
|
|
// peephole.
|
|
|
|
// If we are not the first instruction in this basic block...
|
|
if (SRI != &*SRI->getParent()->begin()) {
|
|
SILBasicBlock::iterator Pred = SRI;
|
|
--Pred;
|
|
|
|
// ...and the predecessor instruction is a strong_release on the same value
|
|
// as our strong_retain...
|
|
if (StrongReleaseInst *Release = dyn_cast<StrongReleaseInst>(&*Pred))
|
|
// Remove them...
|
|
if (Release->getOperand() == SRI->getOperand()) {
|
|
eraseInstFromFunction(*Release);
|
|
return eraseInstFromFunction(*SRI);
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SILInstruction *
|
|
SILCombiner::visitRefToRawPointerInst(RefToRawPointerInst *RRPI) {
|
|
// Ref to raw pointer consumption of other ref casts.
|
|
//
|
|
// (ref_to_raw_pointer (unchecked_ref_cast x))
|
|
// -> (ref_to_raw_pointer x)
|
|
if (auto *ROPI = dyn_cast<UncheckedRefCastInst>(RRPI->getOperand())) {
|
|
RRPI->setOperand(ROPI->getOperand());
|
|
return ROPI->use_empty() ? eraseInstFromFunction(*ROPI) : nullptr;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
/// Simplify the following two frontend patterns:
|
|
///
|
|
/// %payload_addr = init_enum_data_addr %payload_allocation
|
|
/// store %payload to %payload_addr
|
|
/// inject_enum_addr %payload_allocation, $EnumType.case
|
|
///
|
|
/// inject_enum_add %nopayload_allocation, $EnumType.case
|
|
///
|
|
/// for a concrete enum type $EnumType.case to:
|
|
///
|
|
/// %1 = enum $EnumType, $EnumType.case, %payload
|
|
/// store %1 to %payload_addr
|
|
///
|
|
/// %1 = enum $EnumType, $EnumType.case
|
|
/// store %1 to %nopayload_addr
|
|
///
|
|
/// We leave the cleaning up to mem2reg.
|
|
SILInstruction *
|
|
SILCombiner::visitInjectEnumAddrInst(InjectEnumAddrInst *IEAI) {
|
|
// Given an inject_enum_addr of a concrete type without payload, promote it to
|
|
// a store of an enum. Mem2reg/load forwarding will clean things up for us. We
|
|
// can't handle the payload case here due to the flow problems caused by the
|
|
// dependency in between the enum and its data.
|
|
assert(IEAI->getOperand().getType().isAddress() && "Must be an address");
|
|
if (IEAI->getOperand().getType().isAddressOnly(IEAI->getModule()))
|
|
return nullptr;
|
|
|
|
// If the enum does not have a payload create the enum/store since we don't
|
|
// need to worry about payloads.
|
|
if (!IEAI->getElement()->hasArgumentType()) {
|
|
EnumInst *E =
|
|
Builder->createEnum(IEAI->getLoc(), SILValue(), IEAI->getElement(),
|
|
IEAI->getOperand().getType().getObjectType());
|
|
Builder->createStore(IEAI->getLoc(), E, IEAI->getOperand());
|
|
return eraseInstFromFunction(*IEAI);
|
|
}
|
|
|
|
// Ok, we have a payload enum, make sure that we have a store previous to
|
|
// us...
|
|
SILBasicBlock::iterator II = IEAI;
|
|
if (II == IEAI->getParent()->begin())
|
|
return nullptr;
|
|
--II;
|
|
auto *SI = dyn_cast<StoreInst>(&*II);
|
|
if (!SI)
|
|
return nullptr;
|
|
|
|
// ... whose destination is taken from an init_enum_data_addr whose only user
|
|
// is the store that points to the same allocation as our inject_enum_addr. We
|
|
// enforce such a strong condition as being directly previously since we want
|
|
// to avoid any flow issues.
|
|
auto *IEDAI = dyn_cast<InitEnumDataAddrInst>(SI->getDest().getDef());
|
|
if (!IEDAI || IEDAI->getOperand() != IEAI->getOperand() ||
|
|
!IEDAI->hasOneUse())
|
|
return nullptr;
|
|
|
|
// In that case, create the payload enum/store.
|
|
EnumInst *E =
|
|
Builder->createEnum(IEDAI->getLoc(), SI->getSrc(), IEDAI->getElement(),
|
|
IEDAI->getOperand().getType().getObjectType());
|
|
Builder->createStore(IEDAI->getLoc(), E, IEDAI->getOperand());
|
|
|
|
// Cleanup.
|
|
eraseInstFromFunction(*SI);
|
|
eraseInstFromFunction(*IEDAI);
|
|
return eraseInstFromFunction(*IEAI);
|
|
}
|
|
|
|
SILInstruction *SILCombiner::visitUpcastInst(UpcastInst *UCI) {
|
|
// Ref to raw pointer consumption of other ref casts.
|
|
//
|
|
// (upcast (upcast x)) -> (upcast x)
|
|
if (auto *Op = dyn_cast<UpcastInst>(UCI->getOperand())) {
|
|
UCI->setOperand(Op->getOperand());
|
|
return Op->use_empty() ? eraseInstFromFunction(*Op) : nullptr;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SILInstruction *
|
|
SILCombiner::
|
|
visitPointerToAddressInst(PointerToAddressInst *PTAI) {
|
|
// If we reach this point, we know that the types must be different since
|
|
// otherwise simplifyInstruction would have handled the identity case. This is
|
|
// always legal to do since address-to-pointer pointer-to-address implies
|
|
// layout compatibility.
|
|
//
|
|
// (pointer-to-address (address-to-pointer %x)) -> unchecked_
|
|
if (auto *ATPI = dyn_cast<AddressToPointerInst>(PTAI->getOperand())) {
|
|
return new (PTAI->getModule()) UncheckedAddrCastInst(PTAI->getLoc(),
|
|
ATPI->getOperand(),
|
|
PTAI->getType());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SILInstruction *
|
|
SILCombiner::visitUncheckedAddrCastInst(UncheckedAddrCastInst *UADCI) {
|
|
// (unchecked-addr-cast (unchecked-addr-cast x X->Y) Y->Z)
|
|
// ->
|
|
// (unchecked-addr-cast x X->Z)
|
|
if (auto *OtherUADCI = dyn_cast<UncheckedAddrCastInst>(UADCI->getOperand()))
|
|
return new (UADCI->getModule()) UncheckedAddrCastInst(
|
|
UADCI->getLoc(), OtherUADCI->getOperand(), UADCI->getType());
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SILInstruction *
|
|
SILCombiner::visitUncheckedRefCastInst(UncheckedRefCastInst *URCI) {
|
|
// (unchecked-ref-cast (unchecked-ref-cast x X->Y) Y->Z)
|
|
// ->
|
|
// (unchecked-ref-cast x X->Z)
|
|
if (auto *OtherURCI = dyn_cast<UncheckedRefCastInst>(URCI->getOperand()))
|
|
return new (URCI->getModule()) UncheckedRefCastInst(
|
|
URCI->getLoc(), OtherURCI->getOperand(), URCI->getType());
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SILInstruction *
|
|
SILCombiner::
|
|
visitRawPointerToRefInst(RawPointerToRefInst *RawToRef) {
|
|
// (raw_pointer_to_ref (ref_to_raw_pointer x X->Y) Y->Z)
|
|
// ->
|
|
// (unchecked_ref_cast X->Z)
|
|
if (auto *RefToRaw = dyn_cast<RefToRawPointerInst>(RawToRef->getOperand())) {
|
|
return new (RawToRef->getModule()) UncheckedRefCastInst(
|
|
RawToRef->getLoc(), RefToRaw->getOperand(), RawToRef->getType());
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Entry Points
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
class SILCombine : public SILFunctionTransform {
|
|
|
|
/// The entry point to the transformation.
|
|
void run() override {
|
|
SILCombiner Combiner(getOptions().RemoveRuntimeAsserts);
|
|
bool Changed = Combiner.runOnFunction(*getFunction());
|
|
if (Changed)
|
|
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
|
|
}
|
|
|
|
StringRef getName() override { return "SIL Combine"; }
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
SILTransform *swift::createSILCombine() {
|
|
return new SILCombine();
|
|
}
|