blob: b5b9f7a15e4c51efa75ef31daa74fca354a047c5 [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 "clspv/Option.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instructions.h"
#include "Constants.h"
#include "PushConstant.h"
#include "Types.h"
#include "PrintfPass.h"
using namespace llvm;
std::optional<std::string> clspv::PrintfPass::GetStringLiteral(Value *Val) {
GlobalVariable *GlobalVal = nullptr;
Instruction *ConstExprEquivInstr = nullptr;
// If opaque pointers aren't enabled, the global may be hidden behind a
// GEP constexpr
if (auto *CE = dyn_cast<ConstantExpr>(Val)) {
ConstExprEquivInstr = CE->getAsInstruction();
if (auto *GEPInstr = dyn_cast<GetElementPtrInst>(ConstExprEquivInstr)) {
auto *Ptr = GEPInstr->getPointerOperand();
if (auto *GV = dyn_cast<GlobalVariable>(Ptr)) {
GlobalVal = GV;
}
}
}
// If there wasn't a GEP then the global will be used directly instead
if (!GlobalVal) {
GlobalVal = dyn_cast<GlobalVariable>(Val);
}
if (GlobalVal) {
auto *Initializer = GlobalVal->getInitializer();
if (auto *InitArray = dyn_cast<ConstantDataArray>(Initializer)) {
if (InitArray->isString()) {
if (ConstExprEquivInstr)
ConstExprEquivInstr->deleteValue();
return InitArray->getAsString().str();
}
}
// The empty C string, and strings of null chars, are
// constant-aggregate-zero.
if (auto *InitArray = dyn_cast<ConstantAggregateZero>(Initializer)) {
if (auto *arrTy = dyn_cast<ArrayType>(InitArray->getType())) {
if (arrTy->getElementType()->isIntegerTy(8)) {
return std::string(arrTy->getNumElements(), '\0');
}
}
}
}
// Not a string.
return {};
}
unsigned clspv::PrintfPass::GetPrintfStoreSize(const DataLayout &DL, Type *Ty) {
return std::max(4ul, (unsigned long)DL.getTypeStoreSize(Ty).getFixedValue());
}
void clspv::PrintfPass::DefinePrintfInstance(Module &M, CallInst *CI,
unsigned PrintfID) {
// Drop the format string from the arguments
SmallVector<Value *, 8> NewArgs;
SmallVector<Type *, 8> NewArgTypes;
unsigned ArgsSize = 0;
auto ArgIter = CI->arg_begin();
ArgIter++;
for (; ArgIter != CI->arg_end(); ArgIter++) {
NewArgs.push_back(*ArgIter);
NewArgTypes.push_back((*ArgIter)->getType());
ArgsSize += GetPrintfStoreSize(M.getDataLayout(), (*ArgIter)->getType());
}
// Create a new function with the new arguments, update the CallInst
std::string FuncName = "__clspv.printf." + std::to_string(PrintfID);
auto &Ctx = M.getContext();
auto Int32Ty = IntegerType::getInt32Ty(Ctx);
auto Int64Ty = IntegerType::getInt64Ty(Ctx);
auto *FuncTy = FunctionType::get(Int32Ty, NewArgTypes, false);
auto FuncCallee = M.getOrInsertFunction(FuncName, FuncTy);
auto *Func = dyn_cast<Function>(FuncCallee.getCallee());
assert(Func);
auto *NewCI = CallInst::Create(Func, NewArgs, "", CI->getIterator());
CI->replaceAllUsesWith(NewCI);
CI->eraseFromParent();
// Define the new function
unsigned AllocSize = ArgsSize + 4;
unsigned AllocSizeInts = AllocSize / 4;
auto *ConstantZero = ConstantInt::get(Int32Ty, 0);
auto *ConstantOne = ConstantInt::get(Int32Ty, 1);
auto *ConstantNegOne = Constant::getAllOnesValue(Int32Ty);
auto *ConstantPrintfID = ConstantInt::get(Int32Ty, PrintfID);
auto *ConstantAllocSize = ConstantInt::get(Int32Ty, AllocSizeInts);
auto *ConstantBuffSize =
ConstantInt::get(Int32Ty, clspv::Option::PrintfBufferSize() / 4);
const auto &DL = M.getDataLayout();
auto *EntryBB = BasicBlock::Create(Ctx, "entry", Func);
auto *CopyArgsBB = BasicBlock::Create(Ctx, "copy_args", Func);
auto *CopyPrintfIdBB = BasicBlock::Create(Ctx, "copy_printf_id", Func);
auto *TestPrintfIdBB = BasicBlock::Create(Ctx, "test_printf_id", Func);
auto *ExitBB = BasicBlock::Create(Ctx, "exit", Func);
IRBuilder<> IR{EntryBB};
auto *BufferArrayTy = ArrayType::get(Int32Ty, 0);
auto *BufferTy = StructType::get(BufferArrayTy);
Value *Buffer;
if (clspv::Option::PhysicalStorageBuffers()) {
auto *BufferAddressPtr = clspv::GetPushConstantPointer(
EntryBB, clspv::PushConstant::PrintfBufferPointer);
auto *BufferAddress = IR.CreateLoad(IR.getInt64Ty(), BufferAddressPtr);
Buffer = IR.CreateIntToPtr(
BufferAddress, PointerType::get(Ctx, clspv::AddressSpace::Global));
} else {
Buffer = M.getNamedGlobal(clspv::PrintfBufferVariableName());
}
// Try to reserve a block of the printf buffer. The first i32 of the buffer
// stores the offset of the next available memory
auto *GEP = GetElementPtrInst::Create(
BufferTy, Buffer, {ConstantZero, ConstantZero, ConstantZero});
IR.Insert(GEP);
Value *Offset =
IR.CreateAtomicRMW(AtomicRMWInst::Add, GEP, ConstantAllocSize,
MaybeAlign(4), AtomicOrdering::SequentiallyConsistent);
Offset = IR.CreateAdd(Offset, ConstantOne);
Value *EndOffset = IR.CreateAdd(Offset, ConstantAllocSize);
Value *ArgsOffset = IR.CreateAdd(Offset, ConstantOne);
// Now check that we have enough space to store the printf id and the args
auto *ICmp = IR.CreateICmpULE(EndOffset, ConstantBuffSize);
IR.CreateCondBr(ICmp, CopyArgsBB, TestPrintfIdBB);
//
// Copy Printf ID
//
IR.SetInsertPoint(CopyPrintfIdBB);
auto *ReturnValue = IR.CreatePHI(Int32Ty, 2);
ReturnValue->addIncoming(ConstantZero, CopyArgsBB);
ReturnValue->addIncoming(ConstantNegOne, TestPrintfIdBB);
// Store the printf ID
auto *PrintfIDGEP = GetElementPtrInst::Create(
BufferTy, Buffer, {ConstantZero, ConstantZero, Offset});
IR.Insert(PrintfIDGEP);
IR.CreateStore(ConstantPrintfID, PrintfIDGEP);
IR.CreateBr(ExitBB);
//
// Test Printf Id
//
IR.SetInsertPoint(TestPrintfIdBB);
ICmp = IR.CreateICmpULE(ArgsOffset, ConstantBuffSize);
IR.CreateCondBr(ICmp, CopyPrintfIdBB, ExitBB);
//
// Copy Args
//
IR.SetInsertPoint(CopyArgsBB);
// Store the value of each argument
for (size_t i = 0; i < Func->arg_size(); i++) {
Value *Arg = Func->getArg(i);
// Everything has to go into an i32 buffer, bitcast floats to ints if needed
if (Arg->getType()->isFloatingPointTy()) {
auto *IntTy =
IntegerType::get(Ctx, Arg->getType()->getScalarSizeInBits());
Arg = IR.CreateBitCast(Arg, IntTy);
} else if (Arg->getType()->isPointerTy()) {
auto IntTy = clspv::PointersAre64Bit(M) ? Int64Ty : Int32Ty;
Arg = IR.CreatePtrToInt(Arg, IntTy);
}
// Vectors that are smaller than 4 bytes need padded to reach this size
if (Arg->getType()->isVectorTy() &&
Arg->getType()->getScalarSizeInBits() < 32) {
auto VecStoreSize = DL.getTypeStoreSizeInBits(Arg->getType());
auto *EquivIntTy = IntegerType::get(Ctx, VecStoreSize);
Arg = IR.CreateBitCast(Arg, EquivIntTy);
if (VecStoreSize < 32)
Arg = IR.CreateZExt(Arg, Int32Ty);
}
Value *ArgStoreGEP = GetElementPtrInst::Create(
BufferTy, Buffer, {ConstantZero, ConstantZero, ArgsOffset});
IR.Insert(ArgStoreGEP);
// If the integer is now anything but i32, bitcast the pointer
if (Arg->getType() != Int32Ty) {
auto *NewTy = PointerType::get(Ctx, clspv::AddressSpace::Global);
ArgStoreGEP = IR.CreatePointerCast(ArgStoreGEP, NewTy);
}
IR.CreateStore(Arg, ArgStoreGEP);
auto *StoreSize =
ConstantInt::get(Int32Ty, DL.getTypeStoreSize(Arg->getType()) / 4);
if (i + 1 < Func->arg_size()) {
ArgsOffset = IR.CreateAdd(ArgsOffset, StoreSize);
}
}
IR.CreateBr(CopyPrintfIdBB);
// Return 0 on success, otherwise -1
IR.SetInsertPoint(ExitBB);
auto *ReturnCodePhi = IR.CreatePHI(Int32Ty, 2);
ReturnCodePhi->addIncoming(ReturnValue, CopyPrintfIdBB);
ReturnCodePhi->addIncoming(ConstantNegOne, TestPrintfIdBB);
IR.CreateRet(ReturnCodePhi);
}
// Create a metadata node for the given printf ID, format string and arguments
MDNode *CreatePrintfArgsMetadata(Module &M, ArrayRef<Metadata *> ArgMDs,
const std::string &String, unsigned int ID) {
auto *ArgsTupleMD = MDTuple::get(M.getContext(), ArgMDs);
auto *PrintfStringMD = MDString::get(M.getContext(), String);
auto *PrintfIDConst = ConstantInt::get(Type::getInt32Ty(M.getContext()), ID);
auto *PrintfIDMD = ConstantAsMetadata::get(PrintfIDConst);
return MDNode::get(M.getContext(), {PrintfIDMD, PrintfStringMD, ArgsTupleMD});
}
PreservedAnalyses clspv::PrintfPass::run(Module &M, ModuleAnalysisManager &) {
PreservedAnalyses PA;
if (!M.getFunction("printf"))
return PA;
auto *PrintfMDs = M.getOrInsertNamedMetadata(PrintfMetadataName());
// Create the printf buffer
auto &Ctx = M.getContext();
auto *Int32Ty = IntegerType::getInt32Ty(Ctx);
auto *BufferTy = ArrayType::get(Int32Ty, 0);
auto *PrintfBufferTy = StructType::get(BufferTy);
if (!clspv::Option::PhysicalStorageBuffers()) {
M.getOrInsertGlobal(PrintfBufferVariableName(), PrintfBufferTy, [&] {
GlobalVariable *GV = new GlobalVariable(
M, PrintfBufferTy, /*isConstant=*/false, GlobalValue::ExternalLinkage,
nullptr, PrintfBufferVariableName(), nullptr,
GlobalValue::NotThreadLocal, clspv::AddressSpace::Global);
GV->setAlignment(Align(1));
return GV;
});
}
unsigned PrintfID = 0;
SmallVector<Function *, 8> FunctionsUsingPrintf;
SmallVector<CallInst *, 8> PrintfsToProcess;
for (auto &F : M) {
bool UsesPrintf = false;
for (auto &BB : F) {
for (auto &Inst : BB) {
if (auto *CI = dyn_cast<CallInst>(&Inst)) {
auto *Callee = CI->getCalledFunction();
if (Callee->getName() == "printf") {
PrintfsToProcess.push_back(CI);
UsesPrintf = true;
}
}
}
}
if (UsesPrintf) {
FunctionsUsingPrintf.push_back(&F);
}
}
for (auto *CI : PrintfsToProcess) {
// Check format string is valid
auto *FmtStringValue = CI->getArgOperand(0);
auto FmtString = GetStringLiteral(FmtStringValue);
if (!FmtString.has_value()) {
continue;
}
auto &DL = M.getDataLayout();
SmallVector<Metadata *, 8> ArgMD;
auto PrintfCallID = PrintfID++;
// Process arguments, build argument metadata
for (unsigned i = 1; i < CI->arg_size(); i++) {
Value *Arg = CI->getArgOperand(i);
Type *ArgType = Arg->getType();
// Literal Arg are store using a 32bits identifier, which differs when
// using 64bits pointers from the size returned by GetPrintfStoreSize.
bool IsStringLiteral = GetStringLiteral(Arg).has_value();
unsigned ArgSize = IsStringLiteral ? 4 : GetPrintfStoreSize(DL, ArgType);
auto *ArgSizeConst = ConstantInt::get(Int32Ty, ArgSize);
ArgMD.push_back(ConstantAsMetadata::get(ArgSizeConst));
}
auto *PrintfMD =
CreatePrintfArgsMetadata(M, ArgMD, FmtString.value(), PrintfCallID);
PrintfMDs->addOperand(PrintfMD);
// Get string literal arguments to printf (not including format string) and
// hoist them out as separate format strings. This way we don't need to
// worry about storing byte-sized strings in an i32 buffer.
for (unsigned i = 1; i < CI->arg_size(); i++) {
Value *Arg = CI->getArgOperand(i);
auto StringLiteral = GetStringLiteral(Arg);
if (StringLiteral.has_value()) {
auto StringLiteralID = PrintfID++;
auto *StringLiteralMD = CreatePrintfArgsMetadata(
M, {}, StringLiteral.value(), StringLiteralID);
PrintfMDs->addOperand(StringLiteralMD);
// The printf now stores the string's ID rather than its data
CI->setArgOperand(i, ConstantInt::get(Int32Ty, StringLiteralID));
}
}
DefinePrintfInstance(M, CI, PrintfCallID);
}
// Tidy up metadata if there were no printfs
if (PrintfMDs->getNumOperands() == 0) {
PrintfMDs->eraseFromParent();
return PA;
}
// The original printf function should be unused now
auto *PrintfFunc = M.getFunction("printf");
if (PrintfFunc) {
PrintfFunc->eraseFromParent();
}
return PA;
}