[clang] [llvm] [HLSL] Implement ddx and ddy HLSL intrinsics (PR #168887)
Alexander Johnston via llvm-commits
llvm-commits at lists.llvm.org
Thu Nov 20 07:28:23 PST 2025
https://github.com/Alexander-Johnston updated https://github.com/llvm/llvm-project/pull/168887
>From 9eeb0f70545165f05235bd117f1867c5fc43132b Mon Sep 17 00:00:00 2001
From: Alexander Johnston <alexander.johnston at amd.com>
Date: Thu, 20 Nov 2025 14:58:13 +0000
Subject: [PATCH 1/2] [HLSL] Implement ddx and ddy HLSL intrinsics
ddx and ddy lower are implemented as ddx_coarse and ddy_coarse in DXIL.
They lower to their own opcodes in SPIRV.
---
clang/include/clang/Basic/BuiltinsSPIRVVK.td | 2 +
clang/lib/CodeGen/TargetBuiltins/SPIR.cpp | 8 ++
.../lib/Headers/hlsl/hlsl_intrinsic_helpers.h | 16 ++++
clang/lib/Headers/hlsl/hlsl_intrinsics.h | 80 +++++++++++++++++
clang/lib/Sema/SemaSPIRV.cpp | 2 +
clang/test/CodeGenHLSL/builtins/ddx.hlsl | 86 +++++++++++++++++++
clang/test/CodeGenHLSL/builtins/ddy.hlsl | 86 +++++++++++++++++++
clang/test/CodeGenSPIRV/Builtins/ddx.c | 41 +++++++++
clang/test/CodeGenSPIRV/Builtins/ddy.c | 41 +++++++++
clang/test/SemaSPIRV/BuiltIns/ddx-errors.c | 24 ++++++
clang/test/SemaSPIRV/BuiltIns/ddy-errors.c | 24 ++++++
llvm/include/llvm/IR/IntrinsicsSPIRV.td | 2 +
.../Target/SPIRV/SPIRVInstructionSelector.cpp | 4 +
.../test/CodeGen/SPIRV/hlsl-intrinsics/ddx.ll | 47 ++++++++++
.../test/CodeGen/SPIRV/hlsl-intrinsics/ddy.ll | 47 ++++++++++
llvm/test/CodeGen/SPIRV/opencl/ddx-error.ll | 12 +++
llvm/test/CodeGen/SPIRV/opencl/ddy-error.ll | 12 +++
17 files changed, 534 insertions(+)
create mode 100644 clang/test/CodeGenHLSL/builtins/ddx.hlsl
create mode 100644 clang/test/CodeGenHLSL/builtins/ddy.hlsl
create mode 100644 clang/test/CodeGenSPIRV/Builtins/ddx.c
create mode 100644 clang/test/CodeGenSPIRV/Builtins/ddy.c
create mode 100644 clang/test/SemaSPIRV/BuiltIns/ddx-errors.c
create mode 100644 clang/test/SemaSPIRV/BuiltIns/ddy-errors.c
create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-intrinsics/ddx.ll
create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-intrinsics/ddy.ll
create mode 100644 llvm/test/CodeGen/SPIRV/opencl/ddx-error.ll
create mode 100644 llvm/test/CodeGen/SPIRV/opencl/ddy-error.ll
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
+
+typedef float float2 __attribute__((ext_vector_type(2)));
+
+void test_too_few_arg()
+{
+ return __builtin_spirv_ddx();
+ // expected-error at -1 {{too few arguments to function call, expected 1, have 0}}
+}
+
+float test_too_many_arg(float p0) {
+ return __builtin_spirv_ddx(p0, p0);
+ // expected-error at -1 {{too many arguments to function call, expected 1, have 2}}
+}
+
+float test_int_scalar_inputs(int p0) {
+ return __builtin_spirv_ddx(p0);
+ // expected-error at -1 {{1st argument must be a scalar or vector of floating-point types (was 'int')}}
+}
+
+float test_mismatched_return(float2 p0) {
+ return __builtin_spirv_ddx(p0);
+ // expected-error at -1 {{returning 'float2' (vector of 2 'float' values) from a function with incompatible result type 'float'}}
+}
diff --git a/clang/test/SemaSPIRV/BuiltIns/ddy-errors.c b/clang/test/SemaSPIRV/BuiltIns/ddy-errors.c
new file mode 100644
index 0000000000000..65c37af1369c5
--- /dev/null
+++ b/clang/test/SemaSPIRV/BuiltIns/ddy-errors.c
@@ -0,0 +1,24 @@
+// RUN: %clang_cc1 %s -triple spirv-pc-vulkan-pixel -verify
+
+typedef float float2 __attribute__((ext_vector_type(2)));
+
+void test_too_few_arg()
+{
+ return __builtin_spirv_ddy();
+ // expected-error at -1 {{too few arguments to function call, expected 1, have 0}}
+}
+
+float test_too_many_arg(float p0) {
+ return __builtin_spirv_ddy(p0, p0);
+ // expected-error at -1 {{too many arguments to function call, expected 1, have 2}}
+}
+
+float test_int_scalar_inputs(int p0) {
+ return __builtin_spirv_ddy(p0);
+ // expected-error at -1 {{1st argument must be a scalar or vector of floating-point types (was 'int')}}
+}
+
+float test_mismatched_return(float2 p0) {
+ return __builtin_spirv_ddy(p0);
+ // expected-error at -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 366f8cf36d75c..c3b4d8e821318 100644
--- a/llvm/include/llvm/IR/IntrinsicsSPIRV.td
+++ b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
@@ -134,6 +134,8 @@ def int_spv_rsqrt : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty]
def int_spv_group_memory_barrier_with_group_sync
: DefaultAttrsIntrinsic<[], [], [IntrConvergent]>;
def int_spv_discard : DefaultAttrsIntrinsic<[], [], []>;
+ def int_spv_ddx : DefaultAttrsIntrinsic<[llvm_anyfloat_ty], [LLVMMatchType<0>], [IntrNoMem]>;
+ def int_spv_ddy : DefaultAttrsIntrinsic<[llvm_anyfloat_ty], [LLVMMatchType<0>], [IntrNoMem]>;
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]>;
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index d3fc08eb56cb3..1ee07fe049611 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -3582,6 +3582,10 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
case Intrinsic::spv_unpackhalf2x16: {
return selectExtInst(ResVReg, ResType, I, GL::UnpackHalf2x16);
}
+ case Intrinsic::spv_ddx:
+ return selectDerivativeInst(ResVReg, ResType, I, SPIRV::OpDPdx);
+ case Intrinsic::spv_ddy:
+ return selectDerivativeInst(ResVReg, ResType, I, SPIRV::OpDPdy);
case Intrinsic::spv_ddx_coarse:
return selectDerivativeInst(ResVReg, ResType, I, SPIRV::OpDPdxCoarse);
case Intrinsic::spv_ddy_coarse:
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/ddx.ll b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/ddx.ll
new file mode 100644
index 0000000000000..c4f65a00d1374
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/ddx.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 --target-env spv1.4 %}
+
+; 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 @ddx_float(float noundef %a) {
+entry:
+; CHECK: %[[#float_32_arg:]] = OpFunctionParameter %[[#float_32]]
+; CHECK: %[[#]] = OpDPdx %[[#float_32]] %[[#float_32_arg]]
+ %elt.ddx = call float @llvm.spv.ddx.f32(float %a)
+ ret float %elt.ddx
+}
+
+define noundef half @ddx_half(half noundef %a) {
+entry:
+; CHECK: %[[#float_16_arg:]] = OpFunctionParameter %[[#float_16]]
+; CHECK: %[[#converted:]] = OpFConvert %[[#float_32:]] %[[#float_16_arg]]
+; CHECK: %[[#ddx:]] = OpDPdx %[[#float_32]] %[[#converted]]
+; CHECK: %[[#]] = OpFConvert %[[#float_16]] %[[#ddx]]
+ %elt.ddx = call half @llvm.spv.ddx.f16(half %a)
+ ret half %elt.ddx
+}
+
+define noundef <4 x float> @ddx_float_vector(<4 x float> noundef %a) {
+entry:
+; CHECK: %[[#vec4_float_32_arg:]] = OpFunctionParameter %[[#vec4_float_32]]
+; CHECK: %[[#]] = OpDPdx %[[#vec4_float_32]] %[[#vec4_float_32_arg]]
+ %elt.ddx = call <4 x float> @llvm.spv.ddx.v4f32(<4 x float> %a)
+ ret <4 x float> %elt.ddx
+}
+
+define noundef <4 x half> @ddx_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: %[[#ddx:]] = OpDPdx %[[#vec4_float_32]] %[[#converted]]
+; CHECK: %[[#]] = OpFConvert %[[#vec4_float_16]] %[[#ddx]]
+ %elt.ddx = call <4 x half> @llvm.spv.ddx.v4f16(<4 x half> %a)
+ ret <4 x half> %elt.ddx
+}
+
+declare float @llvm.spv.ddx.f32(float)
+declare half @llvm.spv.ddx.f16(half)
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/ddy.ll b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/ddy.ll
new file mode 100644
index 0000000000000..dd6250b757d9f
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/ddy.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 --target-env spv1.4 %}
+
+; 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 @ddy_float(float noundef %a) {
+entry:
+; CHECK: %[[#float_32_arg:]] = OpFunctionParameter %[[#float_32]]
+; CHECK: %[[#]] = OpDPdy %[[#float_32]] %[[#float_32_arg]]
+ %elt.ddy = call float @llvm.spv.ddy.f32(float %a)
+ ret float %elt.ddy
+}
+
+define noundef half @ddy_half(half noundef %a) {
+entry:
+; CHECK: %[[#float_16_arg:]] = OpFunctionParameter %[[#float_16]]
+; CHECK: %[[#converted:]] = OpFConvert %[[#float_32:]] %[[#float_16_arg]]
+; CHECK: %[[#ddy:]] = OpDPdy %[[#float_32]] %[[#converted]]
+; CHECK: %[[#]] = OpFConvert %[[#float_16]] %[[#ddy]]
+ %elt.ddy = call half @llvm.spv.ddy.f16(half %a)
+ ret half %elt.ddy
+}
+
+define noundef <4 x float> @ddy_float_vector(<4 x float> noundef %a) {
+entry:
+; CHECK: %[[#vec4_float_32_arg:]] = OpFunctionParameter %[[#vec4_float_32]]
+; CHECK: %[[#]] = OpDPdy %[[#vec4_float_32]] %[[#vec4_float_32_arg]]
+ %elt.ddy = call <4 x float> @llvm.spv.ddy.v4f32(<4 x float> %a)
+ ret <4 x float> %elt.ddy
+}
+
+define noundef <4 x half> @ddy_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: %[[#ddy:]] = OpDPdy %[[#vec4_float_32]] %[[#converted]]
+; CHECK: %[[#]] = OpFConvert %[[#vec4_float_16]] %[[#ddy]]
+ %elt.ddy = call <4 x half> @llvm.spv.ddy.v4f16(<4 x half> %a)
+ ret <4 x half> %elt.ddy
+}
+
+declare float @llvm.spv.ddy.f32(float)
+declare half @llvm.spv.ddy.f16(half)
diff --git a/llvm/test/CodeGen/SPIRV/opencl/ddx-error.ll b/llvm/test/CodeGen/SPIRV/opencl/ddx-error.ll
new file mode 100644
index 0000000000000..5ab1147cee60c
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/opencl/ddx-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.ddx), %{{.*}} is only supported in shaders.
+
+define noundef float @ddx(float noundef %a) {
+entry:
+ %spv.ddx = call float @llvm.spv.ddx.f32(float %a)
+ ret float %spv.ddx
+}
+
+declare float @llvm.spv.ddx.f32(float)
diff --git a/llvm/test/CodeGen/SPIRV/opencl/ddy-error.ll b/llvm/test/CodeGen/SPIRV/opencl/ddy-error.ll
new file mode 100644
index 0000000000000..9b281c338f64c
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/opencl/ddy-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.ddy), %{{.*}} is only supported in shaders.
+
+define noundef float @ddy(float noundef %a) {
+entry:
+ %spv.ddy = call float @llvm.spv.ddy.f32(float %a)
+ ret float %spv.ddy
+}
+
+declare float @llvm.spv.ddy.f32(float)
>From 314a509717649b75f29d2b74e32fcb7961321f3a Mon Sep 17 00:00:00 2001
From: Alexander Johnston <alexander.johnston at amd.com>
Date: Thu, 20 Nov 2025 15:28:08 +0000
Subject: [PATCH 2/2] Fix intrinsics comment
---
clang/lib/Headers/hlsl/hlsl_intrinsics.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsics.h b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
index 052dcb7f7f455..db42972993cbe 100644
--- a/clang/lib/Headers/hlsl/hlsl_intrinsics.h
+++ b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
@@ -672,7 +672,7 @@ smoothstep(__detail::HLSL_FIXED_VECTOR<float, N> Min,
/// \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.
+/// with regard to the x screen space coordinate.
/// \param x [in] The floating-point scalar or vector to process.
///
/// The return value is a floating-point scalar or vector where each element
@@ -712,7 +712,7 @@ ddx(__detail::HLSL_FIXED_VECTOR<float, N> input) {
/// \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.
+/// with regard to the y screen space coordinate.
/// \param x [in] The floating-point scalar or vector to process.
///
/// The return value is a floating-point scalar or vector where each element
More information about the llvm-commits
mailing list