Skip to content

Commit aaa2378

Browse files
authored
Add support for SPV_INTEL_function_variants (#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 daada56 commit aaa2378

19 files changed

+1949
-23
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_subgroup_matrix_multiply_accumulate)
7979
EXT(SPV_KHR_bfloat16)
8080
EXT(SPV_INTEL_ternary_bitwise_function)
8181
EXT(SPV_INTEL_int4)
82+
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"
@@ -1761,6 +1762,20 @@ Value *SPIRVToLLVM::transValueWithoutDecoration(SPIRVValue *BV, Function *F,
17611762
case OpLabel:
17621763
return mapValue(BV, BasicBlock::Create(*Context, BV->getName(), F));
17631764

1765+
case OpSpecConstantArchitectureINTEL:
1766+
llvm_unreachable(
1767+
"Encountered non-specialized OpSpecConstantArchitectureINTEL");
1768+
return nullptr;
1769+
1770+
case OpSpecConstantTargetINTEL:
1771+
llvm_unreachable("Encountered non-specialized OpSpecConstantTargetINTEL");
1772+
return nullptr;
1773+
1774+
case OpSpecConstantCapabilitiesINTEL:
1775+
llvm_unreachable(
1776+
"Encountered non-specialized OpSpecConstantCapabilitiesINTEL");
1777+
return nullptr;
1778+
17641779
default:
17651780
// do nothing
17661781
break;
@@ -5607,6 +5622,31 @@ bool llvm::readSpirv(LLVMContext &C, const SPIRV::TranslatorOpts &Opts,
56075622
if (!BM)
56085623
return false;
56095624

5625+
if (Opts.getFnVarSpecEnable()) {
5626+
if (!specializeFnVariants(BM.get(), ErrMsg)) {
5627+
return false;
5628+
}
5629+
5630+
// Write out the specialized/targeted module
5631+
if (!BM->getFnVarSpvOut().empty()) {
5632+
auto SaveOpt = SPIRVUseTextFormat;
5633+
auto OFSSpv = std::ofstream(BM->getFnVarSpvOut(), std::ios::binary);
5634+
SPIRVUseTextFormat = false;
5635+
OFSSpv << *BM;
5636+
if (BM->getError(ErrMsg) != SPIRVEC_Success) {
5637+
return false;
5638+
}
5639+
SPIRVUseTextFormat = SaveOpt;
5640+
}
5641+
}
5642+
5643+
if (BM->getExtension().find("SPV_INTEL_function_variants") !=
5644+
BM->getExtension().end()) {
5645+
ErrMsg = "Instructions from SPV_INTEL_function_variants are not "
5646+
"convertible to LLVM IR.";
5647+
return false;
5648+
}
5649+
56105650
M = convertSpirvToLLVM(C, *BM, Opts, ErrMsg).release();
56115651

56125652
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
@@ -913,6 +913,9 @@ class SPIRVCapability : public SPIRVEntryNoId<OpCapability> {
913913
return ExtensionID::SPV_INTEL_subgroup_requirements;
914914
case CapabilityFPFastMathModeINTEL:
915915
return ExtensionID::SPV_INTEL_fp_fast_math_mode;
916+
case CapabilityFunctionVariantsINTEL:
917+
case CapabilitySpecConditionalINTEL:
918+
return ExtensionID::SPV_INTEL_function_variants;
916919
default:
917920
return {};
918921
}

lib/SPIRV/libSPIRV/SPIRVErrorEnum.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ _SPIRV_OP(UnsupportedVarArgFunction,
3030
"Variadic functions other than 'printf' are not supported in SPIR-V.")
3131
_SPIRV_OP(DeprecatedExtension,
3232
"Feature requires the following deprecated SPIR-V extension:\n")
33+
_SPIRV_OP(InvalidNumberOfOperands,
34+
"Number of operands does not match the expected count.")
3335

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

0 commit comments

Comments
 (0)