[SPIRV] Add target-feature cli option (#24598)

Allow manually specifying `vulkan-target-features` to overwrite the
target defaults in `VulkanSPIRVTarget.cpp` (e.g. for testing, newly
supported features).

Signed-off-by: Tobias Fuchs <fuchs@roofline.ai>
diff --git a/compiler/plugins/target/VulkanSPIRV/VulkanSPIRVTarget.cpp b/compiler/plugins/target/VulkanSPIRV/VulkanSPIRVTarget.cpp
index 66aab72..23795b5 100644
--- a/compiler/plugins/target/VulkanSPIRV/VulkanSPIRVTarget.cpp
+++ b/compiler/plugins/target/VulkanSPIRV/VulkanSPIRVTarget.cpp
@@ -102,6 +102,7 @@
   // lowest common denominator to guarantee the generated SPIR-V is widely
   // accepted for now. Eventually we want to use a list for multi-targeting.
   std::string target = "vp_android_baseline_2022";
+  std::string targetFeatures;
   VulkanDispatchAbi dispatchAbi = VulkanDispatchAbi::Descriptors;
 
   void bindOptions(OptionsBinder &binder) {
@@ -118,6 +119,12 @@
             "GPUs. See "
             "https://iree.dev/guides/deployment-configurations/gpu-vulkan/ for "
             "more details."));
+    binder.opt<std::string>(
+        "iree-vulkan-target-features", targetFeatures,
+        llvm::cl::desc(
+            "Vulkan target features (SPIR-V version, capabilities, "
+            "extensions, etc.) to use. If provided, replaces the default "
+            "features for the target."));
     binder.opt<VulkanDispatchAbi>(
         "iree-vulkan-dispatch-abi", dispatchAbi,
         llvm::cl::desc("Selects the Vulkan dispatch ABI emitted for generated "
@@ -326,6 +333,11 @@
     Builder b(context);
     SmallVector<NamedAttribute, 1> configItems;
     if (auto target = GPU::getVulkanTargetDetails(options_.target, context)) {
+      if (!options_.targetFeatures.empty()) {
+        target = IREE::GPU::TargetAttr::get(context, target.getArch(),
+                                            options_.targetFeatures,
+                                            target.getWgp(), target.getChip());
+      }
       if (useBdaRootAbi) {
         target = getTargetAttrWithBdaRootAbiFeatures(target);
       }
diff --git a/compiler/plugins/target/VulkanSPIRV/test/hal_target_device_attributes.mlir b/compiler/plugins/target/VulkanSPIRV/test/hal_target_device_attributes.mlir
index 4f59b78..66fe2c8 100644
--- a/compiler/plugins/target/VulkanSPIRV/test/hal_target_device_attributes.mlir
+++ b/compiler/plugins/target/VulkanSPIRV/test/hal_target_device_attributes.mlir
@@ -19,6 +19,29 @@
 //
 // CHECK-ALL: #hal.device.target<"vulkan", [#hal.executable.target<"vulkan-spirv", "vulkan-spirv-bda-v1"
 // CHECK-ALL-SAME: #hal.executable.target<"vulkan-spirv", "vulkan-spirv-fb"
+//
+// Replace the default features
+// RUN: iree-compile --compile-to=preprocessing --iree-hal-target-device=vulkan \
+// RUN:              --iree-vulkan-target-features=spirv:v1.6,cap:Shader,cap:Int64,cap:PhysicalStorageBufferAddresses,ext:SPV_KHR_physical_storage_buffer %s \
+// RUN: | FileCheck %s --check-prefix=CHECK-FEATURES
+//
+// CHECK-FEATURES: #hal.executable.target<"vulkan-spirv", "vulkan-spirv-fb"
+// CHECK-FEATURES-SAME: features = "spirv:v1.6,cap:Shader,cap:Int64,cap:PhysicalStorageBufferAddresses,ext:SPV_KHR_physical_storage_buffer"
+// CHECK-FEATURES-NOT: spirv:v1.3
+//
+// Explicit target combined with a features override: the target still
+// selects the architecture (gfx1100), but the features string overrides the
+// target's default features.
+// RUN: iree-compile --compile-to=preprocessing --iree-hal-target-device=vulkan \
+// RUN:              --iree-vulkan-target=rx7900xtx \
+// RUN:              --iree-vulkan-target-features=spirv:v1.5,cap:Shader,cap:Int64 %s \
+// RUN: | FileCheck %s --check-prefix=CHECK-TARGET-OVERRIDE
+//
+// CHECK-TARGET-OVERRIDE: #hal.executable.target<"vulkan-spirv", "vulkan-spirv-fb"
+// CHECK-TARGET-OVERRIDE-SAME: arch = "gfx1100"
+// CHECK-TARGET-OVERRIDE-SAME: features = "spirv:v1.5,cap:Shader,cap:Int64"
+// CHECK-TARGET-OVERRIDE-NOT: spirv:v1.6
+//
 
 module {
   util.func public @foo(%arg0: tensor<?xf32>) -> tensor<?xf32> {