Skip to content

Commit ef85826

Browse files
committed
Add support for SPV_INTEL_function_variants (KhronosGroup#3246)
This PR implements [SPV_INTEL_function_variants](https://github.com/intel/llvm/blob/sycl/sycl/doc/design/spirv-extensions/SPV_INTEL_function_variants.asciidoc). It adds an optional SPIR-V to SPIR-V specialization pass that converts a multitarget module into a targeted one. The multitarget module does not have a LLVM IR representation, the extension only describes the specialization algorithm that takes place before converting the SPIR-V module into LLVM-IR. For this reason, it is only implemented as a part of SPIRVReader and not SPIRVWriter. The specialization is controlled by the user supplying the target device category, family, architecture, target ISA, supported features and/or supported capabilities via CLI flags. For example, to specialize for an Intel x86_64 CPU with Lion Cove microarchitecture that supports SSE, SSE2, SSE3, SSE4.1, SSE4.2, SSE4a, AVX, AVX2 and AVX512f features and Addresses, Linkage, Kernel, Int64 and Int8 capabilities, the user needs to provide the following flags: ``` llvm-spirv -r \ --spirv-ext=+SPV_INTEL_function_variants \ --fnvar-spec-enable \ --fnvar-spv-out targeted.spv \ --fnvar-category 1 --fnvar-family 1 --fnvar-arch 15 \ --fnvar-target 4 --fnvar-features '4,5,6,7,8,9,10,11,12' \ --fnvar-capabilities '4,5,6,11,39' \ multitarget.spv -o targeted.bc ``` Omitting a flag means that the target device supports all values for the flag. For example, in the above example, leaving out the `--fnvar-features` flag means that that the target device supports all features available for the x86_64 target. The integer values passed to the CLI flags are taken from a proposed [targets _registry_](intel/llvm#18822) accompanying the extension. (Capabilities correspond directly to the values defined in the SPIR-V specification). During the specialization pass, the specialization pass compares these CLI-supplied integers with the operands of `OpSpecConstantTargetINTEL`, `OpSpecConstantArchitectureINTEL` and `OpSpecConstantCapabilitiesINTEL` instructions in the input multitarget module, converts these instructions to constant true/false and proceeds with the specialization according to the rules described in the extension. Providing the CLI values as raw integer is not the most user friendly, and the translator does not validate the values in any way (eg., checking that feature X is allowed for target Y). This can be improved after the _registry_ is merged and more mature (version >0). Note: `--spirv-debug` can be used to print out details about what's happening when evaluating the above spec constants. It's useful for getting an insight into why a certain function variant got selected if the selection does not match the expected outcome.
1 parent 4b0b8fd commit ef85826

File tree

19 files changed

+1949
-23
lines changed

19 files changed

+1949
-23
lines changed

include/LLVMSPIRVExtensions.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,4 @@ EXT(SPV_INTEL_subgroup_matrix_multiply_accumulate)
8080
EXT(SPV_KHR_bfloat16)
8181
EXT(SPV_INTEL_ternary_bitwise_function)
8282
EXT(SPV_INTEL_int4)
83+
EXT(SPV_INTEL_function_variants)

include/LLVMSPIRVOpts.h

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,50 @@ class TranslatorOpts {
252252
void setUseLLVMTarget(bool Flag) noexcept { UseLLVMTarget = Flag; }
253253
bool getUseLLVMTarget() const noexcept { return UseLLVMTarget; }
254254

255+
void setFnVarCategory(uint32_t Category) noexcept {
256+
FnVarCategory = Category;
257+
}
258+
std::optional<uint32_t> getFnVarCategory() const noexcept {
259+
return FnVarCategory;
260+
}
261+
262+
void setFnVarFamily(uint32_t Family) noexcept { FnVarFamily = Family; }
263+
std::optional<uint32_t> getFnVarFamily() const noexcept {
264+
return FnVarFamily;
265+
}
266+
267+
void setFnVarArch(uint32_t Arch) noexcept { FnVarArch = Arch; }
268+
std::optional<uint32_t> getFnVarArch() const noexcept { return FnVarArch; }
269+
270+
void setFnVarTarget(uint32_t Target) noexcept { FnVarTarget = Target; }
271+
std::optional<uint32_t> getFnVarTarget() const noexcept {
272+
return FnVarTarget;
273+
}
274+
275+
void setFnVarFeatures(std::vector<uint32_t> Features) noexcept {
276+
FnVarFeatures = Features;
277+
}
278+
std::vector<uint32_t> getFnVarFeatures() const noexcept {
279+
return FnVarFeatures;
280+
}
281+
282+
void setFnVarCapabilities(std::vector<uint32_t> Capabilities) noexcept {
283+
FnVarCapabilities = Capabilities;
284+
}
285+
std::vector<uint32_t> getFnVarCapabilities() const noexcept {
286+
return FnVarCapabilities;
287+
}
288+
289+
void setFnVarSpecEnable(bool Val) noexcept { FnVarSpecEnable = Val; }
290+
bool getFnVarSpecEnable() const noexcept { return FnVarSpecEnable; }
291+
292+
void setFnVarSpvOut(std::string Val) noexcept { FnVarSpvOut = Val; }
293+
std::string getFnVarSpvOut() const noexcept { return FnVarSpvOut; }
294+
295+
// Check that options passed to --fnvar-xxx flags make sense. Return true on
296+
// success, false on failure.
297+
bool validateFnVarOpts() const;
298+
255299
private:
256300
// Common translation options
257301
VersionNumber MaxVersion = VersionNumber::MaximumVersion;
@@ -301,6 +345,15 @@ class TranslatorOpts {
301345

302346
bool PreserveAuxData = false;
303347

348+
std::optional<uint32_t> FnVarCategory = std::nullopt;
349+
std::optional<uint32_t> FnVarFamily = std::nullopt;
350+
std::optional<uint32_t> FnVarArch = std::nullopt;
351+
std::optional<uint32_t> FnVarTarget = std::nullopt;
352+
std::vector<uint32_t> FnVarFeatures = {};
353+
std::vector<uint32_t> FnVarCapabilities = {};
354+
std::string FnVarSpvOut = "";
355+
bool FnVarSpecEnable = false;
356+
304357
BuiltinFormat SPIRVBuiltinFormat = BuiltinFormat::Function;
305358

306359
// Convert LLVM to SPIR-V using the LLVM SPIR-V Backend target

lib/SPIRV/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ set(SRC_LIST
3939
libSPIRV/SPIRVType.cpp
4040
libSPIRV/SPIRVValue.cpp
4141
libSPIRV/SPIRVError.cpp
42+
libSPIRV/SPIRVFnVar.cpp
4243
)
4344
add_llvm_library(LLVMSPIRVLib
4445
${SRC_LIST}

lib/SPIRV/LLVMSPIRVOpts.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include <llvm/ADT/SmallVector.h>
4545
#include <llvm/ADT/StringRef.h>
4646
#include <llvm/IR/IntrinsicInst.h>
47+
#include <optional>
4748

4849
using namespace llvm;
4950
using namespace SPIRV;
@@ -89,3 +90,25 @@ std::vector<std::string> TranslatorOpts::getAllowedSPIRVExtensionNames(
8990
}
9091
return AllowExtNames;
9192
}
93+
94+
bool TranslatorOpts::validateFnVarOpts() const {
95+
if (getFnVarCategory() == std::nullopt &&
96+
(getFnVarFamily() != std::nullopt || getFnVarArch() != std::nullopt)) {
97+
errs() << "FnVar: Device category must be specified if the family or "
98+
"architecture are specified.";
99+
return false;
100+
}
101+
102+
if (getFnVarFamily() == std::nullopt && getFnVarArch() != std::nullopt) {
103+
errs() << "FnVar: Device family must be specified if the architecture is "
104+
"specified.";
105+
return false;
106+
}
107+
108+
if (getFnVarTarget() == std::nullopt && !getFnVarFeatures().empty()) {
109+
errs() << "Device target must be specified if the features are specified.";
110+
return false;
111+
}
112+
113+
return true;
114+
}

lib/SPIRV/SPIRVReader.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include "SPIRVAsm.h"
4242
#include "SPIRVBasicBlock.h"
4343
#include "SPIRVExtInst.h"
44+
#include "SPIRVFnVar.h"
4445
#include "SPIRVFunction.h"
4546
#include "SPIRVInstruction.h"
4647
#include "SPIRVInternal.h"
@@ -1756,6 +1757,20 @@ Value *SPIRVToLLVM::transValueWithoutDecoration(SPIRVValue *BV, Function *F,
17561757
case OpLabel:
17571758
return mapValue(BV, BasicBlock::Create(*Context, BV->getName(), F));
17581759

1760+
case OpSpecConstantArchitectureINTEL:
1761+
llvm_unreachable(
1762+
"Encountered non-specialized OpSpecConstantArchitectureINTEL");
1763+
return nullptr;
1764+
1765+
case OpSpecConstantTargetINTEL:
1766+
llvm_unreachable("Encountered non-specialized OpSpecConstantTargetINTEL");
1767+
return nullptr;
1768+
1769+
case OpSpecConstantCapabilitiesINTEL:
1770+
llvm_unreachable(
1771+
"Encountered non-specialized OpSpecConstantCapabilitiesINTEL");
1772+
return nullptr;
1773+
17591774
default:
17601775
// do nothing
17611776
break;
@@ -5599,6 +5614,31 @@ bool llvm::readSpirv(LLVMContext &C, const SPIRV::TranslatorOpts &Opts,
55995614
if (!BM)
56005615
return false;
56015616

5617+
if (Opts.getFnVarSpecEnable()) {
5618+
if (!specializeFnVariants(BM.get(), ErrMsg)) {
5619+
return false;
5620+
}
5621+
5622+
// Write out the specialized/targeted module
5623+
if (!BM->getFnVarSpvOut().empty()) {
5624+
auto SaveOpt = SPIRVUseTextFormat;
5625+
auto OFSSpv = std::ofstream(BM->getFnVarSpvOut(), std::ios::binary);
5626+
SPIRVUseTextFormat = false;
5627+
OFSSpv << *BM;
5628+
if (BM->getError(ErrMsg) != SPIRVEC_Success) {
5629+
return false;
5630+
}
5631+
SPIRVUseTextFormat = SaveOpt;
5632+
}
5633+
}
5634+
5635+
if (BM->getExtension().find("SPV_INTEL_function_variants") !=
5636+
BM->getExtension().end()) {
5637+
ErrMsg = "Instructions from SPV_INTEL_function_variants are not "
5638+
"convertible to LLVM IR.";
5639+
return false;
5640+
}
5641+
56025642
M = convertSpirvToLLVM(C, *BM, Opts, ErrMsg).release();
56035643

56045644
if (!M)

lib/SPIRV/libSPIRV/SPIRVEntry.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include "SPIRVBasicBlock.h"
4343
#include "SPIRVDebug.h"
4444
#include "SPIRVDecorate.h"
45+
#include "SPIRVFnVar.h"
4546
#include "SPIRVFunction.h"
4647
#include "SPIRVInstruction.h"
4748
#include "SPIRVMemAliasingINTEL.h"

lib/SPIRV/libSPIRV/SPIRVEntry.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,9 @@ class SPIRVCapability : public SPIRVEntryNoId<OpCapability> {
914914
return ExtensionID::SPV_INTEL_subgroup_requirements;
915915
case CapabilityFPFastMathModeINTEL:
916916
return ExtensionID::SPV_INTEL_fp_fast_math_mode;
917+
case CapabilityFunctionVariantsINTEL:
918+
case CapabilitySpecConditionalINTEL:
919+
return ExtensionID::SPV_INTEL_function_variants;
917920
default:
918921
return {};
919922
}

lib/SPIRV/libSPIRV/SPIRVErrorEnum.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ _SPIRV_OP(UnspecifiedMemoryModel, "Unspecified Memory Model.")
2828
_SPIRV_OP(RepeatedMemoryModel, "Expects a single OpMemoryModel instruction.")
2929
_SPIRV_OP(UnsupportedVarArgFunction,
3030
"Variadic functions other than 'printf' are not supported in SPIR-V.")
31+
_SPIRV_OP(InvalidNumberOfOperands,
32+
"Number of operands does not match the expected count.")
3133

3234
/* This is the last error code to have a maximum valid value to compare to */
3335
_SPIRV_OP(InternalMaxErrorCode, "Unknown error code")

0 commit comments

Comments
 (0)