Index: clang/docs/UsersManual.rst =================================================================== --- clang/docs/UsersManual.rst +++ clang/docs/UsersManual.rst @@ -3093,6 +3093,15 @@ Generic Targets ^^^^^^^^^^^^^^^ +- SPIR-V binary can be produced for 32 or 64 bits. + + .. code-block:: console + + $ clang -target spir32 test.cl + $ clang -target spir64 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,45 @@ `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 external tool llvm-spirv from the +`SPIRV-LLVM-Translator repo +`_. + +Prior to the generation of SPIR-V binary with Clang, llvm-spirv tool +should be built/installed. Please refer to `the following instructions +`_ +for more details. E. g. Clang will expect path to llvm-spirv executable to +be present in PATH environment variable. + +`The versioning of translator +`_ +is aligned with Clang major releases, the same applies to the main +development branch. It is therefore important to use the right version of +llvm-spirv in alignment with Clang. For troubleshooting purposes llvm-spirv +can be `tested in isolation +`_. + +Example of 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 SPIR-V binary file with +default name `test.o` for 32 bit and 64 bit accordingly. This file can be imported +by the OpenCL driver that support SPIR-V consumption or it can be compiled further +by offline SPIR-V consumer tools. + .. _clang-cl: clang-cl Index: clang/include/clang/Basic/DiagnosticDriverKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticDriverKinds.td +++ clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -108,6 +108,9 @@ "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">; + 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< Index: clang/include/clang/Driver/Tool.h =================================================================== --- clang/include/clang/Driver/Tool.h +++ 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; } Index: clang/include/clang/Driver/ToolChain.h =================================================================== --- clang/include/clang/Driver/ToolChain.h +++ clang/include/clang/Driver/ToolChain.h @@ -387,6 +387,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; } Index: clang/lib/Driver/Driver.cpp =================================================================== --- clang/lib/Driver/Driver.cpp +++ clang/lib/Driver/Driver.cpp @@ -42,6 +42,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" @@ -3724,6 +3725,14 @@ } } + // 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. @@ -3763,8 +3772,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; } @@ -4337,6 +4353,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) { @@ -4406,6 +4428,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; @@ -5429,6 +5457,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, Index: clang/lib/Driver/ToolChain.cpp =================================================================== --- clang/lib/Driver/ToolChain.cpp +++ 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(); } Index: clang/lib/Driver/ToolChains/Clang.h =================================================================== --- clang/lib/Driver/ToolChains/Clang.h +++ 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; } Index: clang/lib/Driver/ToolChains/Clang.cpp =================================================================== --- clang/lib/Driver/ToolChains/Clang.cpp +++ clang/lib/Driver/ToolChains/Clang.cpp @@ -7064,11 +7064,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() {} Index: clang/lib/Driver/ToolChains/SPIRV.h =================================================================== --- clang/lib/Driver/ToolChains/SPIRV.h +++ 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 Index: clang/lib/Driver/ToolChains/SPIRV.cpp =================================================================== --- clang/lib/Driver/ToolChains/SPIRV.cpp +++ 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; @@ -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); +} Index: clang/test/Driver/spirv-toolchain.cl =================================================================== --- /dev/null +++ clang/test/Driver/spirv-toolchain.cl @@ -0,0 +1,57 @@ +// Check object emission. +// RUN: %clang -### -no-canonical-prefixes -target spirv64 -x cl -c %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 -### -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-text" "-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-text" "-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-text" "-o" [[S:".*s"]] +// TMP: {{".*llvm-spirv.*"}} [[S]] "-to-binary" "-o" {{".*o"}} Index: clang/test/Misc/warning-flags.c =================================================================== --- clang/test/Misc/warning-flags.c +++ clang/test/Misc/warning-flags.c @@ -18,7 +18,7 @@ The list of warnings below should NEVER grow. It should gradually shrink to 0. -CHECK: Warnings without flags (67): +CHECK: Warnings without flags (68): CHECK-NEXT: ext_expected_semi_decl_list CHECK-NEXT: ext_explicit_specialization_storage_class @@ -47,6 +47,7 @@ CHECK-NEXT: warn_drv_assuming_mfloat_abi_is CHECK-NEXT: warn_drv_clang_unsupported CHECK-NEXT: warn_drv_pch_not_first_include +CHECK-NEXT: warn_drv_spirv_linking_multiple_inputs_unsupported CHECK-NEXT: warn_dup_category_def CHECK-NEXT: warn_enum_value_overflow CHECK-NEXT: warn_expected_qualified_after_typename