Skip to content

Commit 70cb5e1

Browse files
jdnkvmaksimo
authored andcommitted
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_](#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. Original commit: KhronosGroup/SPIRV-LLVM-Translator@aaa23785dcbd7ca
1 parent 1c89640 commit 70cb5e1

19 files changed

+1953
-23
lines changed

llvm-spirv/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)

llvm-spirv/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

llvm-spirv/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}

llvm-spirv/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+
}

llvm-spirv/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"
@@ -1763,6 +1764,20 @@ Value *SPIRVToLLVM::transValueWithoutDecoration(SPIRVValue *BV, Function *F,
17631764
case OpLabel:
17641765
return mapValue(BV, BasicBlock::Create(*Context, BV->getName(), F));
17651766

1767+
case OpSpecConstantArchitectureINTEL:
1768+
llvm_unreachable(
1769+
"Encountered non-specialized OpSpecConstantArchitectureINTEL");
1770+
return nullptr;
1771+
1772+
case OpSpecConstantTargetINTEL:
1773+
llvm_unreachable("Encountered non-specialized OpSpecConstantTargetINTEL");
1774+
return nullptr;
1775+
1776+
case OpSpecConstantCapabilitiesINTEL:
1777+
llvm_unreachable(
1778+
"Encountered non-specialized OpSpecConstantCapabilitiesINTEL");
1779+
return nullptr;
1780+
17661781
default:
17671782
// do nothing
17681783
break;
@@ -5609,6 +5624,31 @@ bool llvm::readSpirv(LLVMContext &C, const SPIRV::TranslatorOpts &Opts,
56095624
if (!BM)
56105625
return false;
56115626

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

56145654
if (!M)

llvm-spirv/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"

llvm-spirv/lib/SPIRV/libSPIRV/SPIRVEntry.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,11 @@ class SPIRVCapability : public SPIRVEntryNoId<OpCapability> {
911911
return ExtensionID::SPV_INTEL_vector_compute;
912912
case internal::CapabilitySubgroupRequirementsINTEL:
913913
return ExtensionID::SPV_INTEL_subgroup_requirements;
914+
case CapabilityFPFastMathModeINTEL:
915+
return ExtensionID::SPV_INTEL_fp_fast_math_mode;
916+
case CapabilityFunctionVariantsINTEL:
917+
case CapabilitySpecConditionalINTEL:
918+
return ExtensionID::SPV_INTEL_function_variants;
914919
default:
915920
return {};
916921
}

llvm-spirv/lib/SPIRV/libSPIRV/SPIRVErrorEnum.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ _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(DeprecatedExtension,
32+
"Feature requires the following deprecated SPIR-V extension:\n")
33+
_SPIRV_OP(InvalidNumberOfOperands,
34+
"Number of operands does not match the expected count.")
3135

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

0 commit comments

Comments
 (0)