Skip to content

Commit 008efe8

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 d250449 commit 008efe8

File tree

19 files changed

+1931
-6
lines changed

19 files changed

+1931
-6
lines changed

include/LLVMSPIRVExtensions.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,4 @@ EXT(SPV_INTEL_subgroup_matrix_multiply_accumulate)
7878
EXT(SPV_KHR_bfloat16)
7979
EXT(SPV_INTEL_ternary_bitwise_function)
8080
EXT(SPV_INTEL_int4)
81+
EXT(SPV_INTEL_function_variants)

include/LLVMSPIRVOpts.h

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,50 @@ class TranslatorOpts {
240240
}
241241
BuiltinFormat getBuiltinFormat() const noexcept { return SPIRVBuiltinFormat; }
242242

243+
void setFnVarCategory(uint32_t Category) noexcept {
244+
FnVarCategory = Category;
245+
}
246+
std::optional<uint32_t> getFnVarCategory() const noexcept {
247+
return FnVarCategory;
248+
}
249+
250+
void setFnVarFamily(uint32_t Family) noexcept { FnVarFamily = Family; }
251+
std::optional<uint32_t> getFnVarFamily() const noexcept {
252+
return FnVarFamily;
253+
}
254+
255+
void setFnVarArch(uint32_t Arch) noexcept { FnVarArch = Arch; }
256+
std::optional<uint32_t> getFnVarArch() const noexcept { return FnVarArch; }
257+
258+
void setFnVarTarget(uint32_t Target) noexcept { FnVarTarget = Target; }
259+
std::optional<uint32_t> getFnVarTarget() const noexcept {
260+
return FnVarTarget;
261+
}
262+
263+
void setFnVarFeatures(std::vector<uint32_t> Features) noexcept {
264+
FnVarFeatures = Features;
265+
}
266+
std::vector<uint32_t> getFnVarFeatures() const noexcept {
267+
return FnVarFeatures;
268+
}
269+
270+
void setFnVarCapabilities(std::vector<uint32_t> Capabilities) noexcept {
271+
FnVarCapabilities = Capabilities;
272+
}
273+
std::vector<uint32_t> getFnVarCapabilities() const noexcept {
274+
return FnVarCapabilities;
275+
}
276+
277+
void setFnVarSpecEnable(bool Val) noexcept { FnVarSpecEnable = Val; }
278+
bool getFnVarSpecEnable() const noexcept { return FnVarSpecEnable; }
279+
280+
void setFnVarSpvOut(std::string Val) noexcept { FnVarSpvOut = Val; }
281+
std::string getFnVarSpvOut() const noexcept { return FnVarSpvOut; }
282+
283+
// Check that options passed to --fnvar-xxx flags make sense. Return true on
284+
// success, false on failure.
285+
bool validateFnVarOpts() const;
286+
243287
private:
244288
// Common translation options
245289
VersionNumber MaxVersion = VersionNumber::MaximumVersion;
@@ -287,6 +331,15 @@ class TranslatorOpts {
287331

288332
bool PreserveAuxData = false;
289333

334+
std::optional<uint32_t> FnVarCategory = std::nullopt;
335+
std::optional<uint32_t> FnVarFamily = std::nullopt;
336+
std::optional<uint32_t> FnVarArch = std::nullopt;
337+
std::optional<uint32_t> FnVarTarget = std::nullopt;
338+
std::vector<uint32_t> FnVarFeatures = {};
339+
std::vector<uint32_t> FnVarCapabilities = {};
340+
std::string FnVarSpvOut = "";
341+
bool FnVarSpecEnable = false;
342+
290343
BuiltinFormat SPIRVBuiltinFormat = BuiltinFormat::Function;
291344
};
292345

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
@@ -43,6 +43,7 @@
4343
#include <llvm/ADT/SmallVector.h>
4444
#include <llvm/ADT/StringRef.h>
4545
#include <llvm/IR/IntrinsicInst.h>
46+
#include <optional>
4647

4748
using namespace llvm;
4849
using namespace SPIRV;
@@ -68,3 +69,25 @@ void TranslatorOpts::setSPIRVAllowUnknownIntrinsics(
6869
TranslatorOpts::ArgList IntrinsicPrefixList) noexcept {
6970
SPIRVAllowUnknownIntrinsics = IntrinsicPrefixList;
7071
}
72+
73+
bool TranslatorOpts::validateFnVarOpts() const {
74+
if (getFnVarCategory() == std::nullopt &&
75+
(getFnVarFamily() != std::nullopt || getFnVarArch() != std::nullopt)) {
76+
errs() << "FnVar: Device category must be specified if the family or "
77+
"architecture are specified.";
78+
return false;
79+
}
80+
81+
if (getFnVarFamily() == std::nullopt && getFnVarArch() != std::nullopt) {
82+
errs() << "FnVar: Device family must be specified if the architecture is "
83+
"specified.";
84+
return false;
85+
}
86+
87+
if (getFnVarTarget() == std::nullopt && !getFnVarFeatures().empty()) {
88+
errs() << "Device target must be specified if the features are specified.";
89+
return false;
90+
}
91+
92+
return true;
93+
}

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"
@@ -1795,6 +1796,20 @@ Value *SPIRVToLLVM::transValueWithoutDecoration(SPIRVValue *BV, Function *F,
17951796
case OpLabel:
17961797
return mapValue(BV, BasicBlock::Create(*Context, BV->getName(), F));
17971798

1799+
case OpSpecConstantArchitectureINTEL:
1800+
llvm_unreachable(
1801+
"Encountered non-specialized OpSpecConstantArchitectureINTEL");
1802+
return nullptr;
1803+
1804+
case OpSpecConstantTargetINTEL:
1805+
llvm_unreachable("Encountered non-specialized OpSpecConstantTargetINTEL");
1806+
return nullptr;
1807+
1808+
case OpSpecConstantCapabilitiesINTEL:
1809+
llvm_unreachable(
1810+
"Encountered non-specialized OpSpecConstantCapabilitiesINTEL");
1811+
return nullptr;
1812+
17981813
default:
17991814
// do nothing
18001815
break;
@@ -5426,6 +5441,31 @@ bool llvm::readSpirv(LLVMContext &C, const SPIRV::TranslatorOpts &Opts,
54265441
if (!BM)
54275442
return false;
54285443

5444+
if (Opts.getFnVarSpecEnable()) {
5445+
if (!specializeFnVariants(BM.get(), ErrMsg)) {
5446+
return false;
5447+
}
5448+
5449+
// Write out the specialized/targeted module
5450+
if (!BM->getFnVarSpvOut().empty()) {
5451+
auto SaveOpt = SPIRVUseTextFormat;
5452+
auto OFSSpv = std::ofstream(BM->getFnVarSpvOut(), std::ios::binary);
5453+
SPIRVUseTextFormat = false;
5454+
OFSSpv << *BM;
5455+
if (BM->getError(ErrMsg) != SPIRVEC_Success) {
5456+
return false;
5457+
}
5458+
SPIRVUseTextFormat = SaveOpt;
5459+
}
5460+
}
5461+
5462+
if (BM->getExtension().find("SPV_INTEL_function_variants") !=
5463+
BM->getExtension().end()) {
5464+
ErrMsg = "Instructions from SPV_INTEL_function_variants are not "
5465+
"convertible to LLVM IR.";
5466+
return false;
5467+
}
5468+
54295469
M = convertSpirvToLLVM(C, *BM, Opts, ErrMsg).release();
54305470

54315471
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
@@ -907,6 +907,9 @@ class SPIRVCapability : public SPIRVEntryNoId<OpCapability> {
907907
case CapabilityVectorComputeINTEL:
908908
case CapabilityVectorAnyINTEL:
909909
return ExtensionID::SPV_INTEL_vector_compute;
910+
case CapabilityFunctionVariantsINTEL:
911+
case CapabilitySpecConditionalINTEL:
912+
return ExtensionID::SPV_INTEL_function_variants;
910913
default:
911914
return {};
912915
}

lib/SPIRV/libSPIRV/SPIRVErrorEnum.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ _SPIRV_OP(UnspecifiedMemoryModel, "Unspecified Memory Model.")
3131
_SPIRV_OP(RepeatedMemoryModel, "Expects a single OpMemoryModel instruction.")
3232
_SPIRV_OP(DeprecatedExtension,
3333
"Feature requires the following deprecated SPIR-V extension:\n")
34+
_SPIRV_OP(InvalidNumberOfOperands,
35+
"Number of operands does not match the expected count.")
3436

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

0 commit comments

Comments
 (0)