[polly] r309560 - [GPGPU] Add support for NVIDIA libdevice

Tobias Grosser via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 31 07:03:16 PDT 2017


Author: grosser
Date: Mon Jul 31 07:03:16 2017
New Revision: 309560

URL: http://llvm.org/viewvc/llvm-project?rev=309560&view=rev
Log:
[GPGPU] Add support for NVIDIA libdevice

Summary:
This allows us to map functions such as exp, expf, expl, for which no
LLVM intrinsics exist. Instead, we link to NVIDIA's libdevice which provides
high-performance implementations of a wide range of (math) functions. We
currently link only a small subset, the exp, cos and copysign functions. Other
functions will be enabled as needed.

Reviewers: bollu, singam-sanjay

Reviewed By: bollu

Subscribers: tstellar, tra, nemanjai, pollydev, mgorny, llvm-commits, kbarton

Tags: #polly

Differential Revision: https://reviews.llvm.org/D35703

Added:
    polly/trunk/test/GPGPU/Inputs/
    polly/trunk/test/GPGPU/Inputs/libdevice-functions-copied-into-kernel_libdevice.ll
    polly/trunk/test/GPGPU/libdevice-functions-copied-into-kernel.ll
Modified:
    polly/trunk/lib/CMakeLists.txt
    polly/trunk/lib/CodeGen/PPCGCodeGeneration.cpp
    polly/trunk/test/lit.site.cfg.in

Modified: polly/trunk/lib/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/polly/trunk/lib/CMakeLists.txt?rev=309560&r1=309559&r2=309560&view=diff
==============================================================================
--- polly/trunk/lib/CMakeLists.txt (original)
+++ polly/trunk/lib/CMakeLists.txt Mon Jul 31 07:03:16 2017
@@ -107,6 +107,8 @@ else ()
     LLVMipo
     LLVMMC
     LLVMPasses
+    LLVMLinker
+    LLVMIRReader
     ${nvptx_libs}
     # The libraries below are required for darwin: http://PR26392
     LLVMBitReader

Modified: polly/trunk/lib/CodeGen/PPCGCodeGeneration.cpp
URL: http://llvm.org/viewvc/llvm-project/polly/trunk/lib/CodeGen/PPCGCodeGeneration.cpp?rev=309560&r1=309559&r2=309560&view=diff
==============================================================================
--- polly/trunk/lib/CodeGen/PPCGCodeGeneration.cpp (original)
+++ polly/trunk/lib/CodeGen/PPCGCodeGeneration.cpp Mon Jul 31 07:03:16 2017
@@ -31,6 +31,8 @@
 #include "llvm/Analysis/TargetTransformInfo.h"
 #include "llvm/IR/LegacyPassManager.h"
 #include "llvm/IR/Verifier.h"
+#include "llvm/IRReader/IRReader.h"
+#include "llvm/Linker/Linker.h"
 #include "llvm/Support/TargetRegistry.h"
 #include "llvm/Support/TargetSelect.h"
 #include "llvm/Target/TargetMachine.h"
@@ -102,6 +104,11 @@ static cl::opt<bool>
                               cl::Hidden, cl::init(false), cl::ZeroOrMore,
                               cl::cat(PollyCategory));
 
+static cl::opt<std::string> CUDALibDevice(
+    "polly-acc-libdevice", cl::desc("Path to CUDA libdevice"), cl::Hidden,
+    cl::init("/usr/local/cuda/nvvm/libdevice/libdevice.compute_20.10.ll"),
+    cl::ZeroOrMore, cl::cat(PollyCategory));
+
 static cl::opt<std::string>
     CudaVersion("polly-acc-cuda-version",
                 cl::desc("The CUDA version to compile for"), cl::Hidden,
@@ -605,6 +612,12 @@ private:
   /// @param F The function to remove references to.
   void clearLoops(Function *F);
 
+  /// Check if the scop requires to be linked with CUDA's libdevice.
+  bool requiresCUDALibDevice();
+
+  /// Link with the NVIDIA libdevice library (if needed and available).
+  void addCUDALibDevice();
+
   /// Finalize the generation of the kernel function.
   ///
   /// Free the LLVM-IR module corresponding to the kernel and -- if requested --
@@ -1324,13 +1337,32 @@ isl_bool collectReferencesInGPUStmt(__is
   return isl_bool_true;
 }
 
+/// A list of functions that are available in NVIDIA's libdevice.
+const std::set<std::string> CUDALibDeviceFunctions = {
+    "exp",  "expf",  "expl",     "cos",       "cosf",
+    "sqrt", "sqrtf", "copysign", "copysignf", "copysignl"};
+
+/// Return the corresponding CUDA libdevice function name for @p F.
+///
+/// Return "" if we are not compiling for CUDA.
+std::string getCUDALibDeviceFuntion(Function *F) {
+  if (CUDALibDeviceFunctions.count(F->getName()))
+    return std::string("__nv_") + std::string(F->getName());
+
+  return "";
+}
+
 /// Check if F is a function that we can code-generate in a GPU kernel.
-static bool isValidFunctionInKernel(llvm::Function *F) {
+static bool isValidFunctionInKernel(llvm::Function *F, bool AllowLibDevice) {
   assert(F && "F is an invalid pointer");
   // We string compare against the name of the function to allow
   // all variants of the intrinsic "llvm.sqrt.*", "llvm.fabs", and
   // "llvm.copysign".
   const StringRef Name = F->getName();
+
+  if (AllowLibDevice && getCUDALibDeviceFuntion(F).length() > 0)
+    return true;
+
   return F->isIntrinsic() &&
          (Name.startswith("llvm.sqrt") || Name.startswith("llvm.fabs") ||
           Name.startswith("llvm.copysign"));
@@ -1346,14 +1378,16 @@ static bool isValidSubtreeValue(llvm::Va
 
 /// Return `Function`s from `RawSubtreeValues`.
 static SetVector<Function *>
-getFunctionsFromRawSubtreeValues(SetVector<Value *> RawSubtreeValues) {
+getFunctionsFromRawSubtreeValues(SetVector<Value *> RawSubtreeValues,
+                                 bool AllowCUDALibDevice) {
   SetVector<Function *> SubtreeFunctions;
   for (Value *It : RawSubtreeValues) {
     Function *F = dyn_cast<Function>(It);
     if (F) {
-      assert(isValidFunctionInKernel(F) && "Code should have bailed out by "
-                                           "this point if an invalid function "
-                                           "were present in a kernel.");
+      assert(isValidFunctionInKernel(F, AllowCUDALibDevice) &&
+             "Code should have bailed out by "
+             "this point if an invalid function "
+             "were present in a kernel.");
       SubtreeFunctions.insert(F);
     }
   }
@@ -1407,8 +1441,11 @@ GPUNodeBuilder::getReferencesInKernel(pp
       make_filter_range(SubtreeValues, isValidSubtreeValue);
   SetVector<Value *> ValidSubtreeValues(ValidSubtreeValuesIt.begin(),
                                         ValidSubtreeValuesIt.end());
+
+  bool AllowCUDALibDevice = Arch == GPUArch::NVPTX64;
+
   SetVector<Function *> ValidSubtreeFunctions(
-      getFunctionsFromRawSubtreeValues(SubtreeValues));
+      getFunctionsFromRawSubtreeValues(SubtreeValues, AllowCUDALibDevice));
 
   // @see IslNodeBuilder::getReferencesInSubtree
   SetVector<Value *> ReplacedValues;
@@ -2232,6 +2269,49 @@ std::string GPUNodeBuilder::createKernel
   return ASMStream.str();
 }
 
+bool GPUNodeBuilder::requiresCUDALibDevice() {
+  for (Function &F : GPUModule->functions()) {
+    if (!F.isDeclaration())
+      continue;
+
+    std::string CUDALibDeviceFunc = getCUDALibDeviceFuntion(&F);
+    if (CUDALibDeviceFunc.length() != 0) {
+      F.setName(CUDALibDeviceFunc);
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void GPUNodeBuilder::addCUDALibDevice() {
+  if (Arch != GPUArch::NVPTX64)
+    return;
+
+  if (requiresCUDALibDevice()) {
+    SMDiagnostic Error;
+
+    errs() << CUDALibDevice << "\n";
+    auto LibDeviceModule =
+        parseIRFile(CUDALibDevice, Error, GPUModule->getContext());
+
+    if (!LibDeviceModule) {
+      BuildSuccessful = false;
+      report_fatal_error("Could not find or load libdevice. Skipping GPU "
+                         "kernel generation. Please set -polly-acc-libdevice "
+                         "accordingly.\n");
+      return;
+    }
+
+    Linker L(*GPUModule);
+
+    // Set an nvptx64 target triple to avoid linker warnings. The original
+    // triple of the libdevice files are nvptx-unknown-unknown.
+    LibDeviceModule->setTargetTriple(Triple::normalize("nvptx64-nvidia-cuda"));
+    L.linkInModule(std::move(LibDeviceModule), Linker::LinkOnlyNeeded);
+  }
+}
+
 std::string GPUNodeBuilder::finalizeKernelFunction() {
 
   if (verifyModule(*GPUModule)) {
@@ -2247,6 +2327,8 @@ std::string GPUNodeBuilder::finalizeKern
     return "";
   }
 
+  addCUDALibDevice();
+
   if (DumpKernelIR)
     outs() << *GPUModule << "\n";
 
@@ -3116,10 +3198,12 @@ public:
   ///
   /// If this basic block does something with a `Function` other than calling
   /// a function that we support in a kernel, return true.
-  bool containsInvalidKernelFunctionInBlock(const BasicBlock *BB) {
+  bool containsInvalidKernelFunctionInBlock(const BasicBlock *BB,
+                                            bool AllowCUDALibDevice) {
     for (const Instruction &Inst : *BB) {
       const CallInst *Call = dyn_cast<CallInst>(&Inst);
-      if (Call && isValidFunctionInKernel(Call->getCalledFunction())) {
+      if (Call && isValidFunctionInKernel(Call->getCalledFunction(),
+                                          AllowCUDALibDevice)) {
         continue;
       }
 
@@ -3135,16 +3219,17 @@ public:
   }
 
   /// Return whether the Scop S uses functions in a way that we do not support.
-  bool containsInvalidKernelFunction(const Scop &S) {
+  bool containsInvalidKernelFunction(const Scop &S, bool AllowCUDALibDevice) {
     for (auto &Stmt : S) {
       if (Stmt.isBlockStmt()) {
-        if (containsInvalidKernelFunctionInBlock(Stmt.getBasicBlock()))
+        if (containsInvalidKernelFunctionInBlock(Stmt.getBasicBlock(),
+                                                 AllowCUDALibDevice))
           return true;
       } else {
         assert(Stmt.isRegionStmt() &&
                "Stmt was neither block nor region statement");
         for (const BasicBlock *BB : Stmt.getRegion()->blocks())
-          if (containsInvalidKernelFunctionInBlock(BB))
+          if (containsInvalidKernelFunctionInBlock(BB, AllowCUDALibDevice))
             return true;
       }
     }
@@ -3232,7 +3317,8 @@ public:
     // kernel. This may lead to a kernel trying to call a function on the host.
     // This also allows us to prevent codegen from trying to take the
     // address of an intrinsic function to send to the kernel.
-    if (containsInvalidKernelFunction(CurrentScop)) {
+    if (containsInvalidKernelFunction(CurrentScop,
+                                      Architecture == GPUArch::NVPTX64)) {
       DEBUG(
           dbgs()
               << "Scop contains function which cannot be materialised in a GPU "

Added: polly/trunk/test/GPGPU/Inputs/libdevice-functions-copied-into-kernel_libdevice.ll
URL: http://llvm.org/viewvc/llvm-project/polly/trunk/test/GPGPU/Inputs/libdevice-functions-copied-into-kernel_libdevice.ll?rev=309560&view=auto
==============================================================================
--- polly/trunk/test/GPGPU/Inputs/libdevice-functions-copied-into-kernel_libdevice.ll (added)
+++ polly/trunk/test/GPGPU/Inputs/libdevice-functions-copied-into-kernel_libdevice.ll Mon Jul 31 07:03:16 2017
@@ -0,0 +1,3 @@
+define float @__nv_expf(float %a) {
+  ret float %a
+}

Added: polly/trunk/test/GPGPU/libdevice-functions-copied-into-kernel.ll
URL: http://llvm.org/viewvc/llvm-project/polly/trunk/test/GPGPU/libdevice-functions-copied-into-kernel.ll?rev=309560&view=auto
==============================================================================
--- polly/trunk/test/GPGPU/libdevice-functions-copied-into-kernel.ll (added)
+++ polly/trunk/test/GPGPU/libdevice-functions-copied-into-kernel.ll Mon Jul 31 07:03:16 2017
@@ -0,0 +1,74 @@
+; RUN: opt %loadPolly -analyze -polly-scops < %s \
+; RUN: -polly-acc-libdevice=%S/Inputs/libdevice-functions-copied-into-kernel_libdevice.ll \
+; RUN:     | FileCheck %s --check-prefix=SCOP
+; RUN: opt %loadPolly -analyze -polly-codegen-ppcg -polly-acc-dump-kernel-ir \
+; RUN: -polly-acc-libdevice=%S/Inputs/libdevice-functions-copied-into-kernel_libdevice.ll \
+; RUN:     < %s | FileCheck %s --check-prefix=KERNEL-IR
+; RUN: opt %loadPolly -S -polly-codegen-ppcg  < %s \
+; RUN: -polly-acc-libdevice=%S/Inputs/libdevice-functions-copied-into-kernel_libdevice.ll \
+; RUN:     | FileCheck %s --check-prefix=HOST-IR
+
+; Test that we do recognise and codegen a kernel that has functions that can
+; be mapped to NVIDIA's libdevice
+
+; REQUIRES: pollyacc
+
+; Check that we model the kernel as a scop.
+; SCOP:      Function: f
+; SCOP-NEXT:       Region: %entry.split---%for.end
+
+; Check that the intrinsic call is present in the kernel IR.
+; KERNEL-IR:   %p_expf = tail call float @__nv_expf(float %A.arr.i.val_p_scalar_)
+
+; Check that kernel launch is generated in host IR.
+; the declare would not be generated unless a call to a kernel exists.
+; HOST-IR: declare void @polly_launchKernel(i8*, i32, i32, i32, i32, i32, i8*)
+
+
+; void f(float *A, float *B, int N) {
+;   for(int i = 0; i < N; i++) {
+;       float tmp0 = A[i];
+;       float tmp1 = expf(tmp1);
+;       B[i] = tmp1;
+;   }
+; }
+
+target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
+
+define void @f(float* %A, float* %B, i32 %N) {
+entry:
+  br label %entry.split
+
+entry.split:                                      ; preds = %entry
+  %cmp1 = icmp sgt i32 %N, 0
+  br i1 %cmp1, label %for.body.lr.ph, label %for.end
+
+for.body.lr.ph:                                   ; preds = %entry.split
+  br label %for.body
+
+for.body:                                         ; preds = %for.body.lr.ph, %for.body
+  %indvars.iv = phi i64 [ 0, %for.body.lr.ph ], [ %indvars.iv.next, %for.body ]
+  %A.arr.i = getelementptr inbounds float, float* %A, i64 %indvars.iv
+  %A.arr.i.val = load float, float* %A.arr.i, align 4
+  ; Call to intrinsics that should be part of the kernel.
+  %expf = tail call float @expf(float %A.arr.i.val)
+  %B.arr.i = getelementptr inbounds float, float* %B, i64 %indvars.iv
+  store float %expf, float* %B.arr.i, align 4
+
+  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
+  %wide.trip.count = zext i32 %N to i64
+  %exitcond = icmp ne i64 %indvars.iv.next, %wide.trip.count
+  br i1 %exitcond, label %for.body, label %for.cond.for.end_crit_edge
+
+for.cond.for.end_crit_edge:                       ; preds = %for.body
+  br label %for.end
+
+for.end:                                          ; preds = %for.cond.for.end_crit_edge, %entry.split
+  ret void
+}
+
+; Function Attrs: nounwind readnone
+declare float @expf(float) #0
+
+attributes #0 = { nounwind readnone }
+

Modified: polly/trunk/test/lit.site.cfg.in
URL: http://llvm.org/viewvc/llvm-project/polly/trunk/test/lit.site.cfg.in?rev=309560&r1=309559&r2=309560&view=diff
==============================================================================
--- polly/trunk/test/lit.site.cfg.in (original)
+++ polly/trunk/test/lit.site.cfg.in Mon Jul 31 07:03:16 2017
@@ -31,6 +31,11 @@ except KeyError:
     key, = e.args
     lit_config.fatal("unable to find %r parameter, use '--param=%s=VALUE'" % (key,key))
 
+# excludes: A list of directories to exclude from the testsuite. The 'Inputs'
+# subdirectories contain auxiliary inputs for various tests in their parent
+# directories.
+config.excludes = ['Inputs']
+
 if config.link_polly_into_tools == '' or \
    config.link_polly_into_tools.lower() == '0' or \
    config.link_polly_into_tools.lower() == 'n' or \




More information about the llvm-commits mailing list