Skip to content

Conversation

@Alexander-Johnston
Copy link
Contributor

Implements the ddx and ddy HLSL intrinsics. The DXIL intrinsics call the ddx_coarse and ddy_builtins as they lower to the coarse variants.
The SPIRV intrinsics lower to their own opcodes OpDPdx and OpDPdy. Tests are added to ensure the SPIRV builtins are not available outside of shaders stage.

Closes #99096
Closes #99099

ddx and ddy lower are implemented as ddx_coarse and ddy_coarse in DXIL.
They lower to their own opcodes in SPIRV.
@llvmbot llvmbot added backend:X86 clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:headers Headers provided by Clang, e.g. for intrinsics clang:codegen IR generation bugs: mangling, exceptions, etc. HLSL HLSL Language Support backend:SPIR-V llvm:ir labels Nov 20, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 20, 2025

@llvm/pr-subscribers-llvm-ir
@llvm/pr-subscribers-hlsl

@llvm/pr-subscribers-clang-codegen

Author: Alexander Johnston (Alexander-Johnston)

Changes

Implements the ddx and ddy HLSL intrinsics. The DXIL intrinsics call the ddx_coarse and ddy_builtins as they lower to the coarse variants.
The SPIRV intrinsics lower to their own opcodes OpDPdx and OpDPdy. Tests are added to ensure the SPIRV builtins are not available outside of shaders stage.

Closes #99096
Closes #99099


Patch is 29.38 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/168887.diff

17 Files Affected:

  • (modified) clang/include/clang/Basic/BuiltinsSPIRVVK.td (+2)
  • (modified) clang/lib/CodeGen/TargetBuiltins/SPIR.cpp (+8)
  • (modified) clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h (+16)
  • (modified) clang/lib/Headers/hlsl/hlsl_intrinsics.h (+80)
  • (modified) clang/lib/Sema/SemaSPIRV.cpp (+2)
  • (added) clang/test/CodeGenHLSL/builtins/ddx.hlsl (+86)
  • (added) clang/test/CodeGenHLSL/builtins/ddy.hlsl (+86)
  • (added) clang/test/CodeGenSPIRV/Builtins/ddx.c (+41)
  • (added) clang/test/CodeGenSPIRV/Builtins/ddy.c (+41)
  • (added) clang/test/SemaSPIRV/BuiltIns/ddx-errors.c (+24)
  • (added) clang/test/SemaSPIRV/BuiltIns/ddy-errors.c (+24)
  • (modified) llvm/include/llvm/IR/IntrinsicsSPIRV.td (+2)
  • (modified) llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp (+4)
  • (added) llvm/test/CodeGen/SPIRV/hlsl-intrinsics/ddx.ll (+47)
  • (added) llvm/test/CodeGen/SPIRV/hlsl-intrinsics/ddy.ll (+47)
  • (added) llvm/test/CodeGen/SPIRV/opencl/ddx-error.ll (+12)
  • (added) llvm/test/CodeGen/SPIRV/opencl/ddy-error.ll (+12)
diff --git a/clang/include/clang/Basic/BuiltinsSPIRVVK.td b/clang/include/clang/Basic/BuiltinsSPIRVVK.td
index a077a763923d6..67daa16390cf2 100644
--- a/clang/include/clang/Basic/BuiltinsSPIRVVK.td
+++ b/clang/include/clang/Basic/BuiltinsSPIRVVK.td
@@ -12,4 +12,6 @@ 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 ddx : SPIRVBuiltin<"void(...)", [NoThrow, Const, CustomTypeChecking]>;
+def ddy : 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 43b05a128e876..9b0ca3eb0035a 100644
--- a/clang/lib/CodeGen/TargetBuiltins/SPIR.cpp
+++ b/clang/lib/CodeGen/TargetBuiltins/SPIR.cpp
@@ -151,6 +151,14 @@ Value *CodeGenFunction::EmitSPIRVBuiltinExpr(unsigned BuiltinID,
         Intrinsic::spv_global_offset,
         ArrayRef<Value *>{EmitScalarExpr(E->getArg(0))}, nullptr,
         "spv.global.offset");
+  case SPIRV::BI__builtin_spirv_ddx:
+    return Builder.CreateIntrinsic(
+        /*ReturnType=*/getTypes().ConvertType(E->getType()), Intrinsic::spv_ddx,
+        ArrayRef<Value *>{EmitScalarExpr(E->getArg(0))}, nullptr, "spv.ddx");
+  case SPIRV::BI__builtin_spirv_ddy:
+    return Builder.CreateIntrinsic(
+        /*ReturnType=*/getTypes().ConvertType(E->getType()), Intrinsic::spv_ddy,
+        ArrayRef<Value *>{EmitScalarExpr(E->getArg(0))}, nullptr, "spv.ddy");
   case SPIRV::BI__builtin_spirv_fwidth:
     return Builder.CreateIntrinsic(
         /*ReturnType=*/getTypes().ConvertType(E->getType()),
diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h b/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
index d1dc8275431c0..d0e1ec8b02797 100644
--- a/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
+++ b/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
@@ -160,6 +160,22 @@ constexpr K firstbithigh_impl(T X) {
   return FBH;
 }
 
+template <typename T> constexpr T ddx_impl(T input) {
+#if (__has_builtin(__builtin_spirv_ddx))
+  return __builtin_spirv_ddx(input);
+#else
+  return __builtin_hlsl_elementwise_ddx_coarse(input);
+#endif
+}
+
+template <typename T> constexpr T ddy_impl(T input) {
+#if (__has_builtin(__builtin_spirv_ddy))
+  return __builtin_spirv_ddy(input);
+#else
+  return __builtin_hlsl_elementwise_ddy_coarse(input);
+#endif
+}
+
 template <typename T> constexpr T fwidth_impl(T input) {
 #if (__has_builtin(__builtin_spirv_fwidth))
   return __builtin_spirv_fwidth(input);
diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsics.h b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
index c26c8bb5261d4..052dcb7f7f455 100644
--- a/clang/lib/Headers/hlsl/hlsl_intrinsics.h
+++ b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
@@ -666,6 +666,86 @@ smoothstep(__detail::HLSL_FIXED_VECTOR<float, N> Min,
   return __detail::smoothstep_vec_impl(Min, Max, X);
 }
 
+//===----------------------------------------------------------------------===//
+// ddx builtin
+//===----------------------------------------------------------------------===//
+
+/// \fn T ddx(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> ddx(T input) {
+  return __detail::ddx_impl(input);
+}
+
+template <typename T>
+const inline __detail::enable_if_t<
+    __detail::is_arithmetic<T>::Value && __detail::is_same<float, T>::value, T>
+ddx(T input) {
+  return __detail::ddx_impl(input);
+}
+
+template <int N>
+_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
+const inline __detail::HLSL_FIXED_VECTOR<half, N> ddx(
+    __detail::HLSL_FIXED_VECTOR<half, N> input) {
+  return __detail::ddx_impl(input);
+}
+
+template <int N>
+const inline __detail::HLSL_FIXED_VECTOR<float, N>
+ddx(__detail::HLSL_FIXED_VECTOR<float, N> input) {
+  return __detail::ddx_impl(input);
+}
+
+//===----------------------------------------------------------------------===//
+// ddy builtin
+//===----------------------------------------------------------------------===//
+
+/// \fn T ddy(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> ddy(T input) {
+  return __detail::ddy_impl(input);
+}
+
+template <typename T>
+const inline __detail::enable_if_t<
+    __detail::is_arithmetic<T>::Value && __detail::is_same<float, T>::value, T>
+ddy(T input) {
+  return __detail::ddy_impl(input);
+}
+
+template <int N>
+_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
+const inline __detail::HLSL_FIXED_VECTOR<half, N> ddy(
+    __detail::HLSL_FIXED_VECTOR<half, N> input) {
+  return __detail::ddy_impl(input);
+}
+
+template <int N>
+const inline __detail::HLSL_FIXED_VECTOR<float, N>
+ddy(__detail::HLSL_FIXED_VECTOR<float, N> input) {
+  return __detail::ddy_impl(input);
+}
+
 //===----------------------------------------------------------------------===//
 // fwidth builtin
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/Sema/SemaSPIRV.cpp b/clang/lib/Sema/SemaSPIRV.cpp
index 0e78cff9c1774..fa30e149c209a 100644
--- a/clang/lib/Sema/SemaSPIRV.cpp
+++ b/clang/lib/Sema/SemaSPIRV.cpp
@@ -360,6 +360,8 @@ 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_ddx:
+  case SPIRV::BI__builtin_spirv_ddy:
   case SPIRV::BI__builtin_spirv_fwidth: {
     if (SemaRef.checkArgCount(TheCall, 1))
       return true;
diff --git a/clang/test/CodeGenHLSL/builtins/ddx.hlsl b/clang/test/CodeGenHLSL/builtins/ddx.hlsl
new file mode 100644
index 0000000000000..1a736e20c47ae
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/ddx.hlsl
@@ -0,0 +1,86 @@
+// 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-pixel  %s \
+// RUN:  -emit-llvm -disable-llvm-passes -fnative-half-type -o - | \
+// RUN:  FileCheck %s --check-prefixes=CHECK-SPIRV
+
+// CHECK-LABEL: define {{.*}} half @_ZN4hlsl8__detail8ddx_implIDhEET_S2_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} half @llvm.dx.ddx.coarse.f16(half %{{.*}})
+// CHECK: ret half %hlsl.ddx.coarse
+// CHECK-LABEL-SPIRV: half @_ZN4hlsl8__detail8ddx_implIDhEET_S2_
+// CHECK-SPIRV: %spv.ddx = call {{.*}} half @llvm.spv.ddx.f16(half %{{.*}})
+// CHECK-SPIRV: ret half %spv.ddx
+half test_f16_ddx(half val) {
+    return ddx(val);
+}
+
+// CHECK-LABEL: define {{.*}} <2 x half> @_ZN4hlsl8__detail8ddx_implIDv2_DhEET_S3_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} <2 x half> @llvm.dx.ddx.coarse.v2f16(<2 x half> %{{.*}})
+// CHECK: ret <2 x half> %hlsl.ddx.coarse
+// CHECK-LABEL-SPIRV: <2 x half> @_ZN4hlsl8__detail8ddx_implIDv2_DhEET_S3_
+// CHECK-SPIRV: %spv.ddx = call {{.*}} <2 x half> @llvm.spv.ddx.v2f16(<2 x half> %{{.*}})
+// CHECK-SPIRV: ret <2 x half> %spv.ddx
+half2 test_f16_ddx2(half2 val) {
+    return ddx(val);
+}
+
+// CHECK-LABEL: define {{.*}} <3 x half> @_ZN4hlsl8__detail8ddx_implIDv3_DhEET_S3_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} <3 x half> @llvm.dx.ddx.coarse.v3f16(<3 x half> %{{.*}})
+// CHECK: ret <3 x half> %hlsl.ddx.coarse
+// CHECK-LABEL-SPIRV: <3 x half> @_ZN4hlsl8__detail8ddx_implIDv3_DhEET_S3_
+// CHECK-SPIRV: %spv.ddx = call {{.*}} <3 x half> @llvm.spv.ddx.v3f16(<3 x half> %{{.*}})
+// CHECK-SPIRV: ret <3 x half> %spv.ddx
+half3 test_f16_ddx3(half3 val) {
+    return ddx(val);
+}
+
+// CHECK-LABEL: define {{.*}} <4 x half> @_ZN4hlsl8__detail8ddx_implIDv4_DhEET_S3_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} <4 x half> @llvm.dx.ddx.coarse.v4f16(<4 x half> %{{.*}})
+// CHECK: ret <4 x half> %hlsl.ddx.coarse
+// CHECK-LABEL-SPIRV: <4 x half> @_ZN4hlsl8__detail8ddx_implIDv4_DhEET_S3_
+// CHECK-SPIRV: %spv.ddx = call {{.*}} <4 x half> @llvm.spv.ddx.v4f16(<4 x half> %{{.*}})
+// CHECK-SPIRV: ret <4 x half> %spv.ddx
+half4 test_f16_ddx4(half4 val) {
+    return ddx(val);
+}
+
+// CHECK-LABEL: define {{.*}} float @_ZN4hlsl8__detail8ddx_implIfEET_S2_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} float @llvm.dx.ddx.coarse.f32(float %{{.*}})
+// CHECK: ret float %hlsl.ddx.coarse
+// CHECK-LABEL-SPIRV: float @_ZN4hlsl8__detail8ddx_implIfEET_S2_
+// CHECK-SPIRV: %spv.ddx = call {{.*}} float @llvm.spv.ddx.f32(float %{{.*}})
+// CHECK-SPIRV: ret float %spv.ddx
+float test_f32_ddx(float val) {
+    return ddx(val);
+}
+
+// CHECK-LABEL: define {{.*}} <2 x float> @_ZN4hlsl8__detail8ddx_implIDv2_fEET_S3_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} <2 x float> @llvm.dx.ddx.coarse.v2f32(<2 x float> %{{.*}})
+// CHECK: ret <2 x float> %hlsl.ddx.coarse
+// CHECK-LABEL-SPIRV: <2 x float> @_ZN4hlsl8__detail8ddx_implIDv2_fEET_S3_
+// CHECK-SPIRV: %spv.ddx = call {{.*}} <2 x float> @llvm.spv.ddx.v2f32(<2 x float> %{{.*}})
+// CHECK-SPIRV: ret <2 x float> %spv.ddx
+float2 test_f32_ddx2(float2 val) {
+    return ddx(val);
+}
+
+// CHECK-LABEL: define {{.*}} <3 x float> @_ZN4hlsl8__detail8ddx_implIDv3_fEET_S3_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} <3 x float> @llvm.dx.ddx.coarse.v3f32(<3 x float> %{{.*}})
+// CHECK: ret <3 x float> %hlsl.ddx.coarse
+// CHECK-LABEL-SPIRV: <3 x float> @_ZN4hlsl8__detail8ddx_implIDv3_fEET_S3_
+// CHECK-SPIRV: %spv.ddx = call {{.*}} <3 x float> @llvm.spv.ddx.v3f32(<3 x float> %{{.*}})
+// CHECK-SPIRV: ret <3 x float> %spv.ddx
+float3 test_f32_ddx3(float3 val) {
+    return ddx(val);
+}
+
+// CHECK-LABEL: define {{.*}} <4 x float> @_ZN4hlsl8__detail8ddx_implIDv4_fEET_S3_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} <4 x float> @llvm.dx.ddx.coarse.v4f32(<4 x float> %{{.*}})
+// CHECK: ret <4 x float> %hlsl.ddx.coarse
+// CHECK-LABEL-SPIRV: <4 x float> @_ZN4hlsl8__detail8ddx_implIDv4_fEET_S3_
+// CHECK-SPIRV: %spv.ddx = call {{.*}} <4 x float> @llvm.spv.ddx.v4f32(<4 x float> %{{.*}})
+// CHECK-SPIRV: ret <4 x float> %spv.ddx
+float4 test_f32_ddx4(float4 val) {
+    return ddx(val);
+}
diff --git a/clang/test/CodeGenHLSL/builtins/ddy.hlsl b/clang/test/CodeGenHLSL/builtins/ddy.hlsl
new file mode 100644
index 0000000000000..635838327dc1f
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/ddy.hlsl
@@ -0,0 +1,86 @@
+// 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-pixel  %s \
+// RUN:  -emit-llvm -disable-llvm-passes -fnative-half-type -o - | \
+// RUN:  FileCheck %s --check-prefixes=CHECK-SPIRV
+
+// CHECK-LABEL: define {{.*}} half @_ZN4hlsl8__detail8ddy_implIDhEET_S2_
+// CHECK: %hlsl.ddy.coarse = call {{.*}} half @llvm.dx.ddy.coarse.f16(half %{{.*}})
+// CHECK: ret half %hlsl.ddy.coarse
+// CHECK-LABEL-SPIRV: half @_ZN4hlsl8__detail8ddy_implIDhEET_S2_
+// CHECK-SPIRV: %spv.ddy = call {{.*}} half @llvm.spv.ddy.f16(half %{{.*}})
+// CHECK-SPIRV: ret half %spv.ddy
+half test_f16_ddy(half val) {
+    return ddy(val);
+}
+
+// CHECK-LABEL: define {{.*}} <2 x half> @_ZN4hlsl8__detail8ddy_implIDv2_DhEET_S3_
+// CHECK: %hlsl.ddy.coarse = call {{.*}} <2 x half> @llvm.dx.ddy.coarse.v2f16(<2 x half> %{{.*}})
+// CHECK: ret <2 x half> %hlsl.ddy.coarse
+// CHECK-LABEL-SPIRV: <2 x half> @_ZN4hlsl8__detail8ddy_implIDv2_DhEET_S3_
+// CHECK-SPIRV: %spv.ddy = call {{.*}} <2 x half> @llvm.spv.ddy.v2f16(<2 x half> %{{.*}})
+// CHECK-SPIRV: ret <2 x half> %spv.ddy
+half2 test_f16_ddy2(half2 val) {
+    return ddy(val);
+}
+
+// CHECK-LABEL: define {{.*}} <3 x half> @_ZN4hlsl8__detail8ddy_implIDv3_DhEET_S3_
+// CHECK: %hlsl.ddy.coarse = call {{.*}} <3 x half> @llvm.dx.ddy.coarse.v3f16(<3 x half> %{{.*}})
+// CHECK: ret <3 x half> %hlsl.ddy.coarse
+// CHECK-LABEL-SPIRV: <3 x half> @_ZN4hlsl8__detail8ddy_implIDv3_DhEET_S3_
+// CHECK-SPIRV: %spv.ddy = call {{.*}} <3 x half> @llvm.spv.ddy.v3f16(<3 x half> %{{.*}})
+// CHECK-SPIRV: ret <3 x half> %spv.ddy
+half3 test_f16_ddy3(half3 val) {
+    return ddy(val);
+}
+
+// CHECK-LABEL: define {{.*}} <4 x half> @_ZN4hlsl8__detail8ddy_implIDv4_DhEET_S3_
+// CHECK: %hlsl.ddy.coarse = call {{.*}} <4 x half> @llvm.dx.ddy.coarse.v4f16(<4 x half> %{{.*}})
+// CHECK: ret <4 x half> %hlsl.ddy.coarse
+// CHECK-LABEL-SPIRV: <4 x half> @_ZN4hlsl8__detail8ddy_implIDv4_DhEET_S3_
+// CHECK-SPIRV: %spv.ddy = call {{.*}} <4 x half> @llvm.spv.ddy.v4f16(<4 x half> %{{.*}})
+// CHECK-SPIRV: ret <4 x half> %spv.ddy
+half4 test_f16_ddy4(half4 val) {
+    return ddy(val);
+}
+
+// CHECK-LABEL: define {{.*}} float @_ZN4hlsl8__detail8ddy_implIfEET_S2_
+// CHECK: %hlsl.ddy.coarse = call {{.*}} float @llvm.dx.ddy.coarse.f32(float %{{.*}})
+// CHECK: ret float %hlsl.ddy.coarse
+// CHECK-LABEL-SPIRV: float @_ZN4hlsl8__detail8ddy_implIfEET_S2_
+// CHECK-SPIRV: %spv.ddy = call {{.*}} float @llvm.spv.ddy.f32(float %{{.*}})
+// CHECK-SPIRV: ret float %spv.ddy
+float test_f32_ddy(float val) {
+    return ddy(val);
+}
+
+// CHECK-LABEL: define {{.*}} <2 x float> @_ZN4hlsl8__detail8ddy_implIDv2_fEET_S3_
+// CHECK: %hlsl.ddy.coarse = call {{.*}} <2 x float> @llvm.dx.ddy.coarse.v2f32(<2 x float> %{{.*}})
+// CHECK: ret <2 x float> %hlsl.ddy.coarse
+// CHECK-LABEL-SPIRV: <2 x float> @_ZN4hlsl8__detail8ddy_implIDv2_fEET_S3_
+// CHECK-SPIRV: %spv.ddy = call {{.*}} <2 x float> @llvm.spv.ddy.v2f32(<2 x float> %{{.*}})
+// CHECK-SPIRV: ret <2 x float> %spv.ddy
+float2 test_f32_ddy2(float2 val) {
+    return ddy(val);
+}
+
+// CHECK-LABEL: define {{.*}} <3 x float> @_ZN4hlsl8__detail8ddy_implIDv3_fEET_S3_
+// CHECK: %hlsl.ddy.coarse = call {{.*}} <3 x float> @llvm.dx.ddy.coarse.v3f32(<3 x float> %{{.*}})
+// CHECK: ret <3 x float> %hlsl.ddy.coarse
+// CHECK-LABEL-SPIRV: <3 x float> @_ZN4hlsl8__detail8ddy_implIDv3_fEET_S3_
+// CHECK-SPIRV: %spv.ddy = call {{.*}} <3 x float> @llvm.spv.ddy.v3f32(<3 x float> %{{.*}})
+// CHECK-SPIRV: ret <3 x float> %spv.ddy
+float3 test_f32_ddy3(float3 val) {
+    return ddy(val);
+}
+
+// CHECK-LABEL: define {{.*}} <4 x float> @_ZN4hlsl8__detail8ddy_implIDv4_fEET_S3_
+// CHECK: %hlsl.ddy.coarse = call {{.*}} <4 x float> @llvm.dx.ddy.coarse.v4f32(<4 x float> %{{.*}})
+// CHECK: ret <4 x float> %hlsl.ddy.coarse
+// CHECK-LABEL-SPIRV: <4 x float> @_ZN4hlsl8__detail8ddy_implIDv4_fEET_S3_
+// CHECK-SPIRV: %spv.ddy = call {{.*}} <4 x float> @llvm.spv.ddy.v4f32(<4 x float> %{{.*}})
+// CHECK-SPIRV: ret <4 x float> %spv.ddy
+float4 test_f32_ddy4(float4 val) {
+    return ddy(val);
+}
diff --git a/clang/test/CodeGenSPIRV/Builtins/ddx.c b/clang/test/CodeGenSPIRV/Builtins/ddx.c
new file mode 100644
index 0000000000000..d3cfd1fa7f471
--- /dev/null
+++ b/clang/test/CodeGenSPIRV/Builtins/ddx.c
@@ -0,0 +1,41 @@
+// RUN: %clang_cc1 -O1 -triple spirv-pc-vulkan-pixel %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: [[ddx0:%.*]] = tail call half @llvm.spv.ddx.f16(half {{%.*}})
+// CHECK: ret half [[ddx0]]
+half test_ddx_half(half X) { return __builtin_spirv_ddx(X); }
+
+// CHECK: [[ddx0:%.*]] = tail call <2 x half> @llvm.spv.ddx.v2f16(<2 x half>  {{%.*}})
+// CHECK: ret <2 x half> [[ddx0]]
+half2 test_ddx_half2(half2 X) { return __builtin_spirv_ddx(X); }
+
+// CHECK: [[ddx0:%.*]] = tail call <3 x half> @llvm.spv.ddx.v3f16(<3 x half> {{%.*}})
+// CHECK: ret <3 x half> [[ddx0]]
+half3 test_ddx_half3(half3 X) { return __builtin_spirv_ddx(X); }
+
+// CHECK: [[ddx0:%.*]] = tail call <4 x half> @llvm.spv.ddx.v4f16(<4 x half> {{%.*}})
+// CHECK: ret <4 x half> [[ddx0]]
+half4 test_ddx_half4(half4 X) { return __builtin_spirv_ddx(X); }
+
+// CHECK: [[ddx0:%.*]] = tail call float @llvm.spv.ddx.f32(float {{%.*}})
+// CHECK: ret float [[ddx0]]
+float test_ddx_float(float X) { return __builtin_spirv_ddx(X); }
+
+// CHECK: [[ddx1:%.*]] = tail call <2 x float> @llvm.spv.ddx.v2f32(<2 x float> {{%.*}})
+// CHECK: ret <2 x float> [[ddx1]]
+float2 test_ddx_float2(float2 X) { return __builtin_spirv_ddx(X); }
+
+// CHECK: [[ddx2:%.*]] = tail call <3 x float> @llvm.spv.ddx.v3f32(<3 x float> {{%.*}})
+// CHECK: ret <3 x float> [[ddx2]]
+float3 test_ddx_float3(float3 X) { return __builtin_spirv_ddx(X); }
+
+// CHECK: [[ddx3:%.*]] = tail call <4 x float> @llvm.spv.ddx.v4f32(<4 x float> {{%.*}})
+// CHECK: ret <4 x float> [[ddx3]]
+float4 test_ddx_float4(float4 X) { return __builtin_spirv_ddx(X); }
diff --git a/clang/test/CodeGenSPIRV/Builtins/ddy.c b/clang/test/CodeGenSPIRV/Builtins/ddy.c
new file mode 100644
index 0000000000000..bb14b76a9fb86
--- /dev/null
+++ b/clang/test/CodeGenSPIRV/Builtins/ddy.c
@@ -0,0 +1,41 @@
+// RUN: %clang_cc1 -O1 -triple spirv-pc-vulkan-pixel %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: [[ddy0:%.*]] = tail call half @llvm.spv.ddy.f16(half {{%.*}})
+// CHECK: ret half [[ddy0]]
+half test_ddy_half(half X) { return __builtin_spirv_ddy(X); }
+
+// CHECK: [[ddy0:%.*]] = tail call <2 x half> @llvm.spv.ddy.v2f16(<2 x half>  {{%.*}})
+// CHECK: ret <2 x half> [[ddy0]]
+half2 test_ddy_half2(half2 X) { return __builtin_spirv_ddy(X); }
+
+// CHECK: [[ddy0:%.*]] = tail call <3 x half> @llvm.spv.ddy.v3f16(<3 x half> {{%.*}})
+// CHECK: ret <3 x half> [[ddy0]]
+half3 test_ddy_half3(half3 X) { return __builtin_spirv_ddy(X); }
+
+// CHECK: [[ddy0:%.*]] = tail call <4 x half> @llvm.spv.ddy.v4f16(<4 x half> {{%.*}})
+// CHECK: ret <4 x half> [[ddy0]]
+half4 test_ddy_half4(half4 X) { return __builtin_spirv_ddy(X); }
+
+// CHECK: [[ddy0:%.*]] = tail call float @llvm.spv.ddy.f32(float {{%.*}})
+// CHECK: ret float [[ddy0]]
+float test_ddy_float(float X) { return __builtin_spirv_ddy(X); }
+
+// CHECK: [[ddy1:%.*]] = tail call <2 x float> @llvm.spv.ddy.v2f32(<2 x float> {{%.*}})
+// CHECK: ret <2 x float> [[ddy1]]
+float2 test_ddy_float2(float2 X) { return __builtin_spirv_ddy(X); }
+
+// CHECK: [[ddy2:%.*]] = tail call <3 x float> @llvm.spv.ddy.v3f32(<3 x float> {{%.*}})
+// CHECK: ret <3 x float> [[ddy2]]
+float3 test_ddy_float3(float3 X) { return __builtin_spirv_ddy(X); }
+
+// CHECK: [[ddy3:%.*]] = tail call <4 x float> @llvm.spv.ddy.v4f32(<4 x float> {{%.*}})
+// CHECK: ret <4 x float> [[ddy3]]
+float4 test_ddy_float4(float4 X) { return __builtin_spirv_ddy(X); }
diff --git a/clang/test/SemaSPIRV/BuiltIns/ddx-errors.c b/clang/test/SemaSPIRV/BuiltIns/ddx-errors.c
new file mode 100644
index 0000000000000..a29af5dc43e30
--- /dev/null
+++ b/clang/test/SemaSPIRV/BuiltIns/ddx-errors.c
@@ -0,0 +1,24 @@
+// RUN: %clang_cc1 %s -triple spirv-pc-vulkan-pixel -verify
+
+type...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Nov 20, 2025

@llvm/pr-subscribers-backend-spir-v

Author: Alexander Johnston (Alexander-Johnston)

Changes

Implements the ddx and ddy HLSL intrinsics. The DXIL intrinsics call the ddx_coarse and ddy_builtins as they lower to the coarse variants.
The SPIRV intrinsics lower to their own opcodes OpDPdx and OpDPdy. Tests are added to ensure the SPIRV builtins are not available outside of shaders stage.

Closes #99096
Closes #99099


Patch is 29.38 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/168887.diff

17 Files Affected:

  • (modified) clang/include/clang/Basic/BuiltinsSPIRVVK.td (+2)
  • (modified) clang/lib/CodeGen/TargetBuiltins/SPIR.cpp (+8)
  • (modified) clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h (+16)
  • (modified) clang/lib/Headers/hlsl/hlsl_intrinsics.h (+80)
  • (modified) clang/lib/Sema/SemaSPIRV.cpp (+2)
  • (added) clang/test/CodeGenHLSL/builtins/ddx.hlsl (+86)
  • (added) clang/test/CodeGenHLSL/builtins/ddy.hlsl (+86)
  • (added) clang/test/CodeGenSPIRV/Builtins/ddx.c (+41)
  • (added) clang/test/CodeGenSPIRV/Builtins/ddy.c (+41)
  • (added) clang/test/SemaSPIRV/BuiltIns/ddx-errors.c (+24)
  • (added) clang/test/SemaSPIRV/BuiltIns/ddy-errors.c (+24)
  • (modified) llvm/include/llvm/IR/IntrinsicsSPIRV.td (+2)
  • (modified) llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp (+4)
  • (added) llvm/test/CodeGen/SPIRV/hlsl-intrinsics/ddx.ll (+47)
  • (added) llvm/test/CodeGen/SPIRV/hlsl-intrinsics/ddy.ll (+47)
  • (added) llvm/test/CodeGen/SPIRV/opencl/ddx-error.ll (+12)
  • (added) llvm/test/CodeGen/SPIRV/opencl/ddy-error.ll (+12)
diff --git a/clang/include/clang/Basic/BuiltinsSPIRVVK.td b/clang/include/clang/Basic/BuiltinsSPIRVVK.td
index a077a763923d6..67daa16390cf2 100644
--- a/clang/include/clang/Basic/BuiltinsSPIRVVK.td
+++ b/clang/include/clang/Basic/BuiltinsSPIRVVK.td
@@ -12,4 +12,6 @@ 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 ddx : SPIRVBuiltin<"void(...)", [NoThrow, Const, CustomTypeChecking]>;
+def ddy : 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 43b05a128e876..9b0ca3eb0035a 100644
--- a/clang/lib/CodeGen/TargetBuiltins/SPIR.cpp
+++ b/clang/lib/CodeGen/TargetBuiltins/SPIR.cpp
@@ -151,6 +151,14 @@ Value *CodeGenFunction::EmitSPIRVBuiltinExpr(unsigned BuiltinID,
         Intrinsic::spv_global_offset,
         ArrayRef<Value *>{EmitScalarExpr(E->getArg(0))}, nullptr,
         "spv.global.offset");
+  case SPIRV::BI__builtin_spirv_ddx:
+    return Builder.CreateIntrinsic(
+        /*ReturnType=*/getTypes().ConvertType(E->getType()), Intrinsic::spv_ddx,
+        ArrayRef<Value *>{EmitScalarExpr(E->getArg(0))}, nullptr, "spv.ddx");
+  case SPIRV::BI__builtin_spirv_ddy:
+    return Builder.CreateIntrinsic(
+        /*ReturnType=*/getTypes().ConvertType(E->getType()), Intrinsic::spv_ddy,
+        ArrayRef<Value *>{EmitScalarExpr(E->getArg(0))}, nullptr, "spv.ddy");
   case SPIRV::BI__builtin_spirv_fwidth:
     return Builder.CreateIntrinsic(
         /*ReturnType=*/getTypes().ConvertType(E->getType()),
diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h b/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
index d1dc8275431c0..d0e1ec8b02797 100644
--- a/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
+++ b/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
@@ -160,6 +160,22 @@ constexpr K firstbithigh_impl(T X) {
   return FBH;
 }
 
+template <typename T> constexpr T ddx_impl(T input) {
+#if (__has_builtin(__builtin_spirv_ddx))
+  return __builtin_spirv_ddx(input);
+#else
+  return __builtin_hlsl_elementwise_ddx_coarse(input);
+#endif
+}
+
+template <typename T> constexpr T ddy_impl(T input) {
+#if (__has_builtin(__builtin_spirv_ddy))
+  return __builtin_spirv_ddy(input);
+#else
+  return __builtin_hlsl_elementwise_ddy_coarse(input);
+#endif
+}
+
 template <typename T> constexpr T fwidth_impl(T input) {
 #if (__has_builtin(__builtin_spirv_fwidth))
   return __builtin_spirv_fwidth(input);
diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsics.h b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
index c26c8bb5261d4..052dcb7f7f455 100644
--- a/clang/lib/Headers/hlsl/hlsl_intrinsics.h
+++ b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
@@ -666,6 +666,86 @@ smoothstep(__detail::HLSL_FIXED_VECTOR<float, N> Min,
   return __detail::smoothstep_vec_impl(Min, Max, X);
 }
 
+//===----------------------------------------------------------------------===//
+// ddx builtin
+//===----------------------------------------------------------------------===//
+
+/// \fn T ddx(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> ddx(T input) {
+  return __detail::ddx_impl(input);
+}
+
+template <typename T>
+const inline __detail::enable_if_t<
+    __detail::is_arithmetic<T>::Value && __detail::is_same<float, T>::value, T>
+ddx(T input) {
+  return __detail::ddx_impl(input);
+}
+
+template <int N>
+_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
+const inline __detail::HLSL_FIXED_VECTOR<half, N> ddx(
+    __detail::HLSL_FIXED_VECTOR<half, N> input) {
+  return __detail::ddx_impl(input);
+}
+
+template <int N>
+const inline __detail::HLSL_FIXED_VECTOR<float, N>
+ddx(__detail::HLSL_FIXED_VECTOR<float, N> input) {
+  return __detail::ddx_impl(input);
+}
+
+//===----------------------------------------------------------------------===//
+// ddy builtin
+//===----------------------------------------------------------------------===//
+
+/// \fn T ddy(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> ddy(T input) {
+  return __detail::ddy_impl(input);
+}
+
+template <typename T>
+const inline __detail::enable_if_t<
+    __detail::is_arithmetic<T>::Value && __detail::is_same<float, T>::value, T>
+ddy(T input) {
+  return __detail::ddy_impl(input);
+}
+
+template <int N>
+_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
+const inline __detail::HLSL_FIXED_VECTOR<half, N> ddy(
+    __detail::HLSL_FIXED_VECTOR<half, N> input) {
+  return __detail::ddy_impl(input);
+}
+
+template <int N>
+const inline __detail::HLSL_FIXED_VECTOR<float, N>
+ddy(__detail::HLSL_FIXED_VECTOR<float, N> input) {
+  return __detail::ddy_impl(input);
+}
+
 //===----------------------------------------------------------------------===//
 // fwidth builtin
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/Sema/SemaSPIRV.cpp b/clang/lib/Sema/SemaSPIRV.cpp
index 0e78cff9c1774..fa30e149c209a 100644
--- a/clang/lib/Sema/SemaSPIRV.cpp
+++ b/clang/lib/Sema/SemaSPIRV.cpp
@@ -360,6 +360,8 @@ 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_ddx:
+  case SPIRV::BI__builtin_spirv_ddy:
   case SPIRV::BI__builtin_spirv_fwidth: {
     if (SemaRef.checkArgCount(TheCall, 1))
       return true;
diff --git a/clang/test/CodeGenHLSL/builtins/ddx.hlsl b/clang/test/CodeGenHLSL/builtins/ddx.hlsl
new file mode 100644
index 0000000000000..1a736e20c47ae
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/ddx.hlsl
@@ -0,0 +1,86 @@
+// 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-pixel  %s \
+// RUN:  -emit-llvm -disable-llvm-passes -fnative-half-type -o - | \
+// RUN:  FileCheck %s --check-prefixes=CHECK-SPIRV
+
+// CHECK-LABEL: define {{.*}} half @_ZN4hlsl8__detail8ddx_implIDhEET_S2_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} half @llvm.dx.ddx.coarse.f16(half %{{.*}})
+// CHECK: ret half %hlsl.ddx.coarse
+// CHECK-LABEL-SPIRV: half @_ZN4hlsl8__detail8ddx_implIDhEET_S2_
+// CHECK-SPIRV: %spv.ddx = call {{.*}} half @llvm.spv.ddx.f16(half %{{.*}})
+// CHECK-SPIRV: ret half %spv.ddx
+half test_f16_ddx(half val) {
+    return ddx(val);
+}
+
+// CHECK-LABEL: define {{.*}} <2 x half> @_ZN4hlsl8__detail8ddx_implIDv2_DhEET_S3_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} <2 x half> @llvm.dx.ddx.coarse.v2f16(<2 x half> %{{.*}})
+// CHECK: ret <2 x half> %hlsl.ddx.coarse
+// CHECK-LABEL-SPIRV: <2 x half> @_ZN4hlsl8__detail8ddx_implIDv2_DhEET_S3_
+// CHECK-SPIRV: %spv.ddx = call {{.*}} <2 x half> @llvm.spv.ddx.v2f16(<2 x half> %{{.*}})
+// CHECK-SPIRV: ret <2 x half> %spv.ddx
+half2 test_f16_ddx2(half2 val) {
+    return ddx(val);
+}
+
+// CHECK-LABEL: define {{.*}} <3 x half> @_ZN4hlsl8__detail8ddx_implIDv3_DhEET_S3_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} <3 x half> @llvm.dx.ddx.coarse.v3f16(<3 x half> %{{.*}})
+// CHECK: ret <3 x half> %hlsl.ddx.coarse
+// CHECK-LABEL-SPIRV: <3 x half> @_ZN4hlsl8__detail8ddx_implIDv3_DhEET_S3_
+// CHECK-SPIRV: %spv.ddx = call {{.*}} <3 x half> @llvm.spv.ddx.v3f16(<3 x half> %{{.*}})
+// CHECK-SPIRV: ret <3 x half> %spv.ddx
+half3 test_f16_ddx3(half3 val) {
+    return ddx(val);
+}
+
+// CHECK-LABEL: define {{.*}} <4 x half> @_ZN4hlsl8__detail8ddx_implIDv4_DhEET_S3_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} <4 x half> @llvm.dx.ddx.coarse.v4f16(<4 x half> %{{.*}})
+// CHECK: ret <4 x half> %hlsl.ddx.coarse
+// CHECK-LABEL-SPIRV: <4 x half> @_ZN4hlsl8__detail8ddx_implIDv4_DhEET_S3_
+// CHECK-SPIRV: %spv.ddx = call {{.*}} <4 x half> @llvm.spv.ddx.v4f16(<4 x half> %{{.*}})
+// CHECK-SPIRV: ret <4 x half> %spv.ddx
+half4 test_f16_ddx4(half4 val) {
+    return ddx(val);
+}
+
+// CHECK-LABEL: define {{.*}} float @_ZN4hlsl8__detail8ddx_implIfEET_S2_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} float @llvm.dx.ddx.coarse.f32(float %{{.*}})
+// CHECK: ret float %hlsl.ddx.coarse
+// CHECK-LABEL-SPIRV: float @_ZN4hlsl8__detail8ddx_implIfEET_S2_
+// CHECK-SPIRV: %spv.ddx = call {{.*}} float @llvm.spv.ddx.f32(float %{{.*}})
+// CHECK-SPIRV: ret float %spv.ddx
+float test_f32_ddx(float val) {
+    return ddx(val);
+}
+
+// CHECK-LABEL: define {{.*}} <2 x float> @_ZN4hlsl8__detail8ddx_implIDv2_fEET_S3_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} <2 x float> @llvm.dx.ddx.coarse.v2f32(<2 x float> %{{.*}})
+// CHECK: ret <2 x float> %hlsl.ddx.coarse
+// CHECK-LABEL-SPIRV: <2 x float> @_ZN4hlsl8__detail8ddx_implIDv2_fEET_S3_
+// CHECK-SPIRV: %spv.ddx = call {{.*}} <2 x float> @llvm.spv.ddx.v2f32(<2 x float> %{{.*}})
+// CHECK-SPIRV: ret <2 x float> %spv.ddx
+float2 test_f32_ddx2(float2 val) {
+    return ddx(val);
+}
+
+// CHECK-LABEL: define {{.*}} <3 x float> @_ZN4hlsl8__detail8ddx_implIDv3_fEET_S3_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} <3 x float> @llvm.dx.ddx.coarse.v3f32(<3 x float> %{{.*}})
+// CHECK: ret <3 x float> %hlsl.ddx.coarse
+// CHECK-LABEL-SPIRV: <3 x float> @_ZN4hlsl8__detail8ddx_implIDv3_fEET_S3_
+// CHECK-SPIRV: %spv.ddx = call {{.*}} <3 x float> @llvm.spv.ddx.v3f32(<3 x float> %{{.*}})
+// CHECK-SPIRV: ret <3 x float> %spv.ddx
+float3 test_f32_ddx3(float3 val) {
+    return ddx(val);
+}
+
+// CHECK-LABEL: define {{.*}} <4 x float> @_ZN4hlsl8__detail8ddx_implIDv4_fEET_S3_
+// CHECK: %hlsl.ddx.coarse = call {{.*}} <4 x float> @llvm.dx.ddx.coarse.v4f32(<4 x float> %{{.*}})
+// CHECK: ret <4 x float> %hlsl.ddx.coarse
+// CHECK-LABEL-SPIRV: <4 x float> @_ZN4hlsl8__detail8ddx_implIDv4_fEET_S3_
+// CHECK-SPIRV: %spv.ddx = call {{.*}} <4 x float> @llvm.spv.ddx.v4f32(<4 x float> %{{.*}})
+// CHECK-SPIRV: ret <4 x float> %spv.ddx
+float4 test_f32_ddx4(float4 val) {
+    return ddx(val);
+}
diff --git a/clang/test/CodeGenHLSL/builtins/ddy.hlsl b/clang/test/CodeGenHLSL/builtins/ddy.hlsl
new file mode 100644
index 0000000000000..635838327dc1f
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/ddy.hlsl
@@ -0,0 +1,86 @@
+// 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-pixel  %s \
+// RUN:  -emit-llvm -disable-llvm-passes -fnative-half-type -o - | \
+// RUN:  FileCheck %s --check-prefixes=CHECK-SPIRV
+
+// CHECK-LABEL: define {{.*}} half @_ZN4hlsl8__detail8ddy_implIDhEET_S2_
+// CHECK: %hlsl.ddy.coarse = call {{.*}} half @llvm.dx.ddy.coarse.f16(half %{{.*}})
+// CHECK: ret half %hlsl.ddy.coarse
+// CHECK-LABEL-SPIRV: half @_ZN4hlsl8__detail8ddy_implIDhEET_S2_
+// CHECK-SPIRV: %spv.ddy = call {{.*}} half @llvm.spv.ddy.f16(half %{{.*}})
+// CHECK-SPIRV: ret half %spv.ddy
+half test_f16_ddy(half val) {
+    return ddy(val);
+}
+
+// CHECK-LABEL: define {{.*}} <2 x half> @_ZN4hlsl8__detail8ddy_implIDv2_DhEET_S3_
+// CHECK: %hlsl.ddy.coarse = call {{.*}} <2 x half> @llvm.dx.ddy.coarse.v2f16(<2 x half> %{{.*}})
+// CHECK: ret <2 x half> %hlsl.ddy.coarse
+// CHECK-LABEL-SPIRV: <2 x half> @_ZN4hlsl8__detail8ddy_implIDv2_DhEET_S3_
+// CHECK-SPIRV: %spv.ddy = call {{.*}} <2 x half> @llvm.spv.ddy.v2f16(<2 x half> %{{.*}})
+// CHECK-SPIRV: ret <2 x half> %spv.ddy
+half2 test_f16_ddy2(half2 val) {
+    return ddy(val);
+}
+
+// CHECK-LABEL: define {{.*}} <3 x half> @_ZN4hlsl8__detail8ddy_implIDv3_DhEET_S3_
+// CHECK: %hlsl.ddy.coarse = call {{.*}} <3 x half> @llvm.dx.ddy.coarse.v3f16(<3 x half> %{{.*}})
+// CHECK: ret <3 x half> %hlsl.ddy.coarse
+// CHECK-LABEL-SPIRV: <3 x half> @_ZN4hlsl8__detail8ddy_implIDv3_DhEET_S3_
+// CHECK-SPIRV: %spv.ddy = call {{.*}} <3 x half> @llvm.spv.ddy.v3f16(<3 x half> %{{.*}})
+// CHECK-SPIRV: ret <3 x half> %spv.ddy
+half3 test_f16_ddy3(half3 val) {
+    return ddy(val);
+}
+
+// CHECK-LABEL: define {{.*}} <4 x half> @_ZN4hlsl8__detail8ddy_implIDv4_DhEET_S3_
+// CHECK: %hlsl.ddy.coarse = call {{.*}} <4 x half> @llvm.dx.ddy.coarse.v4f16(<4 x half> %{{.*}})
+// CHECK: ret <4 x half> %hlsl.ddy.coarse
+// CHECK-LABEL-SPIRV: <4 x half> @_ZN4hlsl8__detail8ddy_implIDv4_DhEET_S3_
+// CHECK-SPIRV: %spv.ddy = call {{.*}} <4 x half> @llvm.spv.ddy.v4f16(<4 x half> %{{.*}})
+// CHECK-SPIRV: ret <4 x half> %spv.ddy
+half4 test_f16_ddy4(half4 val) {
+    return ddy(val);
+}
+
+// CHECK-LABEL: define {{.*}} float @_ZN4hlsl8__detail8ddy_implIfEET_S2_
+// CHECK: %hlsl.ddy.coarse = call {{.*}} float @llvm.dx.ddy.coarse.f32(float %{{.*}})
+// CHECK: ret float %hlsl.ddy.coarse
+// CHECK-LABEL-SPIRV: float @_ZN4hlsl8__detail8ddy_implIfEET_S2_
+// CHECK-SPIRV: %spv.ddy = call {{.*}} float @llvm.spv.ddy.f32(float %{{.*}})
+// CHECK-SPIRV: ret float %spv.ddy
+float test_f32_ddy(float val) {
+    return ddy(val);
+}
+
+// CHECK-LABEL: define {{.*}} <2 x float> @_ZN4hlsl8__detail8ddy_implIDv2_fEET_S3_
+// CHECK: %hlsl.ddy.coarse = call {{.*}} <2 x float> @llvm.dx.ddy.coarse.v2f32(<2 x float> %{{.*}})
+// CHECK: ret <2 x float> %hlsl.ddy.coarse
+// CHECK-LABEL-SPIRV: <2 x float> @_ZN4hlsl8__detail8ddy_implIDv2_fEET_S3_
+// CHECK-SPIRV: %spv.ddy = call {{.*}} <2 x float> @llvm.spv.ddy.v2f32(<2 x float> %{{.*}})
+// CHECK-SPIRV: ret <2 x float> %spv.ddy
+float2 test_f32_ddy2(float2 val) {
+    return ddy(val);
+}
+
+// CHECK-LABEL: define {{.*}} <3 x float> @_ZN4hlsl8__detail8ddy_implIDv3_fEET_S3_
+// CHECK: %hlsl.ddy.coarse = call {{.*}} <3 x float> @llvm.dx.ddy.coarse.v3f32(<3 x float> %{{.*}})
+// CHECK: ret <3 x float> %hlsl.ddy.coarse
+// CHECK-LABEL-SPIRV: <3 x float> @_ZN4hlsl8__detail8ddy_implIDv3_fEET_S3_
+// CHECK-SPIRV: %spv.ddy = call {{.*}} <3 x float> @llvm.spv.ddy.v3f32(<3 x float> %{{.*}})
+// CHECK-SPIRV: ret <3 x float> %spv.ddy
+float3 test_f32_ddy3(float3 val) {
+    return ddy(val);
+}
+
+// CHECK-LABEL: define {{.*}} <4 x float> @_ZN4hlsl8__detail8ddy_implIDv4_fEET_S3_
+// CHECK: %hlsl.ddy.coarse = call {{.*}} <4 x float> @llvm.dx.ddy.coarse.v4f32(<4 x float> %{{.*}})
+// CHECK: ret <4 x float> %hlsl.ddy.coarse
+// CHECK-LABEL-SPIRV: <4 x float> @_ZN4hlsl8__detail8ddy_implIDv4_fEET_S3_
+// CHECK-SPIRV: %spv.ddy = call {{.*}} <4 x float> @llvm.spv.ddy.v4f32(<4 x float> %{{.*}})
+// CHECK-SPIRV: ret <4 x float> %spv.ddy
+float4 test_f32_ddy4(float4 val) {
+    return ddy(val);
+}
diff --git a/clang/test/CodeGenSPIRV/Builtins/ddx.c b/clang/test/CodeGenSPIRV/Builtins/ddx.c
new file mode 100644
index 0000000000000..d3cfd1fa7f471
--- /dev/null
+++ b/clang/test/CodeGenSPIRV/Builtins/ddx.c
@@ -0,0 +1,41 @@
+// RUN: %clang_cc1 -O1 -triple spirv-pc-vulkan-pixel %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: [[ddx0:%.*]] = tail call half @llvm.spv.ddx.f16(half {{%.*}})
+// CHECK: ret half [[ddx0]]
+half test_ddx_half(half X) { return __builtin_spirv_ddx(X); }
+
+// CHECK: [[ddx0:%.*]] = tail call <2 x half> @llvm.spv.ddx.v2f16(<2 x half>  {{%.*}})
+// CHECK: ret <2 x half> [[ddx0]]
+half2 test_ddx_half2(half2 X) { return __builtin_spirv_ddx(X); }
+
+// CHECK: [[ddx0:%.*]] = tail call <3 x half> @llvm.spv.ddx.v3f16(<3 x half> {{%.*}})
+// CHECK: ret <3 x half> [[ddx0]]
+half3 test_ddx_half3(half3 X) { return __builtin_spirv_ddx(X); }
+
+// CHECK: [[ddx0:%.*]] = tail call <4 x half> @llvm.spv.ddx.v4f16(<4 x half> {{%.*}})
+// CHECK: ret <4 x half> [[ddx0]]
+half4 test_ddx_half4(half4 X) { return __builtin_spirv_ddx(X); }
+
+// CHECK: [[ddx0:%.*]] = tail call float @llvm.spv.ddx.f32(float {{%.*}})
+// CHECK: ret float [[ddx0]]
+float test_ddx_float(float X) { return __builtin_spirv_ddx(X); }
+
+// CHECK: [[ddx1:%.*]] = tail call <2 x float> @llvm.spv.ddx.v2f32(<2 x float> {{%.*}})
+// CHECK: ret <2 x float> [[ddx1]]
+float2 test_ddx_float2(float2 X) { return __builtin_spirv_ddx(X); }
+
+// CHECK: [[ddx2:%.*]] = tail call <3 x float> @llvm.spv.ddx.v3f32(<3 x float> {{%.*}})
+// CHECK: ret <3 x float> [[ddx2]]
+float3 test_ddx_float3(float3 X) { return __builtin_spirv_ddx(X); }
+
+// CHECK: [[ddx3:%.*]] = tail call <4 x float> @llvm.spv.ddx.v4f32(<4 x float> {{%.*}})
+// CHECK: ret <4 x float> [[ddx3]]
+float4 test_ddx_float4(float4 X) { return __builtin_spirv_ddx(X); }
diff --git a/clang/test/CodeGenSPIRV/Builtins/ddy.c b/clang/test/CodeGenSPIRV/Builtins/ddy.c
new file mode 100644
index 0000000000000..bb14b76a9fb86
--- /dev/null
+++ b/clang/test/CodeGenSPIRV/Builtins/ddy.c
@@ -0,0 +1,41 @@
+// RUN: %clang_cc1 -O1 -triple spirv-pc-vulkan-pixel %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: [[ddy0:%.*]] = tail call half @llvm.spv.ddy.f16(half {{%.*}})
+// CHECK: ret half [[ddy0]]
+half test_ddy_half(half X) { return __builtin_spirv_ddy(X); }
+
+// CHECK: [[ddy0:%.*]] = tail call <2 x half> @llvm.spv.ddy.v2f16(<2 x half>  {{%.*}})
+// CHECK: ret <2 x half> [[ddy0]]
+half2 test_ddy_half2(half2 X) { return __builtin_spirv_ddy(X); }
+
+// CHECK: [[ddy0:%.*]] = tail call <3 x half> @llvm.spv.ddy.v3f16(<3 x half> {{%.*}})
+// CHECK: ret <3 x half> [[ddy0]]
+half3 test_ddy_half3(half3 X) { return __builtin_spirv_ddy(X); }
+
+// CHECK: [[ddy0:%.*]] = tail call <4 x half> @llvm.spv.ddy.v4f16(<4 x half> {{%.*}})
+// CHECK: ret <4 x half> [[ddy0]]
+half4 test_ddy_half4(half4 X) { return __builtin_spirv_ddy(X); }
+
+// CHECK: [[ddy0:%.*]] = tail call float @llvm.spv.ddy.f32(float {{%.*}})
+// CHECK: ret float [[ddy0]]
+float test_ddy_float(float X) { return __builtin_spirv_ddy(X); }
+
+// CHECK: [[ddy1:%.*]] = tail call <2 x float> @llvm.spv.ddy.v2f32(<2 x float> {{%.*}})
+// CHECK: ret <2 x float> [[ddy1]]
+float2 test_ddy_float2(float2 X) { return __builtin_spirv_ddy(X); }
+
+// CHECK: [[ddy2:%.*]] = tail call <3 x float> @llvm.spv.ddy.v3f32(<3 x float> {{%.*}})
+// CHECK: ret <3 x float> [[ddy2]]
+float3 test_ddy_float3(float3 X) { return __builtin_spirv_ddy(X); }
+
+// CHECK: [[ddy3:%.*]] = tail call <4 x float> @llvm.spv.ddy.v4f32(<4 x float> {{%.*}})
+// CHECK: ret <4 x float> [[ddy3]]
+float4 test_ddy_float4(float4 X) { return __builtin_spirv_ddy(X); }
diff --git a/clang/test/SemaSPIRV/BuiltIns/ddx-errors.c b/clang/test/SemaSPIRV/BuiltIns/ddx-errors.c
new file mode 100644
index 0000000000000..a29af5dc43e30
--- /dev/null
+++ b/clang/test/SemaSPIRV/BuiltIns/ddx-errors.c
@@ -0,0 +1,24 @@
+// RUN: %clang_cc1 %s -triple spirv-pc-vulkan-pixel -verify
+
+type...
[truncated]

@github-actions
Copy link

🐧 Linux x64 Test Results

  • 192864 tests passed
  • 6184 tests skipped

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend:SPIR-V backend:X86 clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:headers Headers provided by Clang, e.g. for intrinsics HLSL HLSL Language Support llvm:ir

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement the ddy HLSL Function Implement the ddx HLSL Function

2 participants