Skip to content

Commit 7e13ed6

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 6a96a62 commit 7e13ed6

File tree

19 files changed

+1933
-6
lines changed

19 files changed

+1933
-6
lines changed

include/LLVMSPIRVExtensions.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,4 @@ EXT(SPV_INTEL_2d_block_io)
7979
EXT(SPV_INTEL_subgroup_matrix_multiply_accumulate)
8080
EXT(SPV_KHR_bfloat16)
8181
EXT(SPV_INTEL_ternary_bitwise_function)
82+
EXT(SPV_INTEL_function_variants)

include/LLVMSPIRVOpts.h

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

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

296340
bool PreserveAuxData = false;
297341

342+
std::optional<uint32_t> FnVarCategory = std::nullopt;
343+
std::optional<uint32_t> FnVarFamily = std::nullopt;
344+
std::optional<uint32_t> FnVarArch = std::nullopt;
345+
std::optional<uint32_t> FnVarTarget = std::nullopt;
346+
std::vector<uint32_t> FnVarFeatures = {};
347+
std::vector<uint32_t> FnVarCapabilities = {};
348+
std::string FnVarSpvOut = "";
349+
bool FnVarSpecEnable = false;
350+
298351
BuiltinFormat SPIRVBuiltinFormat = BuiltinFormat::Function;
299352
};
300353

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;
@@ -74,3 +75,25 @@ void TranslatorOpts::setSPIRVAllowUnknownIntrinsics(
7475
TranslatorOpts::ArgList IntrinsicPrefixList) noexcept {
7576
SPIRVAllowUnknownIntrinsics = IntrinsicPrefixList;
7677
}
78+
79+
bool TranslatorOpts::validateFnVarOpts() const {
80+
if (getFnVarCategory() == std::nullopt &&
81+
(getFnVarFamily() != std::nullopt || getFnVarArch() != std::nullopt)) {
82+
errs() << "FnVar: Device category must be specified if the family or "
83+
"architecture are specified.";
84+
return false;
85+
}
86+
87+
if (getFnVarFamily() == std::nullopt && getFnVarArch() != std::nullopt) {
88+
errs() << "FnVar: Device family must be specified if the architecture is "
89+
"specified.";
90+
return false;
91+
}
92+
93+
if (getFnVarTarget() == std::nullopt && !getFnVarFeatures().empty()) {
94+
errs() << "Device target must be specified if the features are specified.";
95+
return false;
96+
}
97+
98+
return true;
99+
}

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"
@@ -1743,6 +1744,20 @@ Value *SPIRVToLLVM::transValueWithoutDecoration(SPIRVValue *BV, Function *F,
17431744
case OpLabel:
17441745
return mapValue(BV, BasicBlock::Create(*Context, BV->getName(), F));
17451746

1747+
case OpSpecConstantArchitectureINTEL:
1748+
llvm_unreachable(
1749+
"Encountered non-specialized OpSpecConstantArchitectureINTEL");
1750+
return nullptr;
1751+
1752+
case OpSpecConstantTargetINTEL:
1753+
llvm_unreachable("Encountered non-specialized OpSpecConstantTargetINTEL");
1754+
return nullptr;
1755+
1756+
case OpSpecConstantCapabilitiesINTEL:
1757+
llvm_unreachable(
1758+
"Encountered non-specialized OpSpecConstantCapabilitiesINTEL");
1759+
return nullptr;
1760+
17461761
default:
17471762
// do nothing
17481763
break;
@@ -5406,6 +5421,31 @@ bool llvm::readSpirv(LLVMContext &C, const SPIRV::TranslatorOpts &Opts,
54065421
if (!BM)
54075422
return false;
54085423

5424+
if (Opts.getFnVarSpecEnable()) {
5425+
if (!specializeFnVariants(BM.get(), ErrMsg)) {
5426+
return false;
5427+
}
5428+
5429+
// Write out the specialized/targeted module
5430+
if (!BM->getFnVarSpvOut().empty()) {
5431+
auto SaveOpt = SPIRVUseTextFormat;
5432+
auto OFSSpv = std::ofstream(BM->getFnVarSpvOut(), std::ios::binary);
5433+
SPIRVUseTextFormat = false;
5434+
OFSSpv << *BM;
5435+
if (BM->getError(ErrMsg) != SPIRVEC_Success) {
5436+
return false;
5437+
}
5438+
SPIRVUseTextFormat = SaveOpt;
5439+
}
5440+
}
5441+
5442+
if (BM->getExtension().find("SPV_INTEL_function_variants") !=
5443+
BM->getExtension().end()) {
5444+
ErrMsg = "Instructions from SPV_INTEL_function_variants are not "
5445+
"convertible to LLVM IR.";
5446+
return false;
5447+
}
5448+
54095449
M = convertSpirvToLLVM(C, *BM, Opts, ErrMsg).release();
54105450

54115451
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
@@ -911,6 +911,9 @@ class SPIRVCapability : public SPIRVEntryNoId<OpCapability> {
911911
return ExtensionID::SPV_INTEL_subgroup_requirements;
912912
case CapabilityFPFastMathModeINTEL:
913913
return ExtensionID::SPV_INTEL_fp_fast_math_mode;
914+
case CapabilityFunctionVariantsINTEL:
915+
case CapabilitySpecConditionalINTEL:
916+
return ExtensionID::SPV_INTEL_function_variants;
914917
default:
915918
return {};
916919
}

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)