#include <cassert>
#include <utility>
#include <functional>
#include <iterator>
#include <vector>
#include <ranges>
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Casting.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/IR/Value.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/PatternMatch.h"
#include "llvm/IR/Dominators.h"
#include "llvm/Analysis/AliasAnalysis.h"
#include "llvm/Analysis/AliasSetTracker.h"
#include "llvm/Analysis/ValueTracking.h"
#include "llvm/Transforms/UnitLICM.h"
#include "llvm/Analysis/UnitLoopInfo.h"
#define DEBUG_TYPE "unit-licm"
STATISTIC(NumInstsHoisted, "Number of instructions hoisted");
STATISTIC(NumStoresHoisted, "Number of stores hoisted");
STATISTIC(NumLoadsHoisted, "Number of loads hoisted");
STATISTIC(NumComputationalHoisted, "Number of computational instructions (excluding casts and GEPs) hoisted");
STATISTIC(NumVisited, "Number of instructions considered for hoisting");
using namespace llvm;
using namespace ece479k;
static bool is_supported(Instruction const&) __attribute((pure));
static bool is_computational(Instruction const&) __attribute((pure));
namespace {
struct HoistArgs {
UnitLoopInfo const& loop;
DenseMap<Instruction const*, bool> cache;
DominatorTree const& DT;
};
}
static bool can_hoist(Instruction const&, HoistArgs &) __attribute((pure));
static void hoist(Instruction&, HoistArgs &);
PreservedAnalyses UnitLICM::run(Function &F, FunctionAnalysisManager &FAM) {
for (auto const& loop: FAM.getResult<UnitLoopAnalysis>(F)) {
auto hoist_args = HoistArgs{
loop,
DenseMap<Instruction const*, bool>(32),
FAM.getResult<DominatorTreeAnalysis>(F)
};
hoist_args.cache.clear();
auto const is_hoistable = [&loop, &hoist_args](Instruction const& inst) {
return is_supported(inst) && can_hoist(inst, hoist_args);
};
for (auto i: loop.instructions(is_hoistable))
{
hoist(*i, hoist_args);
if (isa<LoadInst>(i))
++NumLoadsHoisted;
else if (isa<StoreInst>(i))
++NumStoresHoisted;
else if (is_computational(*i))
++NumComputationalHoisted;
++NumInstsHoisted;
}
}
return PreservedAnalyses::none();
}
static bool is_supported(Instruction const& inst)
{
switch (inst.getOpcode()) {
default:
return false;
case Instruction::FNeg:
case Instruction::Add:
case Instruction::FAdd:
case Instruction::Sub:
case Instruction::FSub:
case Instruction::Mul:
case Instruction::FMul:
case Instruction::UDiv:
case Instruction::SDiv:
case Instruction::FDiv:
case Instruction::URem:
case Instruction::SRem:
case Instruction::FRem:
case Instruction::Shl:
case Instruction::LShr:
case Instruction::AShr:
case Instruction::And:
case Instruction::Or:
case Instruction::Xor:
case Instruction::Load:
case Instruction::Store:
case Instruction::GetElementPtr:
case Instruction::Trunc:
case Instruction::ZExt:
case Instruction::SExt:
case Instruction::FPToUI:
case Instruction::FPToSI:
case Instruction::UIToFP:
case Instruction::SIToFP:
case Instruction::FPTrunc:
case Instruction::FPExt:
case Instruction::PtrToInt:
case Instruction::IntToPtr:
case Instruction::BitCast:
case Instruction::AddrSpaceCast:
case Instruction::Select:
case Instruction::ICmp:
case Instruction::FCmp:
return true;
}
}
static bool is_computational(Instruction const& inst)
{
switch (inst.getOpcode()) {
default:
return false;
case Instruction::FNeg:
case Instruction::Add:
case Instruction::FAdd:
case Instruction::Sub:
case Instruction::FSub:
case Instruction::Mul:
case Instruction::FMul:
case Instruction::UDiv:
case Instruction::SDiv:
case Instruction::FDiv:
case Instruction::URem:
case Instruction::SRem:
case Instruction::FRem:
case Instruction::Shl:
case Instruction::LShr:
case Instruction::AShr:
case Instruction::And:
case Instruction::Or:
case Instruction::Xor:
case Instruction::Select:
case Instruction::ICmp:
case Instruction::FCmp:
return true;
}
}
static void hoist(Instruction& inst, HoistArgs &args)
{
assert(!args.loop.is_invariant(inst) && "Cannot hoist variant");
auto* insertion_point = const_cast<Instruction *>(args.loop.preheader->getTerminator());
inst.moveBefore(insertion_point);
assert(args.loop.is_invariant(inst) && "Hoist did not make invariant");
}
static bool can_hoist_impl(Instruction const&, HoistArgs &) __attribute((pure));
static bool can_hoist(Value const&, HoistArgs &) __attribute((pure));
static bool can_hoist_udiv(Instruction const&, HoistArgs &) __attribute((pure));
static bool can_hoist_sdiv(Instruction const&, HoistArgs &) __attribute((pure));
static bool can_hoist_store(Instruction const&, HoistArgs &) __attribute((pure));
static bool can_hoist_load(Instruction const&, HoistArgs &) __attribute((pure));
static bool can_hoist_load_impl(Instruction const&, HoistArgs &) __attribute((pure));
static bool can_hoist_sidefx(Instruction const&, HoistArgs&) __attribute((pure));
static bool can_hoist_operands(Instruction const&, HoistArgs &) __attribute((pure));
static bool can_hoist(Instruction const& inst, HoistArgs &args)
{
if (args.loop.is_invariant(inst))
return true;
if (!is_supported(inst)) {
LLVM_DEBUG(dbgs() << "UnitLICM: Can't hoist instruction: Depends on unsupported " << inst.getOpcodeName() << " `" << inst.getName() << "`: ");
return false;
}
auto const it = args.cache.find(&inst);
if (it != args.cache.end()) {
LLVM_DEBUG(if (!it->second)
dbgs() << "UnitLICM: Can't hoist instruction: Depends on not-hoistable " << inst.getOpcodeName() << " `" << inst.getName() << "`: ");
return it->second;
}
bool const dfs = can_hoist_impl(inst, args);
args.cache.insert({&inst, dfs});
return dfs;
}
static bool can_hoist(Value const& val, HoistArgs &args)
{
if (auto const* inst = dyn_cast<Instruction>(&val))
return can_hoist(*inst, args);
return true;
}
static bool can_hoist_impl(Instruction const& inst, HoistArgs &args)
{
assert(is_supported(inst) && "Instruction is not supported");
++NumVisited;
switch (inst.getOpcode()) {
default: return can_hoist_operands(inst, args);
case Instruction::Load: return can_hoist_load(inst, args);
case Instruction::Store: return can_hoist_store(inst, args);
case Instruction::UDiv:
case Instruction::URem: return can_hoist_udiv(inst, args);
case Instruction::SDiv:
case Instruction::SRem: return can_hoist_sdiv(inst, args);
}
}
static bool can_hoist_sdiv(Instruction const& inst, HoistArgs &args)
{
using namespace llvm::PatternMatch;
const APInt *Numerator, *Denominator;
if (!match(inst.getOperand(1), m_APInt(Denominator))) {
return can_hoist_sidefx(inst, args);
}
if (*Denominator == 0) {
return can_hoist_sidefx(inst, args);
}
if (!Denominator->isAllOnes()) {
return can_hoist_operands(inst, args);
}
if (match(inst.getOperand(0), m_APInt(Numerator))) {
if (Numerator->isMinSignedValue()) {
return can_hoist_sidefx(inst, args);
}
return can_hoist_operands(inst, args);
}
return can_hoist_sidefx(inst, args);
}
static bool can_hoist_udiv(Instruction const& inst, HoistArgs &args)
{
using namespace llvm::PatternMatch;
const APInt *V;
if (match(inst.getOperand(1), m_APInt(V))) {
if (!*V) {
return can_hoist_sidefx(inst, args);
}
return can_hoist_operands(inst, args);
}
return false;
}
static bool can_hoist_load(Instruction const& inst, HoistArgs &args)
{
const auto *LI = dyn_cast<LoadInst>(&inst);
if (!LI) {
LLVM_DEBUG(dbgs() << "UnitLICM: Invalid instruction (expected load): " << inst << "\n");
return false;
}
if (mustSuppressSpeculation(*LI)) {
LLVM_DEBUG(dbgs() << "UnitLICM: Can't hoist load: Suppressed by sanitizer:" << inst << "\n");
return false;
}
return can_hoist_load_impl(inst, args);
}
static bool can_hoist_load_impl(Instruction const& inst, HoistArgs &args)
{
auto const *LI = dyn_cast<LoadInst>(&inst);
if (!LI) {
LLVM_DEBUG(dbgs() << "UnitLICM: Invalid instruction (expected load): " << inst << "\n");
return false;
}
auto const& aa = args.loop.alias_for(LI);
if (aa.isMod()) {
LLVM_DEBUG(dbgs() << "UnitLICM: Can't hoist load: Load aliases modified memory:" << inst << "\n");
return false;
}
return can_hoist_operands(inst, args);
}
static bool can_hoist_store(Instruction const& inst, HoistArgs &args)
{
auto const *SI = dyn_cast<StoreInst>(&inst);
if (!SI) {
LLVM_DEBUG(dbgs() << "UnitLICM: Invalid instruction (expected store):\t" << inst << "\n");
return false;
}
if (SI->isVolatile())
return false;
auto const& aa = args.loop.alias_for(SI);
if (!aa.isMustAlias()) {
LLVM_DEBUG(dbgs() << "UnitLICM: Can't hoist store: Store does not always write the same memory:" << inst << "\n");
return false;
}
if (aa.isRef()) {
LLVM_DEBUG(dbgs() << "UnitLICM: Can't hoist store: Store aliases read memory:" << inst << "\n");
return false;
}
return can_hoist_sidefx(inst, args);
}
static bool can_hoist_sidefx(Instruction const& inst, HoistArgs& args)
{
if (!all_of(args.loop.exits, [&args, &inst](BasicBlock const* exit) {
return args.DT.dominates(&inst, exit);
})) {
LLVM_DEBUG(dbgs() << "UnitLICM: Can't hoist inst w/ sidefx: Does not dominate all exits:" << inst << "\n");
return false;
}
return can_hoist_operands(inst, args);
}
static bool can_hoist_operands(Instruction const& inst, HoistArgs &args)
{
return std::ranges::all_of(inst.operands(), [&args, &inst](Use const& V) {
bool const r = can_hoist(*V.get(), args);
LLVM_DEBUG(if (!r) dbgs() << inst << "\n");
return r;
});
}