blob: 733ac41b8c7a15b8adddfd5ef633a93a8d2f4d99 [file] [log] [blame]
//===-- lib/CodeGen/GlobalISel/Combiner.cpp -------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file constains common code to combine machine functions at generic
// level.
//===----------------------------------------------------------------------===//
#include "llvm/CodeGen/GlobalISel/Combiner.h"
#include "llvm/ADT/PostOrderIterator.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/CodeGen/GlobalISel/CSEInfo.h"
#include "llvm/CodeGen/GlobalISel/CSEMIRBuilder.h"
#include "llvm/CodeGen/GlobalISel/CombinerInfo.h"
#include "llvm/CodeGen/GlobalISel/GISelChangeObserver.h"
#include "llvm/CodeGen/GlobalISel/GISelWorkList.h"
#include "llvm/CodeGen/GlobalISel/MachineIRBuilder.h"
#include "llvm/CodeGen/GlobalISel/Utils.h"
#include "llvm/CodeGen/MachineOptimizationRemarkEmitter.h"
#include "llvm/Support/Debug.h"
#define DEBUG_TYPE "gi-combiner"
using namespace llvm;
STATISTIC(NumOneIteration, "Number of functions with one iteration");
STATISTIC(NumTwoIterations, "Number of functions with two iterations");
STATISTIC(NumThreeOrMoreIterations,
"Number of functions with three or more iterations");
namespace llvm {
cl::OptionCategory GICombinerOptionCategory(
"GlobalISel Combiner",
"Control the rules which are enabled. These options all take a comma "
"separated list of rules to disable and may be specified by number "
"or number range (e.g. 1-10)."
#ifndef NDEBUG
" They may also be specified by name."
#endif
);
} // end namespace llvm
/// This class acts as the glue that joins the CombinerHelper to the overall
/// Combine algorithm. The CombinerHelper is intended to report the
/// modifications it makes to the MIR to the GISelChangeObserver and the
/// observer subclass will act on these events.
class Combiner::WorkListMaintainer : public GISelChangeObserver {
protected:
#ifndef NDEBUG
/// The instructions that have been created but we want to report once they
/// have their operands. This is only maintained if debug output is requested.
SmallSetVector<const MachineInstr *, 32> CreatedInstrs;
#endif
using Level = CombinerInfo::ObserverLevel;
public:
static std::unique_ptr<WorkListMaintainer>
create(Level Lvl, WorkListTy &WorkList, MachineRegisterInfo &MRI);
virtual ~WorkListMaintainer() = default;
void reportFullyCreatedInstrs() {
LLVM_DEBUG({
for (auto *MI : CreatedInstrs) {
dbgs() << "Created: " << *MI;
}
CreatedInstrs.clear();
});
}
virtual void reset() = 0;
virtual void appliedCombine() = 0;
};
/// A configurable WorkListMaintainer implementation.
/// The ObserverLevel determines how the WorkListMaintainer reacts to MIR
/// changes.
template <CombinerInfo::ObserverLevel Lvl>
class Combiner::WorkListMaintainerImpl : public Combiner::WorkListMaintainer {
WorkListTy &WorkList;
MachineRegisterInfo &MRI;
// Defer handling these instructions until the combine finishes.
SmallSetVector<MachineInstr *, 32> DeferList;
// Track VRegs that (might) have lost a use.
SmallSetVector<Register, 32> LostUses;
public:
WorkListMaintainerImpl(WorkListTy &WorkList, MachineRegisterInfo &MRI)
: WorkList(WorkList), MRI(MRI) {}
virtual ~WorkListMaintainerImpl() = default;
void reset() override {
DeferList.clear();
LostUses.clear();
}
void erasingInstr(MachineInstr &MI) override {
// MI will become dangling, remove it from all lists.
LLVM_DEBUG(dbgs() << "Erasing: " << MI; CreatedInstrs.remove(&MI));
WorkList.remove(&MI);
if constexpr (Lvl != Level::Basic) {
DeferList.remove(&MI);
noteLostUses(MI);
}
}
void createdInstr(MachineInstr &MI) override {
LLVM_DEBUG(dbgs() << "Creating: " << MI; CreatedInstrs.insert(&MI));
if constexpr (Lvl == Level::Basic)
WorkList.insert(&MI);
else
// Defer handling newly created instructions, because they don't have
// operands yet. We also insert them into the WorkList in reverse
// order so that they will be combined top down.
DeferList.insert(&MI);
}
void changingInstr(MachineInstr &MI) override {
LLVM_DEBUG(dbgs() << "Changing: " << MI);
// Some uses might get dropped when MI is changed.
// For now, overapproximate by assuming all uses will be dropped.
// TODO: Is a more precise heuristic or manual tracking of use count
// decrements worth it?
if constexpr (Lvl != Level::Basic)
noteLostUses(MI);
}
void changedInstr(MachineInstr &MI) override {
LLVM_DEBUG(dbgs() << "Changed: " << MI);
if constexpr (Lvl == Level::Basic)
WorkList.insert(&MI);
else
// Defer this for DCE
DeferList.insert(&MI);
}
// Only track changes during the combine and then walk the def/use-chains once
// the combine is finished, because:
// - instructions might have multiple defs during the combine.
// - use counts aren't accurate during the combine.
void appliedCombine() override {
if constexpr (Lvl == Level::Basic)
return;
// DCE deferred instructions and add them to the WorkList bottom up.
while (!DeferList.empty()) {
MachineInstr &MI = *DeferList.pop_back_val();
if (tryDCE(MI, MRI))
continue;
if constexpr (Lvl >= Level::SinglePass)
addUsersToWorkList(MI);
WorkList.insert(&MI);
}
// Handle instructions that have lost a user.
while (!LostUses.empty()) {
Register Use = LostUses.pop_back_val();
MachineInstr *UseMI = MRI.getVRegDef(Use);
if (!UseMI)
continue;
// If DCE succeeds, UseMI's uses are added back to LostUses by
// erasingInstr.
if (tryDCE(*UseMI, MRI))
continue;
if constexpr (Lvl >= Level::SinglePass) {
// OneUse checks are relatively common, so we might be able to combine
// the single remaining user of this Reg.
if (MRI.hasOneNonDBGUser(Use))
WorkList.insert(&*MRI.use_instr_nodbg_begin(Use));
WorkList.insert(UseMI);
}
}
}
void noteLostUses(MachineInstr &MI) {
for (auto &Use : MI.explicit_uses()) {
if (!Use.isReg() || !Use.getReg().isVirtual())
continue;
LostUses.insert(Use.getReg());
}
}
void addUsersToWorkList(MachineInstr &MI) {
for (auto &Def : MI.defs()) {
Register DefReg = Def.getReg();
if (!DefReg.isVirtual())
continue;
for (auto &UseMI : MRI.use_nodbg_instructions(DefReg)) {
WorkList.insert(&UseMI);
}
}
}
};
std::unique_ptr<Combiner::WorkListMaintainer>
Combiner::WorkListMaintainer::create(Level Lvl, WorkListTy &WorkList,
MachineRegisterInfo &MRI) {
switch (Lvl) {
case Level::Basic:
return std::make_unique<WorkListMaintainerImpl<Level::Basic>>(WorkList,
MRI);
case Level::DCE:
return std::make_unique<WorkListMaintainerImpl<Level::DCE>>(WorkList, MRI);
case Level::SinglePass:
return std::make_unique<WorkListMaintainerImpl<Level::SinglePass>>(WorkList,
MRI);
}
llvm_unreachable("Illegal ObserverLevel");
}
Combiner::Combiner(MachineFunction &MF, CombinerInfo &CInfo,
const TargetPassConfig *TPC, GISelValueTracking *VT,
GISelCSEInfo *CSEInfo)
: Builder(CSEInfo ? std::make_unique<CSEMIRBuilder>()
: std::make_unique<MachineIRBuilder>()),
WLObserver(WorkListMaintainer::create(CInfo.ObserverLvl, WorkList,
MF.getRegInfo())),
ObserverWrapper(std::make_unique<GISelObserverWrapper>()), CInfo(CInfo),
Observer(*ObserverWrapper), B(*Builder), MF(MF), MRI(MF.getRegInfo()),
VT(VT), TPC(TPC), CSEInfo(CSEInfo) {
(void)this->TPC; // FIXME: Remove when used.
// Setup builder.
B.setMF(MF);
if (CSEInfo)
B.setCSEInfo(CSEInfo);
B.setChangeObserver(*ObserverWrapper);
}
Combiner::~Combiner() = default;
bool Combiner::tryDCE(MachineInstr &MI, MachineRegisterInfo &MRI) {
if (!isTriviallyDead(MI, MRI))
return false;
LLVM_DEBUG(dbgs() << "Dead: " << MI);
llvm::salvageDebugInfo(MRI, MI);
MI.eraseFromParent();
return true;
}
bool Combiner::combineMachineInstrs() {
// If the ISel pipeline failed, do not bother running this pass.
// FIXME: Should this be here or in individual combiner passes.
if (MF.getProperties().hasProperty(
MachineFunctionProperties::Property::FailedISel))
return false;
// We can't call this in the constructor because the derived class is
// uninitialized at that time.
if (!HasSetupMF) {
HasSetupMF = true;
setupMF(MF, VT);
}
LLVM_DEBUG(dbgs() << "Generic MI Combiner for: " << MF.getName() << '\n');
MachineOptimizationRemarkEmitter MORE(MF, /*MBFI=*/nullptr);
bool MFChanged = false;
bool Changed;
unsigned Iteration = 0;
while (true) {
++Iteration;
LLVM_DEBUG(dbgs() << "\n\nCombiner iteration #" << Iteration << '\n');
Changed = false;
WorkList.clear();
WLObserver->reset();
ObserverWrapper->clearObservers();
if (CSEInfo)
ObserverWrapper->addObserver(CSEInfo);
// If Observer-based DCE is enabled, perform full DCE only before the first
// iteration.
bool EnableDCE = CInfo.ObserverLvl >= CombinerInfo::ObserverLevel::DCE
? CInfo.EnableFullDCE && Iteration == 1
: CInfo.EnableFullDCE;
// Collect all instructions. Do a post order traversal for basic blocks and
// insert with list bottom up, so while we pop_back_val, we'll traverse top
// down RPOT.
RAIIMFObsDelInstaller DelInstall(MF, *ObserverWrapper);
for (MachineBasicBlock *MBB : post_order(&MF)) {
for (MachineInstr &CurMI :
llvm::make_early_inc_range(llvm::reverse(*MBB))) {
// Erase dead insts before even adding to the list.
if (EnableDCE && tryDCE(CurMI, MRI))
continue;
WorkList.deferred_insert(&CurMI);
}
}
WorkList.finalize();
// Only notify WLObserver during actual combines
ObserverWrapper->addObserver(WLObserver.get());
// Main Loop. Process the instructions here.
while (!WorkList.empty()) {
MachineInstr &CurrInst = *WorkList.pop_back_val();
LLVM_DEBUG(dbgs() << "\nTry combining " << CurrInst);
bool AppliedCombine = tryCombineAll(CurrInst);
LLVM_DEBUG(WLObserver->reportFullyCreatedInstrs());
Changed |= AppliedCombine;
if (AppliedCombine)
WLObserver->appliedCombine();
}
MFChanged |= Changed;
if (!Changed) {
LLVM_DEBUG(dbgs() << "\nCombiner reached fixed-point after iteration #"
<< Iteration << '\n');
break;
}
// Iterate until a fixed-point is reached if MaxIterations == 0,
// otherwise limit the number of iterations.
if (CInfo.MaxIterations && Iteration >= CInfo.MaxIterations) {
LLVM_DEBUG(
dbgs() << "\nCombiner reached iteration limit after iteration #"
<< Iteration << '\n');
break;
}
}
if (Iteration == 1)
++NumOneIteration;
else if (Iteration == 2)
++NumTwoIterations;
else
++NumThreeOrMoreIterations;
#ifndef NDEBUG
if (CSEInfo) {
if (auto E = CSEInfo->verify()) {
errs() << E << '\n';
assert(false && "CSEInfo is not consistent. Likely missing calls to "
"observer on mutations.");
}
}
#endif
return MFChanged;
}