blob: ce5be56d3923e2a586d3ffa76f0fc4dde746a38e [file] [edit]
// Copyright 2022 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 "llvm/Analysis/ScalarEvolution.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/Instructions.h"
#include "llvm/Transforms/Utils/Cloning.h"
#include "clspv/AddressSpace.h"
#include "clspv/Option.h"
#include "BitcastUtils.h"
#include "Types.h"
#include "LogicalPointerToIntPass.h"
using namespace llvm;
// Support limited uses of PtrToInt on logical address spaces, in particular
// uses where a pointer is converted to an integer but not used to form an
// integer which is converted back to a pointer.
//
// This is safe to do because:
// * Private and local addresses are meaningless on the host, and cannot be
// dereferenced by any subsequent kernels.
// * The actual numeric ranges of the address spaces may overlap, so we do not
// need to consider the physical address range of global and constant memory.
//
// The primary use of this limited functionality is to inspect the memory layout
// of struct types.
// Arbitrary 'large enough' size. We have a 64-bit range to use and in practice
// the amount of private and local memory will be quite small.
uint64_t MaxAllocSize = 0x0000100000000000;
static bool IsTargetAddrSpace(unsigned AS) {
return AS == clspv::AddressSpace::Private ||
AS == clspv::AddressSpace::Local ||
!clspv::Option::PhysicalStorageBuffers();
}
static bool FunctionShouldBeInlined(Function &F) {
for (BasicBlock &BB : F) {
for (Instruction &I : BB) {
if (isa<IntToPtrInst>(I) || isa<PtrToIntInst>(I) ||
isa<PtrToAddrInst>(I)) {
return true;
}
}
}
return false;
}
bool clspv::LogicalPointerToIntPass::InlineFunctions(Module &M) {
bool Changed = false;
std::vector<CallInst *> to_inline;
for (auto &F : M) {
if (F.isDeclaration() || F.getCallingConv() == CallingConv::SPIR_KERNEL)
continue;
if (!FunctionShouldBeInlined(F)) {
continue;
}
for (auto user : F.users()) {
if (auto call = dyn_cast<CallInst>(user))
to_inline.push_back(call);
}
}
for (auto call : to_inline) {
InlineFunctionInfo IFI;
Changed |= InlineFunction(*call, IFI, false, nullptr, false).isSuccess();
}
return Changed;
}
PreservedAnalyses
clspv::LogicalPointerToIntPass::run(Module &M, ModuleAnalysisManager &MAM) {
PreservedAnalyses PA;
if (!clspv::PointersAre64Bit(M)) {
nextBaseAddress = 0x10000000ULL;
MaxAllocSize = 0x10000000ULL;
}
while (InlineFunctions(M)) {
}
SmallVector<Instruction *, 8> InstrsToProcess;
SmallVector<ICmpInst *, 8> PointerICmpToProcess;
SmallVector<Instruction *, 8> NullPtrToProcess;
for (auto &F : M) {
BitcastUtils::RemoveCstExprFromFunction(&F);
for (auto &BB : F) {
for (auto &I : BB) {
if (auto *Cast = dyn_cast<CastInst>(&I)) {
if (Cast->getOpcode() == Instruction::PtrToInt ||
Cast->getOpcode() == Instruction::PtrToAddr) {
if (auto *PtrTy =
cast<PointerType>(Cast->getOperand(0)->getType())) {
if (IsTargetAddrSpace(PtrTy->getAddressSpace())) {
InstrsToProcess.push_back(Cast);
} else if (auto AddrSpaceCast =
dyn_cast<AddrSpaceCastInst>(Cast->getOperand(0))) {
if (isa<ConstantPointerNull>(
AddrSpaceCast->getPointerOperand())) {
NullPtrToProcess.push_back(Cast);
}
}
}
} else if (Cast->getOpcode() == Instruction::IntToPtr) {
if (auto *PtrTy = cast<PointerType>(Cast->getType())) {
if (IsTargetAddrSpace(PtrTy->getAddressSpace())) {
// Bail out if we see a relevant IntToPtr anywhere, as it means
// we cannot guarantee a generated address won't be dereferenced
return PA;
}
}
}
} else if (auto ICmp = dyn_cast<ICmpInst>(&I)) {
if (auto *PtrTy =
dyn_cast<PointerType>(ICmp->getOperand(0)->getType())) {
if (IsTargetAddrSpace(PtrTy->getAddressSpace())) {
PointerICmpToProcess.push_back(ICmp);
}
}
}
}
}
}
for (auto *ICmp : PointerICmpToProcess) {
IntegerType *SizeT = clspv::PointersAre64Bit(M)
? IntegerType::get(M.getContext(), 64)
: IntegerType::get(M.getContext(), 32);
IRBuilder<> B(ICmp);
auto LHS = B.CreatePtrToInt(ICmp->getOperand(0), SizeT);
if (auto *Cast = dyn_cast<CastInst>(LHS)) {
InstrsToProcess.push_back(Cast);
}
auto RHS = B.CreatePtrToInt(ICmp->getOperand(1), SizeT);
if (auto *Cast = dyn_cast<CastInst>(RHS)) {
InstrsToProcess.push_back(Cast);
}
ICmp->replaceAllUsesWith(B.CreateICmp(ICmp->getPredicate(), LHS, RHS));
}
for (auto *Instr : InstrsToProcess) {
auto *IntTy = cast<IntegerType>(Instr->getType());
auto *PtrOp = Instr->getOperand(0);
Value *MemBase = nullptr;
int64_t CstOffset = 0;
Value *DynOffset = nullptr;
if (processValue(M.getDataLayout(), PtrOp, CstOffset, DynOffset, MemBase)) {
auto BaseAddr = getMemBaseAddr(MemBase);
Value *Replacement;
Replacement = ConstantInt::get(IntTy, BaseAddr + CstOffset, false, true);
if (DynOffset != nullptr) {
IRBuilder<> B(Instr);
Replacement = B.CreateAdd(Replacement, DynOffset);
}
Instr->replaceAllUsesWith(Replacement);
}
}
for (auto *Instr : NullPtrToProcess) {
Instr->replaceAllUsesWith(ConstantInt::get(Instr->getType(), 0));
}
return PA;
}
bool clspv::LogicalPointerToIntPass::processValue(const DataLayout &DL,
Value *Val,
int64_t &CstOffset,
llvm::Value *&DynOffset,
Value *&MemBase) {
if (auto *GEP = dyn_cast<GetElementPtrInst>(Val)) {
// Convert GEP indices to byte offset
IRBuilder<> B(GEP);
size_t SmallerBitWidths;
int64_t CstVal = 0;
Value *DynVal = nullptr;
BitcastUtils::ExtractOffsetFromGEP(DL, B, GEP, CstVal, DynVal,
SmallerBitWidths);
CstOffset += CstVal * SmallerBitWidths / CHAR_BIT;
if (DynVal) {
DynVal = BitcastUtils::CreateMul(B, SmallerBitWidths / CHAR_BIT, DynVal);
}
if (DynOffset && DynVal) {
DynOffset = B.CreateAdd(DynOffset, DynVal);
} else if (DynVal) {
DynOffset = DynVal;
}
return processValue(DL, GEP->getPointerOperand(), CstOffset, DynOffset,
MemBase);
} else if (auto *Bitcast = dyn_cast<BitCastInst>(Val)) {
return processValue(DL, Bitcast->getOperand(0), CstOffset, DynOffset,
MemBase);
} else if (isMemBase(Val)) {
MemBase = Val;
return true;
}
return false;
}
bool clspv::LogicalPointerToIntPass::isMemBase(Value *Val) {
if (isa<AllocaInst>(Val)) {
return true;
} else if (auto *Arg = dyn_cast<Argument>(Val)) {
auto *F = Arg->getParent();
// Local memory args are only allowed in actual kernels, pointers passed
// to regular functions might not be the base of the actual allocation
if (F->getCallingConv() == llvm::CallingConv::SPIR_KERNEL &&
Arg->getType()->isPointerTy() &&
IsTargetAddrSpace(Arg->getType()->getPointerAddressSpace())) {
return true;
}
} else if (auto *GV = dyn_cast<GlobalVariable>(Val)) {
if (IsTargetAddrSpace(GV->getAddressSpace())) {
return true;
}
}
return false;
}
uint64_t clspv::LogicalPointerToIntPass::getMemBaseAddr(llvm::Value *MemBase) {
auto InsertInfo = baseAddressMap.insert({MemBase, nextBaseAddress});
if (InsertInfo.second) {
nextBaseAddress += MaxAllocSize;
}
return InsertInfo.first->second;
}