Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HLSL] Implement the smoothstep intrinsic #132288

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

kmpeng
Copy link
Contributor

@kmpeng kmpeng commented Mar 20, 2025

Closes #99156.

Tasks completed:

  • Implement smoothstep using HLSL source in hlsl_intrinsics.h
  • Implement the smoothstep SPIR-V target built-in in clang/include/clang/Basic/BuiltinsSPIRV.td
  • Add sema checks for smoothstep to CheckSPIRVBuiltinFunctionCall in clang/lib/Sema/SemaSPIRV.cpp
  • Add codegen for spv smoothstep to EmitSPIRVBuiltinExpr in clang/lib/CodeGen/TargetBuiltins/SPIR.cpp
  • Add codegen tests to clang/test/CodeGenHLSL/builtins/smoothstep.hlsl
  • Add spv codegen test to clang/test/CodeGenSPIRV/Builtins/smoothstep.c
  • Add sema tests to clang/test/SemaHLSL/BuiltIns/smoothstep-errors.hlsl
  • Add spv sema tests to clang/test/SemaSPIRV/BuiltIns/smoothstep-errors.c
  • Create the int_spv_smoothstep intrinsic in IntrinsicsSPIRV.td
  • In SPIRVInstructionSelector.cpp create the smoothstep lowering and map it to int_spv_smoothstep in SPIRVInstructionSelector::selectIntrinsic
  • Create SPIR-V backend test case in llvm/test/CodeGen/SPIRV/hlsl-intrinsics/smoothstep.ll
  • Create SPIR-V backend test case in llvm/test/CodeGen/SPIRV/opencl/smoothstep.ll

@llvmbot llvmbot added clang Clang issues not falling into any other category 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 Mar 20, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 20, 2025

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

@llvm/pr-subscribers-clang

Author: Kaitlin Peng (kmpeng)

Changes

Closes #99156.

Tasks completed:

  • Implement smoothstep using HLSL source in hlsl_intrinsics.h
  • Implement the smoothstep SPIR-V target built-in in clang/include/clang/Basic/BuiltinsSPIRV.td
  • Add sema checks for smoothstep to CheckSPIRVBuiltinFunctionCall in clang/lib/Sema/SemaSPIRV.cpp
  • Add codegen for spv smoothstep to EmitSPIRVBuiltinExpr in CGBuiltin.cpp
  • Add codegen tests to clang/test/CodeGenHLSL/builtins/smoothstep.hlsl
  • Add spv codegen test to clang/test/CodeGenSPIRV/Builtins/smoothstep.c
  • Add sema tests to clang/test/SemaHLSL/BuiltIns/smoothstep-errors.hlsl
  • Add spv sema tests to clang/test/SemaSPIRV/BuiltIns/smoothstep-errors.c
  • Create the int_spv_smoothstep intrinsic in IntrinsicsSPIRV.td
  • In SPIRVInstructionSelector.cpp create the smoothstep lowering and map it to int_spv_smoothstep in SPIRVInstructionSelector::selectIntrinsic
  • Create SPIR-V backend test case in llvm/test/CodeGen/SPIRV/hlsl-intrinsics/smoothstep.ll
  • Create SPIR-V backend test case in llvm/test/CodeGen/SPIRV/opencl/smoothstep.ll

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

13 Files Affected:

  • (modified) clang/include/clang/Basic/BuiltinsSPIRV.td (+6)
  • (modified) clang/lib/CodeGen/CGBuiltin.cpp (+19)
  • (modified) clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h (+20)
  • (modified) clang/lib/Headers/hlsl/hlsl_intrinsics.h (+48)
  • (modified) clang/lib/Sema/SemaSPIRV.cpp (+38)
  • (added) clang/test/CodeGenHLSL/builtins/smoothstep.hlsl (+183)
  • (added) clang/test/CodeGenSPIRV/Builtins/smoothstep.c (+44)
  • (added) clang/test/SemaHLSL/BuiltIns/smoothstep-errors.hlsl (+66)
  • (added) clang/test/SemaSPIRV/BuiltIns/smoothstep-errors.c (+18)
  • (modified) llvm/include/llvm/IR/IntrinsicsSPIRV.td (+1)
  • (modified) llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp (+2)
  • (added) llvm/test/CodeGen/SPIRV/hlsl-intrinsics/smoothstep.ll (+60)
  • (added) llvm/test/CodeGen/SPIRV/opencl/smoothstep.ll (+61)
diff --git a/clang/include/clang/Basic/BuiltinsSPIRV.td b/clang/include/clang/Basic/BuiltinsSPIRV.td
index 34933e889ba31..2eb4c733b0a42 100644
--- a/clang/include/clang/Basic/BuiltinsSPIRV.td
+++ b/clang/include/clang/Basic/BuiltinsSPIRV.td
@@ -25,3 +25,9 @@ def SPIRVReflect : Builtin {
   let Attributes = [NoThrow, Const];
   let Prototype = "void(...)";
 }
+
+def SPIRVSmoothStep : Builtin {
+  let Spellings = ["__builtin_spirv_smoothstep"];
+  let Attributes = [NoThrow, Const];
+  let Prototype = "void(...)";
+}
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index c126f88b9e3a5..ac92f89415724 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -20955,6 +20955,25 @@ Value *CodeGenFunction::EmitSPIRVBuiltinExpr(unsigned BuiltinID,
         /*ReturnType=*/I->getType(), Intrinsic::spv_reflect,
         ArrayRef<Value *>{I, N}, nullptr, "spv.reflect");
   }
+  case SPIRV::BI__builtin_spirv_smoothstep: {
+    Value *Min = EmitScalarExpr(E->getArg(0));
+    Value *Max = EmitScalarExpr(E->getArg(1));
+    Value *X = EmitScalarExpr(E->getArg(2));
+    assert(E->getArg(0)->getType()->hasFloatingRepresentation() &&
+           E->getArg(1)->getType()->hasFloatingRepresentation() &&
+           E->getArg(2)->getType()->hasFloatingRepresentation() &&
+           "SmoothStep operands must have a float representation");
+    assert((E->getArg(0)->getType()->isScalarType() ||
+            E->getArg(0)->getType()->isVectorType()) &&
+           (E->getArg(1)->getType()->isScalarType() ||
+            E->getArg(1)->getType()->isVectorType()) &&
+           (E->getArg(2)->getType()->isScalarType() ||
+            E->getArg(2)->getType()->isVectorType()) &&
+           "SmoothStep operands must be a scalar or vector");
+    return Builder.CreateIntrinsic(
+        /*ReturnType=*/Min->getType(), Intrinsic::spv_smoothstep,
+        ArrayRef<Value *>{Min, Max, X}, nullptr, "spv.smoothstep");
+  }
   }
   return nullptr;
 }
diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h b/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
index 89ab664e90ba9..8cdd63d7e07bb 100644
--- a/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
+++ b/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
@@ -81,6 +81,26 @@ constexpr vector<T, N> fmod_vec_impl(vector<T, N> X, vector<T, N> Y) {
 #endif
 }
 
+template <typename T> constexpr T smoothstep_impl(T Min, T Max, T X) {
+#if (__has_builtin(__builtin_spirv_smoothstep))
+  return __builtin_spirv_smoothstep(Min, Max, X);
+#else
+  T S = saturate((X - Min) / (Max - Min));
+  return (3 - 2 * S) * S * S;
+#endif
+}
+
+template <typename T, int N>
+constexpr vector<T, N> smoothstep_vec_impl(vector<T, N> Min, vector<T, N> Max,
+                                           vector<T, N> X) {
+#if (__has_builtin(__builtin_spirv_smoothstep))
+  return __builtin_spirv_smoothstep(Min, Max, X);
+#else
+  vector<T, N> S = saturate((X - Min) / (Max - Min));
+  return (3 - 2 * S) * S * S;
+#endif
+}
+
 } // namespace __detail
 } // namespace hlsl
 
diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsics.h b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
index a48a8e998a015..3803a87d32afc 100644
--- a/clang/lib/Headers/hlsl/hlsl_intrinsics.h
+++ b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
@@ -264,5 +264,53 @@ reflect(__detail::HLSL_FIXED_VECTOR<float, L> I,
         __detail::HLSL_FIXED_VECTOR<float, L> N) {
   return __detail::reflect_vec_impl(I, N);
 }
+
+//===----------------------------------------------------------------------===//
+// smoothstep builtin
+//===----------------------------------------------------------------------===//
+
+/// \fn T smoothstep(T Min, T Max, T X)
+/// \brief Returns a smooth Hermite interpolation between 0 and 1, if \a X is in
+/// the range [\a Min, \a Max].
+/// \param Min The minimum range of the x parameter.
+/// \param Max The maximum range of the x parameter.
+/// \param X The specified value to be interpolated.
+///
+/// The return value is 0.0 if \a X ≤ \a Min and 1.0 if \a X ≥ \a Max. When \a
+/// Min < \a X < \a Max, the function performs smooth Hermite interpolation
+/// between 0 and 1.
+
+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> smoothstep(T Min, T Max, T X) {
+  return __detail::smoothstep_impl(Min, Max, X);
+}
+
+template <typename T>
+const inline __detail::enable_if_t<
+    __detail::is_arithmetic<T>::Value && __detail::is_same<float, T>::value, T>
+smoothstep(T Min, T Max, T X) {
+  return __detail::smoothstep_impl(Min, Max, X);
+}
+
+template <int N>
+_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
+const inline __detail::HLSL_FIXED_VECTOR<half, N> smoothstep(
+    __detail::HLSL_FIXED_VECTOR<half, N> Min,
+    __detail::HLSL_FIXED_VECTOR<half, N> Max,
+    __detail::HLSL_FIXED_VECTOR<half, N> X) {
+  return __detail::smoothstep_vec_impl(Min, Max, X);
+}
+
+template <int N>
+const inline __detail::HLSL_FIXED_VECTOR<float, N>
+smoothstep(__detail::HLSL_FIXED_VECTOR<float, N> Min,
+           __detail::HLSL_FIXED_VECTOR<float, N> Max,
+           __detail::HLSL_FIXED_VECTOR<float, N> X) {
+  return __detail::smoothstep_vec_impl(Min, Max, X);
+}
+
 } // namespace hlsl
 #endif //_HLSL_HLSL_INTRINSICS_H_
diff --git a/clang/lib/Sema/SemaSPIRV.cpp b/clang/lib/Sema/SemaSPIRV.cpp
index 94534485e07c3..2cbb57a739a1a 100644
--- a/clang/lib/Sema/SemaSPIRV.cpp
+++ b/clang/lib/Sema/SemaSPIRV.cpp
@@ -101,6 +101,44 @@ bool SemaSPIRV::CheckSPIRVBuiltinFunctionCall(unsigned BuiltinID,
     TheCall->setType(RetTy);
     break;
   }
+  case SPIRV::BI__builtin_spirv_smoothstep: {
+    if (SemaRef.checkArgCount(TheCall, 3))
+      return true;
+
+    ExprResult A = TheCall->getArg(0);
+    QualType ArgTyA = A.get()->getType();
+    auto *VTyA = ArgTyA->getAs<VectorType>();
+    if (!(ArgTyA->isScalarType() || VTyA)) {
+      SemaRef.Diag(A.get()->getBeginLoc(),
+                   diag::err_typecheck_expect_any_scalar_or_vector)
+          << ArgTyA << 1;
+      return true;
+    }
+
+    ExprResult B = TheCall->getArg(1);
+    QualType ArgTyB = B.get()->getType();
+    auto *VTyB = ArgTyB->getAs<VectorType>();
+    if (!(ArgTyB->isScalarType() || VTyB)) {
+      SemaRef.Diag(A.get()->getBeginLoc(),
+                   diag::err_typecheck_expect_any_scalar_or_vector)
+          << ArgTyB << 1;
+      return true;
+    }
+
+    ExprResult C = TheCall->getArg(2);
+    QualType ArgTyC = C.get()->getType();
+    auto *VTyC = ArgTyC->getAs<VectorType>();
+    if (!(ArgTyC->isScalarType() || VTyC)) {
+      SemaRef.Diag(A.get()->getBeginLoc(),
+                   diag::err_typecheck_expect_any_scalar_or_vector)
+          << ArgTyC << 1;
+      return true;
+    }
+
+    QualType RetTy = ArgTyA;
+    TheCall->setType(RetTy);
+    break;
+  }
   }
   return false;
 }
diff --git a/clang/test/CodeGenHLSL/builtins/smoothstep.hlsl b/clang/test/CodeGenHLSL/builtins/smoothstep.hlsl
new file mode 100644
index 0000000000000..bbb19692f1225
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/smoothstep.hlsl
@@ -0,0 +1,183 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 5
+// RUN: %clang_cc1 -finclude-default-header -x hlsl -triple \
+// RUN:   dxil-pc-shadermodel6.3-library %s -fnative-half-type \
+// RUN:   -emit-llvm -O1 -o - | FileCheck %s
+// RUN: %clang_cc1 -finclude-default-header -triple \
+// RUN:   spirv-unknown-vulkan-compute %s -fnative-half-type \
+// RUN:   -emit-llvm -O1 -o - | FileCheck %s --check-prefix=SPVCHECK
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) half @_Z20test_smoothstep_halfDhDhDh(
+// CHECK-SAME: half noundef nofpclass(nan inf) [[MIN:%.*]], half noundef nofpclass(nan inf) [[MAX:%.*]], half noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn half [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn half [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn half [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn half @llvm.dx.saturate.f16(half [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn half [[HLSL_SATURATE_I]], 0xH4000
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn half 0xH4200, [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn half [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn half [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret half [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) half @_Z20test_smoothstep_halfDhDhDh(
+// SPVCHECK-SAME: half noundef nofpclass(nan inf) [[MIN:%.*]], half noundef nofpclass(nan inf) [[MAX:%.*]], half noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[CONV_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn half [[MIN]] to double
+// SPVCHECK-NEXT:    [[CONV1_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn half [[MAX]] to double
+// SPVCHECK-NEXT:    [[CONV2_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn half [[X]] to double
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn double @llvm.spv.smoothstep.f64(double [[CONV_I]], double [[CONV1_I]], double [[CONV2_I]])
+// SPVCHECK-NEXT:    [[CONV3_I:%.*]] = fptrunc reassoc nnan ninf nsz arcp afn double [[SPV_SMOOTHSTEP_I]] to half
+// SPVCHECK-NEXT:    ret half [[CONV3_I]]
+//
+half test_smoothstep_half(half Min, half Max, half X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) <2 x half> @_Z21test_smoothstep_half2Dv2_DhS_S_(
+// CHECK-SAME: <2 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <2 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <2 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x half> [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x half> [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn <2 x half> [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn <2 x half> @llvm.dx.saturate.v2f16(<2 x half> [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x half> [[HLSL_SATURATE_I]], splat (half 0xH4000)
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x half> splat (half 0xH4200), [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x half> [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x half> [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret <2 x half> [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) <2 x half> @_Z21test_smoothstep_half2Dv2_DhS_S_(
+// SPVCHECK-SAME: <2 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <2 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <2 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn noundef <2 x half> @llvm.spv.smoothstep.v2f16(<2 x half> [[MIN]], <2 x half> [[MAX]], <2 x half> [[X]])
+// SPVCHECK-NEXT:    ret <2 x half> [[SPV_SMOOTHSTEP_I]]
+//
+half2 test_smoothstep_half2(half2 Min, half2 Max, half2 X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) <3 x half> @_Z21test_smoothstep_half3Dv3_DhS_S_(
+// CHECK-SAME: <3 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <3 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <3 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <3 x half> [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <3 x half> [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn <3 x half> [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn <3 x half> @llvm.dx.saturate.v3f16(<3 x half> [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <3 x half> [[HLSL_SATURATE_I]], splat (half 0xH4000)
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <3 x half> splat (half 0xH4200), [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn <3 x half> [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <3 x half> [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret <3 x half> [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) <3 x half> @_Z21test_smoothstep_half3Dv3_DhS_S_(
+// SPVCHECK-SAME: <3 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <3 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <3 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn noundef <3 x half> @llvm.spv.smoothstep.v3f16(<3 x half> [[MIN]], <3 x half> [[MAX]], <3 x half> [[X]])
+// SPVCHECK-NEXT:    ret <3 x half> [[SPV_SMOOTHSTEP_I]]
+//
+half3 test_smoothstep_half3(half3 Min, half3 Max, half3 X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) <4 x half> @_Z21test_smoothstep_half4Dv4_DhS_S_(
+// CHECK-SAME: <4 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <4 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <4 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <4 x half> [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <4 x half> [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn <4 x half> [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn <4 x half> @llvm.dx.saturate.v4f16(<4 x half> [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <4 x half> [[HLSL_SATURATE_I]], splat (half 0xH4000)
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <4 x half> splat (half 0xH4200), [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn <4 x half> [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <4 x half> [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret <4 x half> [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) <4 x half> @_Z21test_smoothstep_half4Dv4_DhS_S_(
+// SPVCHECK-SAME: <4 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <4 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <4 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn noundef <4 x half> @llvm.spv.smoothstep.v4f16(<4 x half> [[MIN]], <4 x half> [[MAX]], <4 x half> [[X]])
+// SPVCHECK-NEXT:    ret <4 x half> [[SPV_SMOOTHSTEP_I]]
+//
+half4 test_smoothstep_half4(half4 Min, half4 Max, half4 X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) float @_Z21test_smoothstep_floatfff(
+// CHECK-SAME: float noundef nofpclass(nan inf) [[MIN:%.*]], float noundef nofpclass(nan inf) [[MAX:%.*]], float noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn float [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn float [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn float [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn float @llvm.dx.saturate.f32(float [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn float [[HLSL_SATURATE_I]], 2.000000e+00
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn float 3.000000e+00, [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn float [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn float [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret float [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) float @_Z21test_smoothstep_floatfff(
+// SPVCHECK-SAME: float noundef nofpclass(nan inf) [[MIN:%.*]], float noundef nofpclass(nan inf) [[MAX:%.*]], float noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[CONV_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn float [[MIN]] to double
+// SPVCHECK-NEXT:    [[CONV1_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn float [[MAX]] to double
+// SPVCHECK-NEXT:    [[CONV2_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn float [[X]] to double
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn double @llvm.spv.smoothstep.f64(double [[CONV_I]], double [[CONV1_I]], double [[CONV2_I]])
+// SPVCHECK-NEXT:    [[CONV3_I:%.*]] = fptrunc reassoc nnan ninf nsz arcp afn double [[SPV_SMOOTHSTEP_I]] to float
+// SPVCHECK-NEXT:    ret float [[CONV3_I]]
+//
+float test_smoothstep_float(float Min, float Max, float X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) <2 x float> @_Z22test_smoothstep_float2Dv2_fS_S_(
+// CHECK-SAME: <2 x float> noundef nofpclass(nan inf) [[MIN:%.*]], <2 x float> noundef nofpclass(nan inf) [[MAX:%.*]], <2 x float> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x float> [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x float> [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn <2 x float> [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn <2 x float> @llvm.dx.saturate.v2f32(<2 x float> [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x float> [[HLSL_SATURATE_I]], splat (float 2.000000e+00)
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x float> splat (float 3.000000e+00), [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x float> [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x float> [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret <2 x float> [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) <2 x float> @_Z22test_smoothstep_float2Dv2_fS_S_(
+// SPVCHECK-SAME: <2 x float> noundef nofpclass(nan inf) [[MIN:%.*]], <2 x float> noundef nofpclass(nan inf) [[MAX:%.*]], <2 x float> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn noundef <2 x float> @llvm.spv.smoothstep.v2f32(<2 x float> [[MIN]], <2 x float> [[MAX]], <2 x float> [[X]])
+// SPVCHECK-NEXT:    ret <2 x float> [[SPV_SMOOTHSTEP_I]]
+//
+float2 test_smoothstep_float2(float2 Min, float2 Max, float2 X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) <3 x float> @_Z22test_smoothstep_float3Dv3_fS_S_(
+// CHECK-SAME: <3 x float> noundef nofpclass(nan inf) [[MIN:%.*]], <3 x float> noundef nofpclass(nan inf) [[MAX:%.*]], <3 x float> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr ...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Mar 20, 2025

@llvm/pr-subscribers-clang-codegen

Author: Kaitlin Peng (kmpeng)

Changes

Closes #99156.

Tasks completed:

  • Implement smoothstep using HLSL source in hlsl_intrinsics.h
  • Implement the smoothstep SPIR-V target built-in in clang/include/clang/Basic/BuiltinsSPIRV.td
  • Add sema checks for smoothstep to CheckSPIRVBuiltinFunctionCall in clang/lib/Sema/SemaSPIRV.cpp
  • Add codegen for spv smoothstep to EmitSPIRVBuiltinExpr in CGBuiltin.cpp
  • Add codegen tests to clang/test/CodeGenHLSL/builtins/smoothstep.hlsl
  • Add spv codegen test to clang/test/CodeGenSPIRV/Builtins/smoothstep.c
  • Add sema tests to clang/test/SemaHLSL/BuiltIns/smoothstep-errors.hlsl
  • Add spv sema tests to clang/test/SemaSPIRV/BuiltIns/smoothstep-errors.c
  • Create the int_spv_smoothstep intrinsic in IntrinsicsSPIRV.td
  • In SPIRVInstructionSelector.cpp create the smoothstep lowering and map it to int_spv_smoothstep in SPIRVInstructionSelector::selectIntrinsic
  • Create SPIR-V backend test case in llvm/test/CodeGen/SPIRV/hlsl-intrinsics/smoothstep.ll
  • Create SPIR-V backend test case in llvm/test/CodeGen/SPIRV/opencl/smoothstep.ll

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

13 Files Affected:

  • (modified) clang/include/clang/Basic/BuiltinsSPIRV.td (+6)
  • (modified) clang/lib/CodeGen/CGBuiltin.cpp (+19)
  • (modified) clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h (+20)
  • (modified) clang/lib/Headers/hlsl/hlsl_intrinsics.h (+48)
  • (modified) clang/lib/Sema/SemaSPIRV.cpp (+38)
  • (added) clang/test/CodeGenHLSL/builtins/smoothstep.hlsl (+183)
  • (added) clang/test/CodeGenSPIRV/Builtins/smoothstep.c (+44)
  • (added) clang/test/SemaHLSL/BuiltIns/smoothstep-errors.hlsl (+66)
  • (added) clang/test/SemaSPIRV/BuiltIns/smoothstep-errors.c (+18)
  • (modified) llvm/include/llvm/IR/IntrinsicsSPIRV.td (+1)
  • (modified) llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp (+2)
  • (added) llvm/test/CodeGen/SPIRV/hlsl-intrinsics/smoothstep.ll (+60)
  • (added) llvm/test/CodeGen/SPIRV/opencl/smoothstep.ll (+61)
diff --git a/clang/include/clang/Basic/BuiltinsSPIRV.td b/clang/include/clang/Basic/BuiltinsSPIRV.td
index 34933e889ba31..2eb4c733b0a42 100644
--- a/clang/include/clang/Basic/BuiltinsSPIRV.td
+++ b/clang/include/clang/Basic/BuiltinsSPIRV.td
@@ -25,3 +25,9 @@ def SPIRVReflect : Builtin {
   let Attributes = [NoThrow, Const];
   let Prototype = "void(...)";
 }
+
+def SPIRVSmoothStep : Builtin {
+  let Spellings = ["__builtin_spirv_smoothstep"];
+  let Attributes = [NoThrow, Const];
+  let Prototype = "void(...)";
+}
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index c126f88b9e3a5..ac92f89415724 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -20955,6 +20955,25 @@ Value *CodeGenFunction::EmitSPIRVBuiltinExpr(unsigned BuiltinID,
         /*ReturnType=*/I->getType(), Intrinsic::spv_reflect,
         ArrayRef<Value *>{I, N}, nullptr, "spv.reflect");
   }
+  case SPIRV::BI__builtin_spirv_smoothstep: {
+    Value *Min = EmitScalarExpr(E->getArg(0));
+    Value *Max = EmitScalarExpr(E->getArg(1));
+    Value *X = EmitScalarExpr(E->getArg(2));
+    assert(E->getArg(0)->getType()->hasFloatingRepresentation() &&
+           E->getArg(1)->getType()->hasFloatingRepresentation() &&
+           E->getArg(2)->getType()->hasFloatingRepresentation() &&
+           "SmoothStep operands must have a float representation");
+    assert((E->getArg(0)->getType()->isScalarType() ||
+            E->getArg(0)->getType()->isVectorType()) &&
+           (E->getArg(1)->getType()->isScalarType() ||
+            E->getArg(1)->getType()->isVectorType()) &&
+           (E->getArg(2)->getType()->isScalarType() ||
+            E->getArg(2)->getType()->isVectorType()) &&
+           "SmoothStep operands must be a scalar or vector");
+    return Builder.CreateIntrinsic(
+        /*ReturnType=*/Min->getType(), Intrinsic::spv_smoothstep,
+        ArrayRef<Value *>{Min, Max, X}, nullptr, "spv.smoothstep");
+  }
   }
   return nullptr;
 }
diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h b/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
index 89ab664e90ba9..8cdd63d7e07bb 100644
--- a/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
+++ b/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
@@ -81,6 +81,26 @@ constexpr vector<T, N> fmod_vec_impl(vector<T, N> X, vector<T, N> Y) {
 #endif
 }
 
+template <typename T> constexpr T smoothstep_impl(T Min, T Max, T X) {
+#if (__has_builtin(__builtin_spirv_smoothstep))
+  return __builtin_spirv_smoothstep(Min, Max, X);
+#else
+  T S = saturate((X - Min) / (Max - Min));
+  return (3 - 2 * S) * S * S;
+#endif
+}
+
+template <typename T, int N>
+constexpr vector<T, N> smoothstep_vec_impl(vector<T, N> Min, vector<T, N> Max,
+                                           vector<T, N> X) {
+#if (__has_builtin(__builtin_spirv_smoothstep))
+  return __builtin_spirv_smoothstep(Min, Max, X);
+#else
+  vector<T, N> S = saturate((X - Min) / (Max - Min));
+  return (3 - 2 * S) * S * S;
+#endif
+}
+
 } // namespace __detail
 } // namespace hlsl
 
diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsics.h b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
index a48a8e998a015..3803a87d32afc 100644
--- a/clang/lib/Headers/hlsl/hlsl_intrinsics.h
+++ b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
@@ -264,5 +264,53 @@ reflect(__detail::HLSL_FIXED_VECTOR<float, L> I,
         __detail::HLSL_FIXED_VECTOR<float, L> N) {
   return __detail::reflect_vec_impl(I, N);
 }
+
+//===----------------------------------------------------------------------===//
+// smoothstep builtin
+//===----------------------------------------------------------------------===//
+
+/// \fn T smoothstep(T Min, T Max, T X)
+/// \brief Returns a smooth Hermite interpolation between 0 and 1, if \a X is in
+/// the range [\a Min, \a Max].
+/// \param Min The minimum range of the x parameter.
+/// \param Max The maximum range of the x parameter.
+/// \param X The specified value to be interpolated.
+///
+/// The return value is 0.0 if \a X ≤ \a Min and 1.0 if \a X ≥ \a Max. When \a
+/// Min < \a X < \a Max, the function performs smooth Hermite interpolation
+/// between 0 and 1.
+
+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> smoothstep(T Min, T Max, T X) {
+  return __detail::smoothstep_impl(Min, Max, X);
+}
+
+template <typename T>
+const inline __detail::enable_if_t<
+    __detail::is_arithmetic<T>::Value && __detail::is_same<float, T>::value, T>
+smoothstep(T Min, T Max, T X) {
+  return __detail::smoothstep_impl(Min, Max, X);
+}
+
+template <int N>
+_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
+const inline __detail::HLSL_FIXED_VECTOR<half, N> smoothstep(
+    __detail::HLSL_FIXED_VECTOR<half, N> Min,
+    __detail::HLSL_FIXED_VECTOR<half, N> Max,
+    __detail::HLSL_FIXED_VECTOR<half, N> X) {
+  return __detail::smoothstep_vec_impl(Min, Max, X);
+}
+
+template <int N>
+const inline __detail::HLSL_FIXED_VECTOR<float, N>
+smoothstep(__detail::HLSL_FIXED_VECTOR<float, N> Min,
+           __detail::HLSL_FIXED_VECTOR<float, N> Max,
+           __detail::HLSL_FIXED_VECTOR<float, N> X) {
+  return __detail::smoothstep_vec_impl(Min, Max, X);
+}
+
 } // namespace hlsl
 #endif //_HLSL_HLSL_INTRINSICS_H_
diff --git a/clang/lib/Sema/SemaSPIRV.cpp b/clang/lib/Sema/SemaSPIRV.cpp
index 94534485e07c3..2cbb57a739a1a 100644
--- a/clang/lib/Sema/SemaSPIRV.cpp
+++ b/clang/lib/Sema/SemaSPIRV.cpp
@@ -101,6 +101,44 @@ bool SemaSPIRV::CheckSPIRVBuiltinFunctionCall(unsigned BuiltinID,
     TheCall->setType(RetTy);
     break;
   }
+  case SPIRV::BI__builtin_spirv_smoothstep: {
+    if (SemaRef.checkArgCount(TheCall, 3))
+      return true;
+
+    ExprResult A = TheCall->getArg(0);
+    QualType ArgTyA = A.get()->getType();
+    auto *VTyA = ArgTyA->getAs<VectorType>();
+    if (!(ArgTyA->isScalarType() || VTyA)) {
+      SemaRef.Diag(A.get()->getBeginLoc(),
+                   diag::err_typecheck_expect_any_scalar_or_vector)
+          << ArgTyA << 1;
+      return true;
+    }
+
+    ExprResult B = TheCall->getArg(1);
+    QualType ArgTyB = B.get()->getType();
+    auto *VTyB = ArgTyB->getAs<VectorType>();
+    if (!(ArgTyB->isScalarType() || VTyB)) {
+      SemaRef.Diag(A.get()->getBeginLoc(),
+                   diag::err_typecheck_expect_any_scalar_or_vector)
+          << ArgTyB << 1;
+      return true;
+    }
+
+    ExprResult C = TheCall->getArg(2);
+    QualType ArgTyC = C.get()->getType();
+    auto *VTyC = ArgTyC->getAs<VectorType>();
+    if (!(ArgTyC->isScalarType() || VTyC)) {
+      SemaRef.Diag(A.get()->getBeginLoc(),
+                   diag::err_typecheck_expect_any_scalar_or_vector)
+          << ArgTyC << 1;
+      return true;
+    }
+
+    QualType RetTy = ArgTyA;
+    TheCall->setType(RetTy);
+    break;
+  }
   }
   return false;
 }
diff --git a/clang/test/CodeGenHLSL/builtins/smoothstep.hlsl b/clang/test/CodeGenHLSL/builtins/smoothstep.hlsl
new file mode 100644
index 0000000000000..bbb19692f1225
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/smoothstep.hlsl
@@ -0,0 +1,183 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 5
+// RUN: %clang_cc1 -finclude-default-header -x hlsl -triple \
+// RUN:   dxil-pc-shadermodel6.3-library %s -fnative-half-type \
+// RUN:   -emit-llvm -O1 -o - | FileCheck %s
+// RUN: %clang_cc1 -finclude-default-header -triple \
+// RUN:   spirv-unknown-vulkan-compute %s -fnative-half-type \
+// RUN:   -emit-llvm -O1 -o - | FileCheck %s --check-prefix=SPVCHECK
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) half @_Z20test_smoothstep_halfDhDhDh(
+// CHECK-SAME: half noundef nofpclass(nan inf) [[MIN:%.*]], half noundef nofpclass(nan inf) [[MAX:%.*]], half noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn half [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn half [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn half [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn half @llvm.dx.saturate.f16(half [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn half [[HLSL_SATURATE_I]], 0xH4000
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn half 0xH4200, [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn half [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn half [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret half [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) half @_Z20test_smoothstep_halfDhDhDh(
+// SPVCHECK-SAME: half noundef nofpclass(nan inf) [[MIN:%.*]], half noundef nofpclass(nan inf) [[MAX:%.*]], half noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[CONV_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn half [[MIN]] to double
+// SPVCHECK-NEXT:    [[CONV1_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn half [[MAX]] to double
+// SPVCHECK-NEXT:    [[CONV2_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn half [[X]] to double
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn double @llvm.spv.smoothstep.f64(double [[CONV_I]], double [[CONV1_I]], double [[CONV2_I]])
+// SPVCHECK-NEXT:    [[CONV3_I:%.*]] = fptrunc reassoc nnan ninf nsz arcp afn double [[SPV_SMOOTHSTEP_I]] to half
+// SPVCHECK-NEXT:    ret half [[CONV3_I]]
+//
+half test_smoothstep_half(half Min, half Max, half X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) <2 x half> @_Z21test_smoothstep_half2Dv2_DhS_S_(
+// CHECK-SAME: <2 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <2 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <2 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x half> [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x half> [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn <2 x half> [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn <2 x half> @llvm.dx.saturate.v2f16(<2 x half> [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x half> [[HLSL_SATURATE_I]], splat (half 0xH4000)
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x half> splat (half 0xH4200), [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x half> [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x half> [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret <2 x half> [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) <2 x half> @_Z21test_smoothstep_half2Dv2_DhS_S_(
+// SPVCHECK-SAME: <2 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <2 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <2 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn noundef <2 x half> @llvm.spv.smoothstep.v2f16(<2 x half> [[MIN]], <2 x half> [[MAX]], <2 x half> [[X]])
+// SPVCHECK-NEXT:    ret <2 x half> [[SPV_SMOOTHSTEP_I]]
+//
+half2 test_smoothstep_half2(half2 Min, half2 Max, half2 X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) <3 x half> @_Z21test_smoothstep_half3Dv3_DhS_S_(
+// CHECK-SAME: <3 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <3 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <3 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <3 x half> [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <3 x half> [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn <3 x half> [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn <3 x half> @llvm.dx.saturate.v3f16(<3 x half> [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <3 x half> [[HLSL_SATURATE_I]], splat (half 0xH4000)
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <3 x half> splat (half 0xH4200), [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn <3 x half> [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <3 x half> [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret <3 x half> [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) <3 x half> @_Z21test_smoothstep_half3Dv3_DhS_S_(
+// SPVCHECK-SAME: <3 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <3 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <3 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn noundef <3 x half> @llvm.spv.smoothstep.v3f16(<3 x half> [[MIN]], <3 x half> [[MAX]], <3 x half> [[X]])
+// SPVCHECK-NEXT:    ret <3 x half> [[SPV_SMOOTHSTEP_I]]
+//
+half3 test_smoothstep_half3(half3 Min, half3 Max, half3 X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) <4 x half> @_Z21test_smoothstep_half4Dv4_DhS_S_(
+// CHECK-SAME: <4 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <4 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <4 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <4 x half> [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <4 x half> [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn <4 x half> [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn <4 x half> @llvm.dx.saturate.v4f16(<4 x half> [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <4 x half> [[HLSL_SATURATE_I]], splat (half 0xH4000)
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <4 x half> splat (half 0xH4200), [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn <4 x half> [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <4 x half> [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret <4 x half> [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) <4 x half> @_Z21test_smoothstep_half4Dv4_DhS_S_(
+// SPVCHECK-SAME: <4 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <4 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <4 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn noundef <4 x half> @llvm.spv.smoothstep.v4f16(<4 x half> [[MIN]], <4 x half> [[MAX]], <4 x half> [[X]])
+// SPVCHECK-NEXT:    ret <4 x half> [[SPV_SMOOTHSTEP_I]]
+//
+half4 test_smoothstep_half4(half4 Min, half4 Max, half4 X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) float @_Z21test_smoothstep_floatfff(
+// CHECK-SAME: float noundef nofpclass(nan inf) [[MIN:%.*]], float noundef nofpclass(nan inf) [[MAX:%.*]], float noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn float [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn float [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn float [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn float @llvm.dx.saturate.f32(float [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn float [[HLSL_SATURATE_I]], 2.000000e+00
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn float 3.000000e+00, [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn float [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn float [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret float [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) float @_Z21test_smoothstep_floatfff(
+// SPVCHECK-SAME: float noundef nofpclass(nan inf) [[MIN:%.*]], float noundef nofpclass(nan inf) [[MAX:%.*]], float noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[CONV_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn float [[MIN]] to double
+// SPVCHECK-NEXT:    [[CONV1_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn float [[MAX]] to double
+// SPVCHECK-NEXT:    [[CONV2_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn float [[X]] to double
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn double @llvm.spv.smoothstep.f64(double [[CONV_I]], double [[CONV1_I]], double [[CONV2_I]])
+// SPVCHECK-NEXT:    [[CONV3_I:%.*]] = fptrunc reassoc nnan ninf nsz arcp afn double [[SPV_SMOOTHSTEP_I]] to float
+// SPVCHECK-NEXT:    ret float [[CONV3_I]]
+//
+float test_smoothstep_float(float Min, float Max, float X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) <2 x float> @_Z22test_smoothstep_float2Dv2_fS_S_(
+// CHECK-SAME: <2 x float> noundef nofpclass(nan inf) [[MIN:%.*]], <2 x float> noundef nofpclass(nan inf) [[MAX:%.*]], <2 x float> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x float> [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x float> [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn <2 x float> [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn <2 x float> @llvm.dx.saturate.v2f32(<2 x float> [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x float> [[HLSL_SATURATE_I]], splat (float 2.000000e+00)
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x float> splat (float 3.000000e+00), [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x float> [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x float> [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret <2 x float> [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) <2 x float> @_Z22test_smoothstep_float2Dv2_fS_S_(
+// SPVCHECK-SAME: <2 x float> noundef nofpclass(nan inf) [[MIN:%.*]], <2 x float> noundef nofpclass(nan inf) [[MAX:%.*]], <2 x float> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn noundef <2 x float> @llvm.spv.smoothstep.v2f32(<2 x float> [[MIN]], <2 x float> [[MAX]], <2 x float> [[X]])
+// SPVCHECK-NEXT:    ret <2 x float> [[SPV_SMOOTHSTEP_I]]
+//
+float2 test_smoothstep_float2(float2 Min, float2 Max, float2 X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) <3 x float> @_Z22test_smoothstep_float3Dv3_fS_S_(
+// CHECK-SAME: <3 x float> noundef nofpclass(nan inf) [[MIN:%.*]], <3 x float> noundef nofpclass(nan inf) [[MAX:%.*]], <3 x float> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr ...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Mar 20, 2025

@llvm/pr-subscribers-llvm-ir

Author: Kaitlin Peng (kmpeng)

Changes

Closes #99156.

Tasks completed:

  • Implement smoothstep using HLSL source in hlsl_intrinsics.h
  • Implement the smoothstep SPIR-V target built-in in clang/include/clang/Basic/BuiltinsSPIRV.td
  • Add sema checks for smoothstep to CheckSPIRVBuiltinFunctionCall in clang/lib/Sema/SemaSPIRV.cpp
  • Add codegen for spv smoothstep to EmitSPIRVBuiltinExpr in CGBuiltin.cpp
  • Add codegen tests to clang/test/CodeGenHLSL/builtins/smoothstep.hlsl
  • Add spv codegen test to clang/test/CodeGenSPIRV/Builtins/smoothstep.c
  • Add sema tests to clang/test/SemaHLSL/BuiltIns/smoothstep-errors.hlsl
  • Add spv sema tests to clang/test/SemaSPIRV/BuiltIns/smoothstep-errors.c
  • Create the int_spv_smoothstep intrinsic in IntrinsicsSPIRV.td
  • In SPIRVInstructionSelector.cpp create the smoothstep lowering and map it to int_spv_smoothstep in SPIRVInstructionSelector::selectIntrinsic
  • Create SPIR-V backend test case in llvm/test/CodeGen/SPIRV/hlsl-intrinsics/smoothstep.ll
  • Create SPIR-V backend test case in llvm/test/CodeGen/SPIRV/opencl/smoothstep.ll

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

13 Files Affected:

  • (modified) clang/include/clang/Basic/BuiltinsSPIRV.td (+6)
  • (modified) clang/lib/CodeGen/CGBuiltin.cpp (+19)
  • (modified) clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h (+20)
  • (modified) clang/lib/Headers/hlsl/hlsl_intrinsics.h (+48)
  • (modified) clang/lib/Sema/SemaSPIRV.cpp (+38)
  • (added) clang/test/CodeGenHLSL/builtins/smoothstep.hlsl (+183)
  • (added) clang/test/CodeGenSPIRV/Builtins/smoothstep.c (+44)
  • (added) clang/test/SemaHLSL/BuiltIns/smoothstep-errors.hlsl (+66)
  • (added) clang/test/SemaSPIRV/BuiltIns/smoothstep-errors.c (+18)
  • (modified) llvm/include/llvm/IR/IntrinsicsSPIRV.td (+1)
  • (modified) llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp (+2)
  • (added) llvm/test/CodeGen/SPIRV/hlsl-intrinsics/smoothstep.ll (+60)
  • (added) llvm/test/CodeGen/SPIRV/opencl/smoothstep.ll (+61)
diff --git a/clang/include/clang/Basic/BuiltinsSPIRV.td b/clang/include/clang/Basic/BuiltinsSPIRV.td
index 34933e889ba31..2eb4c733b0a42 100644
--- a/clang/include/clang/Basic/BuiltinsSPIRV.td
+++ b/clang/include/clang/Basic/BuiltinsSPIRV.td
@@ -25,3 +25,9 @@ def SPIRVReflect : Builtin {
   let Attributes = [NoThrow, Const];
   let Prototype = "void(...)";
 }
+
+def SPIRVSmoothStep : Builtin {
+  let Spellings = ["__builtin_spirv_smoothstep"];
+  let Attributes = [NoThrow, Const];
+  let Prototype = "void(...)";
+}
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index c126f88b9e3a5..ac92f89415724 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -20955,6 +20955,25 @@ Value *CodeGenFunction::EmitSPIRVBuiltinExpr(unsigned BuiltinID,
         /*ReturnType=*/I->getType(), Intrinsic::spv_reflect,
         ArrayRef<Value *>{I, N}, nullptr, "spv.reflect");
   }
+  case SPIRV::BI__builtin_spirv_smoothstep: {
+    Value *Min = EmitScalarExpr(E->getArg(0));
+    Value *Max = EmitScalarExpr(E->getArg(1));
+    Value *X = EmitScalarExpr(E->getArg(2));
+    assert(E->getArg(0)->getType()->hasFloatingRepresentation() &&
+           E->getArg(1)->getType()->hasFloatingRepresentation() &&
+           E->getArg(2)->getType()->hasFloatingRepresentation() &&
+           "SmoothStep operands must have a float representation");
+    assert((E->getArg(0)->getType()->isScalarType() ||
+            E->getArg(0)->getType()->isVectorType()) &&
+           (E->getArg(1)->getType()->isScalarType() ||
+            E->getArg(1)->getType()->isVectorType()) &&
+           (E->getArg(2)->getType()->isScalarType() ||
+            E->getArg(2)->getType()->isVectorType()) &&
+           "SmoothStep operands must be a scalar or vector");
+    return Builder.CreateIntrinsic(
+        /*ReturnType=*/Min->getType(), Intrinsic::spv_smoothstep,
+        ArrayRef<Value *>{Min, Max, X}, nullptr, "spv.smoothstep");
+  }
   }
   return nullptr;
 }
diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h b/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
index 89ab664e90ba9..8cdd63d7e07bb 100644
--- a/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
+++ b/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
@@ -81,6 +81,26 @@ constexpr vector<T, N> fmod_vec_impl(vector<T, N> X, vector<T, N> Y) {
 #endif
 }
 
+template <typename T> constexpr T smoothstep_impl(T Min, T Max, T X) {
+#if (__has_builtin(__builtin_spirv_smoothstep))
+  return __builtin_spirv_smoothstep(Min, Max, X);
+#else
+  T S = saturate((X - Min) / (Max - Min));
+  return (3 - 2 * S) * S * S;
+#endif
+}
+
+template <typename T, int N>
+constexpr vector<T, N> smoothstep_vec_impl(vector<T, N> Min, vector<T, N> Max,
+                                           vector<T, N> X) {
+#if (__has_builtin(__builtin_spirv_smoothstep))
+  return __builtin_spirv_smoothstep(Min, Max, X);
+#else
+  vector<T, N> S = saturate((X - Min) / (Max - Min));
+  return (3 - 2 * S) * S * S;
+#endif
+}
+
 } // namespace __detail
 } // namespace hlsl
 
diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsics.h b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
index a48a8e998a015..3803a87d32afc 100644
--- a/clang/lib/Headers/hlsl/hlsl_intrinsics.h
+++ b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
@@ -264,5 +264,53 @@ reflect(__detail::HLSL_FIXED_VECTOR<float, L> I,
         __detail::HLSL_FIXED_VECTOR<float, L> N) {
   return __detail::reflect_vec_impl(I, N);
 }
+
+//===----------------------------------------------------------------------===//
+// smoothstep builtin
+//===----------------------------------------------------------------------===//
+
+/// \fn T smoothstep(T Min, T Max, T X)
+/// \brief Returns a smooth Hermite interpolation between 0 and 1, if \a X is in
+/// the range [\a Min, \a Max].
+/// \param Min The minimum range of the x parameter.
+/// \param Max The maximum range of the x parameter.
+/// \param X The specified value to be interpolated.
+///
+/// The return value is 0.0 if \a X ≤ \a Min and 1.0 if \a X ≥ \a Max. When \a
+/// Min < \a X < \a Max, the function performs smooth Hermite interpolation
+/// between 0 and 1.
+
+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> smoothstep(T Min, T Max, T X) {
+  return __detail::smoothstep_impl(Min, Max, X);
+}
+
+template <typename T>
+const inline __detail::enable_if_t<
+    __detail::is_arithmetic<T>::Value && __detail::is_same<float, T>::value, T>
+smoothstep(T Min, T Max, T X) {
+  return __detail::smoothstep_impl(Min, Max, X);
+}
+
+template <int N>
+_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
+const inline __detail::HLSL_FIXED_VECTOR<half, N> smoothstep(
+    __detail::HLSL_FIXED_VECTOR<half, N> Min,
+    __detail::HLSL_FIXED_VECTOR<half, N> Max,
+    __detail::HLSL_FIXED_VECTOR<half, N> X) {
+  return __detail::smoothstep_vec_impl(Min, Max, X);
+}
+
+template <int N>
+const inline __detail::HLSL_FIXED_VECTOR<float, N>
+smoothstep(__detail::HLSL_FIXED_VECTOR<float, N> Min,
+           __detail::HLSL_FIXED_VECTOR<float, N> Max,
+           __detail::HLSL_FIXED_VECTOR<float, N> X) {
+  return __detail::smoothstep_vec_impl(Min, Max, X);
+}
+
 } // namespace hlsl
 #endif //_HLSL_HLSL_INTRINSICS_H_
diff --git a/clang/lib/Sema/SemaSPIRV.cpp b/clang/lib/Sema/SemaSPIRV.cpp
index 94534485e07c3..2cbb57a739a1a 100644
--- a/clang/lib/Sema/SemaSPIRV.cpp
+++ b/clang/lib/Sema/SemaSPIRV.cpp
@@ -101,6 +101,44 @@ bool SemaSPIRV::CheckSPIRVBuiltinFunctionCall(unsigned BuiltinID,
     TheCall->setType(RetTy);
     break;
   }
+  case SPIRV::BI__builtin_spirv_smoothstep: {
+    if (SemaRef.checkArgCount(TheCall, 3))
+      return true;
+
+    ExprResult A = TheCall->getArg(0);
+    QualType ArgTyA = A.get()->getType();
+    auto *VTyA = ArgTyA->getAs<VectorType>();
+    if (!(ArgTyA->isScalarType() || VTyA)) {
+      SemaRef.Diag(A.get()->getBeginLoc(),
+                   diag::err_typecheck_expect_any_scalar_or_vector)
+          << ArgTyA << 1;
+      return true;
+    }
+
+    ExprResult B = TheCall->getArg(1);
+    QualType ArgTyB = B.get()->getType();
+    auto *VTyB = ArgTyB->getAs<VectorType>();
+    if (!(ArgTyB->isScalarType() || VTyB)) {
+      SemaRef.Diag(A.get()->getBeginLoc(),
+                   diag::err_typecheck_expect_any_scalar_or_vector)
+          << ArgTyB << 1;
+      return true;
+    }
+
+    ExprResult C = TheCall->getArg(2);
+    QualType ArgTyC = C.get()->getType();
+    auto *VTyC = ArgTyC->getAs<VectorType>();
+    if (!(ArgTyC->isScalarType() || VTyC)) {
+      SemaRef.Diag(A.get()->getBeginLoc(),
+                   diag::err_typecheck_expect_any_scalar_or_vector)
+          << ArgTyC << 1;
+      return true;
+    }
+
+    QualType RetTy = ArgTyA;
+    TheCall->setType(RetTy);
+    break;
+  }
   }
   return false;
 }
diff --git a/clang/test/CodeGenHLSL/builtins/smoothstep.hlsl b/clang/test/CodeGenHLSL/builtins/smoothstep.hlsl
new file mode 100644
index 0000000000000..bbb19692f1225
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/smoothstep.hlsl
@@ -0,0 +1,183 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 5
+// RUN: %clang_cc1 -finclude-default-header -x hlsl -triple \
+// RUN:   dxil-pc-shadermodel6.3-library %s -fnative-half-type \
+// RUN:   -emit-llvm -O1 -o - | FileCheck %s
+// RUN: %clang_cc1 -finclude-default-header -triple \
+// RUN:   spirv-unknown-vulkan-compute %s -fnative-half-type \
+// RUN:   -emit-llvm -O1 -o - | FileCheck %s --check-prefix=SPVCHECK
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) half @_Z20test_smoothstep_halfDhDhDh(
+// CHECK-SAME: half noundef nofpclass(nan inf) [[MIN:%.*]], half noundef nofpclass(nan inf) [[MAX:%.*]], half noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn half [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn half [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn half [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn half @llvm.dx.saturate.f16(half [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn half [[HLSL_SATURATE_I]], 0xH4000
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn half 0xH4200, [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn half [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn half [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret half [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) half @_Z20test_smoothstep_halfDhDhDh(
+// SPVCHECK-SAME: half noundef nofpclass(nan inf) [[MIN:%.*]], half noundef nofpclass(nan inf) [[MAX:%.*]], half noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[CONV_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn half [[MIN]] to double
+// SPVCHECK-NEXT:    [[CONV1_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn half [[MAX]] to double
+// SPVCHECK-NEXT:    [[CONV2_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn half [[X]] to double
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn double @llvm.spv.smoothstep.f64(double [[CONV_I]], double [[CONV1_I]], double [[CONV2_I]])
+// SPVCHECK-NEXT:    [[CONV3_I:%.*]] = fptrunc reassoc nnan ninf nsz arcp afn double [[SPV_SMOOTHSTEP_I]] to half
+// SPVCHECK-NEXT:    ret half [[CONV3_I]]
+//
+half test_smoothstep_half(half Min, half Max, half X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) <2 x half> @_Z21test_smoothstep_half2Dv2_DhS_S_(
+// CHECK-SAME: <2 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <2 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <2 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x half> [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x half> [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn <2 x half> [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn <2 x half> @llvm.dx.saturate.v2f16(<2 x half> [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x half> [[HLSL_SATURATE_I]], splat (half 0xH4000)
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x half> splat (half 0xH4200), [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x half> [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x half> [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret <2 x half> [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) <2 x half> @_Z21test_smoothstep_half2Dv2_DhS_S_(
+// SPVCHECK-SAME: <2 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <2 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <2 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn noundef <2 x half> @llvm.spv.smoothstep.v2f16(<2 x half> [[MIN]], <2 x half> [[MAX]], <2 x half> [[X]])
+// SPVCHECK-NEXT:    ret <2 x half> [[SPV_SMOOTHSTEP_I]]
+//
+half2 test_smoothstep_half2(half2 Min, half2 Max, half2 X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) <3 x half> @_Z21test_smoothstep_half3Dv3_DhS_S_(
+// CHECK-SAME: <3 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <3 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <3 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <3 x half> [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <3 x half> [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn <3 x half> [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn <3 x half> @llvm.dx.saturate.v3f16(<3 x half> [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <3 x half> [[HLSL_SATURATE_I]], splat (half 0xH4000)
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <3 x half> splat (half 0xH4200), [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn <3 x half> [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <3 x half> [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret <3 x half> [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) <3 x half> @_Z21test_smoothstep_half3Dv3_DhS_S_(
+// SPVCHECK-SAME: <3 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <3 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <3 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn noundef <3 x half> @llvm.spv.smoothstep.v3f16(<3 x half> [[MIN]], <3 x half> [[MAX]], <3 x half> [[X]])
+// SPVCHECK-NEXT:    ret <3 x half> [[SPV_SMOOTHSTEP_I]]
+//
+half3 test_smoothstep_half3(half3 Min, half3 Max, half3 X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) <4 x half> @_Z21test_smoothstep_half4Dv4_DhS_S_(
+// CHECK-SAME: <4 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <4 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <4 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <4 x half> [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <4 x half> [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn <4 x half> [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn <4 x half> @llvm.dx.saturate.v4f16(<4 x half> [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <4 x half> [[HLSL_SATURATE_I]], splat (half 0xH4000)
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <4 x half> splat (half 0xH4200), [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn <4 x half> [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <4 x half> [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret <4 x half> [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) <4 x half> @_Z21test_smoothstep_half4Dv4_DhS_S_(
+// SPVCHECK-SAME: <4 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <4 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <4 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn noundef <4 x half> @llvm.spv.smoothstep.v4f16(<4 x half> [[MIN]], <4 x half> [[MAX]], <4 x half> [[X]])
+// SPVCHECK-NEXT:    ret <4 x half> [[SPV_SMOOTHSTEP_I]]
+//
+half4 test_smoothstep_half4(half4 Min, half4 Max, half4 X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) float @_Z21test_smoothstep_floatfff(
+// CHECK-SAME: float noundef nofpclass(nan inf) [[MIN:%.*]], float noundef nofpclass(nan inf) [[MAX:%.*]], float noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn float [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn float [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn float [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn float @llvm.dx.saturate.f32(float [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn float [[HLSL_SATURATE_I]], 2.000000e+00
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn float 3.000000e+00, [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn float [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn float [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret float [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) float @_Z21test_smoothstep_floatfff(
+// SPVCHECK-SAME: float noundef nofpclass(nan inf) [[MIN:%.*]], float noundef nofpclass(nan inf) [[MAX:%.*]], float noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[CONV_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn float [[MIN]] to double
+// SPVCHECK-NEXT:    [[CONV1_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn float [[MAX]] to double
+// SPVCHECK-NEXT:    [[CONV2_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn float [[X]] to double
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn double @llvm.spv.smoothstep.f64(double [[CONV_I]], double [[CONV1_I]], double [[CONV2_I]])
+// SPVCHECK-NEXT:    [[CONV3_I:%.*]] = fptrunc reassoc nnan ninf nsz arcp afn double [[SPV_SMOOTHSTEP_I]] to float
+// SPVCHECK-NEXT:    ret float [[CONV3_I]]
+//
+float test_smoothstep_float(float Min, float Max, float X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) <2 x float> @_Z22test_smoothstep_float2Dv2_fS_S_(
+// CHECK-SAME: <2 x float> noundef nofpclass(nan inf) [[MIN:%.*]], <2 x float> noundef nofpclass(nan inf) [[MAX:%.*]], <2 x float> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x float> [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x float> [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn <2 x float> [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn <2 x float> @llvm.dx.saturate.v2f32(<2 x float> [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x float> [[HLSL_SATURATE_I]], splat (float 2.000000e+00)
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x float> splat (float 3.000000e+00), [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x float> [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x float> [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret <2 x float> [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) <2 x float> @_Z22test_smoothstep_float2Dv2_fS_S_(
+// SPVCHECK-SAME: <2 x float> noundef nofpclass(nan inf) [[MIN:%.*]], <2 x float> noundef nofpclass(nan inf) [[MAX:%.*]], <2 x float> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn noundef <2 x float> @llvm.spv.smoothstep.v2f32(<2 x float> [[MIN]], <2 x float> [[MAX]], <2 x float> [[X]])
+// SPVCHECK-NEXT:    ret <2 x float> [[SPV_SMOOTHSTEP_I]]
+//
+float2 test_smoothstep_float2(float2 Min, float2 Max, float2 X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) <3 x float> @_Z22test_smoothstep_float3Dv3_fS_S_(
+// CHECK-SAME: <3 x float> noundef nofpclass(nan inf) [[MIN:%.*]], <3 x float> noundef nofpclass(nan inf) [[MAX:%.*]], <3 x float> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr ...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Mar 20, 2025

@llvm/pr-subscribers-hlsl

Author: Kaitlin Peng (kmpeng)

Changes

Closes #99156.

Tasks completed:

  • Implement smoothstep using HLSL source in hlsl_intrinsics.h
  • Implement the smoothstep SPIR-V target built-in in clang/include/clang/Basic/BuiltinsSPIRV.td
  • Add sema checks for smoothstep to CheckSPIRVBuiltinFunctionCall in clang/lib/Sema/SemaSPIRV.cpp
  • Add codegen for spv smoothstep to EmitSPIRVBuiltinExpr in CGBuiltin.cpp
  • Add codegen tests to clang/test/CodeGenHLSL/builtins/smoothstep.hlsl
  • Add spv codegen test to clang/test/CodeGenSPIRV/Builtins/smoothstep.c
  • Add sema tests to clang/test/SemaHLSL/BuiltIns/smoothstep-errors.hlsl
  • Add spv sema tests to clang/test/SemaSPIRV/BuiltIns/smoothstep-errors.c
  • Create the int_spv_smoothstep intrinsic in IntrinsicsSPIRV.td
  • In SPIRVInstructionSelector.cpp create the smoothstep lowering and map it to int_spv_smoothstep in SPIRVInstructionSelector::selectIntrinsic
  • Create SPIR-V backend test case in llvm/test/CodeGen/SPIRV/hlsl-intrinsics/smoothstep.ll
  • Create SPIR-V backend test case in llvm/test/CodeGen/SPIRV/opencl/smoothstep.ll

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

13 Files Affected:

  • (modified) clang/include/clang/Basic/BuiltinsSPIRV.td (+6)
  • (modified) clang/lib/CodeGen/CGBuiltin.cpp (+19)
  • (modified) clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h (+20)
  • (modified) clang/lib/Headers/hlsl/hlsl_intrinsics.h (+48)
  • (modified) clang/lib/Sema/SemaSPIRV.cpp (+38)
  • (added) clang/test/CodeGenHLSL/builtins/smoothstep.hlsl (+183)
  • (added) clang/test/CodeGenSPIRV/Builtins/smoothstep.c (+44)
  • (added) clang/test/SemaHLSL/BuiltIns/smoothstep-errors.hlsl (+66)
  • (added) clang/test/SemaSPIRV/BuiltIns/smoothstep-errors.c (+18)
  • (modified) llvm/include/llvm/IR/IntrinsicsSPIRV.td (+1)
  • (modified) llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp (+2)
  • (added) llvm/test/CodeGen/SPIRV/hlsl-intrinsics/smoothstep.ll (+60)
  • (added) llvm/test/CodeGen/SPIRV/opencl/smoothstep.ll (+61)
diff --git a/clang/include/clang/Basic/BuiltinsSPIRV.td b/clang/include/clang/Basic/BuiltinsSPIRV.td
index 34933e889ba31..2eb4c733b0a42 100644
--- a/clang/include/clang/Basic/BuiltinsSPIRV.td
+++ b/clang/include/clang/Basic/BuiltinsSPIRV.td
@@ -25,3 +25,9 @@ def SPIRVReflect : Builtin {
   let Attributes = [NoThrow, Const];
   let Prototype = "void(...)";
 }
+
+def SPIRVSmoothStep : Builtin {
+  let Spellings = ["__builtin_spirv_smoothstep"];
+  let Attributes = [NoThrow, Const];
+  let Prototype = "void(...)";
+}
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index c126f88b9e3a5..ac92f89415724 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -20955,6 +20955,25 @@ Value *CodeGenFunction::EmitSPIRVBuiltinExpr(unsigned BuiltinID,
         /*ReturnType=*/I->getType(), Intrinsic::spv_reflect,
         ArrayRef<Value *>{I, N}, nullptr, "spv.reflect");
   }
+  case SPIRV::BI__builtin_spirv_smoothstep: {
+    Value *Min = EmitScalarExpr(E->getArg(0));
+    Value *Max = EmitScalarExpr(E->getArg(1));
+    Value *X = EmitScalarExpr(E->getArg(2));
+    assert(E->getArg(0)->getType()->hasFloatingRepresentation() &&
+           E->getArg(1)->getType()->hasFloatingRepresentation() &&
+           E->getArg(2)->getType()->hasFloatingRepresentation() &&
+           "SmoothStep operands must have a float representation");
+    assert((E->getArg(0)->getType()->isScalarType() ||
+            E->getArg(0)->getType()->isVectorType()) &&
+           (E->getArg(1)->getType()->isScalarType() ||
+            E->getArg(1)->getType()->isVectorType()) &&
+           (E->getArg(2)->getType()->isScalarType() ||
+            E->getArg(2)->getType()->isVectorType()) &&
+           "SmoothStep operands must be a scalar or vector");
+    return Builder.CreateIntrinsic(
+        /*ReturnType=*/Min->getType(), Intrinsic::spv_smoothstep,
+        ArrayRef<Value *>{Min, Max, X}, nullptr, "spv.smoothstep");
+  }
   }
   return nullptr;
 }
diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h b/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
index 89ab664e90ba9..8cdd63d7e07bb 100644
--- a/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
+++ b/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
@@ -81,6 +81,26 @@ constexpr vector<T, N> fmod_vec_impl(vector<T, N> X, vector<T, N> Y) {
 #endif
 }
 
+template <typename T> constexpr T smoothstep_impl(T Min, T Max, T X) {
+#if (__has_builtin(__builtin_spirv_smoothstep))
+  return __builtin_spirv_smoothstep(Min, Max, X);
+#else
+  T S = saturate((X - Min) / (Max - Min));
+  return (3 - 2 * S) * S * S;
+#endif
+}
+
+template <typename T, int N>
+constexpr vector<T, N> smoothstep_vec_impl(vector<T, N> Min, vector<T, N> Max,
+                                           vector<T, N> X) {
+#if (__has_builtin(__builtin_spirv_smoothstep))
+  return __builtin_spirv_smoothstep(Min, Max, X);
+#else
+  vector<T, N> S = saturate((X - Min) / (Max - Min));
+  return (3 - 2 * S) * S * S;
+#endif
+}
+
 } // namespace __detail
 } // namespace hlsl
 
diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsics.h b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
index a48a8e998a015..3803a87d32afc 100644
--- a/clang/lib/Headers/hlsl/hlsl_intrinsics.h
+++ b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
@@ -264,5 +264,53 @@ reflect(__detail::HLSL_FIXED_VECTOR<float, L> I,
         __detail::HLSL_FIXED_VECTOR<float, L> N) {
   return __detail::reflect_vec_impl(I, N);
 }
+
+//===----------------------------------------------------------------------===//
+// smoothstep builtin
+//===----------------------------------------------------------------------===//
+
+/// \fn T smoothstep(T Min, T Max, T X)
+/// \brief Returns a smooth Hermite interpolation between 0 and 1, if \a X is in
+/// the range [\a Min, \a Max].
+/// \param Min The minimum range of the x parameter.
+/// \param Max The maximum range of the x parameter.
+/// \param X The specified value to be interpolated.
+///
+/// The return value is 0.0 if \a X ≤ \a Min and 1.0 if \a X ≥ \a Max. When \a
+/// Min < \a X < \a Max, the function performs smooth Hermite interpolation
+/// between 0 and 1.
+
+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> smoothstep(T Min, T Max, T X) {
+  return __detail::smoothstep_impl(Min, Max, X);
+}
+
+template <typename T>
+const inline __detail::enable_if_t<
+    __detail::is_arithmetic<T>::Value && __detail::is_same<float, T>::value, T>
+smoothstep(T Min, T Max, T X) {
+  return __detail::smoothstep_impl(Min, Max, X);
+}
+
+template <int N>
+_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
+const inline __detail::HLSL_FIXED_VECTOR<half, N> smoothstep(
+    __detail::HLSL_FIXED_VECTOR<half, N> Min,
+    __detail::HLSL_FIXED_VECTOR<half, N> Max,
+    __detail::HLSL_FIXED_VECTOR<half, N> X) {
+  return __detail::smoothstep_vec_impl(Min, Max, X);
+}
+
+template <int N>
+const inline __detail::HLSL_FIXED_VECTOR<float, N>
+smoothstep(__detail::HLSL_FIXED_VECTOR<float, N> Min,
+           __detail::HLSL_FIXED_VECTOR<float, N> Max,
+           __detail::HLSL_FIXED_VECTOR<float, N> X) {
+  return __detail::smoothstep_vec_impl(Min, Max, X);
+}
+
 } // namespace hlsl
 #endif //_HLSL_HLSL_INTRINSICS_H_
diff --git a/clang/lib/Sema/SemaSPIRV.cpp b/clang/lib/Sema/SemaSPIRV.cpp
index 94534485e07c3..2cbb57a739a1a 100644
--- a/clang/lib/Sema/SemaSPIRV.cpp
+++ b/clang/lib/Sema/SemaSPIRV.cpp
@@ -101,6 +101,44 @@ bool SemaSPIRV::CheckSPIRVBuiltinFunctionCall(unsigned BuiltinID,
     TheCall->setType(RetTy);
     break;
   }
+  case SPIRV::BI__builtin_spirv_smoothstep: {
+    if (SemaRef.checkArgCount(TheCall, 3))
+      return true;
+
+    ExprResult A = TheCall->getArg(0);
+    QualType ArgTyA = A.get()->getType();
+    auto *VTyA = ArgTyA->getAs<VectorType>();
+    if (!(ArgTyA->isScalarType() || VTyA)) {
+      SemaRef.Diag(A.get()->getBeginLoc(),
+                   diag::err_typecheck_expect_any_scalar_or_vector)
+          << ArgTyA << 1;
+      return true;
+    }
+
+    ExprResult B = TheCall->getArg(1);
+    QualType ArgTyB = B.get()->getType();
+    auto *VTyB = ArgTyB->getAs<VectorType>();
+    if (!(ArgTyB->isScalarType() || VTyB)) {
+      SemaRef.Diag(A.get()->getBeginLoc(),
+                   diag::err_typecheck_expect_any_scalar_or_vector)
+          << ArgTyB << 1;
+      return true;
+    }
+
+    ExprResult C = TheCall->getArg(2);
+    QualType ArgTyC = C.get()->getType();
+    auto *VTyC = ArgTyC->getAs<VectorType>();
+    if (!(ArgTyC->isScalarType() || VTyC)) {
+      SemaRef.Diag(A.get()->getBeginLoc(),
+                   diag::err_typecheck_expect_any_scalar_or_vector)
+          << ArgTyC << 1;
+      return true;
+    }
+
+    QualType RetTy = ArgTyA;
+    TheCall->setType(RetTy);
+    break;
+  }
   }
   return false;
 }
diff --git a/clang/test/CodeGenHLSL/builtins/smoothstep.hlsl b/clang/test/CodeGenHLSL/builtins/smoothstep.hlsl
new file mode 100644
index 0000000000000..bbb19692f1225
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/smoothstep.hlsl
@@ -0,0 +1,183 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 5
+// RUN: %clang_cc1 -finclude-default-header -x hlsl -triple \
+// RUN:   dxil-pc-shadermodel6.3-library %s -fnative-half-type \
+// RUN:   -emit-llvm -O1 -o - | FileCheck %s
+// RUN: %clang_cc1 -finclude-default-header -triple \
+// RUN:   spirv-unknown-vulkan-compute %s -fnative-half-type \
+// RUN:   -emit-llvm -O1 -o - | FileCheck %s --check-prefix=SPVCHECK
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) half @_Z20test_smoothstep_halfDhDhDh(
+// CHECK-SAME: half noundef nofpclass(nan inf) [[MIN:%.*]], half noundef nofpclass(nan inf) [[MAX:%.*]], half noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn half [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn half [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn half [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn half @llvm.dx.saturate.f16(half [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn half [[HLSL_SATURATE_I]], 0xH4000
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn half 0xH4200, [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn half [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn half [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret half [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) half @_Z20test_smoothstep_halfDhDhDh(
+// SPVCHECK-SAME: half noundef nofpclass(nan inf) [[MIN:%.*]], half noundef nofpclass(nan inf) [[MAX:%.*]], half noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[CONV_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn half [[MIN]] to double
+// SPVCHECK-NEXT:    [[CONV1_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn half [[MAX]] to double
+// SPVCHECK-NEXT:    [[CONV2_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn half [[X]] to double
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn double @llvm.spv.smoothstep.f64(double [[CONV_I]], double [[CONV1_I]], double [[CONV2_I]])
+// SPVCHECK-NEXT:    [[CONV3_I:%.*]] = fptrunc reassoc nnan ninf nsz arcp afn double [[SPV_SMOOTHSTEP_I]] to half
+// SPVCHECK-NEXT:    ret half [[CONV3_I]]
+//
+half test_smoothstep_half(half Min, half Max, half X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) <2 x half> @_Z21test_smoothstep_half2Dv2_DhS_S_(
+// CHECK-SAME: <2 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <2 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <2 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x half> [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x half> [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn <2 x half> [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn <2 x half> @llvm.dx.saturate.v2f16(<2 x half> [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x half> [[HLSL_SATURATE_I]], splat (half 0xH4000)
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x half> splat (half 0xH4200), [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x half> [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x half> [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret <2 x half> [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) <2 x half> @_Z21test_smoothstep_half2Dv2_DhS_S_(
+// SPVCHECK-SAME: <2 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <2 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <2 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn noundef <2 x half> @llvm.spv.smoothstep.v2f16(<2 x half> [[MIN]], <2 x half> [[MAX]], <2 x half> [[X]])
+// SPVCHECK-NEXT:    ret <2 x half> [[SPV_SMOOTHSTEP_I]]
+//
+half2 test_smoothstep_half2(half2 Min, half2 Max, half2 X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) <3 x half> @_Z21test_smoothstep_half3Dv3_DhS_S_(
+// CHECK-SAME: <3 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <3 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <3 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <3 x half> [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <3 x half> [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn <3 x half> [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn <3 x half> @llvm.dx.saturate.v3f16(<3 x half> [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <3 x half> [[HLSL_SATURATE_I]], splat (half 0xH4000)
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <3 x half> splat (half 0xH4200), [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn <3 x half> [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <3 x half> [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret <3 x half> [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) <3 x half> @_Z21test_smoothstep_half3Dv3_DhS_S_(
+// SPVCHECK-SAME: <3 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <3 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <3 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn noundef <3 x half> @llvm.spv.smoothstep.v3f16(<3 x half> [[MIN]], <3 x half> [[MAX]], <3 x half> [[X]])
+// SPVCHECK-NEXT:    ret <3 x half> [[SPV_SMOOTHSTEP_I]]
+//
+half3 test_smoothstep_half3(half3 Min, half3 Max, half3 X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) <4 x half> @_Z21test_smoothstep_half4Dv4_DhS_S_(
+// CHECK-SAME: <4 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <4 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <4 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <4 x half> [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <4 x half> [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn <4 x half> [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn <4 x half> @llvm.dx.saturate.v4f16(<4 x half> [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <4 x half> [[HLSL_SATURATE_I]], splat (half 0xH4000)
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <4 x half> splat (half 0xH4200), [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn <4 x half> [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <4 x half> [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret <4 x half> [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) <4 x half> @_Z21test_smoothstep_half4Dv4_DhS_S_(
+// SPVCHECK-SAME: <4 x half> noundef nofpclass(nan inf) [[MIN:%.*]], <4 x half> noundef nofpclass(nan inf) [[MAX:%.*]], <4 x half> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn noundef <4 x half> @llvm.spv.smoothstep.v4f16(<4 x half> [[MIN]], <4 x half> [[MAX]], <4 x half> [[X]])
+// SPVCHECK-NEXT:    ret <4 x half> [[SPV_SMOOTHSTEP_I]]
+//
+half4 test_smoothstep_half4(half4 Min, half4 Max, half4 X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) float @_Z21test_smoothstep_floatfff(
+// CHECK-SAME: float noundef nofpclass(nan inf) [[MIN:%.*]], float noundef nofpclass(nan inf) [[MAX:%.*]], float noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn float [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn float [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn float [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn float @llvm.dx.saturate.f32(float [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn float [[HLSL_SATURATE_I]], 2.000000e+00
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn float 3.000000e+00, [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn float [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn float [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret float [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) float @_Z21test_smoothstep_floatfff(
+// SPVCHECK-SAME: float noundef nofpclass(nan inf) [[MIN:%.*]], float noundef nofpclass(nan inf) [[MAX:%.*]], float noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[CONV_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn float [[MIN]] to double
+// SPVCHECK-NEXT:    [[CONV1_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn float [[MAX]] to double
+// SPVCHECK-NEXT:    [[CONV2_I:%.*]] = fpext reassoc nnan ninf nsz arcp afn float [[X]] to double
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn double @llvm.spv.smoothstep.f64(double [[CONV_I]], double [[CONV1_I]], double [[CONV2_I]])
+// SPVCHECK-NEXT:    [[CONV3_I:%.*]] = fptrunc reassoc nnan ninf nsz arcp afn double [[SPV_SMOOTHSTEP_I]] to float
+// SPVCHECK-NEXT:    ret float [[CONV3_I]]
+//
+float test_smoothstep_float(float Min, float Max, float X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) <2 x float> @_Z22test_smoothstep_float2Dv2_fS_S_(
+// CHECK-SAME: <2 x float> noundef nofpclass(nan inf) [[MIN:%.*]], <2 x float> noundef nofpclass(nan inf) [[MAX:%.*]], <2 x float> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[SUB_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x float> [[X]], [[MIN]]
+// CHECK-NEXT:    [[SUB1_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x float> [[MAX]], [[MIN]]
+// CHECK-NEXT:    [[DIV_I:%.*]] = fdiv reassoc nnan ninf nsz arcp afn <2 x float> [[SUB_I]], [[SUB1_I]]
+// CHECK-NEXT:    [[HLSL_SATURATE_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn <2 x float> @llvm.dx.saturate.v2f32(<2 x float> [[DIV_I]])
+// CHECK-NEXT:    [[MUL_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x float> [[HLSL_SATURATE_I]], splat (float 2.000000e+00)
+// CHECK-NEXT:    [[SUB2_I:%.*]] = fsub reassoc nnan ninf nsz arcp afn <2 x float> splat (float 3.000000e+00), [[MUL_I]]
+// CHECK-NEXT:    [[TMP0:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x float> [[HLSL_SATURATE_I]], [[HLSL_SATURATE_I]]
+// CHECK-NEXT:    [[MUL4_I:%.*]] = fmul reassoc nnan ninf nsz arcp afn <2 x float> [[TMP0]], [[SUB2_I]]
+// CHECK-NEXT:    ret <2 x float> [[MUL4_I]]
+//
+// SPVCHECK-LABEL: define spir_func noundef nofpclass(nan inf) <2 x float> @_Z22test_smoothstep_float2Dv2_fS_S_(
+// SPVCHECK-SAME: <2 x float> noundef nofpclass(nan inf) [[MIN:%.*]], <2 x float> noundef nofpclass(nan inf) [[MAX:%.*]], <2 x float> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// SPVCHECK-NEXT:  [[ENTRY:.*:]]
+// SPVCHECK-NEXT:    [[SPV_SMOOTHSTEP_I:%.*]] = tail call reassoc nnan ninf nsz arcp afn noundef <2 x float> @llvm.spv.smoothstep.v2f32(<2 x float> [[MIN]], <2 x float> [[MAX]], <2 x float> [[X]])
+// SPVCHECK-NEXT:    ret <2 x float> [[SPV_SMOOTHSTEP_I]]
+//
+float2 test_smoothstep_float2(float2 Min, float2 Max, float2 X) { return smoothstep(Min, Max, X); }
+
+// CHECK-LABEL: define noundef nofpclass(nan inf) <3 x float> @_Z22test_smoothstep_float3Dv3_fS_S_(
+// CHECK-SAME: <3 x float> noundef nofpclass(nan inf) [[MIN:%.*]], <3 x float> noundef nofpclass(nan inf) [[MAX:%.*]], <3 x float> noundef nofpclass(nan inf) [[X:%.*]]) local_unnamed_addr ...
[truncated]

@kmpeng kmpeng force-pushed the smoothstep-intrinsic branch from c8f6bf5 to ac7c175 Compare March 25, 2025 17:39
Copy link

github-actions bot commented Mar 25, 2025

✅ With the latest revision this PR passed the undef deprecator.

@kmpeng kmpeng force-pushed the smoothstep-intrinsic branch from 53d55ef to c8f6bf5 Compare March 25, 2025 17:55
@kmpeng kmpeng force-pushed the smoothstep-intrinsic branch from ff214b4 to 9a8739e Compare March 25, 2025 18:40
}

template <typename T>
const inline __detail::enable_if_t<
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the clang format seems very different than the half case. See if you can get these to look a little closer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how to do this when clang-format is an automatic process. The other intrinsics are also formatted the same way as mine.

QualType ArgTyA = A.get()->getType();
if (!ArgTyA->hasFloatingRepresentation()) {
SemaRef.Diag(A.get()->getBeginLoc(),
diag::err_typecheck_convert_incompatible)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are in the process of removing out usages of err_typecheck_convert_incompatible because it forces an assumption on our part of what the expected type is, in this case SemaRef.Context.FloatTy. Look into err_builtin_invalid_arg_type instead. It should give us the option to tell the user that this api supports vector floats and and half types.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we also want to say the api supports matrices though? I don't think I can do that with err_builtin_invalid_arg_type unless I edit DiagnosticSemaKinds.td—the closest I can get is this message:

1st argument must be a scalar or vector of floating-point types (was 'int')

return true;

// check if the all arguments have floating representation
ExprResult A = TheCall->getArg(0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it might be worth looping over the args and checking hasFloatingRepresentation for each arg to determing which diag to emit. Feel free to write a helper if you need it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm already checking hasFloatingRepresentation for all of the args and emitting the corresponding diag. Do you want me to move the individual checks into a for loop?

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 clang Clang issues not falling into any other category HLSL HLSL Language Support llvm:ir
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

Implement the smoothstep HLSL Function
3 participants