#include "llvm/CodeGen/GlobalISel/Legalizer.h"
#include "llvm/ADT/PostOrderIterator.h"
#include "llvm/Analysis/OptimizationRemarkEmitter.h"
#include "llvm/CodeGen/GlobalISel/CSEInfo.h"
#include "llvm/CodeGen/GlobalISel/CSEMIRBuilder.h"
#include "llvm/CodeGen/GlobalISel/GISelChangeObserver.h"
#include "llvm/CodeGen/GlobalISel/GISelWorkList.h"
#include "llvm/CodeGen/GlobalISel/LegalizationArtifactCombiner.h"
#include "llvm/CodeGen/GlobalISel/LegalizerHelper.h"
#include "llvm/CodeGen/GlobalISel/LostDebugLocObserver.h"
#include "llvm/CodeGen/GlobalISel/Utils.h"
#include "llvm/CodeGen/MachineOptimizationRemarkEmitter.h"
#include "llvm/CodeGen/TargetPassConfig.h"
#include "llvm/CodeGen/TargetSubtargetInfo.h"
#include "llvm/InitializePasses.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Error.h"
#define DEBUG_TYPE "legalizer"
using namespace llvm;
static cl::opt<bool>
EnableCSEInLegalizer("enable-cse-in-legalizer",
cl::desc("Should enable CSE in Legalizer"),
cl::Optional, cl::init(false));
static cl::opt<bool> AllowGInsertAsArtifact(
"allow-ginsert-as-artifact",
cl::desc("Allow G_INSERT to be considered an artifact. Hack around AMDGPU "
"test infinite loops."),
cl::Optional, cl::init(true));
enum class DebugLocVerifyLevel {
None,
Legalizations,
LegalizationsAndArtifactCombiners,
};
#ifndef NDEBUG
static cl::opt<DebugLocVerifyLevel> VerifyDebugLocs(
"verify-legalizer-debug-locs",
cl::desc("Verify that debug locations are handled"),
cl::values(
clEnumValN(DebugLocVerifyLevel::None, "none", "No verification"),
clEnumValN(DebugLocVerifyLevel::Legalizations, "legalizations",
"Verify legalizations"),
clEnumValN(DebugLocVerifyLevel::LegalizationsAndArtifactCombiners,
"legalizations+artifactcombiners",
"Verify legalizations and artifact combines")),
cl::init(DebugLocVerifyLevel::Legalizations));
#else
static const DebugLocVerifyLevel VerifyDebugLocs = DebugLocVerifyLevel::None;
#endif
char Legalizer::ID = 0;
INITIALIZE_PASS_BEGIN(Legalizer, DEBUG_TYPE,
"Legalize the Machine IR a function's Machine IR", false,
false)
INITIALIZE_PASS_DEPENDENCY(TargetPassConfig)
INITIALIZE_PASS_DEPENDENCY(GISelCSEAnalysisWrapperPass)
INITIALIZE_PASS_END(Legalizer, DEBUG_TYPE,
"Legalize the Machine IR a function's Machine IR", false,
false)
Legalizer::Legalizer() : MachineFunctionPass(ID) { }
void Legalizer::getAnalysisUsage(AnalysisUsage &AU) const {
AU.addRequired<TargetPassConfig>();
AU.addRequired<GISelCSEAnalysisWrapperPass>();
AU.addPreserved<GISelCSEAnalysisWrapperPass>();
getSelectionDAGFallbackAnalysisUsage(AU);
MachineFunctionPass::getAnalysisUsage(AU);
}
void Legalizer::init(MachineFunction &MF) {
}
static bool isArtifact(const MachineInstr &MI) {
switch (MI.getOpcode()) {
default:
return false;
case TargetOpcode::G_TRUNC:
case TargetOpcode::G_ZEXT:
case TargetOpcode::G_ANYEXT:
case TargetOpcode::G_SEXT:
case TargetOpcode::G_MERGE_VALUES:
case TargetOpcode::G_UNMERGE_VALUES:
case TargetOpcode::G_CONCAT_VECTORS:
case TargetOpcode::G_BUILD_VECTOR:
case TargetOpcode::G_EXTRACT:
return true;
case TargetOpcode::G_INSERT:
return AllowGInsertAsArtifact;
}
}
using InstListTy = GISelWorkList<256>;
using ArtifactListTy = GISelWorkList<128>;
namespace {
class LegalizerWorkListManager : public GISelChangeObserver {
InstListTy &InstList;
ArtifactListTy &ArtifactList;
#ifndef NDEBUG
SmallVector<MachineInstr *, 4> NewMIs;
#endif
public:
LegalizerWorkListManager(InstListTy &Insts, ArtifactListTy &Arts)
: InstList(Insts), ArtifactList(Arts) {}
void createdOrChangedInstr(MachineInstr &MI) {
if (isPreISelGenericOpcode(MI.getOpcode())) {
if (isArtifact(MI))
ArtifactList.insert(&MI);
else
InstList.insert(&MI);
}
}
void createdInstr(MachineInstr &MI) override {
LLVM_DEBUG(NewMIs.push_back(&MI));
createdOrChangedInstr(MI);
}
void printNewInstrs() {
LLVM_DEBUG({
for (const auto *MI : NewMIs)
dbgs() << ".. .. New MI: " << *MI;
NewMIs.clear();
});
}
void erasingInstr(MachineInstr &MI) override {
LLVM_DEBUG(dbgs() << ".. .. Erasing: " << MI);
InstList.remove(&MI);
ArtifactList.remove(&MI);
}
void changingInstr(MachineInstr &MI) override {
LLVM_DEBUG(dbgs() << ".. .. Changing MI: " << MI);
}
void changedInstr(MachineInstr &MI) override {
LLVM_DEBUG(dbgs() << ".. .. Changed MI: " << MI);
createdOrChangedInstr(MI);
}
};
}
Legalizer::MFResult
Legalizer::legalizeMachineFunction(MachineFunction &MF, const LegalizerInfo &LI,
ArrayRef<GISelChangeObserver *> AuxObservers,
LostDebugLocObserver &LocObserver,
MachineIRBuilder &MIRBuilder) {
MIRBuilder.setMF(MF);
MachineRegisterInfo &MRI = MF.getRegInfo();
InstListTy InstList;
ArtifactListTy ArtifactList;
ReversePostOrderTraversal<MachineFunction *> RPOT(&MF);
for (auto *MBB : RPOT) {
if (MBB->empty())
continue;
for (MachineInstr &MI : *MBB) {
if (!isPreISelGenericOpcode(MI.getOpcode()))
continue;
if (isArtifact(MI))
ArtifactList.deferred_insert(&MI);
else
InstList.deferred_insert(&MI);
}
}
ArtifactList.finalize();
InstList.finalize();
LegalizerWorkListManager WorkListObserver(InstList, ArtifactList);
GISelObserverWrapper WrapperObserver(&WorkListObserver);
for (GISelChangeObserver *Observer : AuxObservers)
WrapperObserver.addObserver(Observer);
RAIIMFObsDelInstaller Installer(MF, WrapperObserver);
LegalizerHelper Helper(MF, LI, WrapperObserver, MIRBuilder);
LegalizationArtifactCombiner ArtCombiner(MIRBuilder, MRI, LI);
bool Changed = false;
SmallVector<MachineInstr *, 128> RetryList;
do {
LLVM_DEBUG(dbgs() << "=== New Iteration ===\n");
assert(RetryList.empty() && "Expected no instructions in RetryList");
unsigned NumArtifacts = ArtifactList.size();
while (!InstList.empty()) {
MachineInstr &MI = *InstList.pop_back_val();
assert(isPreISelGenericOpcode(MI.getOpcode()) &&
"Expecting generic opcode");
if (isTriviallyDead(MI, MRI)) {
eraseInstr(MI, MRI, &LocObserver);
continue;
}
auto Res = Helper.legalizeInstrStep(MI, LocObserver);
if (Res == LegalizerHelper::UnableToLegalize) {
if (isArtifact(MI)) {
LLVM_DEBUG(dbgs() << ".. Not legalized, moving to artifacts retry\n");
assert(NumArtifacts == 0 &&
"Artifacts are only expected in instruction list starting the "
"second iteration, but each iteration starting second must "
"start with an empty artifacts list");
(void)NumArtifacts;
RetryList.push_back(&MI);
continue;
}
Helper.MIRBuilder.stopObservingChanges();
return {Changed, &MI};
}
WorkListObserver.printNewInstrs();
LocObserver.checkpoint();
Changed |= Res == LegalizerHelper::Legalized;
}
if (!RetryList.empty()) {
if (!ArtifactList.empty()) {
while (!RetryList.empty())
ArtifactList.insert(RetryList.pop_back_val());
} else {
LLVM_DEBUG(dbgs() << "No new artifacts created, not retrying!\n");
Helper.MIRBuilder.stopObservingChanges();
return {Changed, RetryList.front()};
}
}
LocObserver.checkpoint();
while (!ArtifactList.empty()) {
MachineInstr &MI = *ArtifactList.pop_back_val();
assert(isPreISelGenericOpcode(MI.getOpcode()) &&
"Expecting generic opcode");
if (isTriviallyDead(MI, MRI)) {
eraseInstr(MI, MRI, &LocObserver);
continue;
}
SmallVector<MachineInstr *, 4> DeadInstructions;
LLVM_DEBUG(dbgs() << "Trying to combine: " << MI);
if (ArtCombiner.tryCombineInstruction(MI, DeadInstructions,
WrapperObserver)) {
WorkListObserver.printNewInstrs();
eraseInstrs(DeadInstructions, MRI, &LocObserver);
LocObserver.checkpoint(
VerifyDebugLocs ==
DebugLocVerifyLevel::LegalizationsAndArtifactCombiners);
Changed = true;
continue;
}
else {
LLVM_DEBUG(dbgs() << ".. Not combined, moving to instructions list\n");
InstList.insert(&MI);
}
}
} while (!InstList.empty());
return {Changed, nullptr};
}
bool Legalizer::runOnMachineFunction(MachineFunction &MF) {
if (MF.getProperties().hasProperty(
MachineFunctionProperties::Property::FailedISel))
return false;
LLVM_DEBUG(dbgs() << "Legalize Machine IR for: " << MF.getName() << '\n');
init(MF);
const TargetPassConfig &TPC = getAnalysis<TargetPassConfig>();
GISelCSEAnalysisWrapper &Wrapper =
getAnalysis<GISelCSEAnalysisWrapperPass>().getCSEWrapper();
MachineOptimizationRemarkEmitter MORE(MF, nullptr);
const size_t NumBlocks = MF.size();
std::unique_ptr<MachineIRBuilder> MIRBuilder;
GISelCSEInfo *CSEInfo = nullptr;
bool EnableCSE = EnableCSEInLegalizer.getNumOccurrences()
? EnableCSEInLegalizer
: TPC.isGISelCSEEnabled();
if (EnableCSE) {
MIRBuilder = std::make_unique<CSEMIRBuilder>();
CSEInfo = &Wrapper.get(TPC.getCSEConfig());
MIRBuilder->setCSEInfo(CSEInfo);
} else
MIRBuilder = std::make_unique<MachineIRBuilder>();
SmallVector<GISelChangeObserver *, 1> AuxObservers;
if (EnableCSE && CSEInfo) {
AuxObservers.push_back(CSEInfo);
}
assert(!CSEInfo || !errorToBool(CSEInfo->verify()));
LostDebugLocObserver LocObserver(DEBUG_TYPE);
if (VerifyDebugLocs > DebugLocVerifyLevel::None)
AuxObservers.push_back(&LocObserver);
const LegalizerInfo &LI = *MF.getSubtarget().getLegalizerInfo();
MFResult Result =
legalizeMachineFunction(MF, LI, AuxObservers, LocObserver, *MIRBuilder);
if (Result.FailedOn) {
reportGISelFailure(MF, TPC, MORE, "gisel-legalize",
"unable to legalize instruction", *Result.FailedOn);
return false;
}
if (MF.size() != NumBlocks) {
MachineOptimizationRemarkMissed R("gisel-legalize", "GISelFailure",
MF.getFunction().getSubprogram(),
nullptr);
R << "inserting blocks is not supported yet";
reportGISelFailure(MF, TPC, MORE, R);
return false;
}
if (LocObserver.getNumLostDebugLocs()) {
MachineOptimizationRemarkMissed R("gisel-legalize", "LostDebugLoc",
MF.getFunction().getSubprogram(),
&*MF.begin());
R << "lost "
<< ore::NV("NumLostDebugLocs", LocObserver.getNumLostDebugLocs())
<< " debug locations during pass";
reportGISelWarning(MF, TPC, MORE, R);
}
if (!EnableCSE)
Wrapper.setComputed(false);
return Result.Changed;
}