This enables the AMDGPU specific implementation of `printf` when compiling for AMDGCN flavoured SPIR-V, the consequence being that the expansion into ROCDL calls & friends gets expanded before "lowering" to SPIR-V and gets carried through. The only relatively "novel" aspect is that the `callAppendStringN` is simplified to take the type of the passed in arguments, as opposed to querying them from the module. This is a neutral change since the arguments were passed directly to the call, without any attempt to cast them, hence the assumption that the actual types match the formal ones was already baked in.
223 lines
8.7 KiB
C++
223 lines
8.7 KiB
C++
//===------ CGGPUBuiltin.cpp - Codegen for GPU builtins -------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Generates code for built-in GPU calls which are not runtime-specific.
|
|
// (Runtime-specific codegen lives in programming model specific files.)
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "CodeGenFunction.h"
|
|
#include "clang/Basic/Builtins.h"
|
|
#include "llvm/IR/DataLayout.h"
|
|
#include "llvm/IR/Instruction.h"
|
|
#include "llvm/Support/MathExtras.h"
|
|
#include "llvm/Transforms/Utils/AMDGPUEmitPrintf.h"
|
|
|
|
using namespace clang;
|
|
using namespace CodeGen;
|
|
|
|
namespace {
|
|
llvm::Function *GetVprintfDeclaration(llvm::Module &M) {
|
|
llvm::Type *ArgTypes[] = {llvm::PointerType::getUnqual(M.getContext()),
|
|
llvm::PointerType::getUnqual(M.getContext())};
|
|
llvm::FunctionType *VprintfFuncType = llvm::FunctionType::get(
|
|
llvm::Type::getInt32Ty(M.getContext()), ArgTypes, false);
|
|
|
|
if (auto *F = M.getFunction("vprintf")) {
|
|
// Our CUDA system header declares vprintf with the right signature, so
|
|
// nobody else should have been able to declare vprintf with a bogus
|
|
// signature.
|
|
assert(F->getFunctionType() == VprintfFuncType);
|
|
return F;
|
|
}
|
|
|
|
// vprintf doesn't already exist; create a declaration and insert it into the
|
|
// module.
|
|
return llvm::Function::Create(
|
|
VprintfFuncType, llvm::GlobalVariable::ExternalLinkage, "vprintf", &M);
|
|
}
|
|
|
|
llvm::Function *GetOpenMPVprintfDeclaration(CodeGenModule &CGM) {
|
|
const char *Name = "__llvm_omp_vprintf";
|
|
llvm::Module &M = CGM.getModule();
|
|
llvm::Type *ArgTypes[] = {llvm::PointerType::getUnqual(M.getContext()),
|
|
llvm::PointerType::getUnqual(M.getContext()),
|
|
llvm::Type::getInt32Ty(M.getContext())};
|
|
llvm::FunctionType *VprintfFuncType = llvm::FunctionType::get(
|
|
llvm::Type::getInt32Ty(M.getContext()), ArgTypes, false);
|
|
|
|
if (auto *F = M.getFunction(Name)) {
|
|
if (F->getFunctionType() != VprintfFuncType) {
|
|
CGM.Error(SourceLocation(),
|
|
"Invalid type declaration for __llvm_omp_vprintf");
|
|
return nullptr;
|
|
}
|
|
return F;
|
|
}
|
|
|
|
return llvm::Function::Create(
|
|
VprintfFuncType, llvm::GlobalVariable::ExternalLinkage, Name, &M);
|
|
}
|
|
|
|
// Transforms a call to printf into a call to the NVPTX vprintf syscall (which
|
|
// isn't particularly special; it's invoked just like a regular function).
|
|
// vprintf takes two args: A format string, and a pointer to a buffer containing
|
|
// the varargs.
|
|
//
|
|
// For example, the call
|
|
//
|
|
// printf("format string", arg1, arg2, arg3);
|
|
//
|
|
// is converted into something resembling
|
|
//
|
|
// struct Tmp {
|
|
// Arg1 a1;
|
|
// Arg2 a2;
|
|
// Arg3 a3;
|
|
// };
|
|
// char* buf = alloca(sizeof(Tmp));
|
|
// *(Tmp*)buf = {a1, a2, a3};
|
|
// vprintf("format string", buf);
|
|
//
|
|
// buf is aligned to the max of {alignof(Arg1), ...}. Furthermore, each of the
|
|
// args is itself aligned to its preferred alignment.
|
|
//
|
|
// Note that by the time this function runs, E's args have already undergone the
|
|
// standard C vararg promotion (short -> int, float -> double, etc.).
|
|
|
|
std::pair<llvm::Value *, llvm::TypeSize>
|
|
packArgsIntoNVPTXFormatBuffer(CodeGenFunction *CGF, const CallArgList &Args) {
|
|
const llvm::DataLayout &DL = CGF->CGM.getDataLayout();
|
|
llvm::LLVMContext &Ctx = CGF->CGM.getLLVMContext();
|
|
CGBuilderTy &Builder = CGF->Builder;
|
|
|
|
// Construct and fill the args buffer that we'll pass to vprintf.
|
|
if (Args.size() <= 1) {
|
|
// If there are no args, pass a null pointer and size 0
|
|
llvm::Value *BufferPtr =
|
|
llvm::ConstantPointerNull::get(llvm::PointerType::getUnqual(Ctx));
|
|
return {BufferPtr, llvm::TypeSize::getFixed(0)};
|
|
} else {
|
|
llvm::SmallVector<llvm::Type *, 8> ArgTypes;
|
|
for (unsigned I = 1, NumArgs = Args.size(); I < NumArgs; ++I)
|
|
ArgTypes.push_back(Args[I].getRValue(*CGF).getScalarVal()->getType());
|
|
|
|
// Using llvm::StructType is correct only because printf doesn't accept
|
|
// aggregates. If we had to handle aggregates here, we'd have to manually
|
|
// compute the offsets within the alloca -- we wouldn't be able to assume
|
|
// that the alignment of the llvm type was the same as the alignment of the
|
|
// clang type.
|
|
llvm::Type *AllocaTy = llvm::StructType::create(ArgTypes, "printf_args");
|
|
llvm::Value *Alloca = CGF->CreateTempAlloca(AllocaTy);
|
|
|
|
for (unsigned I = 1, NumArgs = Args.size(); I < NumArgs; ++I) {
|
|
llvm::Value *P = Builder.CreateStructGEP(AllocaTy, Alloca, I - 1);
|
|
llvm::Value *Arg = Args[I].getRValue(*CGF).getScalarVal();
|
|
Builder.CreateAlignedStore(Arg, P, DL.getPrefTypeAlign(Arg->getType()));
|
|
}
|
|
llvm::Value *BufferPtr =
|
|
Builder.CreatePointerCast(Alloca, llvm::PointerType::getUnqual(Ctx));
|
|
return {BufferPtr, DL.getTypeAllocSize(AllocaTy)};
|
|
}
|
|
}
|
|
|
|
bool containsNonScalarVarargs(CodeGenFunction *CGF, const CallArgList &Args) {
|
|
return llvm::any_of(llvm::drop_begin(Args), [&](const CallArg &A) {
|
|
return !A.getRValue(*CGF).isScalar();
|
|
});
|
|
}
|
|
|
|
RValue EmitDevicePrintfCallExpr(const CallExpr *E, CodeGenFunction *CGF,
|
|
llvm::Function *Decl, bool WithSizeArg) {
|
|
CodeGenModule &CGM = CGF->CGM;
|
|
CGBuilderTy &Builder = CGF->Builder;
|
|
assert(E->getBuiltinCallee() == Builtin::BIprintf ||
|
|
E->getBuiltinCallee() == Builtin::BI__builtin_printf);
|
|
assert(E->getNumArgs() >= 1); // printf always has at least one arg.
|
|
|
|
// Uses the same format as nvptx for the argument packing, but also passes
|
|
// an i32 for the total size of the passed pointer
|
|
CallArgList Args;
|
|
CGF->EmitCallArgs(Args,
|
|
E->getDirectCallee()->getType()->getAs<FunctionProtoType>(),
|
|
E->arguments(), E->getDirectCallee(),
|
|
/* ParamsToSkip = */ 0);
|
|
|
|
// We don't know how to emit non-scalar varargs.
|
|
if (containsNonScalarVarargs(CGF, Args)) {
|
|
CGM.ErrorUnsupported(E, "non-scalar arg to printf");
|
|
return RValue::get(llvm::ConstantInt::get(CGF->IntTy, 0));
|
|
}
|
|
|
|
auto r = packArgsIntoNVPTXFormatBuffer(CGF, Args);
|
|
llvm::Value *BufferPtr = r.first;
|
|
|
|
llvm::SmallVector<llvm::Value *, 3> Vec = {
|
|
Args[0].getRValue(*CGF).getScalarVal(), BufferPtr};
|
|
if (WithSizeArg) {
|
|
// Passing > 32bit of data as a local alloca doesn't work for nvptx or
|
|
// amdgpu
|
|
llvm::Constant *Size =
|
|
llvm::ConstantInt::get(llvm::Type::getInt32Ty(CGM.getLLVMContext()),
|
|
static_cast<uint32_t>(r.second.getFixedValue()));
|
|
|
|
Vec.push_back(Size);
|
|
}
|
|
return RValue::get(Builder.CreateCall(Decl, Vec));
|
|
}
|
|
} // namespace
|
|
|
|
RValue CodeGenFunction::EmitNVPTXDevicePrintfCallExpr(const CallExpr *E) {
|
|
assert(getTarget().getTriple().isNVPTX());
|
|
return EmitDevicePrintfCallExpr(
|
|
E, this, GetVprintfDeclaration(CGM.getModule()), false);
|
|
}
|
|
|
|
RValue CodeGenFunction::EmitAMDGPUDevicePrintfCallExpr(const CallExpr *E) {
|
|
assert(getTarget().getTriple().isAMDGCN() ||
|
|
(getTarget().getTriple().isSPIRV() &&
|
|
getTarget().getTriple().getVendor() == llvm::Triple::AMD));
|
|
assert(E->getBuiltinCallee() == Builtin::BIprintf ||
|
|
E->getBuiltinCallee() == Builtin::BI__builtin_printf);
|
|
assert(E->getNumArgs() >= 1); // printf always has at least one arg.
|
|
|
|
CallArgList CallArgs;
|
|
EmitCallArgs(CallArgs,
|
|
E->getDirectCallee()->getType()->getAs<FunctionProtoType>(),
|
|
E->arguments(), E->getDirectCallee(),
|
|
/* ParamsToSkip = */ 0);
|
|
|
|
SmallVector<llvm::Value *, 8> Args;
|
|
for (const auto &A : CallArgs) {
|
|
// We don't know how to emit non-scalar varargs.
|
|
if (!A.getRValue(*this).isScalar()) {
|
|
CGM.ErrorUnsupported(E, "non-scalar arg to printf");
|
|
return RValue::get(llvm::ConstantInt::get(IntTy, -1));
|
|
}
|
|
|
|
llvm::Value *Arg = A.getRValue(*this).getScalarVal();
|
|
Args.push_back(Arg);
|
|
}
|
|
|
|
llvm::IRBuilder<> IRB(Builder.GetInsertBlock(), Builder.GetInsertPoint());
|
|
IRB.SetCurrentDebugLocation(Builder.getCurrentDebugLocation());
|
|
|
|
bool isBuffered = (CGM.getTarget().getTargetOpts().AMDGPUPrintfKindVal ==
|
|
clang::TargetOptions::AMDGPUPrintfKind::Buffered);
|
|
auto Printf = llvm::emitAMDGPUPrintfCall(IRB, Args, isBuffered);
|
|
Builder.SetInsertPoint(IRB.GetInsertBlock(), IRB.GetInsertPoint());
|
|
return RValue::get(Printf);
|
|
}
|
|
|
|
RValue CodeGenFunction::EmitOpenMPDevicePrintfCallExpr(const CallExpr *E) {
|
|
assert(getTarget().getTriple().isNVPTX() ||
|
|
getTarget().getTriple().isAMDGCN());
|
|
return EmitDevicePrintfCallExpr(E, this, GetOpenMPVprintfDeclaration(CGM),
|
|
true);
|
|
}
|