diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst --- a/clang/docs/UsersManual.rst +++ b/clang/docs/UsersManual.rst @@ -3093,6 +3093,15 @@ 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 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 `_. There are two flavors @@ -3510,6 +3519,51 @@ `Some tests might fail `_ on ``x86_64-w64-mingw32``. +.. _spir-v: + +SPIR-V support +-------------- + +Clang supports generation of SPIR-V conformant to `the OpenCL Environment +Specification +`_. + +To generate SPIR-V binaries, Clang uses the external ``llvm-spirv`` tool from the +`SPIRV-LLVM-Translator repo +`_. + +Prior to the generation of SPIR-V binary with Clang, ``llvm-spirv`` +should be built or installed. Please refer to `the following 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 +`_. + +`The versioning +`_ 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 +`_. + +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 --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -116,6 +116,10 @@ "ignoring '%0' option as it is not currently supported for target '%1'">, InGroup; +def warn_drv_spirv_linking_multiple_inputs_unsupported: Warning< + "Linking multiple input files is not supported for SPIR-V yet">, + InGroup; + 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 --- a/clang/include/clang/Driver/Tool.h +++ b/clang/include/clang/Driver/Tool.h @@ -52,6 +52,7 @@ 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 --- a/clang/include/clang/Driver/ToolChain.h +++ b/clang/include/clang/Driver/ToolChain.h @@ -384,6 +384,9 @@ /// 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 --- 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 @@ } } + // 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 @@ // 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 @@ 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 @@ 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 @@ case llvm::Triple::ve: TC = std::make_unique(*this, Target, Args); break; + case llvm::Triple::spirv32: + case llvm::Triple::spirv64: + TC = std::make_unique(*this, Target, Args); + break; default: if (Target.getVendor() == llvm::Triple::Myriad) TC = std::make_unique(*this, Target, diff --git a/clang/lib/Driver/ToolChain.cpp b/clang/lib/Driver/ToolChain.cpp --- a/clang/lib/Driver/ToolChain.cpp +++ b/clang/lib/Driver/ToolChain.cpp @@ -260,7 +260,7 @@ 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.h b/clang/lib/Driver/ToolChains/Clang.h --- a/clang/lib/Driver/ToolChains/Clang.h +++ b/clang/lib/Driver/ToolChains/Clang.h @@ -26,6 +26,10 @@ /// 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 @@ 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/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7126,11 +7126,11 @@ 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/SPIRV.h b/clang/lib/Driver/ToolChains/SPIRV.h --- a/clang/lib/Driver/ToolChains/SPIRV.h +++ b/clang/lib/Driver/ToolChains/SPIRV.h @@ -41,6 +41,39 @@ } // namespace SPIRV } // namespace tools + +namespace toolchains { + +class LLVM_LIBRARY_VISIBILITY SPIRVToolChain final : public ToolChain { + mutable std::unique_ptr 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/lib/Driver/ToolChains/SPIRV.cpp b/clang/lib/Driver/ToolChains/SPIRV.cpp --- 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 @@ 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 @@ 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(*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/test/Driver/spirv-toolchain.cl b/clang/test/Driver/spirv-toolchain.cl new file mode 100644 --- /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