blob: 9650ee9e72dabefb7f2324e7ca82dd41ce369c4b [file] [edit]
// Copyright 2023 The Clspv Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "LowerAddrSpaceCastPass.h"
#include "BitcastUtils.h"
#include "Builtins.h"
#include "Constants.h"
#include "Types.h"
#include "clspv/AddressSpace.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Transforms/Utils/Local.h"
using namespace llvm;
#define DEBUG_TYPE "LowerAddrSpaceCast"
namespace {
using PartitionCallback = std::function<void(Instruction *)>;
/// Partition the @p Instructions based on their liveness.
void partitionInstructions(ArrayRef<WeakTrackingVH> Instructions,
PartitionCallback OnDead,
PartitionCallback OnAlive) {
for (auto OldValueHandle : Instructions) {
// Handle situations when the weak handle is no longer valid.
if (!OldValueHandle.pointsToAliveValue()) {
continue; // Nothing else to do for this handle.
}
auto *OldInstruction = cast<Instruction>(OldValueHandle);
bool Dead = OldInstruction->use_empty();
if (Dead) {
OnDead(OldInstruction);
} else {
OnAlive(OldInstruction);
}
}
}
bool isGenericPTy(Type *Ty) {
return Ty && Ty->isPointerTy() &&
Ty->getPointerAddressSpace() == clspv::AddressSpace::Generic;
}
} // namespace
PreservedAnalyses clspv::LowerAddrSpaceCastPass::run(Module &M,
ModuleAnalysisManager &) {
PreservedAnalyses PA;
for (auto &F : M.functions()) {
BitcastUtils::RemoveCstExprFromFunction(&F);
runOnFunction(F);
}
cleanModule(M);
return PA;
}
Value *clspv::LowerAddrSpaceCastPass::visit(Value *V) {
auto it = ValueMap.find(V);
if (it != ValueMap.end()) {
return it->second;
}
auto *I = dyn_cast<Instruction>(V);
if (I == nullptr) {
return V;
}
if (auto *alloca = dyn_cast<AllocaInst>(I)) {
if (alloca->getAllocatedType()->isPointerTy() &&
alloca->getAllocatedType()->getPointerAddressSpace() !=
clspv::AddressSpace::Private) {
return visit(alloca);
}
}
if (isGenericPTy(I->getType())) {
return visit(I);
}
for (auto &Operand : I->operands()) {
if (isGenericPTy(Operand->getType())) {
return visit(I);
}
}
return V;
}
llvm::Value *
clspv::LowerAddrSpaceCastPass::visitAllocaInst(llvm::AllocaInst &I) {
IRBuilder<> B(&I);
auto alloca = B.CreateAlloca(
PointerType::get(I.getContext(), clspv::AddressSpace::Private),
I.getArraySize(), I.getName());
registerReplacement(&I, alloca);
return alloca;
}
llvm::Value *
clspv::LowerAddrSpaceCastPass::visitAtomicRMWInst(llvm::AtomicRMWInst &I) {
IRBuilder<> B(&I);
auto atomic =
B.CreateAtomicRMW(I.getOperation(), visit(I.getPointerOperand()),
I.getValOperand(), I.getAlign(), I.getOrdering());
registerReplacement(&I, atomic);
I.replaceAllUsesWith(atomic);
return atomic;
}
llvm::Value *clspv::LowerAddrSpaceCastPass::visitLoadInst(llvm::LoadInst &I) {
IRBuilder<> B(&I);
Type *Ty = I.getType();
Value *Ptr = visit(I.getPointerOperand());
if (isGenericPTy(Ty)) {
Ty = clspv::InferType(Ptr, I.getContext(), &TypeCache);
}
auto load = B.CreateLoad(Ty, Ptr, I.getName());
registerReplacement(&I, load);
if (!isGenericPTy(I.getType())) {
I.replaceAllUsesWith(load);
}
return load;
}
llvm::Value *clspv::LowerAddrSpaceCastPass::visitStoreInst(llvm::StoreInst &I) {
IRBuilder<> B(&I);
Value *Val = visit(I.getValueOperand());
Value *Ptr = visit(I.getPointerOperand());
if (isa<ConstantPointerNull>(Val)) {
Val = ConstantPointerNull::get(PointerType::get(
I.getContext(), clspv::InferType(Ptr, I.getContext(), &TypeCache)
->getPointerAddressSpace()));
}
auto store = B.CreateStore(Val, Ptr);
registerReplacement(&I, store);
return store;
}
llvm::Value *clspv::LowerAddrSpaceCastPass::visitGetElementPtrInst(
llvm::GetElementPtrInst &I) {
IRBuilder<> B(&I);
auto gep = B.CreateGEP(I.getSourceElementType(), visit(I.getPointerOperand()),
SmallVector<Value *>{I.indices()}, I.getName(),
I.isInBounds());
registerReplacement(&I, gep);
return gep;
}
llvm::Value *clspv::LowerAddrSpaceCastPass::visitAddrSpaceCastInst(
llvm::AddrSpaceCastInst &I) {
auto ptr = visit(I.getPointerOperand());
// Returns a pointer that points to a region in the address space if
// "to_addrspace" can cast ptr to the address space. Otherwise it returns
// NULL.
if (isa<ConstantPointerNull>(ptr) ||
(ptr->getType() != I.getSrcTy() && ptr->getType() != I.getDestTy())) {
ptr = ConstantPointerNull::get(cast<PointerType>(I.getType()));
}
if (ptr->getType() == I.getDestTy()) {
I.replaceAllUsesWith(ptr);
}
registerReplacement(&I, ptr);
return ptr;
}
llvm::Value *clspv::LowerAddrSpaceCastPass::visitICmpInst(llvm::ICmpInst &I) {
IRBuilder<> B(&I);
Value *Op0 = visit(I.getOperand(0));
Value *Op1 = visit(I.getOperand(1));
if (Op0->getType() != Op1->getType()) {
if (isa<ConstantPointerNull>(Op0)) {
Op0 = ConstantPointerNull::get(cast<PointerType>(Op1->getType()));
} else if (isa<ConstantPointerNull>(Op1)) {
Op1 = ConstantPointerNull::get(cast<PointerType>(Op0->getType()));
} else {
llvm_unreachable("unsupported operand of icmp in loweraddrspacecast");
}
}
auto icmp = B.CreateICmp(I.getPredicate(), Op0, Op1, I.getName());
registerReplacement(&I, icmp);
I.replaceAllUsesWith(icmp);
return icmp;
}
llvm::Value *clspv::LowerAddrSpaceCastPass::visitCallInst(llvm::CallInst &I) {
SmallVector<Value *, 16> EquivalentArgs;
SmallVector<Type *, 8> EquivalentTypes;
for (auto &ArgUse : I.args()) {
Value *Arg = ArgUse.get();
Value *EquivalentArg = visit(Arg);
EquivalentArgs.push_back(EquivalentArg);
EquivalentTypes.push_back(EquivalentArg->getType());
}
Function *F = I.getCalledFunction();
assert(F && "Only function calls are supported.");
auto FunctionTy =
FunctionType::get(F->getReturnType(), EquivalentTypes, F->isVarArg());
const auto &Info = clspv::Builtins::Lookup(F);
auto fixNameSuffix = [&F](std::string Name) {
std::string AS_pattern = "PU3AS";
size_t AS_pattern_size = AS_pattern.size() + 1;
auto pos = Name.find(AS_pattern);
size_t pattern_size = AS_pattern_size;
if (pos == std::string::npos) {
// if AS_pattern was not found, it means that we are most probably looking
// for a private pointer pattern
pos = Name.find("P");
pattern_size = strlen("P");
if (pos == std::string::npos) {
// if this pattern was also not found, just return the input string
return Name;
}
}
auto Name_start = pos + pattern_size;
auto Name_end = Name.size() - Name_start;
auto subName = Name.substr(Name_start, Name_end);
auto FName = F->getName();
auto FName_start = FName.find(AS_pattern) + AS_pattern_size;
auto FName_end = FName.size() - FName_start;
auto subFName = FName.substr(FName_start, FName_end);
if (subName != subFName) {
Name = Name.replace(Name_start, Name_end, subFName);
}
return Name;
};
std::string Name = fixNameSuffix(clspv::Builtins::GetMangledFunctionName(
Info.getName().c_str(), FunctionTy));
Module *M = I.getModule();
auto getEquivalentFunction = [&Name, &M, &FunctionTy, this, &F]() {
Function *eqF = M->getFunction(Name);
if (eqF != nullptr)
return eqF;
eqF = FunctionMap[F];
if (eqF != nullptr)
return eqF;
eqF = Function::Create(FunctionTy, F->getLinkage(), Name);
FunctionMap[F] = eqF;
M->getFunctionList().push_front(eqF);
return eqF;
};
Function *EquivalentFunction = getEquivalentFunction();
EquivalentFunction->copyAttributesFrom(F);
EquivalentFunction->setCallingConv(F->getCallingConv());
IRBuilder<> B(&I);
auto call = B.CreateCall(EquivalentFunction, EquivalentArgs);
call->copyIRFlags(&I);
call->copyMetadata(I);
call->setCallingConv(I.getCallingConv());
registerReplacement(&I, call);
I.replaceAllUsesWith(call);
return call;
}
Value *clspv::LowerAddrSpaceCastPass::visitIntToPtrInst(IntToPtrInst &I) {
SmallVector<Instruction *> Uses;
for (auto &use : I.uses()) {
if (auto Iuse = dyn_cast<Instruction>(&use)) {
Uses.push_back(Iuse);
}
}
clspv::AddressSpace::Type AS = clspv::AddressSpace::Global;
bool found = false;
DenseSet<Value *> seen;
while (!Uses.empty()) {
auto *U = Uses.pop_back_val();
if (seen.contains(U)) {
continue;
}
seen.insert(U);
if (auto ASCast = dyn_cast<AddrSpaceCastInst>(U)) {
clspv::AddressSpace::Type ASCastAS =
(clspv::AddressSpace::Type)ASCast->getDestAddressSpace();
if (!found) {
AS = ASCastAS;
found = true;
} else if (AS != ASCastAS) {
llvm_unreachable(
"Result of IntToPtr is casted into 2 different address space");
}
} else {
for (auto &use : U->uses()) {
if (auto Iuse = dyn_cast<Instruction>(&use)) {
Uses.push_back(Iuse);
}
}
}
}
IRBuilder<> B(&I);
Value *V =
B.CreateIntToPtr(I.getOperand(0), PointerType::get(I.getContext(), AS));
registerReplacement(&I, V);
return V;
}
Value *clspv::LowerAddrSpaceCastPass::visitPtrToIntInst(PtrToIntInst &I) {
auto ptr = visit(I.getPointerOperand());
IRBuilder<> B(&I);
auto ptrToInt = B.CreatePtrToInt(ptr, I.getDestTy());
registerReplacement(&I, ptrToInt);
I.replaceAllUsesWith(ptrToInt);
return ptrToInt;
}
Value *clspv::LowerAddrSpaceCastPass::visitPtrToAddrInst(PtrToAddrInst &I) {
auto ptr = visit(I.getPointerOperand());
IRBuilder<> B(&I);
auto ptrToAddr = B.CreatePtrToAddr(ptr);
registerReplacement(&I, ptrToAddr);
I.replaceAllUsesWith(ptrToAddr);
return ptrToAddr;
}
static bool DependsOnPhiNode(const Value *V, const PHINode *PhiNode) {
DenseSet<const llvm::Value *> Visited;
if (!isa<Instruction>(V)) {
return false;
}
SmallVector<const Value *, 16> Stack;
Stack.push_back(V);
while (!Stack.empty()) {
const Value *Current = Stack.pop_back_val();
if (Visited.contains(Current)) {
continue;
}
Visited.insert(Current);
if (Current == PhiNode) {
return true;
}
if (const auto *Inst = dyn_cast<Instruction>(Current)) {
for (const auto *Op : Inst->operand_values()) {
if (!Visited.contains(Op)) {
Stack.push_back(Op);
}
}
}
}
return false;
}
Value *clspv::LowerAddrSpaceCastPass::visitPHINode(llvm::PHINode &I) {
IRBuilder<> B(&I);
unsigned N = I.getNumIncomingValues();
// Analyse the incoming values anche check whether the types agree.
// Ignore any incoming value that depends on the node itself, as that would
// lead to infinite recursion. Instead, we delay processing them until
// after we have registered the replacement.
SmallVector<Value *, 2> Replacements(N);
SmallVector<unsigned, 2> DependentOperands;
Type *CommonTy = nullptr;
bool HasCommonTy = true;
for (unsigned j = 0; j < N; ++j) {
auto *V = I.getIncomingValue(j);
if (DependsOnPhiNode(V, &I)) {
DependentOperands.push_back(j);
} else {
V = visit(V);
if (HasCommonTy && !CommonTy) {
CommonTy = V->getType();
}
HasCommonTy &= CommonTy == V->getType();
}
Replacements[j] = V;
}
if (!HasCommonTy) {
// We don't have a common address space.
llvm_unreachable("PHI nodes with different address spaces are unsupported");
}
auto *Phi = B.CreatePHI(CommonTy, N);
registerReplacement(&I, Phi);
// Now that we have registered the replacement, we can process the dependent
// operands.
for (auto i : DependentOperands) {
Replacements[i] = visit(Replacements[i]);
}
for (unsigned i = 0; i < I.getNumIncomingValues(); ++i) {
Phi->addIncoming(Replacements[i], I.getIncomingBlock(i));
}
return Phi;
}
Value *clspv::LowerAddrSpaceCastPass::visitInstruction(Instruction &I) {
#ifndef NDEBUG
dbgs() << "Instruction not handled: " << I << '\n';
#endif
llvm_unreachable("Missing support for instruction");
}
void clspv::LowerAddrSpaceCastPass::registerReplacement(Value *U, Value *V) {
LLVM_DEBUG(dbgs() << "Replacement for " << *U << ": " << *V << '\n');
assert(ValueMap.count(U) == 0 && "Value already registered");
ValueMap.insert({U, V});
}
void clspv::LowerAddrSpaceCastPass::runOnFunction(Function &F) {
LLVM_DEBUG(dbgs() << "Processing " << F.getName() << '\n');
// Skip declarations.
if (F.isDeclaration()) {
return;
}
for (Instruction &I : instructions(&F)) {
// Use the Value overload of visit to ensure cache is used.
visit(static_cast<Value *>(&I));
}
cleanDeadInstructions();
LLVM_DEBUG(dbgs() << "Final version for " << F.getName() << '\n');
LLVM_DEBUG(dbgs() << F << '\n');
}
void clspv::LowerAddrSpaceCastPass::cleanModule(Module &M) {
for (auto &GV : M.globals()) {
if (GV.getName() == CLSPVBuiltinsUsed()) {
assert(GV.use_empty());
GV.eraseFromParent();
break;
}
}
SmallVector<Function *> ToBeRemoved;
for (auto &F : M) {
bool useCLSPVBuiltinsUsed = false;
if (F.getNumUses() == 1) {
auto C = dyn_cast<Constant>(F.user_back());
useCLSPVBuiltinsUsed = C != nullptr && C->getNumUses() == 0;
}
if ((F.use_empty() || useCLSPVBuiltinsUsed) &&
F.getCallingConv() != CallingConv::SPIR_KERNEL) {
ToBeRemoved.push_back(&F);
}
}
for (auto F : ToBeRemoved) {
F->eraseFromParent();
}
}
void clspv::LowerAddrSpaceCastPass::cleanDeadInstructions() {
// Collect all instructions that have been replaced by another one, and remove
// them from the function. To address dependencies, use a fixed-point
// algorithm:
// 1. Collect the instructions that have been replaced.
// 2. Collect among these instructions the ones which have no uses and remove
// them.
// 3. Repeat step 2 until no progress is made.
// Select instructions that were replaced by another one.
// Ignore constants as they are not owned by the module and therefore don't
// need to be removed.
using WeakInstructions = SmallVector<WeakTrackingVH, 32>;
WeakInstructions OldInstructions;
for (const auto &Mapping : ValueMap) {
if (Mapping.getSecond() != nullptr) {
if (auto *OldInstruction = dyn_cast<Instruction>(Mapping.getFirst())) {
OldInstructions.push_back(OldInstruction);
} else {
assert(isa<Constant>(Mapping.getFirst()) &&
"Only Instruction and Constant are expected in ValueMap");
}
}
}
// Erase any mapping, as they won't be valid anymore.
ValueMap.clear();
for (bool Progress = true; Progress;) {
std::size_t PreviousSize = OldInstructions.size();
// Identify instructions that are actually dead and can be removed using
// RecursivelyDeleteTriviallyDeadInstructions.
// Use a third buffer to capture the instructions that are still alive to
// avoid mutating OldInstructions while iterating over it.
WeakInstructions NextBatch;
WeakInstructions TriviallyDeads;
partitionInstructions(
OldInstructions,
[&TriviallyDeads](Instruction *DeadInstruction) {
// Additionally, manually remove from the parent instructions with
// possible side-effect, generally speaking, such as call or alloca
// instructions. Those are not trivially dead.
if (isInstructionTriviallyDead(DeadInstruction)) {
TriviallyDeads.push_back(DeadInstruction);
} else {
DeadInstruction->eraseFromParent();
}
},
[&NextBatch](Instruction *AliveInstruction) {
if (PHINode *Phi = dyn_cast<PHINode>(AliveInstruction)) {
if (RecursivelyDeleteDeadPHINode(Phi)) {
return;
}
}
NextBatch.push_back(AliveInstruction);
});
RecursivelyDeleteTriviallyDeadInstructions(TriviallyDeads);
// Update OldInstructions for the next iteration of the fixed-point.
OldInstructions = std::move(NextBatch);
Progress = (OldInstructions.size() < PreviousSize);
}
#ifndef NDEBUG
if (!OldInstructions.empty()) {
dbgs() << "These values were expected to be removed:\n";
for (auto ValueHandle : OldInstructions) {
dbgs() << '\t' << *ValueHandle << '\n';
}
llvm_unreachable("Not all supposedly-dead instruction were removed!");
}
#endif
}