[clang] 0045d01 - [SPIR-V] Add a toolchain for SPIR-V in clang

Anastasia Stulova via cfe-commits cfe-commits at lists.llvm.org
Thu Dec 23 07:10:28 PST 2021


Author: Anastasia Stulova
Date: 2021-12-23T15:10:09Z
New Revision: 0045d01af96ff56c5b62d133be076020a33867ff

URL: https://github.com/llvm/llvm-project/commit/0045d01af96ff56c5b62d133be076020a33867ff
DIFF: https://github.com/llvm/llvm-project/commit/0045d01af96ff56c5b62d133be076020a33867ff.diff

LOG: [SPIR-V] Add a toolchain for SPIR-V in clang

This patch adds a toolchain (TC) for SPIR-V along with the
following changes in Driver and base ToolChain and Tool.
This is required to provide a mechanism in clang to bypass
SPIR-V backend in LLVM for SPIR-V until it lands in LLVM and
matures.

The SPIR-V code is generated by the SPIRV-LLVM translator tool
named 'llvm-spirv' that is sought in 'PATH'.

The compilation phases/actions should be bound for SPIR-V in
the meantime as following:

    compile -> tools::Clang
    backend -> tools::SPIRV::Translator
    assemble -> tools::SPIRV::Translator

However, Driver’s ToolSelector collapses compile-backend-assemble
and compile-backend sequences to tools::Clang. To prevent this,
added new {use,has}IntegratedBackend properties in ToolChain and
Tool to which the ToolSelector reacts on, and which SPIR-V TC
overrides.

Linking of multiple input files is currently not supported but
can be added separately.

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

Co-authored-by: Henry Linjamäki <henry.linjamaki at parmance.com>

Added: 
    clang/test/Driver/spirv-toolchain.cl

Modified: 
    clang/docs/UsersManual.rst
    clang/include/clang/Basic/DiagnosticDriverKinds.td
    clang/include/clang/Driver/Tool.h
    clang/include/clang/Driver/ToolChain.h
    clang/lib/Driver/Driver.cpp
    clang/lib/Driver/ToolChain.cpp
    clang/lib/Driver/ToolChains/Clang.cpp
    clang/lib/Driver/ToolChains/Clang.h
    clang/lib/Driver/ToolChains/SPIRV.cpp
    clang/lib/Driver/ToolChains/SPIRV.h

Removed: 
    


################################################################################
diff  --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst
index ce66ef58fca62..3f9947afc29be 100644
--- a/clang/docs/UsersManual.rst
+++ b/clang/docs/UsersManual.rst
@@ -3093,6 +3093,15 @@ There is a set of concrete HW architectures that OpenCL can be compiled for.
 Generic Targets
 ^^^^^^^^^^^^^^^
 
+- A SPIR-V binary can be produced for 32 or 64 bit targets.
+
+   .. code-block:: console
+
+    $ clang -target spirv32 test.cl
+    $ clang -target spirv64 test.cl
+
+  More details can be found in :ref:`the SPIR-V support section <spir-v>`.
+
 - SPIR is available as a generic target to allow portable bitcode to be produced
   that can be used across GPU toolchains. The implementation follows `the SPIR
   specification <https://www.khronos.org/spir>`_. There are two flavors
@@ -3510,6 +3519,51 @@ Clang expects the GCC executable "gcc.exe" compiled for
 `Some tests might fail <https://bugs.llvm.org/show_bug.cgi?id=9072>`_ on
 ``x86_64-w64-mingw32``.
 
+.. _spir-v:
+
+SPIR-V support
+--------------
+
+Clang supports generation of SPIR-V conformant to `the OpenCL Environment
+Specification
+<https://www.khronos.org/registry/OpenCL/specs/3.0-unified/html/OpenCL_Env.html>`_.
+
+To generate SPIR-V binaries, Clang uses the external ``llvm-spirv`` tool from the
+`SPIRV-LLVM-Translator repo
+<https://github.com/KhronosGroup/SPIRV-LLVM-Translator>`_.
+
+Prior to the generation of SPIR-V binary with Clang, ``llvm-spirv``
+should be built or installed. Please refer to `the following instructions
+<https://github.com/KhronosGroup/SPIRV-LLVM-Translator#build-instructions>`_
+for more details. Clang will expects the ``llvm-spirv`` executable to
+be present in the ``PATH`` environment variable. Clang uses ``llvm-spirv``
+with `the conformant assembly syntax package
+<https://github.com/KhronosGroup/SPIRV-LLVM-Translator/#build-with-spirv-tools>`_.
+
+`The versioning
+<https://github.com/KhronosGroup/SPIRV-LLVM-Translator/releases>`_ of
+``llvm-spirv`` is aligned with Clang major releases. The same applies to the
+main development branch. It is therefore important to ensure the ``llvm-spirv``
+version is in alignment with the Clang version. For troubleshooting purposes
+``llvm-spirv`` can be `tested in isolation
+<https://github.com/KhronosGroup/SPIRV-LLVM-Translator#test-instructions>`_.
+
+Example usage for OpenCL kernel compilation:
+
+   .. code-block:: console
+
+     $ clang -target spirv32 test.cl
+     $ clang -target spirv64 test.cl
+
+Both invocations of Clang will result in the generation of a SPIR-V binary file
+`test.o` for 32 bit and 64 bit respectively. This file can be imported
+by an OpenCL driver that support SPIR-V consumption or it can be compiled
+further by offline SPIR-V consumer tools.
+
+Converting to SPIR-V produced with the optimization levels other than `-O0` is
+currently available as an experimental feature and it is not guaranteed to work
+in all cases.
+
 .. _clang-cl:
 
 clang-cl

diff  --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td
index 2aeb987725e19..9776cd7b7b250 100644
--- a/clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -116,6 +116,10 @@ def warn_drv_unsupported_option_for_target : Warning<
   "ignoring '%0' option as it is not currently supported for target '%1'">,
   InGroup<OptionIgnored>;
 
+def warn_drv_spirv_linking_multiple_inputs_unsupported: Warning<
+  "Linking multiple input files is not supported for SPIR-V yet">,
+  InGroup<OptionIgnored>;
+
 def err_drv_invalid_thread_model_for_target : Error<
   "invalid thread model '%0' in '%1' for this target">;
 def err_drv_invalid_linker_name : Error<

diff  --git a/clang/include/clang/Driver/Tool.h b/clang/include/clang/Driver/Tool.h
index cc0a09fb27472..42cf99a4a9703 100644
--- a/clang/include/clang/Driver/Tool.h
+++ b/clang/include/clang/Driver/Tool.h
@@ -52,6 +52,7 @@ class Tool {
   const ToolChain &getToolChain() const { return TheToolChain; }
 
   virtual bool hasIntegratedAssembler() const { return false; }
+  virtual bool hasIntegratedBackend() const { return true; }
   virtual bool canEmitIR() const { return false; }
   virtual bool hasIntegratedCPP() const = 0;
   virtual bool isLinkJob() const { return false; }

diff  --git a/clang/include/clang/Driver/ToolChain.h b/clang/include/clang/Driver/ToolChain.h
index e7b13eec3fc14..4afc9bf36b5f7 100644
--- a/clang/include/clang/Driver/ToolChain.h
+++ b/clang/include/clang/Driver/ToolChain.h
@@ -384,6 +384,9 @@ class ToolChain {
   /// Check if the toolchain should use the integrated assembler.
   virtual bool useIntegratedAs() const;
 
+  /// Check if the toolchain should use the integrated backend.
+  virtual bool useIntegratedBackend() const { return true; }
+
   /// Check if the toolchain should use AsmParser to parse inlineAsm when
   /// integrated assembler is not default.
   virtual bool parseInlineAsmUsingAsmParser() const { return false; }

diff  --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index 45bfcefd49f4f..3b551ea94cc24 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -43,6 +43,7 @@
 #include "ToolChains/PPCLinux.h"
 #include "ToolChains/PS4CPU.h"
 #include "ToolChains/RISCVToolchain.h"
+#include "ToolChains/SPIRV.h"
 #include "ToolChains/Solaris.h"
 #include "ToolChains/TCE.h"
 #include "ToolChains/VEToolchain.h"
@@ -3774,6 +3775,14 @@ void Driver::BuildActions(Compilation &C, DerivedArgList &Args,
     }
   }
 
+  // FIXME: Linking separate translation units for SPIR-V is not supported yet.
+  // It can be done either by LLVM IR linking before conversion of the final
+  // linked module to SPIR-V or external SPIR-V linkers can be used e.g.
+  // spirv-link.
+  if (C.getDefaultToolChain().getTriple().isSPIRV() && Inputs.size() > 1) {
+    Diag(clang::diag::warn_drv_spirv_linking_multiple_inputs_unsupported);
+  }
+
   handleArguments(C, Args, Inputs, Actions);
 
   // Builder to be used to build offloading actions.
@@ -3813,8 +3822,15 @@ void Driver::BuildActions(Compilation &C, DerivedArgList &Args,
       // Queue linker inputs.
       if (Phase == phases::Link) {
         assert(Phase == PL.back() && "linking must be final compilation step.");
-        LinkerInputs.push_back(Current);
-        Current = nullptr;
+        // Compilation phases are setup per language, however for SPIR-V the
+        // final linking phase is meaningless since the compilation phase
+        // produces the final binary.
+        // FIXME: OpenCL - we could strip linking phase out from OpenCL
+        // compilation phases if we could verify it is not needed by any target.
+        if (!C.getDefaultToolChain().getTriple().isSPIRV()) {
+          LinkerInputs.push_back(Current);
+          Current = nullptr;
+        }
         break;
       }
 
@@ -4387,6 +4403,12 @@ class ToolSelector final {
     if (!T)
       return nullptr;
 
+    // Can't collapse if we don't have codegen support unless we are
+    // emitting LLVM IR.
+    bool OutputIsLLVM = types::isLLVMIR(ActionInfo[0].JA->getType());
+    if (!T->hasIntegratedBackend() && !(OutputIsLLVM && T->canEmitIR()))
+      return nullptr;
+
     // When using -fembed-bitcode, it is required to have the same tool (clang)
     // for both CompilerJA and BackendJA. Otherwise, combine two stages.
     if (EmbedBitcode) {
@@ -4456,6 +4478,12 @@ class ToolSelector final {
     if (!T)
       return nullptr;
 
+    // Can't collapse if we don't have codegen support unless we are
+    // emitting LLVM IR.
+    bool OutputIsLLVM = types::isLLVMIR(ActionInfo[0].JA->getType());
+    if (!T->hasIntegratedBackend() && !(OutputIsLLVM && T->canEmitIR()))
+      return nullptr;
+
     if (T->canEmitIR() && ((SaveTemps && !InputIsBitcode) || EmbedBitcode))
       return nullptr;
 
@@ -5479,6 +5507,10 @@ const ToolChain &Driver::getToolChain(const ArgList &Args,
       case llvm::Triple::ve:
         TC = std::make_unique<toolchains::VEToolChain>(*this, Target, Args);
         break;
+      case llvm::Triple::spirv32:
+      case llvm::Triple::spirv64:
+        TC = std::make_unique<toolchains::SPIRVToolChain>(*this, Target, Args);
+        break;
       default:
         if (Target.getVendor() == llvm::Triple::Myriad)
           TC = std::make_unique<toolchains::MyriadToolChain>(*this, Target,

diff  --git a/clang/lib/Driver/ToolChain.cpp b/clang/lib/Driver/ToolChain.cpp
index ac033dd427c2d..50c89aaadc189 100644
--- a/clang/lib/Driver/ToolChain.cpp
+++ b/clang/lib/Driver/ToolChain.cpp
@@ -260,7 +260,7 @@ bool ToolChain::IsUnwindTablesDefault(const ArgList &Args) const {
 
 Tool *ToolChain::getClang() const {
   if (!Clang)
-    Clang.reset(new tools::Clang(*this));
+    Clang.reset(new tools::Clang(*this, useIntegratedBackend()));
   return Clang.get();
 }
 

diff  --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 2a3723975568b..ca62229b8153e 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -7126,11 +7126,11 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
   Args.ClaimAllArgs(options::OPT_emit_llvm);
 }
 
-Clang::Clang(const ToolChain &TC)
+Clang::Clang(const ToolChain &TC, bool HasIntegratedBackend)
     // CAUTION! The first constructor argument ("clang") is not arbitrary,
     // as it is for other tools. Some operations on a Tool actually test
     // whether that tool is Clang based on the Tool's Name as a string.
-    : Tool("clang", "clang frontend", TC) {}
+    : Tool("clang", "clang frontend", TC), HasBackend(HasIntegratedBackend) {}
 
 Clang::~Clang() {}
 

diff  --git a/clang/lib/Driver/ToolChains/Clang.h b/clang/lib/Driver/ToolChains/Clang.h
index d4b4988b4a8cc..00e0490e069b5 100644
--- a/clang/lib/Driver/ToolChains/Clang.h
+++ b/clang/lib/Driver/ToolChains/Clang.h
@@ -26,6 +26,10 @@ namespace tools {
 
 /// Clang compiler tool.
 class LLVM_LIBRARY_VISIBILITY Clang : public Tool {
+  // Indicates whether this instance has integrated backend using
+  // internal LLVM infrastructure.
+  bool HasBackend;
+
 public:
   static const char *getBaseInputName(const llvm::opt::ArgList &Args,
                                       const InputInfo &Input);
@@ -99,11 +103,12 @@ class LLVM_LIBRARY_VISIBILITY Clang : public Tool {
       const InputInfo &Input, const llvm::opt::ArgList &Args) const;
 
 public:
-  Clang(const ToolChain &TC);
+  Clang(const ToolChain &TC, bool HasIntegratedBackend = true);
   ~Clang() override;
 
   bool hasGoodDiagnostics() const override { return true; }
   bool hasIntegratedAssembler() const override { return true; }
+  bool hasIntegratedBackend() const override { return HasBackend; }
   bool hasIntegratedCPP() const override { return true; }
   bool canEmitIR() const override { return true; }
 

diff  --git a/clang/lib/Driver/ToolChains/SPIRV.cpp b/clang/lib/Driver/ToolChains/SPIRV.cpp
index 16e72d3c733f1..50d03e79bbb08 100644
--- a/clang/lib/Driver/ToolChains/SPIRV.cpp
+++ b/clang/lib/Driver/ToolChains/SPIRV.cpp
@@ -13,6 +13,7 @@
 #include "clang/Driver/Options.h"
 
 using namespace clang::driver;
+using namespace clang::driver::toolchains;
 using namespace clang::driver::tools;
 using namespace llvm::opt;
 
@@ -27,7 +28,7 @@ void SPIRV::constructTranslateCommand(Compilation &C, const Tool &T,
   if (Input.getType() == types::TY_PP_Asm)
     CmdArgs.push_back("-to-binary");
   if (Output.getType() == types::TY_PP_Asm)
-    CmdArgs.push_back("-spirv-text");
+    CmdArgs.push_back("--spirv-tools-dis");
 
   CmdArgs.append({"-o", Output.getFilename()});
 
@@ -47,3 +48,25 @@ void SPIRV::Translator::ConstructJob(Compilation &C, const JobAction &JA,
     llvm_unreachable("Invalid number of input files.");
   constructTranslateCommand(C, *this, JA, Output, Inputs[0], {});
 }
+
+clang::driver::Tool *SPIRVToolChain::getTranslator() const {
+  if (!Translator)
+    Translator = std::make_unique<SPIRV::Translator>(*this);
+  return Translator.get();
+}
+
+clang::driver::Tool *SPIRVToolChain::SelectTool(const JobAction &JA) const {
+  Action::ActionClass AC = JA.getKind();
+  return SPIRVToolChain::getTool(AC);
+}
+
+clang::driver::Tool *SPIRVToolChain::getTool(Action::ActionClass AC) const {
+  switch (AC) {
+  default:
+    break;
+  case Action::BackendJobClass:
+  case Action::AssembleJobClass:
+    return SPIRVToolChain::getTranslator();
+  }
+  return ToolChain::getTool(AC);
+}

diff  --git a/clang/lib/Driver/ToolChains/SPIRV.h b/clang/lib/Driver/ToolChains/SPIRV.h
index 35d0446bd8b84..229f7018e3b50 100644
--- a/clang/lib/Driver/ToolChains/SPIRV.h
+++ b/clang/lib/Driver/ToolChains/SPIRV.h
@@ -41,6 +41,39 @@ class LLVM_LIBRARY_VISIBILITY Translator : public Tool {
 
 } // namespace SPIRV
 } // namespace tools
+
+namespace toolchains {
+
+class LLVM_LIBRARY_VISIBILITY SPIRVToolChain final : public ToolChain {
+  mutable std::unique_ptr<Tool> Translator;
+
+public:
+  SPIRVToolChain(const Driver &D, const llvm::Triple &Triple,
+                 const llvm::opt::ArgList &Args)
+      : ToolChain(D, Triple, Args) {}
+
+  bool useIntegratedAs() const override { return true; }
+  bool useIntegratedBackend() const override { return false; }
+
+  bool IsMathErrnoDefault() const override { return false; }
+  bool isCrossCompiling() const override { return true; }
+  bool isPICDefault() const override { return false; }
+  bool isPIEDefault(const llvm::opt::ArgList &Args) const override {
+    return false;
+  }
+  bool isPICDefaultForced() const override { return false; }
+  bool SupportsProfiling() const override { return false; }
+
+  clang::driver::Tool *SelectTool(const JobAction &JA) const override;
+
+protected:
+  clang::driver::Tool *getTool(Action::ActionClass AC) const override;
+
+private:
+  clang::driver::Tool *getTranslator() const;
+};
+
+} // namespace toolchains
 } // namespace driver
 } // namespace clang
 #endif

diff  --git a/clang/test/Driver/spirv-toolchain.cl b/clang/test/Driver/spirv-toolchain.cl
new file mode 100644
index 0000000000000..d2562c0b667d7
--- /dev/null
+++ b/clang/test/Driver/spirv-toolchain.cl
@@ -0,0 +1,65 @@
+// Check object emission.
+// RUN: %clang -### -no-canonical-prefixes -target spirv64 -x cl -c %s 2>&1 | FileCheck --check-prefix=SPV64 %s
+// RUN: %clang -### -target spirv64 %s 2>&1 | FileCheck --check-prefix=SPV64 %s
+// RUN: %clang -### -no-canonical-prefixes -target spirv64 -x ir -c %s 2>&1 | FileCheck --check-prefix=SPV64 %s
+// RUN: %clang -### -no-canonical-prefixes -target spirv64 -x clcpp -c %s 2>&1 | FileCheck --check-prefix=SPV64 %s
+// RUN: %clang -### -no-canonical-prefixes -target spirv64 -x c -c %s 2>&1 | FileCheck --check-prefix=SPV64 %s
+
+// SPV64: clang{{.*}} "-cc1" "-triple" "spirv64"
+// SPV64-SAME: "-o" [[BC:".*bc"]]
+// SPV64: {{llvm-spirv.*"}} [[BC]] "-o" {{".*o"}}
+
+// RUN: %clang -### -no-canonical-prefixes -target spirv32 -x cl -c %s 2>&1 | FileCheck --check-prefix=SPV32 %s
+// RUN: %clang -### -target spirv32 %s 2>&1 | FileCheck --check-prefix=SPV32 %s
+// RUN: %clang -### -no-canonical-prefixes -target spirv32 -x ir -c %s 2>&1 | FileCheck --check-prefix=SPV32 %s
+// RUN: %clang -### -no-canonical-prefixes -target spirv32 -x clcpp -c %s 2>&1 | FileCheck --check-prefix=SPV32 %s
+// RUN: %clang -### -no-canonical-prefixes -target spirv32 -x c -c %s 2>&1 | FileCheck --check-prefix=SPV32 %s
+
+// SPV32: clang{{.*}} "-cc1" "-triple" "spirv32"
+// SPV32-SAME: "-o" [[BC:".*bc"]]
+// SPV32: {{llvm-spirv.*"}} [[BC]] "-o" {{".*o"}}
+
+//-----------------------------------------------------------------------------
+// Check Assembly emission.
+// RUN: %clang -### -no-canonical-prefixes -target spirv64 -x cl -S %s 2>&1 | FileCheck --check-prefix=SPT64 %s
+// RUN: %clang -### -no-canonical-prefixes -target spirv64 -x ir -S %s 2>&1 | FileCheck --check-prefix=SPT64 %s
+// RUN: %clang -### -no-canonical-prefixes -target spirv64 -x clcpp -c %s 2>&1 | FileCheck --check-prefix=SPV64 %s
+// RUN: %clang -### -no-canonical-prefixes -target spirv64 -x c -S %s 2>&1 | FileCheck --check-prefix=SPT64 %s
+
+// SPT64: clang{{.*}} "-cc1" "-triple" "spirv64"
+// SPT64-SAME: "-o" [[BC:".*bc"]]
+// SPT64: {{llvm-spirv.*"}} [[BC]] "--spirv-tools-dis" "-o" {{".*s"}}
+
+// RUN: %clang -### -no-canonical-prefixes -target spirv32 -x cl -S %s 2>&1 | FileCheck --check-prefix=SPT32 %s
+// RUN: %clang -### -no-canonical-prefixes -target spirv32 -x ir -S %s 2>&1 | FileCheck --check-prefix=SPT32 %s
+// RUN: %clang -### -no-canonical-prefixes -target spirv32 -x clcpp -c %s 2>&1 | FileCheck --check-prefix=SPV32 %s
+// RUN: %clang -### -no-canonical-prefixes -target spirv32 -x c -S %s 2>&1 | FileCheck --check-prefix=SPT32 %s
+
+// SPT32: clang{{.*}} "-cc1" "-triple" "spirv32"
+// SPT32-SAME: "-o" [[BC:".*bc"]]
+// SPT32: {{llvm-spirv.*"}} [[BC]] "--spirv-tools-dis" "-o" {{".*s"}}
+
+//-----------------------------------------------------------------------------
+// Check assembly input -> object output
+// RUN: %clang -### -no-canonical-prefixes -target spirv64 -x assembler -c %s 2>&1 | FileCheck --check-prefix=ASM %s
+// RUN: %clang -### -no-canonical-prefixes -target spirv32 -x assembler -c %s 2>&1 | FileCheck --check-prefix=ASM %s
+// ASM: {{llvm-spirv.*"}} {{".*"}} "-to-binary" "-o" {{".*o"}}
+
+//-----------------------------------------------------------------------------
+// Check --save-temps.
+// RUN: %clang -### -no-canonical-prefixes -target spirv64 -x cl -c %s --save-temps 2>&1 | FileCheck --check-prefix=TMP %s
+
+// TMP: clang{{.*}} "-cc1" "-triple" "spirv64"
+// TMP-SAME: "-E"
+// TMP-SAME: "-o" [[I:".*i"]]
+// TMP: clang{{.*}} "-cc1" "-triple" "spirv64"
+// TMP-SAME: "-o" [[BC:".*bc"]]
+// TMP-SAME: [[I]]
+// TMP: {{llvm-spirv.*"}} [[BC]] "--spirv-tools-dis" "-o" [[S:".*s"]]
+// TMP: {{llvm-spirv.*"}} [[S]] "-to-binary" "-o" {{".*o"}}
+
+//-----------------------------------------------------------------------------
+// Check that warning occurs if multiple input files are passed.
+// RUN: %clang -### -target spirv64 %s %s 2>&1 | FileCheck --check-prefix=WARN %s
+
+// WARN: warning: Linking multiple input files is not supported for SPIR-V yet


        


More information about the cfe-commits mailing list