https://github.com/Alexander-Johnston updated 
https://github.com/llvm/llvm-project/pull/161378

>From 5af43f878799ca037a963ba2bd331a7b641d295c Mon Sep 17 00:00:00 2001
From: Alexander Johnston <[email protected]>
Date: Mon, 29 Sep 2025 19:54:02 +0100
Subject: [PATCH 1/3] [HLSL] Implement the `fwidth` intrinsic

Closes #99120
---
 clang/include/clang/Basic/BuiltinsSPIRVVK.td  |   1 +
 clang/lib/CodeGen/TargetBuiltins/SPIR.cpp     |   5 +
 .../lib/Headers/hlsl/hlsl_intrinsic_helpers.h |  12 ++
 clang/lib/Headers/hlsl/hlsl_intrinsics.h      |  40 ++++++
 clang/lib/Sema/SemaSPIRV.cpp                  |  18 +++
 clang/test/CodeGenHLSL/builtins/fwidth.hlsl   | 118 ++++++++++++++++++
 clang/test/CodeGenSPIRV/Builtins/fwidth.c     |  41 ++++++
 clang/test/SemaSPIRV/BuiltIns/fwidth-errors.c |  24 ++++
 llvm/include/llvm/IR/IntrinsicsSPIRV.td       |   1 +
 .../Target/SPIRV/SPIRVInstructionSelector.cpp |  18 +--
 .../CodeGen/SPIRV/hlsl-intrinsics/fwidth.ll   |  47 +++++++
 .../test/CodeGen/SPIRV/opencl/fwidth-error.ll |  12 ++
 12 files changed, 329 insertions(+), 8 deletions(-)
 create mode 100644 clang/test/CodeGenHLSL/builtins/fwidth.hlsl
 create mode 100644 clang/test/CodeGenSPIRV/Builtins/fwidth.c
 create mode 100644 clang/test/SemaSPIRV/BuiltIns/fwidth-errors.c
 create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-intrinsics/fwidth.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/opencl/fwidth-error.ll

diff --git a/clang/include/clang/Basic/BuiltinsSPIRVVK.td 
b/clang/include/clang/Basic/BuiltinsSPIRVVK.td
index 5dc3c7588cd2a..a077a763923d6 100644
--- a/clang/include/clang/Basic/BuiltinsSPIRVVK.td
+++ b/clang/include/clang/Basic/BuiltinsSPIRVVK.td
@@ -12,3 +12,4 @@ include "clang/Basic/BuiltinsSPIRVBase.td"
 def reflect : SPIRVBuiltin<"void(...)", [NoThrow, Const]>;
 def faceforward : SPIRVBuiltin<"void(...)", [NoThrow, Const, 
CustomTypeChecking]>;
 def refract : SPIRVBuiltin<"void(...)", [NoThrow, Const, CustomTypeChecking]>;
+def fwidth : SPIRVBuiltin<"void(...)", [NoThrow, Const, CustomTypeChecking]>;
diff --git a/clang/lib/CodeGen/TargetBuiltins/SPIR.cpp 
b/clang/lib/CodeGen/TargetBuiltins/SPIR.cpp
index 243aad8bf7083..43b05a128e876 100644
--- a/clang/lib/CodeGen/TargetBuiltins/SPIR.cpp
+++ b/clang/lib/CodeGen/TargetBuiltins/SPIR.cpp
@@ -151,6 +151,11 @@ Value *CodeGenFunction::EmitSPIRVBuiltinExpr(unsigned 
BuiltinID,
         Intrinsic::spv_global_offset,
         ArrayRef<Value *>{EmitScalarExpr(E->getArg(0))}, nullptr,
         "spv.global.offset");
+  case SPIRV::BI__builtin_spirv_fwidth:
+    return Builder.CreateIntrinsic(
+        /*ReturnType=*/getTypes().ConvertType(E->getType()),
+        Intrinsic::spv_fwidth, ArrayRef<Value *>{EmitScalarExpr(E->getArg(0))},
+        nullptr, "spv.fwidth");
   }
   return nullptr;
 }
diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h 
b/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
index 3d8fe7ea701a6..d1dc8275431c0 100644
--- a/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
+++ b/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
@@ -160,6 +160,18 @@ constexpr K firstbithigh_impl(T X) {
   return FBH;
 }
 
+template <typename T> constexpr T fwidth_impl(T input) {
+#if (__has_builtin(__builtin_spirv_fwidth))
+  return __builtin_spirv_fwidth(input);
+#else
+  T derivCoarseX = ddx_coarse(input);
+  derivCoarseX = abs(derivCoarseX);
+  T derivCoarseY = ddy_coarse(input);
+  derivCoarseY = abs(derivCoarseY);
+  return derivCoarseX + derivCoarseY;
+#endif
+}
+
 } // namespace __detail
 } // namespace hlsl
 
diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsics.h 
b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
index 33ed14328ee8a..c26c8bb5261d4 100644
--- a/clang/lib/Headers/hlsl/hlsl_intrinsics.h
+++ b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
@@ -666,5 +666,45 @@ smoothstep(__detail::HLSL_FIXED_VECTOR<float, N> Min,
   return __detail::smoothstep_vec_impl(Min, Max, X);
 }
 
+//===----------------------------------------------------------------------===//
+// fwidth builtin
+//===----------------------------------------------------------------------===//
+
+/// \fn T fwidth(T x)
+/// \brief Computes the sum of the absolute values of the partial derivatives
+/// with regard to the x and y screen space coordinates.
+/// \param x [in] The floating-point scalar or vector to process.
+///
+/// The return value is a floating-point scalar or vector where each element
+/// holds the computation of the matching element in the input.
+
+template <typename T>
+_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
+const inline __detail::enable_if_t<__detail::is_arithmetic<T>::Value &&
+                                       __detail::is_same<half, T>::value,
+                                   T> fwidth(T input) {
+  return __detail::fwidth_impl(input);
+}
+
+template <typename T>
+const inline __detail::enable_if_t<
+    __detail::is_arithmetic<T>::Value && __detail::is_same<float, T>::value, T>
+fwidth(T input) {
+  return __detail::fwidth_impl(input);
+}
+
+template <int N>
+_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
+const inline __detail::HLSL_FIXED_VECTOR<half, N> fwidth(
+    __detail::HLSL_FIXED_VECTOR<half, N> input) {
+  return __detail::fwidth_impl(input);
+}
+
+template <int N>
+const inline __detail::HLSL_FIXED_VECTOR<float, N>
+fwidth(__detail::HLSL_FIXED_VECTOR<float, N> input) {
+  return __detail::fwidth_impl(input);
+}
+
 } // namespace hlsl
 #endif //_HLSL_HLSL_INTRINSICS_H_
diff --git a/clang/lib/Sema/SemaSPIRV.cpp b/clang/lib/Sema/SemaSPIRV.cpp
index c8ea0d09c4081..0e78cff9c1774 100644
--- a/clang/lib/Sema/SemaSPIRV.cpp
+++ b/clang/lib/Sema/SemaSPIRV.cpp
@@ -360,6 +360,24 @@ bool SemaSPIRV::CheckSPIRVBuiltinFunctionCall(const 
TargetInfo &TI,
   case SPIRV::BI__builtin_spirv_generic_cast_to_ptr_explicit: {
     return checkGenericCastToPtr(SemaRef, TheCall);
   }
+  case SPIRV::BI__builtin_spirv_fwidth: {
+    if (SemaRef.checkArgCount(TheCall, 1))
+      return true;
+
+    // Check if first argument has floating representation
+    ExprResult A = TheCall->getArg(0);
+    QualType ArgTyA = A.get()->getType();
+    if (!ArgTyA->hasFloatingRepresentation()) {
+      SemaRef.Diag(A.get()->getBeginLoc(), diag::err_builtin_invalid_arg_type)
+          << /* ordinal */ 1 << /* scalar or vector */ 5 << /* no int */ 0
+          << /* fp */ 1 << ArgTyA;
+      return true;
+    }
+
+    QualType RetTy = ArgTyA;
+    TheCall->setType(RetTy);
+    break;
+  }
   }
   return false;
 }
diff --git a/clang/test/CodeGenHLSL/builtins/fwidth.hlsl 
b/clang/test/CodeGenHLSL/builtins/fwidth.hlsl
new file mode 100644
index 0000000000000..2388bbd8d5a1e
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/fwidth.hlsl
@@ -0,0 +1,118 @@
+// RUN: %clang_cc1 -finclude-default-header  -x hlsl  -triple 
dxil-pc-shadermodel6.3-library %s \
+// RUN:  -emit-llvm -disable-llvm-passes -fnative-half-type -o - | \
+// RUN:  FileCheck %s --check-prefixes=CHECK
+// RUN: %clang_cc1 -finclude-default-header  -x hlsl  -triple 
spirv-pc-vulkan-compute  %s \
+// RUN:  -emit-llvm -disable-llvm-passes -fnative-half-type -o - | \
+// RUN:  FileCheck %s --check-prefixes=CHECK-SPIRV
+
+// CHECK-LABEL: define {{.*}} half @_ZN4hlsl8__detail11fwidth_implIDhEET_S2_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} half @llvm.dx.ddx.coarse.f16(half 
%{{.*}})
+// CHECK: %{{.*}} = call {{.*}} half @llvm.fabs.f16(half %{{.*}})
+// CHECK: %hlsl.ddy.coarse = call {{.*}} half @llvm.dx.ddy.coarse.f16(half 
%{{.*}})
+// CHECK: %{{.*}} = call {{.*}} half @llvm.fabs.f16(half %{{.*}})
+// CHECK: %{{.*}} = fadd {{.*}} %{{.*}}, %{{.*}}
+// CHECK: ret half %{{.*}}
+// CHECK-LABEL-SPIRV: half @_Z15test_f16_fwidthDh
+// CHECK-SPIRV: %spv.fwidth = call {{.*}} half @llvm.spv.fwidth.f16(half 
%{{.*}})
+// CHECK-SPIRV: ret half %spv.fwidth
+half test_f16_fwidth(half val) {
+    return fwidth(val);
+}
+
+// CHECK-LABEL: define {{.*}} <2 x half> 
@_ZN4hlsl8__detail11fwidth_implIDv2_DhEET_S3_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} <2 x half> 
@llvm.dx.ddx.coarse.v2f16(<2 x half> %{{.*}})
+// CHECK: %{{.*}} = call {{.*}} <2 x half> @llvm.fabs.v2f16(<2 x half> %{{.*}})
+// CHECK: %hlsl.ddy.coarse = call {{.*}} <2 x half> 
@llvm.dx.ddy.coarse.v2f16(<2 x half> %{{.*}})
+// CHECK: %{{.*}} = call {{.*}} <2 x half> @llvm.fabs.v2f16(<2 x half> %{{.*}})
+// CHECK: %{{.*}} = fadd {{.*}} %{{.*}}, %{{.*}}
+// CHECK: ret <2 x half> %{{.*}}
+// CHECK-LABEL-SPIRV: <2 x half> @_Z16test_f16_fwidth2Dv2_Dh
+// CHECK-SPIRV: %spv.fwidth = call {{.*}} <2 x half> @llvm.spv.fwidth.v2f16(<2 
x half> %{{.*}})
+// CHECK-SPIRV: ret <2 x half> %spv.fwidth
+half2 test_f16_fwidth2(half2 val) {
+    return fwidth(val);
+}
+
+// CHECK-LABEL: define {{.*}} <3 x half> 
@_ZN4hlsl8__detail11fwidth_implIDv3_DhEET_S3_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} <3 x half> 
@llvm.dx.ddx.coarse.v3f16(<3 x half> %{{.*}})
+// CHECK: %{{.*}} = call {{.*}} <3 x half> @llvm.fabs.v3f16(<3 x half> %{{.*}})
+// CHECK: %hlsl.ddy.coarse = call {{.*}} <3 x half> 
@llvm.dx.ddy.coarse.v3f16(<3 x half> %{{.*}})
+// CHECK: %{{.*}} = call {{.*}} <3 x half> @llvm.fabs.v3f16(<3 x half> %{{.*}})
+// CHECK: %{{.*}} = fadd {{.*}} %{{.*}}, %{{.*}}
+// CHECK: ret <3 x half> %{{.*}}
+// CHECK-LABEL-SPIRV: <3 x half> @_Z16test_f16_fwidth3Dv3_Dh
+// CHECK-SPIRV: %spv.fwidth = call {{.*}} <3 x half> @llvm.spv.fwidth.v3f16(<3 
x half> %{{.*}})
+// CHECK-SPIRV: ret <3 x half> %spv.fwidth
+half3 test_f16_fwidth3(half3 val) {
+    return fwidth(val);
+}
+
+// CHECK-LABEL: define {{.*}} <4 x half> 
@_ZN4hlsl8__detail11fwidth_implIDv4_DhEET_S3_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} <4 x half> 
@llvm.dx.ddx.coarse.v4f16(<4 x half> %{{.*}})
+// CHECK: %{{.*}} = call {{.*}} <4 x half> @llvm.fabs.v4f16(<4 x half> %{{.*}})
+// CHECK: %hlsl.ddy.coarse = call {{.*}} <4 x half> 
@llvm.dx.ddy.coarse.v4f16(<4 x half> %{{.*}})
+// CHECK: %{{.*}} = call {{.*}} <4 x half> @llvm.fabs.v4f16(<4 x half> %{{.*}})
+// CHECK: %{{.*}} = fadd {{.*}} %{{.*}}, %{{.*}}
+// CHECK: ret <4 x half> %{{.*}}
+// CHECK-LABEL-SPIRV: <4 x half> @_Z16test_f16_fwidth4Dv4_Dh
+// CHECK-SPIRV: %spv.fwidth = call {{.*}} <4 x half> @llvm.spv.fwidth.v4f16(<4 
x half> %{{.*}})
+// CHECK-SPIRV: ret <4 x half> %spv.fwidth
+half4 test_f16_fwidth4(half4 val) {
+    return fwidth(val);
+}
+
+// CHECK-LABEL: define {{.*}} float @_ZN4hlsl8__detail11fwidth_implIfEET_S2_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} float @llvm.dx.ddx.coarse.f32(float 
%{{.*}})
+// CHECK: %{{.*}} = call {{.*}} float @llvm.fabs.f32(float %{{.*}})
+// CHECK: %hlsl.ddy.coarse = call {{.*}} float @llvm.dx.ddy.coarse.f32(float 
%{{.*}})
+// CHECK: %{{.*}} = call {{.*}} float @llvm.fabs.f32(float %{{.*}})
+// CHECK: %{{.*}} = fadd {{.*}} %{{.*}}, %{{.*}}
+// CHECK: ret float %{{.*}}
+// CHECK-LABEL-SPIRV: float @_Z15test_f32_fwidthf
+// CHECK-SPIRV: %spv.fwidth = call {{.*}} float @llvm.spv.fwidth.f32(float 
%{{.*}})
+// CHECK-SPIRV: ret float %spv.fwidth
+float test_f32_fwidth(float val) {
+    return fwidth(val);
+}
+
+// CHECK-LABEL: define {{.*}} <2 x float> 
@_ZN4hlsl8__detail11fwidth_implIDv2_fEET_S3_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} <2 x float> 
@llvm.dx.ddx.coarse.v2f32(<2 x float> %{{.*}})
+// CHECK: %{{.*}} = call {{.*}} <2 x float> @llvm.fabs.v2f32(<2 x float> 
%{{.*}})
+// CHECK: %hlsl.ddy.coarse = call {{.*}} <2 x float> 
@llvm.dx.ddy.coarse.v2f32(<2 x float> %{{.*}})
+// CHECK: %{{.*}} = call {{.*}} <2 x float> @llvm.fabs.v2f32(<2 x float> 
%{{.*}})
+// CHECK: %{{.*}} = fadd {{.*}} %{{.*}}, %{{.*}}
+// CHECK: ret <2 x float> %{{.*}}
+// CHECK-LABEL-SPIRV: <2 x float> @_Z16test_f32_fwidth2Dv2_f
+// CHECK-SPIRV: %spv.fwidth = call {{.*}} <2 x float> 
@llvm.spv.fwidth.v2f32(<2 x float> %{{.*}})
+// CHECK-SPIRV: ret <2 x float> %spv.fwidth
+float2 test_f32_fwidth2(float2 val) {
+    return fwidth(val);
+}
+
+// CHECK-LABEL: define {{.*}} <3 x float> 
@_ZN4hlsl8__detail11fwidth_implIDv3_fEET_S3_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} <3 x float> 
@llvm.dx.ddx.coarse.v3f32(<3 x float> %{{.*}})
+// CHECK: %{{.*}} = call {{.*}} <3 x float> @llvm.fabs.v3f32(<3 x float> 
%{{.*}})
+// CHECK: %hlsl.ddy.coarse = call {{.*}} <3 x float> 
@llvm.dx.ddy.coarse.v3f32(<3 x float> %{{.*}})
+// CHECK: %{{.*}} = call {{.*}} <3 x float> @llvm.fabs.v3f32(<3 x float> 
%{{.*}})
+// CHECK: %{{.*}} = fadd {{.*}} %{{.*}}, %{{.*}}
+// CHECK: ret <3 x float> %{{.*}}
+// CHECK-LABEL-SPIRV: <3 x float> @_Z16test_f32_fwidth3Dv3_f
+// CHECK-SPIRV: %spv.fwidth = call {{.*}} <3 x float> 
@llvm.spv.fwidth.v3f32(<3 x float> %{{.*}})
+// CHECK-SPIRV: ret <3 x float> %spv.fwidth
+float3 test_f32_fwidth3(float3 val) {
+    return fwidth(val);
+}
+
+// CHECK-LABEL: define {{.*}} <4 x float> 
@_ZN4hlsl8__detail11fwidth_implIDv4_fEET_S3_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} <4 x float> 
@llvm.dx.ddx.coarse.v4f32(<4 x float> %{{.*}})
+// CHECK: %{{.*}} = call {{.*}} <4 x float> @llvm.fabs.v4f32(<4 x float> 
%{{.*}})
+// CHECK: %hlsl.ddy.coarse = call {{.*}} <4 x float> 
@llvm.dx.ddy.coarse.v4f32(<4 x float> %{{.*}})
+// CHECK: %{{.*}} = call {{.*}} <4 x float> @llvm.fabs.v4f32(<4 x float> 
%{{.*}})
+// CHECK: %{{.*}} = fadd {{.*}} %{{.*}}, %{{.*}}
+// CHECK: ret <4 x float> %{{.*}}
+// CHECK-LABEL-SPIRV: <4 x float> @_Z16test_f32_fwidth4Dv4_f
+// CHECK-SPIRV: %spv.fwidth = call {{.*}} <4 x float> 
@llvm.spv.fwidth.v4f32(<4 x float> %{{.*}})
+// CHECK-SPIRV: ret <4 x float> %spv.fwidth
+float4 test_f32_fwidth4(float4 val) {
+    return fwidth(val);
+}
diff --git a/clang/test/CodeGenSPIRV/Builtins/fwidth.c 
b/clang/test/CodeGenSPIRV/Builtins/fwidth.c
new file mode 100644
index 0000000000000..db19c1362c501
--- /dev/null
+++ b/clang/test/CodeGenSPIRV/Builtins/fwidth.c
@@ -0,0 +1,41 @@
+// RUN: %clang_cc1 -O1 -triple spirv-pc-vulkan-compute %s -emit-llvm -o - | 
FileCheck %s
+
+typedef _Float16 half;
+typedef half half2 __attribute__((ext_vector_type(2)));
+typedef half half3 __attribute__((ext_vector_type(3)));
+typedef half half4 __attribute__((ext_vector_type(4)));
+typedef float float2 __attribute__((ext_vector_type(2)));
+typedef float float3 __attribute__((ext_vector_type(3)));
+typedef float float4 __attribute__((ext_vector_type(4)));
+
+// CHECK: [[fwidth0:%.*]] = tail call half @llvm.spv.fwidth.f16(half {{%.*}})
+// CHECK: ret half [[fwidth0]]
+half test_fwidth_half(half X) { return __builtin_spirv_fwidth(X); }
+
+// CHECK: [[fwidth0:%.*]] = tail call <2 x half> @llvm.spv.fwidth.v2f16(<2 x 
half>  {{%.*}})
+// CHECK: ret <2 x half> [[fwidth0]]
+half2 test_fwidth_half2(half2 X) { return __builtin_spirv_fwidth(X); }
+
+// CHECK: [[fwidth0:%.*]] = tail call <3 x half> @llvm.spv.fwidth.v3f16(<3 x 
half> {{%.*}})
+// CHECK: ret <3 x half> [[fwidth0]]
+half3 test_fwidth_half3(half3 X) { return __builtin_spirv_fwidth(X); }
+
+// CHECK: [[fwidth0:%.*]] = tail call <4 x half> @llvm.spv.fwidth.v4f16(<4 x 
half> {{%.*}})
+// CHECK: ret <4 x half> [[fwidth0]]
+half4 test_fwidth_half4(half4 X) { return __builtin_spirv_fwidth(X); }
+
+// CHECK: [[fwidth0:%.*]] = tail call float @llvm.spv.fwidth.f32(float {{%.*}})
+// CHECK: ret float [[fwidth0]]
+float test_fwidth_float(float X) { return __builtin_spirv_fwidth(X); }
+
+// CHECK: [[fwidth1:%.*]] = tail call <2 x float> @llvm.spv.fwidth.v2f32(<2 x 
float> {{%.*}})
+// CHECK: ret <2 x float> [[fwidth1]]
+float2 test_fwidth_float2(float2 X) { return __builtin_spirv_fwidth(X); }
+
+// CHECK: [[fwidth2:%.*]] = tail call <3 x float> @llvm.spv.fwidth.v3f32(<3 x 
float> {{%.*}})
+// CHECK: ret <3 x float> [[fwidth2]]
+float3 test_fwidth_float3(float3 X) { return __builtin_spirv_fwidth(X); }
+
+// CHECK: [[fwidth3:%.*]] = tail call <4 x float> @llvm.spv.fwidth.v4f32(<4 x 
float> {{%.*}})
+// CHECK: ret <4 x float> [[fwidth3]]
+float4 test_fwidth_float4(float4 X) { return __builtin_spirv_fwidth(X); }
diff --git a/clang/test/SemaSPIRV/BuiltIns/fwidth-errors.c 
b/clang/test/SemaSPIRV/BuiltIns/fwidth-errors.c
new file mode 100644
index 0000000000000..44cdd819e4332
--- /dev/null
+++ b/clang/test/SemaSPIRV/BuiltIns/fwidth-errors.c
@@ -0,0 +1,24 @@
+// RUN: %clang_cc1 %s -triple spirv-pc-vulkan-compute -verify
+
+typedef float float2 __attribute__((ext_vector_type(2)));
+
+void test_too_few_arg()
+{
+  return __builtin_spirv_fwidth();
+  // expected-error@-1 {{too few arguments to function call, expected 1, have 
0}}
+}
+
+float test_too_many_arg(float p0) {
+  return __builtin_spirv_fwidth(p0, p0);
+  // expected-error@-1 {{too many arguments to function call, expected 1, have 
2}}
+}
+
+float test_int_scalar_inputs(int p0) {
+  return __builtin_spirv_fwidth(p0);
+  //  expected-error@-1 {{1st argument must be a scalar or vector of 
floating-point types (was 'int')}}
+}
+
+float test_mismatched_return(float2 p0) {
+  return __builtin_spirv_fwidth(p0);
+  // expected-error@-1 {{returning 'float2' (vector of 2 'float' values) from 
a function with incompatible result type 'float'}}
+}
diff --git a/llvm/include/llvm/IR/IntrinsicsSPIRV.td 
b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
index 2f7c25550a0cc..366f8cf36d75c 100644
--- a/llvm/include/llvm/IR/IntrinsicsSPIRV.td
+++ b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
@@ -136,6 +136,7 @@ def int_spv_rsqrt : 
DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty]
   def int_spv_discard : DefaultAttrsIntrinsic<[], [], []>;
   def int_spv_ddx_coarse : DefaultAttrsIntrinsic<[llvm_anyfloat_ty], 
[LLVMMatchType<0>], [IntrNoMem]>;
   def int_spv_ddy_coarse : DefaultAttrsIntrinsic<[llvm_anyfloat_ty], 
[LLVMMatchType<0>], [IntrNoMem]>;
+  def int_spv_fwidth : DefaultAttrsIntrinsic<[llvm_anyfloat_ty], 
[LLVMMatchType<0>], [IntrNoMem]>;
   def int_spv_uclamp : DefaultAttrsIntrinsic<[llvm_anyint_ty], 
[LLVMMatchType<0>, LLVMMatchType<0>, LLVMMatchType<0>], [IntrNoMem]>;
   def int_spv_sclamp : DefaultAttrsIntrinsic<[llvm_anyint_ty], 
[LLVMMatchType<0>, LLVMMatchType<0>, LLVMMatchType<0>], [IntrNoMem]>;
   def int_spv_nclamp : DefaultAttrsIntrinsic<[llvm_anyfloat_ty], 
[LLVMMatchType<0>, LLVMMatchType<0>, LLVMMatchType<0>], [IntrNoMem]>;
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp 
b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 0653b4eb9dfe2..40b1142850233 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -328,8 +328,8 @@ class SPIRVInstructionSelector : public InstructionSelector 
{
                            MachineInstr &I) const;
   bool selectFrexp(Register ResVReg, const SPIRVType *ResType,
                    MachineInstr &I) const;
-  bool selectDpdCoarse(Register ResVReg, const SPIRVType *ResType,
-                       MachineInstr &I, const unsigned DPdOpCode) const;
+  bool selectDerivativeInst(Register ResVReg, const SPIRVType *ResType,
+                            MachineInstr &I, const unsigned DPdOpCode) const;
   // Utilities
   std::pair<Register, bool>
   buildI32Constant(uint32_t Val, MachineInstr &I,
@@ -3143,10 +3143,9 @@ bool SPIRVInstructionSelector::wrapIntoSpecConstantOp(
   return Result;
 }
 
-bool SPIRVInstructionSelector::selectDpdCoarse(Register ResVReg,
-                                               const SPIRVType *ResType,
-                                               MachineInstr &I,
-                                               const unsigned DPdOpCode) const 
{
+bool SPIRVInstructionSelector::selectDerivativeInst(
+    Register ResVReg, const SPIRVType *ResType, MachineInstr &I,
+    const unsigned DPdOpCode) const {
   // TODO: This should check specifically for Fragment Execution Model, but STI
   // doesn't provide that information yet. See #167562
   errorIfInstrOutsideShader(I);
@@ -3584,10 +3583,13 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register 
ResVReg,
     return selectExtInst(ResVReg, ResType, I, GL::UnpackHalf2x16);
   }
   case Intrinsic::spv_ddx_coarse: {
-    return selectDpdCoarse(ResVReg, ResType, I, SPIRV::OpDPdxCoarse);
+    return selectDerivativeInst(ResVReg, ResType, I, SPIRV::OpDPdxCoarse);
   }
   case Intrinsic::spv_ddy_coarse: {
-    return selectDpdCoarse(ResVReg, ResType, I, SPIRV::OpDPdyCoarse);
+    return selectDerivativeInst(ResVReg, ResType, I, SPIRV::OpDPdyCoarse);
+  }
+  case Intrinsic::spv_fwidth: {
+    return selectDerivativeInst(ResVReg, ResType, I, SPIRV::OpFwidth);
   }
   default: {
     std::string DiagMsg;
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/fwidth.ll 
b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/fwidth.ll
new file mode 100644
index 0000000000000..1dea47d6b7a21
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/fwidth.ll
@@ -0,0 +1,47 @@
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv-unknown-vulkan %s -o - | 
FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-vulkan %s -o - 
-filetype=obj | spirv-val %}
+
+; CHECK-DAG: %[[#float_32:]] = OpTypeFloat 32
+; CHECK-DAG: %[[#float_16:]] = OpTypeFloat 16
+
+; CHECK-DAG: %[[#vec4_float_32:]] = OpTypeVector %[[#float_32]] 4
+; CHECK-DAG: %[[#vec4_float_16:]] = OpTypeVector %[[#float_16]] 4
+
+define noundef float @fwidth_float(float noundef %a) {
+entry:
+; CHECK: %[[#float_32_arg:]] = OpFunctionParameter %[[#float_32]]
+; CHECK: %[[#]] = OpFwidth %[[#float_32]] %[[#float_32_arg]]
+  %elt.fwidth = call float @llvm.spv.fwidth.f32(float %a)
+  ret float %elt.fwidth
+}
+
+define noundef half @fwidth_half(half noundef %a) {
+entry:
+; CHECK: %[[#float_16_arg:]] = OpFunctionParameter %[[#float_16]]
+; CHECK: %[[#converted:]] = OpFConvert %[[#float_32]] %[[#float_16_arg]]
+; CHECK: %[[#fwidth:]] = OpFwidth %[[#float_32]] %[[#converted]]
+; CHECK: %[[#]] = OpFConvert %[[#float_16]] %[[#fwidth]]
+  %elt.fwidth = call half @llvm.spv.fwidth.f16(half %a)
+  ret half %elt.fwidth
+}
+
+define noundef <4 x float> @fwidth_float_vector(<4 x float> noundef %a) {
+entry:
+; CHECK: %[[#vec4_float_32_arg:]] = OpFunctionParameter %[[#vec4_float_32]]
+; CHECK: %[[#]] = OpFwidth %[[#vec4_float_32]] %[[#vec4_float_32_arg]]
+  %elt.fwidth = call <4 x float> @llvm.spv.fwidth.v4f32(<4 x float> %a)
+  ret <4 x float> %elt.fwidth
+}
+
+define noundef <4 x half> @fwidth_half_vector(<4 x half> noundef %a) {
+entry:
+; CHECK: %[[#vec4_float_16_arg:]] = OpFunctionParameter %[[#vec4_float_16]]
+; CHECK: %[[#converted:]] = OpFConvert %[[#vec4_float_32]] 
%[[#vec4_float_16_arg]]
+; CHECK: %[[#fwidth:]] = OpFwidth %[[#vec4_float_32]] %[[#converted]]
+; CHECK: %[[#]] = OpFConvert %[[#vec4_float_16]] %[[#fwidth]]
+  %elt.fwidth = call <4 x half> @llvm.spv.fwidth.v4f16(<4 x half> %a)
+  ret <4 x half> %elt.fwidth
+}
+
+declare float @llvm.spv.fwidth.f32(float)
+declare half @llvm.spv.fwidth.f16(half)
diff --git a/llvm/test/CodeGen/SPIRV/opencl/fwidth-error.ll 
b/llvm/test/CodeGen/SPIRV/opencl/fwidth-error.ll
new file mode 100644
index 0000000000000..feb994bfd0c71
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/opencl/fwidth-error.ll
@@ -0,0 +1,12 @@
+; RUN: not llc -verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown %s 
-o /dev/null 2>&1 | FileCheck %s
+; RUN: not llc -verify-machineinstrs -O0 -mtriple=spirv32-unknown-unknown %s 
-o /dev/null 2>&1 | FileCheck %s
+
+; CHECK: LLVM ERROR: %{{.*}} = G_INTRINSIC intrinsic(@llvm.spv.fwidth), 
%{{.*}} is only supported in shaders.
+
+define noundef float @fwidth(float noundef %a) {
+entry:
+  %spv.fwidth = call float @llvm.spv.fwidth.f32(float %a)
+  ret float %spv.fwidth
+}
+
+declare float @llvm.spv.fwidth.f32(float)

>From 449b4226ea4a564e7d1e1016b369ca839812bc82 Mon Sep 17 00:00:00 2001
From: Alexander Johnston <[email protected]>
Date: Wed, 19 Nov 2025 13:56:21 +0000
Subject: [PATCH 2/3] Fixup nits: Code style and fwidth test triple

---
 clang/test/CodeGenHLSL/builtins/fwidth.hlsl        | 2 +-
 llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp | 9 +++------
 2 files changed, 4 insertions(+), 7 deletions(-)

diff --git a/clang/test/CodeGenHLSL/builtins/fwidth.hlsl 
b/clang/test/CodeGenHLSL/builtins/fwidth.hlsl
index 2388bbd8d5a1e..2935fbbc4f6a2 100644
--- a/clang/test/CodeGenHLSL/builtins/fwidth.hlsl
+++ b/clang/test/CodeGenHLSL/builtins/fwidth.hlsl
@@ -1,7 +1,7 @@
 // RUN: %clang_cc1 -finclude-default-header  -x hlsl  -triple 
dxil-pc-shadermodel6.3-library %s \
 // RUN:  -emit-llvm -disable-llvm-passes -fnative-half-type -o - | \
 // RUN:  FileCheck %s --check-prefixes=CHECK
-// RUN: %clang_cc1 -finclude-default-header  -x hlsl  -triple 
spirv-pc-vulkan-compute  %s \
+// RUN: %clang_cc1 -finclude-default-header  -x hlsl  -triple 
spirv-unknown-vulkan1.3-library  %s \
 // RUN:  -emit-llvm -disable-llvm-passes -fnative-half-type -o - | \
 // RUN:  FileCheck %s --check-prefixes=CHECK-SPIRV
 
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp 
b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 40b1142850233..d3fc08eb56cb3 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -3582,15 +3582,12 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register 
ResVReg,
   case Intrinsic::spv_unpackhalf2x16: {
     return selectExtInst(ResVReg, ResType, I, GL::UnpackHalf2x16);
   }
-  case Intrinsic::spv_ddx_coarse: {
+  case Intrinsic::spv_ddx_coarse:
     return selectDerivativeInst(ResVReg, ResType, I, SPIRV::OpDPdxCoarse);
-  }
-  case Intrinsic::spv_ddy_coarse: {
+  case Intrinsic::spv_ddy_coarse:
     return selectDerivativeInst(ResVReg, ResType, I, SPIRV::OpDPdyCoarse);
-  }
-  case Intrinsic::spv_fwidth: {
+  case Intrinsic::spv_fwidth:
     return selectDerivativeInst(ResVReg, ResType, I, SPIRV::OpFwidth);
-  }
   default: {
     std::string DiagMsg;
     raw_string_ostream OS(DiagMsg);

>From 6a7ef6876c1aff983b95f5a5347516b5c41fe1f4 Mon Sep 17 00:00:00 2001
From: Alexander Johnston <[email protected]>
Date: Wed, 19 Nov 2025 13:56:45 +0000
Subject: [PATCH 3/3] Add Invalid stage test to ddx/y_coarse tests

---
 .../test/CodeGen/DirectX/ddx_coarse-errors.ll | 20 +++++++++++++++---
 .../test/CodeGen/DirectX/ddy_coarse-errors.ll | 21 ++++++++++++++++---
 2 files changed, 35 insertions(+), 6 deletions(-)

diff --git a/llvm/test/CodeGen/DirectX/ddx_coarse-errors.ll 
b/llvm/test/CodeGen/DirectX/ddx_coarse-errors.ll
index 0679eec31cec1..567a3ef1d1d3c 100644
--- a/llvm/test/CodeGen/DirectX/ddx_coarse-errors.ll
+++ b/llvm/test/CodeGen/DirectX/ddx_coarse-errors.ll
@@ -1,8 +1,9 @@
-; RUN: not opt -S -dxil-op-lower -mtriple=dxil-pc-shadermodel6.3-library %s 
2>&1 | FileCheck %s
+; RUN: not opt -S -dxil-op-lower -mtriple=dxil-pc-shadermodel6.3-library %s 
2>&1 | FileCheck --check-prefixes=CHECK-TYPE %s
+; RUN: not opt -S -dxil-op-lower -mtriple=dxil-pc-shadermodel6.3-compute %s 
2>&1 | FileCheck --check-prefixes=CHECK-STAGE %s
 
 ; DXIL operation ddx.coarse does not support double overload type
-; CHECK: in function ddx.coarse
-; CHECK-SAME: Cannot create DerivCoarseX operation: Invalid overload type
+; CHECK-TYPE: in function ddx.coarse
+; CHECK-TYPE-SAME: Cannot create DerivCoarseX operation: Invalid overload type
 
 ; Function Attrs: noinline nounwind optnone
 define noundef double @ddx.coarse_double(double noundef %a) #0 {
@@ -13,3 +14,16 @@ entry:
   %dx.ddx.coarse = call double @llvm.dx.ddx.coarse.f64(double %0)
   ret double %dx.ddx.coarse
 }
+
+; DXIL operation ddx.coarse does not support compute shader stage
+; CHECK-STAGE: in function ddx.coarse
+; CHECK-STAGE-SAME: Cannot create DerivCoarseX operation: Invalid stage
+; Function Attrs: noinline nounwind optnone
+define noundef float @ddx.coarse_float(float noundef %a) #0 {
+entry:
+  %a.addr = alloca float, align 8
+  store float %a, ptr %a.addr, align 8
+  %0 = load float, ptr %a.addr, align 8
+  %dx.ddx.coarse = call float @llvm.dx.ddx.coarse.f32(float %0)
+  ret float %dx.ddx.coarse
+}
diff --git a/llvm/test/CodeGen/DirectX/ddy_coarse-errors.ll 
b/llvm/test/CodeGen/DirectX/ddy_coarse-errors.ll
index df8e3eb0f7e0b..215ece4017c17 100644
--- a/llvm/test/CodeGen/DirectX/ddy_coarse-errors.ll
+++ b/llvm/test/CodeGen/DirectX/ddy_coarse-errors.ll
@@ -1,8 +1,9 @@
-; RUN: not opt -S -dxil-op-lower -mtriple=dxil-pc-shadermodel6.3-library %s 
2>&1 | FileCheck %s
+; RUN: not opt -S -dxil-op-lower -mtriple=dxil-pc-shadermodel6.3-library %s 
2>&1 | FileCheck --check-prefixes=CHECK-TYPE %s
+; RUN: not opt -S -dxil-op-lower -mtriple=dxil-pc-shadermodel6.3-compute %s 
2>&1 | FileCheck --check-prefixes=CHECK-STAGE %s
 
 ; DXIL operation ddy.coarse does not support double overload type
-; CHECK: in function ddy.coarse
-; CHECK-SAME: Cannot create DerivCoarseY operation: Invalid overload type
+; CHECK-TYPE: in function ddy.coarse
+; CHECK-TYPE-SAME: Cannot create DerivCoarseY operation: Invalid overload type
 
 ; Function Attrs: noinline nounwind optnone
 define noundef double @ddy.coarse_double(double noundef %a) #0 {
@@ -13,3 +14,17 @@ entry:
   %dx.ddy.coarse = call double @llvm.dx.ddy.coarse.f64(double %0)
   ret double %dx.ddy.coarse
 }
+
+; DXIL operation ddy.coarse does not support compute shader stage
+; CHECK-STAGE: in function ddy.coarse
+; CHECK-STAGE-SAME: Cannot create DerivCoarseY operation: Invalid stage
+
+; Function Attrs: noinline nounwind optnone
+define noundef float @ddy.coarse_float(float noundef %a) #0 {
+entry:
+  %a.addr = alloca float, align 8
+  store float %a, ptr %a.addr, align 8
+  %0 = load float, ptr %a.addr, align 8
+  %dx.ddy.coarse = call float @llvm.dx.ddy.coarse.f32(float %0)
+  ret float %dx.ddy.coarse
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to