[clang] [llvm] [HLSL] Implement support for HLSL intrinsic - saturate (PR #104619)

S. Bharadwaj Yadavalli via llvm-commits llvm-commits at lists.llvm.org
Mon Aug 19 13:08:41 PDT 2024


https://github.com/bharadwajy updated https://github.com/llvm/llvm-project/pull/104619

>From e9fd01be8d43b2eb7ccd4894022ecec4c596d957 Mon Sep 17 00:00:00 2001
From: Bharadwaj Yadavalli <Bharadwaj.Yadavalli at microsoft.com>
Date: Thu, 1 Aug 2024 02:46:05 +0000
Subject: [PATCH 1/4] Implement support to compile HLSL intrinsic "saturate" to
 DXIL

Add SPIRV Codegen support to transform saturate(x) to clamp(x, 0.0, 1.0)
Add tests for DXIL and SPIRV CodeGen.
---
 clang/include/clang/Basic/Builtins.td         |   6 +
 clang/lib/CodeGen/CGBuiltin.cpp               |   9 +
 clang/lib/CodeGen/CGHLSLRuntime.h             |   1 +
 clang/lib/Headers/hlsl/hlsl_intrinsics.h      |  41 ++-
 clang/lib/Sema/SemaHLSL.cpp                   |   3 +-
 clang/test/CodeGenHLSL/builtins/saturate.hlsl |  54 ++++
 .../SemaHLSL/BuiltIns/saturate-errors.hlsl    |  31 ++
 llvm/include/llvm/IR/IntrinsicsDirectX.td     |   1 +
 llvm/include/llvm/IR/IntrinsicsSPIRV.td       |   3 +-
 llvm/lib/Target/DirectX/DXIL.td               |  10 +
 .../Target/DirectX/DXILIntrinsicExpansion.cpp |   2 +
 .../Target/SPIRV/SPIRVInstructionSelector.cpp |  80 ++++-
 llvm/test/CodeGen/DirectX/saturate.ll         | 276 ++++++++++++++++++
 llvm/test/CodeGen/DirectX/saturate_errors.ll  |  14 +
 .../CodeGen/SPIRV/hlsl-intrinsics/saturate.ll |  83 ++++++
 15 files changed, 597 insertions(+), 17 deletions(-)
 create mode 100644 clang/test/CodeGenHLSL/builtins/saturate.hlsl
 create mode 100644 clang/test/SemaHLSL/BuiltIns/saturate-errors.hlsl
 create mode 100644 llvm/test/CodeGen/DirectX/saturate.ll
 create mode 100644 llvm/test/CodeGen/DirectX/saturate_errors.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-intrinsics/saturate.ll

diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 036366cdadf4aa..ac33672a32b336 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -4745,6 +4745,12 @@ def HLSLRSqrt : LangBuiltin<"HLSL_LANG"> {
   let Prototype = "void(...)";
 }
 
+def HLSLSaturate : LangBuiltin<"HLSL_LANG"> {
+  let Spellings = ["__builtin_hlsl_elementwise_saturate"];
+  let Attributes = [NoThrow, Const];
+  let Prototype = "void(...)";
+}
+
 // Builtins for XRay.
 def XRayCustomEvent : Builtin {
   let Spellings = ["__xray_customevent"];
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index f424ddaa175400..f4353f595efbac 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -18667,6 +18667,15 @@ case Builtin::BI__builtin_hlsl_elementwise_isinf: {
         /*ReturnType=*/Op0->getType(), CGM.getHLSLRuntime().getRsqrtIntrinsic(),
         ArrayRef<Value *>{Op0}, nullptr, "hlsl.rsqrt");
   }
+  case Builtin::BI__builtin_hlsl_elementwise_saturate: {
+    Value *Op0 = EmitScalarExpr(E->getArg(0));
+    if (!E->getArg(0)->getType()->hasFloatingRepresentation())
+      llvm_unreachable("saturate operand must have a float representation");
+    return Builder.CreateIntrinsic(
+        /*ReturnType=*/Op0->getType(),
+        CGM.getHLSLRuntime().getSaturateIntrinsic(), ArrayRef<Value *>{Op0},
+        nullptr, "hlsl.saturate");
+  }
   case Builtin::BI__builtin_hlsl_wave_get_lane_index: {
     return EmitRuntimeCall(CGM.CreateRuntimeFunction(
         llvm::FunctionType::get(IntTy, {}, false), "__hlsl_wave_get_lane_index",
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h
index cd604bea2e763d..b1455b5779acf9 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -79,6 +79,7 @@ class CGHLSLRuntime {
   GENERATE_HLSL_INTRINSIC_FUNCTION(Lerp, lerp)
   GENERATE_HLSL_INTRINSIC_FUNCTION(Normalize, normalize)
   GENERATE_HLSL_INTRINSIC_FUNCTION(Rsqrt, rsqrt)
+  GENERATE_HLSL_INTRINSIC_FUNCTION(Saturate, saturate)
   GENERATE_HLSL_INTRINSIC_FUNCTION(ThreadId, thread_id)
 
   //===----------------------------------------------------------------------===//
diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsics.h b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
index 678cdc77f8a71b..6d38b668fe770e 100644
--- a/clang/lib/Headers/hlsl/hlsl_intrinsics.h
+++ b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
@@ -916,7 +916,7 @@ float4 lerp(float4, float4, float4);
 /// \brief Returns the length of the specified floating-point vector.
 /// \param x [in] The vector of floats, or a scalar float.
 ///
-/// Length is based on the following formula: sqrt(x[0]^2 + x[1]^2 + �).
+/// Length is based on the following formula: sqrt(x[0]^2 + x[1]^2 + ...).
 
 _HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
 _HLSL_BUILTIN_ALIAS(__builtin_hlsl_length)
@@ -1564,6 +1564,45 @@ float3 round(float3);
 _HLSL_BUILTIN_ALIAS(__builtin_elementwise_roundeven)
 float4 round(float4);
 
+//===----------------------------------------------------------------------===//
+// saturate builtins
+//===----------------------------------------------------------------------===//
+
+/// \fn T saturate(T Val)
+/// \brief Returns input value, \a Val, clamped within the range of 0.0f
+/// to 1.0f. \param Val The input value.
+
+_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
+half saturate(half);
+_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
+half2 saturate(half2);
+_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
+half3 saturate(half3);
+_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
+half4 saturate(half4);
+
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
+float saturate(float);
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
+float2 saturate(float2);
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
+float3 saturate(float3);
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
+float4 saturate(float4);
+
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
+double saturate(double);
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
+double2 saturate(double2);
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
+double3 saturate(double3);
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
+double4 saturate(double4);
+
 //===----------------------------------------------------------------------===//
 // sin builtins
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index e3e926465e799e..df01549cc2eeb6 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -356,7 +356,7 @@ static bool isLegalTypeForHLSLSV_DispatchThreadID(QualType T) {
   return true;
 }
 
-void SemaHLSL::handleSV_DispatchThreadIDAttr(Decl *D, const ParsedAttr &AL) {  
+void SemaHLSL::handleSV_DispatchThreadIDAttr(Decl *D, const ParsedAttr &AL) {
   auto *VD = cast<ValueDecl>(D);
   if (!isLegalTypeForHLSLSV_DispatchThreadID(VD->getType())) {
     Diag(AL.getLoc(), diag::err_hlsl_attr_invalid_type)
@@ -1045,6 +1045,7 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) {
       return true;
     break;
   }
+  case Builtin::BI__builtin_hlsl_elementwise_saturate:
   case Builtin::BI__builtin_hlsl_elementwise_rcp: {
     if (CheckAllArgsHaveFloatRepresentation(&SemaRef, TheCall))
       return true;
diff --git a/clang/test/CodeGenHLSL/builtins/saturate.hlsl b/clang/test/CodeGenHLSL/builtins/saturate.hlsl
new file mode 100644
index 00000000000000..970d7b7371b1eb
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/saturate.hlsl
@@ -0,0 +1,54 @@
+// RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -x hlsl -triple \
+// RUN:   dxil-pc-shadermodel6.3-library %s -fnative-half-type \
+// RUN:   -emit-llvm -disable-llvm-passes -o - | FileCheck %s \
+// RUN:   --check-prefixes=CHECK,NATIVE_HALF
+// RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -x hlsl -triple \
+// RUN:   dxil-pc-shadermodel6.3-library %s -emit-llvm -disable-llvm-passes \
+// RUN:   -o - | FileCheck %s --check-prefixes=CHECK,NO_HALF
+
+// NATIVE_HALF: define noundef half @
+// NATIVE_HALF: call half @llvm.dx.saturate.f16(
+// NO_HALF: define noundef float @"?test_saturate_half
+// NO_HALF: call float @llvm.dx.saturate.f32(
+half test_saturate_half(half p0) { return saturate(p0); }
+// NATIVE_HALF: define noundef <2 x half> @
+// NATIVE_HALF: call <2 x half> @llvm.dx.saturate.v2f16
+// NO_HALF: define noundef <2 x float> @"?test_saturate_half2
+// NO_HALF: call <2 x float> @llvm.dx.saturate.v2f32(
+half2 test_saturate_half2(half2 p0) { return saturate(p0); }
+// NATIVE_HALF: define noundef <3 x half> @
+// NATIVE_HALF: call <3 x half> @llvm.dx.saturate.v3f16
+// NO_HALF: define noundef <3 x float> @"?test_saturate_half3
+// NO_HALF: call <3 x float> @llvm.dx.saturate.v3f32(
+half3 test_saturate_half3(half3 p0) { return saturate(p0); }
+// NATIVE_HALF: define noundef <4 x half> @
+// NATIVE_HALF: call <4 x half> @llvm.dx.saturate.v4f16
+// NO_HALF: define noundef <4 x float> @"?test_saturate_half4
+// NO_HALF: call <4 x float> @llvm.dx.saturate.v4f32(
+half4 test_saturate_half4(half4 p0) { return saturate(p0); }
+
+// CHECK: define noundef float @"?test_saturate_float
+// CHECK: call float @llvm.dx.saturate.f32(
+float test_saturate_float(float p0) { return saturate(p0); }
+// CHECK: define noundef <2 x float> @"?test_saturate_float2
+// CHECK: call <2 x float> @llvm.dx.saturate.v2f32
+float2 test_saturate_float2(float2 p0) { return saturate(p0); }
+// CHECK: define noundef <3 x float> @"?test_saturate_float3
+// CHECK: call <3 x float> @llvm.dx.saturate.v3f32
+float3 test_saturate_float3(float3 p0) { return saturate(p0); }
+// CHECK: define noundef <4 x float> @"?test_saturate_float4
+// CHECK: call <4 x float> @llvm.dx.saturate.v4f32
+float4 test_saturate_float4(float4 p0) { return saturate(p0); }
+
+// CHECK: define noundef double @
+// CHECK: call double @llvm.dx.saturate.f64(
+double test_saturate_double(double p0) { return saturate(p0); }
+// CHECK: define noundef <2 x double> @
+// CHECK: call <2 x double> @llvm.dx.saturate.v2f64
+double2 test_saturate_double2(double2 p0) { return saturate(p0); }
+// CHECK: define noundef <3 x double> @
+// CHECK: call <3 x double> @llvm.dx.saturate.v3f64
+double3 test_saturate_double3(double3 p0) { return saturate(p0); }
+// CHECK: define noundef <4 x double> @
+// CHECK: call <4 x double> @llvm.dx.saturate.v4f64
+double4 test_saturate_double4(double4 p0) { return saturate(p0); }
diff --git a/clang/test/SemaHLSL/BuiltIns/saturate-errors.hlsl b/clang/test/SemaHLSL/BuiltIns/saturate-errors.hlsl
new file mode 100644
index 00000000000000..721b28f86f950f
--- /dev/null
+++ b/clang/test/SemaHLSL/BuiltIns/saturate-errors.hlsl
@@ -0,0 +1,31 @@
+// RUN: %clang_cc1 -finclude-default-header -triple dxil-pc-shadermodel6.6-library %s -fnative-half-type -emit-llvm-only -disable-llvm-passes -verify -verify-ignore-unexpected -Werror
+
+float2 test_no_arg() {
+  return saturate();
+  // expected-error at -1 {{no matching function for call to 'saturate'}}
+}
+
+float2 test_too_many_arg(float2 p0) {
+  return saturate(p0, p0, p0, p0);
+  // expected-error at -1 {{no matching function for call to 'saturate'}}
+}
+
+float2 test_saturate_vector_size_mismatch(float3 p0) {
+  return saturate(p0);
+  // expected-error at -1 {{implicit conversion truncates vector: 'float3' (aka 'vector<float, 3>') to 'vector<float, 2>'}}
+}
+
+float2 test_saturate_float2_int_splat(int p0) {
+  return saturate(p0);
+  // expected-error at -1 {{call to 'saturate' is ambiguous}}
+}
+
+float2 test_saturate_int_vect_to_float_vec_promotion(int2 p0) {
+  return saturate(p0);
+  // expected-error at -1 {{call to 'saturate' is ambiguous}}
+}
+
+float test_saturate_bool_type_promotion(bool p0) {
+  return saturate(p0);
+  // expected-error at -1 {{call to 'saturate' is ambiguous}}
+}
diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td
index c9102aa3dd972b..a0807a01ea5ab2 100644
--- a/llvm/include/llvm/IR/IntrinsicsDirectX.td
+++ b/llvm/include/llvm/IR/IntrinsicsDirectX.td
@@ -34,6 +34,7 @@ def int_dx_all : DefaultAttrsIntrinsic<[llvm_i1_ty], [llvm_any_ty]>;
 def int_dx_any : DefaultAttrsIntrinsic<[llvm_i1_ty], [llvm_any_ty]>;
 def int_dx_clamp : DefaultAttrsIntrinsic<[llvm_any_ty], [LLVMMatchType<0>, LLVMMatchType<0>, LLVMMatchType<0>]>;
 def int_dx_uclamp : DefaultAttrsIntrinsic<[llvm_anyint_ty], [LLVMMatchType<0>, LLVMMatchType<0>, LLVMMatchType<0>]>; 
+def int_dx_saturate : DefaultAttrsIntrinsic<[llvm_anyfloat_ty], [LLVMMatchType<0>]>;
 
 def int_dx_dot2 : 
     Intrinsic<[LLVMVectorElementType<0>], 
diff --git a/llvm/include/llvm/IR/IntrinsicsSPIRV.td b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
index 1b5e463822749e..4e130ad0c907d9 100644
--- a/llvm/include/llvm/IR/IntrinsicsSPIRV.td
+++ b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
@@ -61,9 +61,10 @@ let TargetPrefix = "spv" in {
   def int_spv_all : DefaultAttrsIntrinsic<[llvm_i1_ty], [llvm_any_ty]>;
   def int_spv_any : DefaultAttrsIntrinsic<[llvm_i1_ty], [llvm_any_ty]>;
   def int_spv_frac : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty]>;
-  def int_spv_lerp : Intrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty, LLVMMatchType<0>,LLVMMatchType<0>], 
+  def int_spv_lerp : Intrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty, LLVMMatchType<0>,LLVMMatchType<0>],
     [IntrNoMem, IntrWillReturn] >;
   def int_spv_length : DefaultAttrsIntrinsic<[LLVMVectorElementType<0>], [llvm_anyfloat_ty]>;
   def int_spv_normalize : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty]>;
   def int_spv_rsqrt : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty]>;
+  def int_spv_saturate : DefaultAttrsIntrinsic<[llvm_anyfloat_ty], [LLVMMatchType<0>]>;
 }
diff --git a/llvm/lib/Target/DirectX/DXIL.td b/llvm/lib/Target/DirectX/DXIL.td
index 67015cff78a79a..ac378db2c9b499 100644
--- a/llvm/lib/Target/DirectX/DXIL.td
+++ b/llvm/lib/Target/DirectX/DXIL.td
@@ -325,6 +325,16 @@ def Abs :  DXILOp<6, unary> {
   let attributes = [Attributes<DXIL1_0, [ReadNone]>];
 }
 
+def Saturate :  DXILOp<7, unary> {
+  let Doc = "Clamps a single or double precision floating point value to [0.0f...1.0f].";
+  let LLVMIntrinsic = int_dx_saturate;
+  let arguments = [overloadTy];
+  let result = overloadTy;
+  let overloads = [Overloads<DXIL1_0, [halfTy, floatTy, doubleTy]>];
+  let stages = [Stages<DXIL1_0, [all_stages]>];
+  let attributes = [Attributes<DXIL1_0, [ReadNone]>];
+}
+
 def IsInf :  DXILOp<9, isSpecialFloat> {
   let Doc = "Determines if the specified value is infinite.";
   let LLVMIntrinsic = int_dx_isinf;
diff --git a/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp b/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
index 2c481d15be5bde..9b467dff3e126f 100644
--- a/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
+++ b/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
@@ -15,6 +15,7 @@
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/CodeGen/Passes.h"
+#include "llvm/IR/DerivedTypes.h"
 #include "llvm/IR/IRBuilder.h"
 #include "llvm/IR/Instruction.h"
 #include "llvm/IR/Instructions.h"
@@ -46,6 +47,7 @@ static bool isIntrinsicExpansion(Function &F) {
   case Intrinsic::dx_normalize:
   case Intrinsic::dx_sdot:
   case Intrinsic::dx_udot:
+  case Intrinsic::dx_saturate:
     return true;
   }
   return false;
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 7cb19279518989..ecb3cee4e781af 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -247,6 +247,9 @@ class SPIRVInstructionSelector : public InstructionSelector {
   bool selectNormalize(Register ResVReg, const SPIRVType *ResType,
                        MachineInstr &I) const;
 
+  bool selectSaturate(Register ResVReg, const SPIRVType *ResType,
+                      MachineInstr &I) const;
+
   bool selectSpvThreadId(Register ResVReg, const SPIRVType *ResType,
                          MachineInstr &I) const;
 
@@ -259,6 +262,7 @@ class SPIRVInstructionSelector : public InstructionSelector {
   Register buildZerosValF(const SPIRVType *ResType, MachineInstr &I) const;
   Register buildOnesVal(bool AllOnes, const SPIRVType *ResType,
                         MachineInstr &I) const;
+  Register buildOnesValF(const SPIRVType *ResType, MachineInstr &I) const;
 
   bool wrapIntoSpecConstantOp(MachineInstr &I,
                               SmallVector<Register> &CompositeArgs) const;
@@ -1285,6 +1289,34 @@ static unsigned getBoolCmpOpcode(unsigned PredNum) {
   }
 }
 
+static APFloat getZeroFP(const Type *LLVMFloatTy) {
+  if (!LLVMFloatTy)
+    return APFloat::getZero(APFloat::IEEEsingle());
+  switch (LLVMFloatTy->getScalarType()->getTypeID()) {
+  case Type::HalfTyID:
+    return APFloat::getZero(APFloat::IEEEhalf());
+  default:
+  case Type::FloatTyID:
+    return APFloat::getZero(APFloat::IEEEsingle());
+  case Type::DoubleTyID:
+    return APFloat::getZero(APFloat::IEEEdouble());
+  }
+}
+
+static APFloat getOneFP(const Type *LLVMFloatTy) {
+  if (!LLVMFloatTy)
+    return APFloat::getOne(APFloat::IEEEsingle());
+  switch (LLVMFloatTy->getScalarType()->getTypeID()) {
+  case Type::HalfTyID:
+    return APFloat::getOne(APFloat::IEEEhalf());
+  default:
+  case Type::FloatTyID:
+    return APFloat::getOne(APFloat::IEEEsingle());
+  case Type::DoubleTyID:
+    return APFloat::getOne(APFloat::IEEEdouble());
+  }
+}
+
 bool SPIRVInstructionSelector::selectAnyOrAll(Register ResVReg,
                                               const SPIRVType *ResType,
                                               MachineInstr &I,
@@ -1446,6 +1478,28 @@ bool SPIRVInstructionSelector::selectRsqrt(Register ResVReg,
       .constrainAllUses(TII, TRI, RBI);
 }
 
+/// Transform saturate(x) to clamp(x, 0.0f, 1.0f) as SPIRV
+/// does not have a saturate builtin.
+bool SPIRVInstructionSelector::selectSaturate(Register ResVReg,
+                                              const SPIRVType *ResType,
+                                              MachineInstr &I) const {
+  assert(I.getNumOperands() == 3);
+  assert(I.getOperand(2).isReg());
+  MachineBasicBlock &BB = *I.getParent();
+  Register VZero = buildZerosValF(ResType, I);
+  Register VOne = buildOnesValF(ResType, I);
+
+  return BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpExtInst))
+      .addDef(ResVReg)
+      .addUse(GR.getSPIRVTypeID(ResType))
+      .addImm(static_cast<uint32_t>(SPIRV::InstructionSet::GLSL_std_450))
+      .addImm(GL::FClamp)
+      .addUse(I.getOperand(2).getReg())
+      .addUse(VZero)
+      .addUse(VOne)
+      .constrainAllUses(TII, TRI, RBI);
+}
+
 bool SPIRVInstructionSelector::selectBitreverse(Register ResVReg,
                                                 const SPIRVType *ResType,
                                                 MachineInstr &I) const {
@@ -1724,20 +1778,6 @@ Register SPIRVInstructionSelector::buildZerosVal(const SPIRVType *ResType,
   return GR.getOrCreateConstInt(0, I, ResType, TII, ZeroAsNull);
 }
 
-static APFloat getZeroFP(const Type *LLVMFloatTy) {
-  if (!LLVMFloatTy)
-    return APFloat::getZero(APFloat::IEEEsingle());
-  switch (LLVMFloatTy->getScalarType()->getTypeID()) {
-  case Type::HalfTyID:
-    return APFloat::getZero(APFloat::IEEEhalf());
-  default:
-  case Type::FloatTyID:
-    return APFloat::getZero(APFloat::IEEEsingle());
-  case Type::DoubleTyID:
-    return APFloat::getZero(APFloat::IEEEdouble());
-  }
-}
-
 Register SPIRVInstructionSelector::buildZerosValF(const SPIRVType *ResType,
                                                   MachineInstr &I) const {
   // OpenCL uses nulls for Zero. In HLSL we don't use null constants.
@@ -1748,6 +1788,16 @@ Register SPIRVInstructionSelector::buildZerosValF(const SPIRVType *ResType,
   return GR.getOrCreateConstFP(VZero, I, ResType, TII, ZeroAsNull);
 }
 
+Register SPIRVInstructionSelector::buildOnesValF(const SPIRVType *ResType,
+                                                 MachineInstr &I) const {
+  // OpenCL uses nulls for Zero. In HLSL we don't use null constants.
+  bool ZeroAsNull = STI.isOpenCLEnv();
+  APFloat VOne = getOneFP(GR.getTypeForSPIRVType(ResType));
+  if (ResType->getOpcode() == SPIRV::OpTypeVector)
+    return GR.getOrCreateConstVector(VOne, I, ResType, TII, ZeroAsNull);
+  return GR.getOrCreateConstFP(VOne, I, ResType, TII, ZeroAsNull);
+}
+
 Register SPIRVInstructionSelector::buildOnesVal(bool AllOnes,
                                                 const SPIRVType *ResType,
                                                 MachineInstr &I) const {
@@ -2181,6 +2231,8 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
       Size = 0;
     BuildMI(BB, I, I.getDebugLoc(), TII.get(Op)).addUse(PtrReg).addImm(Size);
   } break;
+  case Intrinsic::spv_saturate:
+    return selectSaturate(ResVReg, ResType, I);
   default: {
     std::string DiagMsg;
     raw_string_ostream OS(DiagMsg);
diff --git a/llvm/test/CodeGen/DirectX/saturate.ll b/llvm/test/CodeGen/DirectX/saturate.ll
new file mode 100644
index 00000000000000..0c96249ccea435
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/saturate.ll
@@ -0,0 +1,276 @@
+; RUN: opt -S -dxil-op-lower -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s
+; Make sure the intrinsic dx.saturate is to appropriate DXIL op for half/float/double data types.
+
+target datalayout = "e-m:e-p:32:32-i1:32-i8:8-i16:16-i32:32-i64:64-f16:16-f32:32-f64:64-n8:16:32:64"
+target triple = "dxilv1.6-unknown-shadermodel6.6-library"
+
+; CHECK-LABEL: test_saturate_half
+define noundef half @test_saturate_half(half noundef %p0) #0 {
+entry:
+  %p0.addr = alloca half, align 2
+  store half %p0, ptr %p0.addr, align 2, !tbaa !4
+  %0 = load half, ptr %p0.addr, align 2, !tbaa !4
+  ; CHECK: %1 = call half @dx.op.unary.f16(i32 7, half %0)
+  %hlsl.saturate = call half @llvm.dx.saturate.f16(half %0)
+  ; CHECK: ret half %1
+  ret half %hlsl.saturate
+}
+
+; Function Attrs: nocallback nofree nosync nounwind willreturn
+declare half @llvm.dx.saturate.f16(half) #1
+
+; CHECK-LABEL: test_saturate_half2
+define noundef <2 x half> @test_saturate_half2(<2 x half> noundef %p0) #0 {
+entry:
+  %p0.addr = alloca <2 x half>, align 4
+  store <2 x half> %p0, ptr %p0.addr, align 4, !tbaa !8
+  %0 = load <2 x half>, ptr %p0.addr, align 4, !tbaa !8
+  ; CHECK: %1 = extractelement <2 x half> %0, i64 0
+  ; CHECK-NEXT: %2 = call half @dx.op.unary.f16(i32 7, half %1)
+  ; CHECK-NEXT: %3 = insertelement <2 x half> %0, half %2, i64 0
+  ; CHECK-NEXT: %4 = extractelement <2 x half> %0, i64 1
+  ; CHECK-NEXT: %5 = call half @dx.op.unary.f16(i32 7, half %4)
+  ; CHECK-NEXT: %6 = insertelement <2 x half> %0, half %5, i64 1
+  %hlsl.saturate = call <2 x half> @llvm.dx.saturate.v2f16(<2 x half> %0)
+  ; CHECK: ret <2 x half> %6
+  ret <2 x half> %hlsl.saturate
+}
+
+; Function Attrs: nocallback nofree nosync nounwind willreturn
+declare <2 x half> @llvm.dx.saturate.v2f16(<2 x half>) #1
+
+; CHECK-LABEL: test_saturate_half3
+define noundef <3 x half> @test_saturate_half3(<3 x half> noundef %p0) #0 {
+entry:
+  %p0.addr = alloca <3 x half>, align 8
+  store <3 x half> %p0, ptr %p0.addr, align 8, !tbaa !8
+  %0 = load <3 x half>, ptr %p0.addr, align 8, !tbaa !8
+  ; CHECK: %1 = extractelement <3 x half> %0, i64 0
+  ; CHECK-NEXT: %2 = call half @dx.op.unary.f16(i32 7, half %1)
+  ; CHECK-NEXT: %3 = insertelement <3 x half> %0, half %2, i64 0
+  ; CHECK-NEXT: %4 = extractelement <3 x half> %0, i64 1
+  ; CHECK-NEXT: %5 = call half @dx.op.unary.f16(i32 7, half %4)
+  ; CHECK-NEXT: %6 = insertelement <3 x half> %0, half %5, i64 1
+  ; CHECK-NEXT: %7 = extractelement <3 x half> %0, i64 2
+  ; CHECK-NEXT: %8 = call half @dx.op.unary.f16(i32 7, half %7)
+  ; CHECK-NEXT: %9 = insertelement <3 x half> %0, half %8, i64 2
+  %hlsl.saturate = call <3 x half> @llvm.dx.saturate.v3f16(<3 x half> %0)
+  ; CHECK: ret <3 x half> %9
+  ret <3 x half> %hlsl.saturate
+}
+
+; Function Attrs: nocallback nofree nosync nounwind willreturn
+declare <3 x half> @llvm.dx.saturate.v3f16(<3 x half>) #1
+
+; CHECK-LABEL: test_saturate_half4
+define noundef <4 x half> @test_saturate_half4(<4 x half> noundef %p0) #0 {
+entry:
+  %p0.addr = alloca <4 x half>, align 8
+  store <4 x half> %p0, ptr %p0.addr, align 8, !tbaa !8
+  %0 = load <4 x half>, ptr %p0.addr, align 8, !tbaa !8
+  ; CHECK: %1 = extractelement <4 x half> %0, i64 0
+  ; CHECK-NEXT: %2 = call half @dx.op.unary.f16(i32 7, half %1)
+  ; CHECK-NEXT: %3 = insertelement <4 x half> %0, half %2, i64 0
+  ; CHECK-NEXT: %4 = extractelement <4 x half> %0, i64 1
+  ; CHECK-NEXT: %5 = call half @dx.op.unary.f16(i32 7, half %4)
+  ; CHECK-NEXT: %6 = insertelement <4 x half> %0, half %5, i64 1
+  ; CHECK-NEXT: %7 = extractelement <4 x half> %0, i64 2
+  ; CHECK-NEXT: %8 = call half @dx.op.unary.f16(i32 7, half %7)
+  ; CHECK-NEXT: %9 = insertelement <4 x half> %0, half %8, i64 2
+  ; CHECK-NEXT: %10 = extractelement <4 x half> %0, i64 3
+  ; CHECK-NEXT: %11 = call half @dx.op.unary.f16(i32 7, half %10)
+  ; CHECK-NEXT: %12 = insertelement <4 x half> %0, half %11, i64 3
+  %hlsl.saturate = call <4 x half> @llvm.dx.saturate.v4f16(<4 x half> %0)
+  ; CHECK: ret <4 x half> %12
+  ret <4 x half> %hlsl.saturate
+}
+
+; Function Attrs: nocallback nofree nosync nounwind willreturn
+declare <4 x half> @llvm.dx.saturate.v4f16(<4 x half>) #1
+
+; CHECK-LABEL: test_saturate_float
+define noundef float @test_saturate_float(float noundef %p0) #0 {
+entry:
+  %p0.addr = alloca float, align 4
+  store float %p0, ptr %p0.addr, align 4, !tbaa !9
+  %0 = load float, ptr %p0.addr, align 4, !tbaa !9
+  ; CHECK: %1 = call float @dx.op.unary.f32(i32 7, float %0)
+  %hlsl.saturate = call float @llvm.dx.saturate.f32(float %0)
+  ; CHECK: ret float %1
+  ret float %hlsl.saturate
+}
+
+; Function Attrs: nocallback nofree nosync nounwind willreturn
+declare float @llvm.dx.saturate.f32(float) #1
+
+; CHECK-LABEL: test_saturate_float2
+define noundef <2 x float> @test_saturate_float2(<2 x float> noundef %p0) #0 {
+entry:
+  %p0.addr = alloca <2 x float>, align 8
+  store <2 x float> %p0, ptr %p0.addr, align 8, !tbaa !8
+  %0 = load <2 x float>, ptr %p0.addr, align 8, !tbaa !8
+  ; CHECK: %1 = extractelement <2 x float> %0, i64 0
+  ; CHECK-NEXT: %2 = call float @dx.op.unary.f32(i32 7, float %1)
+  ; CHECK-NEXT: %3 = insertelement <2 x float> %0, float %2, i64 0
+  ; CHECK-NEXT: %4 = extractelement <2 x float> %0, i64 1
+  ; CHECK-NEXT: %5 = call float @dx.op.unary.f32(i32 7, float %4)
+  ; CHECK-NEXT: %6 = insertelement <2 x float> %0, float %5, i64 1
+  %hlsl.saturate = call <2 x float> @llvm.dx.saturate.v2f32(<2 x float> %0)
+  ; CHECK: ret <2 x float> %6
+  ret <2 x float> %hlsl.saturate
+}
+
+; Function Attrs: nocallback nofree nosync nounwind willreturn
+declare <2 x float> @llvm.dx.saturate.v2f32(<2 x float>) #1
+
+; CHECK-LABEL: test_saturate_float3
+define noundef <3 x float> @test_saturate_float3(<3 x float> noundef %p0) #0 {
+entry:
+  %p0.addr = alloca <3 x float>, align 16
+  store <3 x float> %p0, ptr %p0.addr, align 16, !tbaa !8
+  %0 = load <3 x float>, ptr %p0.addr, align 16, !tbaa !8
+  ; CHECK: %1 = extractelement <3 x float> %0, i64 0
+  ; CHECK-NEXT: %2 = call float @dx.op.unary.f32(i32 7, float %1)
+  ; CHECK-NEXT: %3 = insertelement <3 x float> %0, float %2, i64 0
+  ; CHECK-NEXT: %4 = extractelement <3 x float> %0, i64 1
+  ; CHECK-NEXT: %5 = call float @dx.op.unary.f32(i32 7, float %4)
+  ; CHECK-NEXT: %6 = insertelement <3 x float> %0, float %5, i64 1
+  ; CHECK-NEXT: %7 = extractelement <3 x float> %0, i64 2
+  ; CHECK-NEXT: %8 = call float @dx.op.unary.f32(i32 7, float %7)
+  ; CHECK-NEXT: %9 = insertelement <3 x float> %0, float %8, i64 2
+  %hlsl.saturate = call <3 x float> @llvm.dx.saturate.v3f32(<3 x float> %0)
+  ; CHECK: ret <3 x float> %9
+  ret <3 x float> %hlsl.saturate
+}
+
+; Function Attrs: nocallback nofree nosync nounwind willreturn
+declare <3 x float> @llvm.dx.saturate.v3f32(<3 x float>) #1
+
+; CHECK-LABEL: test_saturate_float4
+define noundef <4 x float> @test_saturate_float4(<4 x float> noundef %p0) #0 {
+entry:
+  %p0.addr = alloca <4 x float>, align 16
+  store <4 x float> %p0, ptr %p0.addr, align 16, !tbaa !8
+  %0 = load <4 x float>, ptr %p0.addr, align 16, !tbaa !8
+  ; CHECK: %1 = extractelement <4 x float> %0, i64 0
+  ; CHECK-NEXT: %2 = call float @dx.op.unary.f32(i32 7, float %1)
+  ; CHECK-NEXT: %3 = insertelement <4 x float> %0, float %2, i64 0
+  ; CHECK-NEXT: %4 = extractelement <4 x float> %0, i64 1
+  ; CHECK-NEXT: %5 = call float @dx.op.unary.f32(i32 7, float %4)
+  ; CHECK-NEXT: %6 = insertelement <4 x float> %0, float %5, i64 1
+  ; CHECK-NEXT: %7 = extractelement <4 x float> %0, i64 2
+  ; CHECK-NEXT: %8 = call float @dx.op.unary.f32(i32 7, float %7)
+  ; CHECK-NEXT: %9 = insertelement <4 x float> %0, float %8, i64 2
+  ; CHECK-NEXT: %10 = extractelement <4 x float> %0, i64 3
+  ; CHECK-NEXT: %11 = call float @dx.op.unary.f32(i32 7, float %10)
+  ; CHECK-NEXT: %12 = insertelement <4 x float> %0, float %11, i64 3
+  %hlsl.saturate = call <4 x float> @llvm.dx.saturate.v4f32(<4 x float> %0)
+  ; CHECK: ret <4 x float> %12
+  ret <4 x float> %hlsl.saturate
+}
+
+; Function Attrs: nocallback nofree nosync nounwind willreturn
+declare <4 x float> @llvm.dx.saturate.v4f32(<4 x float>) #1
+
+; CHECK-LABEL: test_saturate_double
+define noundef double @test_saturate_double(double noundef %p0) #0 {
+entry:
+  %p0.addr = alloca double, align 8
+  store double %p0, ptr %p0.addr, align 8, !tbaa !11
+  %0 = load double, ptr %p0.addr, align 8, !tbaa !11
+  ; CHECK: %1 = call double @dx.op.unary.f64(i32 7, double %0)
+  %hlsl.saturate = call double @llvm.dx.saturate.f64(double %0)
+  ; CHECK: ret double %1
+  ret double %hlsl.saturate
+}
+
+; Function Attrs: nocallback nofree nosync nounwind willreturn
+declare double @llvm.dx.saturate.f64(double) #1
+
+; CHECK-LABEL: test_saturate_double2
+define noundef <2 x double> @test_saturate_double2(<2 x double> noundef %p0) #0 {
+entry:
+  %p0.addr = alloca <2 x double>, align 16
+  store <2 x double> %p0, ptr %p0.addr, align 16, !tbaa !8
+  %0 = load <2 x double>, ptr %p0.addr, align 16, !tbaa !8
+  ; CHECK: %1 = extractelement <2 x double> %0, i64 0
+  ; CHECK-NEXT: %2 = call double @dx.op.unary.f64(i32 7, double %1)
+  ; CHECK-NEXT: %3 = insertelement <2 x double> %0, double %2, i64 0
+  ; CHECK-NEXT: %4 = extractelement <2 x double> %0, i64 1
+  ; CHECK-NEXT: %5 = call double @dx.op.unary.f64(i32 7, double %4)
+  ; CHECK-NEXT: %6 = insertelement <2 x double> %0, double %5, i64 1
+  %hlsl.saturate = call <2 x double> @llvm.dx.saturate.v2f64(<2 x double> %0)
+  ; CHECK: ret <2 x double> %6
+  ret <2 x double> %hlsl.saturate
+}
+
+; Function Attrs: nocallback nofree nosync nounwind willreturn
+declare <2 x double> @llvm.dx.saturate.v2f64(<2 x double>) #1
+
+; CHECK-LABEL: test_saturate_double3
+define noundef <3 x double> @test_saturate_double3(<3 x double> noundef %p0) #0 {
+entry:
+  %p0.addr = alloca <3 x double>, align 32
+  store <3 x double> %p0, ptr %p0.addr, align 32, !tbaa !8
+  %0 = load <3 x double>, ptr %p0.addr, align 32, !tbaa !8
+  ; CHECK: %1 = extractelement <3 x double> %0, i64 0
+  ; CHECK-NEXT: %2 = call double @dx.op.unary.f64(i32 7, double %1)
+  ; CHECK-NEXT: %3 = insertelement <3 x double> %0, double %2, i64 0
+  ; CHECK-NEXT: %4 = extractelement <3 x double> %0, i64 1
+  ; CHECK-NEXT: %5 = call double @dx.op.unary.f64(i32 7, double %4)
+  ; CHECK-NEXT: %6 = insertelement <3 x double> %0, double %5, i64 1
+  ; CHECK-NEXT: %7 = extractelement <3 x double> %0, i64 2
+  ; CHECK-NEXT: %8 = call double @dx.op.unary.f64(i32 7, double %7)
+  ; CHECK-NEXT: %9 = insertelement <3 x double> %0, double %8, i64 2
+  %hlsl.saturate = call <3 x double> @llvm.dx.saturate.v3f64(<3 x double> %0)
+  ; CHECK: ret <3 x double> %9
+  ret <3 x double> %hlsl.saturate
+}
+
+; Function Attrs: nocallback nofree nosync nounwind willreturn
+declare <3 x double> @llvm.dx.saturate.v3f64(<3 x double>) #1
+
+; CHECK-LABEL: test_saturate_double4
+define noundef <4 x double> @test_saturate_double4(<4 x double> noundef %p0) #0 {
+entry:
+  %p0.addr = alloca <4 x double>, align 32
+  store <4 x double> %p0, ptr %p0.addr, align 32, !tbaa !8
+  %0 = load <4 x double>, ptr %p0.addr, align 32, !tbaa !8
+  ; CHECK: %1 = extractelement <4 x double> %0, i64 0
+  ; CHECK-NEXT: %2 = call double @dx.op.unary.f64(i32 7, double %1)
+  ; CHECK-NEXT: %3 = insertelement <4 x double> %0, double %2, i64 0
+  ; CHECK-NEXT: %4 = extractelement <4 x double> %0, i64 1
+  ; CHECK-NEXT: %5 = call double @dx.op.unary.f64(i32 7, double %4)
+  ; CHECK-NEXT: %6 = insertelement <4 x double> %0, double %5, i64 1
+  ; CHECK-NEXT: %7 = extractelement <4 x double> %0, i64 2
+  ; CHECK-NEXT: %8 = call double @dx.op.unary.f64(i32 7, double %7)
+  ; CHECK-NEXT: %9 = insertelement <4 x double> %0, double %8, i64 2
+  ; CHECK-NEXT: %10 = extractelement <4 x double> %0, i64 3
+  ; CHECK-NEXT: %11 = call double @dx.op.unary.f64(i32 7, double %10)
+  ; CHECK-NEXT: %12 = insertelement <4 x double> %0, double %11, i64 3
+  %hlsl.saturate = call <4 x double> @llvm.dx.saturate.v4f64(<4 x double> %0)
+  ; CHECK: ret <4 x double> %12
+  ret <4 x double> %hlsl.saturate
+}
+
+; Function Attrs: nocallback nofree nosync nounwind willreturn
+declare <4 x double> @llvm.dx.saturate.v4f64(<4 x double>) #1
+
+attributes #0 = { convergent nounwind "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" }
+attributes #1 = { nocallback nofree nosync nounwind willreturn }
+
+!llvm.module.flags = !{!0, !1}
+!dx.valver = !{!2}
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 7, !"frame-pointer", i32 2}
+!2 = !{i32 1, i32 7}
+!4 = !{!5, !5, i64 0}
+!5 = !{!"half", !6, i64 0}
+!6 = !{!"omnipotent char", !7, i64 0}
+!7 = !{!"Simple C++ TBAA"}
+!8 = !{!6, !6, i64 0}
+!9 = !{!10, !10, i64 0}
+!10 = !{!"float", !6, i64 0}
+!11 = !{!12, !12, i64 0}
+!12 = !{!"double", !6, i64 0}
diff --git a/llvm/test/CodeGen/DirectX/saturate_errors.ll b/llvm/test/CodeGen/DirectX/saturate_errors.ll
new file mode 100644
index 00000000000000..940843f5e58475
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/saturate_errors.ll
@@ -0,0 +1,14 @@
+; RUN: not opt -S -dxil-op-lower -mtriple=dxil-pc-shadermodel6.3-library %s 2>&1 | FileCheck %s
+; Make sure the intrinsic dx.saturate is to appropriate DXIL op for half/float/double data types.
+
+target datalayout = "e-m:e-p:32:32-i1:32-i8:8-i16:16-i32:32-i64:64-f16:16-f32:32-f64:64-n8:16:32:64"
+target triple = "dxilv1.6-unknown-shadermodel6.6-library"
+
+; DXIL operation saturate does not support i32 overload
+; CHECK: invalid intrinsic signature
+
+define noundef i32 @test_saturate_i32(i32 noundef %p0) #0 {
+entry:
+  %hlsl.saturate = call i32 @llvm.dx.saturate.i32(i32 %p0)
+  ret i32 %hlsl.saturate
+}
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/saturate.ll b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/saturate.ll
new file mode 100644
index 00000000000000..0b05b615c4ad17
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/saturate.ll
@@ -0,0 +1,83 @@
+; RUN: llc -O0 -mtriple=spirv-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; Make sure SPIRV operation function calls for saturate are lowered correctly.
+
+; CHECK-DAG: %[[#op_ext_glsl:]] = OpExtInstImport "GLSL.std.450"
+; CHECK-DAG: %[[#float_16:]] = OpTypeFloat 16
+; CHECK-DAG: %[[#vec4_float_16:]] = OpTypeVector %[[#float_16]] 4
+; CHECK-DAG: %[[#float_32:]] = OpTypeFloat 32
+; CHECK-DAG: %[[#vec4_float_32:]] = OpTypeVector %[[#float_32]] 4
+; CHECK-DAG: %[[#float_64:]] = OpTypeFloat 64
+; CHECK-DAG: %[[#vec4_float_64:]] = OpTypeVector %[[#float_64]] 4
+; CHECK-DAG: %[[#zero_float_16:]] = OpConstant %[[#float_16]] 0
+; CHECK-DAG: %[[#vec4_zero_float_16:]] = OpConstantComposite %[[#vec4_float_16]] %[[#zero_float_16]] %[[#zero_float_16]] %[[#zero_float_16]]
+; CHECK-DAG: %[[#one_float_16:]] = OpConstant %[[#float_16]] 15360
+; CHECK-DAG: %[[#vec4_one_float_16:]] = OpConstantComposite %[[#vec4_float_16]] %[[#one_float_16]] %[[#one_float_16]] %[[#one_float_16]]
+; CHECK-DAG: %[[#zero_float_32:]] = OpConstant %[[#float_32]] 0
+; CHECK-DAG: %[[#vec4_zero_float_32:]] = OpConstantComposite %[[#vec4_float_32]] %[[#zero_float_32]] %[[#zero_float_32]] %[[#zero_float_32]]
+; CHECK-DAG: %[[#one_float_32:]] = OpConstant %[[#float_32]] 1
+; CHECK-DAG: %[[#vec4_one_float_32:]] = OpConstantComposite %[[#vec4_float_32]] %[[#one_float_32]] %[[#one_float_32]] %[[#one_float_32]]
+
+; CHECK-DAG: %[[#zero_float_64:]] = OpConstant %[[#float_64]] 0
+; CHECK-DAG: %[[#vec4_zero_float_64:]] = OpConstantComposite %[[#vec4_float_64]] %[[#zero_float_64]] %[[#zero_float_64]] %[[#zero_float_64]]
+; CHECK-DAG: %[[#one_float_64:]] = OpConstant %[[#float_64]] 1
+; CHECK-DAG: %[[#vec4_one_float_64:]] = OpConstantComposite %[[#vec4_float_64]] %[[#one_float_64]] %[[#one_float_64]] %[[#one_float_64]]
+
+define noundef half @saturate_half(half noundef %a) {
+entry:
+  ; CHECK: %[[#]] = OpFunction %[[#float_16]] None %[[#]]
+  ; CHECK: %[[#arg0:]] = OpFunctionParameter %[[#float_16]]
+  ; CHECK: %[[#]] = OpExtInst %[[#float_16]] %[[#op_ext_glsl]] FClamp %[[#arg0]] %[[#zero_float_16]] %[[#one_float_16]]
+  %hlsl.saturate = call half @llvm.spv.saturate.f16(half %a)
+  ret half %hlsl.saturate
+}
+
+define noundef float @saturate_float(float noundef %a) {
+entry:
+  ; CHECK: %[[#]] = OpFunction %[[#float_32]] None %[[#]]
+  ; CHECK: %[[#arg0:]] = OpFunctionParameter %[[#float_32]]
+  ; CHECK: %[[#]] = OpExtInst %[[#float_32]] %[[#op_ext_glsl]] FClamp %[[#arg0]] %[[#zero_float_32]] %[[#one_float_32]]
+  %hlsl.saturate = call float @llvm.spv.saturate.f32(float %a)
+  ret float %hlsl.saturate
+}
+
+define noundef double @saturate_double(double noundef %a) {
+entry:
+  ; CHECK: %[[#]] = OpFunction %[[#float_64]] None %[[#]]
+  ; CHECK: %[[#arg0:]] = OpFunctionParameter %[[#float_64]]
+  ; CHECK: %[[#]] = OpExtInst %[[#float_64]] %[[#op_ext_glsl]] FClamp %[[#arg0]] %[[#zero_float_64]] %[[#one_float_64]]
+  %hlsl.saturate = call double @llvm.spv.saturate.f64(double %a)
+  ret double %hlsl.saturate
+}
+
+define noundef <4 x half> @saturate_half4(<4 x half> noundef %a) {
+entry:
+  ; CHECK: %[[#]] = OpFunction %[[#vec4_float_16]] None %[[#]]
+  ; CHECK: %[[#arg0:]] = OpFunctionParameter %[[#vec4_float_16]]
+  ; CHECK: %[[#]] = OpExtInst %[[#vec4_float_16]] %[[#op_ext_glsl]] FClamp %[[#arg0]] %[[#vec4_zero_float_16]] %[[#vec4_one_float_16]]
+  %hlsl.saturate = call <4 x half> @llvm.spv.saturate.v4f16(<4 x half> %a)
+  ret <4 x half> %hlsl.saturate
+}
+
+define noundef <4 x float> @saturate_float4(<4 x float> noundef %a) {
+entry:
+  ; CHECK: %[[#]] = OpFunction %[[#vec4_float_32]] None %[[#]]
+  ; CHECK: %[[#arg0:]] = OpFunctionParameter %[[#vec4_float_32]]
+  ; CHECK: %[[#]] = OpExtInst %[[#vec4_float_32]] %[[#op_ext_glsl]] FClamp %[[#arg0]] %[[#vec4_zero_float_32]] %[[#vec4_one_float_32]]
+  %hlsl.saturate = call <4 x float> @llvm.spv.saturate.v4f32(<4 x float> %a)
+  ret <4 x float> %hlsl.saturate
+}
+
+define noundef <4 x double> @saturate_double4(<4 x double> noundef %a) {
+entry:
+  ; CHECK: %[[#]] = OpFunction %[[#vec4_float_64]] None %[[#]]
+  ; CHECK: %[[#arg0:]] = OpFunctionParameter %[[#vec4_float_64]]
+  ; CHECK: %[[#]] = OpExtInst %[[#vec4_float_64]] %[[#op_ext_glsl]] FClamp %[[#arg0]] %[[#vec4_zero_float_64]] %[[#vec4_one_float_64]]
+  %hlsl.saturate = call <4 x double> @llvm.spv.saturate.v4f64(<4 x double> %a)
+  ret <4 x double> %hlsl.saturate
+}
+
+declare <4 x half> @llvm.spv.saturate.v4f16(<4 x half>)
+declare <4 x float> @llvm.spv.saturate.v4f32(<4 x float>)
+declare <4 x double> @llvm.spv.saturate.v4f64(<4 x double>)

>From 80824822c9852d57a137cea85082c1b464f40ec9 Mon Sep 17 00:00:00 2001
From: Bharadwaj Yadavalli <Bharadwaj.Yadavalli at microsoft.com>
Date: Fri, 16 Aug 2024 17:44:53 -0400
Subject: [PATCH 2/4] Add test to verify generation of llvm.spv.saturate.*
 instructions Change type check to assert.

---
 clang/lib/CodeGen/CGBuiltin.cpp               |  4 +-
 clang/test/CodeGenHLSL/builtins/saturate.hlsl | 41 +++++++++++++++++++
 2 files changed, 43 insertions(+), 2 deletions(-)

diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index f4353f595efbac..495fb3e1e5b697 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -18669,8 +18669,8 @@ case Builtin::BI__builtin_hlsl_elementwise_isinf: {
   }
   case Builtin::BI__builtin_hlsl_elementwise_saturate: {
     Value *Op0 = EmitScalarExpr(E->getArg(0));
-    if (!E->getArg(0)->getType()->hasFloatingRepresentation())
-      llvm_unreachable("saturate operand must have a float representation");
+    assert(E->getArg(0)->getType()->hasFloatingRepresentation() &&
+           "saturate operand must have a float representation");
     return Builder.CreateIntrinsic(
         /*ReturnType=*/Op0->getType(),
         CGM.getHLSLRuntime().getSaturateIntrinsic(), ArrayRef<Value *>{Op0},
diff --git a/clang/test/CodeGenHLSL/builtins/saturate.hlsl b/clang/test/CodeGenHLSL/builtins/saturate.hlsl
index 970d7b7371b1eb..65a3cd74621cc0 100644
--- a/clang/test/CodeGenHLSL/builtins/saturate.hlsl
+++ b/clang/test/CodeGenHLSL/builtins/saturate.hlsl
@@ -6,49 +6,90 @@
 // RUN:   dxil-pc-shadermodel6.3-library %s -emit-llvm -disable-llvm-passes \
 // RUN:   -o - | FileCheck %s --check-prefixes=CHECK,NO_HALF
 
+// RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -x hlsl -triple \
+// RUN:   spirv-unknown-vulkan-library %s -fnative-half-type \
+// RUN:   -emit-llvm -disable-llvm-passes -o - | FileCheck %s \
+// RUN:   --check-prefixes=SPIRV,SPIRV_HALF
+// RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -x hlsl -triple \
+// RUN:   spirv-unknown-vulkan-library %s \
+// RUN:   -emit-llvm -disable-llvm-passes -o - | FileCheck %s \
+// RUN:   --check-prefixes=SPIRV,SPIRV_NO_HALF
+
 // NATIVE_HALF: define noundef half @
 // NATIVE_HALF: call half @llvm.dx.saturate.f16(
 // NO_HALF: define noundef float @"?test_saturate_half
 // NO_HALF: call float @llvm.dx.saturate.f32(
+// SPIRV_HALF: define spir_func noundef half @_Z18test_saturate_halfDh(half
+// SPIRV_HALF: call half @llvm.spv.saturate.f16(half
+// SPIRV_NO_HALF: define spir_func noundef float @_Z18test_saturate_halfDh(float
+// SPIRV_NO_HALF: call float @llvm.spv.saturate.f32(float
 half test_saturate_half(half p0) { return saturate(p0); }
 // NATIVE_HALF: define noundef <2 x half> @
 // NATIVE_HALF: call <2 x half> @llvm.dx.saturate.v2f16
 // NO_HALF: define noundef <2 x float> @"?test_saturate_half2
 // NO_HALF: call <2 x float> @llvm.dx.saturate.v2f32(
+// SPIRV_HALF: define spir_func noundef <2 x half> @_Z19test_saturate_half2Dv2_Dh(
+// SPIRV_HALF: call <2 x half> @llvm.spv.saturate.v2f16(<2 x half>
+// SPIRV_NO_HALF: define spir_func noundef <2 x float> @_Z19test_saturate_half2Dv2_Dh(<2 x float>
+// SPIRV_NO_HALF: call <2 x float> @llvm.spv.saturate.v2f32(<2 x float>
 half2 test_saturate_half2(half2 p0) { return saturate(p0); }
 // NATIVE_HALF: define noundef <3 x half> @
 // NATIVE_HALF: call <3 x half> @llvm.dx.saturate.v3f16
 // NO_HALF: define noundef <3 x float> @"?test_saturate_half3
 // NO_HALF: call <3 x float> @llvm.dx.saturate.v3f32(
+// SPIRV_HALF: define spir_func noundef <3 x half> @_Z19test_saturate_half3Dv3_Dh(
+// SPIRV_HALF: call <3 x half> @llvm.spv.saturate.v3f16(<3 x half>
+// SPIRV_NO_HALF: define spir_func noundef <3 x float> @_Z19test_saturate_half3Dv3_Dh(<3 x float>
+// SPIRV_NO_HALF: call <3 x float> @llvm.spv.saturate.v3f32(<3 x float>
 half3 test_saturate_half3(half3 p0) { return saturate(p0); }
 // NATIVE_HALF: define noundef <4 x half> @
 // NATIVE_HALF: call <4 x half> @llvm.dx.saturate.v4f16
 // NO_HALF: define noundef <4 x float> @"?test_saturate_half4
 // NO_HALF: call <4 x float> @llvm.dx.saturate.v4f32(
+// SPIRV_HALF: define spir_func noundef <4 x half> @_Z19test_saturate_half4Dv4_Dh(
+// SPIRV_HALF: call <4 x half> @llvm.spv.saturate.v4f16(<4 x half>
+// SPIRV_NO_HALF: define spir_func noundef <4 x float> @_Z19test_saturate_half4Dv4_Dh(<4 x float>
+// SPIRV_NO_HALF: call <4 x float> @llvm.spv.saturate.v4f32(<4 x float>
 half4 test_saturate_half4(half4 p0) { return saturate(p0); }
 
 // CHECK: define noundef float @"?test_saturate_float
 // CHECK: call float @llvm.dx.saturate.f32(
+// SPIRV: define spir_func noundef float @_Z19test_saturate_floatf(float
+// SPIRV: call float @llvm.spv.saturate.f32(float
 float test_saturate_float(float p0) { return saturate(p0); }
 // CHECK: define noundef <2 x float> @"?test_saturate_float2
 // CHECK: call <2 x float> @llvm.dx.saturate.v2f32
+// SPIRV: define spir_func noundef <2 x float> @_Z20test_saturate_float2Dv2_f(<2 x float>
+// SPIRV: call <2 x float> @llvm.spv.saturate.v2f32(<2 x float>
 float2 test_saturate_float2(float2 p0) { return saturate(p0); }
 // CHECK: define noundef <3 x float> @"?test_saturate_float3
 // CHECK: call <3 x float> @llvm.dx.saturate.v3f32
+// SPIRV: define spir_func noundef <3 x float> @_Z20test_saturate_float3Dv3_f(<3 x float>
+// SPIRV: call <3 x float> @llvm.spv.saturate.v3f32(<3 x float>
 float3 test_saturate_float3(float3 p0) { return saturate(p0); }
 // CHECK: define noundef <4 x float> @"?test_saturate_float4
 // CHECK: call <4 x float> @llvm.dx.saturate.v4f32
+// SPIRV: define spir_func noundef <4 x float> @_Z20test_saturate_float4Dv4_f(<4 x float>
+// SPIRV: call <4 x float> @llvm.spv.saturate.v4f32(<4 x float>
 float4 test_saturate_float4(float4 p0) { return saturate(p0); }
 
 // CHECK: define noundef double @
 // CHECK: call double @llvm.dx.saturate.f64(
+// SPIRV: define spir_func noundef double @_Z20test_saturate_doubled(double
+// SPIRV: call double @llvm.spv.saturate.f64(double
 double test_saturate_double(double p0) { return saturate(p0); }
 // CHECK: define noundef <2 x double> @
 // CHECK: call <2 x double> @llvm.dx.saturate.v2f64
+// SPIRV: define spir_func noundef <2 x double> @_Z21test_saturate_double2Dv2_d(<2 x double>
+// SPIRV: call <2 x double> @llvm.spv.saturate.v2f64(<2 x double>
 double2 test_saturate_double2(double2 p0) { return saturate(p0); }
 // CHECK: define noundef <3 x double> @
 // CHECK: call <3 x double> @llvm.dx.saturate.v3f64
+// SPIRV: define spir_func noundef <3 x double> @_Z21test_saturate_double3Dv3_d(<3 x double>
+// SPIRV: call <3 x double> @llvm.spv.saturate.v3f64(<3 x double>
 double3 test_saturate_double3(double3 p0) { return saturate(p0); }
 // CHECK: define noundef <4 x double> @
 // CHECK: call <4 x double> @llvm.dx.saturate.v4f64
+// SPIRV: define spir_func noundef <4 x double> @_Z21test_saturate_double4Dv4_d(<4 x double>
+// SPIRV: call <4 x double> @llvm.spv.saturate.v4f64(<4 x double>
 double4 test_saturate_double4(double4 p0) { return saturate(p0); }

>From 6fce77ab1180354195e4a74a8c9252567dc9d039 Mon Sep 17 00:00:00 2001
From: Bharadwaj Yadavalli <Bharadwaj.Yadavalli at microsoft.com>
Date: Mon, 19 Aug 2024 11:41:50 -0400
Subject: [PATCH 3/4] Drop vector operation expansion of saturate as it is
 expected to be handled in a scalarization pass planned to be implemented in
 the future.

Remove saturate tests with vector operands.
---
 .../Target/DirectX/DXILIntrinsicExpansion.cpp |   1 -
 llvm/test/CodeGen/DirectX/saturate.ll         | 207 ------------------
 2 files changed, 208 deletions(-)

diff --git a/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp b/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
index 9b467dff3e126f..f18adbccac95ee 100644
--- a/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
+++ b/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
@@ -47,7 +47,6 @@ static bool isIntrinsicExpansion(Function &F) {
   case Intrinsic::dx_normalize:
   case Intrinsic::dx_sdot:
   case Intrinsic::dx_udot:
-  case Intrinsic::dx_saturate:
     return true;
   }
   return false;
diff --git a/llvm/test/CodeGen/DirectX/saturate.ll b/llvm/test/CodeGen/DirectX/saturate.ll
index 0c96249ccea435..bc1945836f6a4b 100644
--- a/llvm/test/CodeGen/DirectX/saturate.ll
+++ b/llvm/test/CodeGen/DirectX/saturate.ll
@@ -19,75 +19,6 @@ entry:
 ; Function Attrs: nocallback nofree nosync nounwind willreturn
 declare half @llvm.dx.saturate.f16(half) #1
 
-; CHECK-LABEL: test_saturate_half2
-define noundef <2 x half> @test_saturate_half2(<2 x half> noundef %p0) #0 {
-entry:
-  %p0.addr = alloca <2 x half>, align 4
-  store <2 x half> %p0, ptr %p0.addr, align 4, !tbaa !8
-  %0 = load <2 x half>, ptr %p0.addr, align 4, !tbaa !8
-  ; CHECK: %1 = extractelement <2 x half> %0, i64 0
-  ; CHECK-NEXT: %2 = call half @dx.op.unary.f16(i32 7, half %1)
-  ; CHECK-NEXT: %3 = insertelement <2 x half> %0, half %2, i64 0
-  ; CHECK-NEXT: %4 = extractelement <2 x half> %0, i64 1
-  ; CHECK-NEXT: %5 = call half @dx.op.unary.f16(i32 7, half %4)
-  ; CHECK-NEXT: %6 = insertelement <2 x half> %0, half %5, i64 1
-  %hlsl.saturate = call <2 x half> @llvm.dx.saturate.v2f16(<2 x half> %0)
-  ; CHECK: ret <2 x half> %6
-  ret <2 x half> %hlsl.saturate
-}
-
-; Function Attrs: nocallback nofree nosync nounwind willreturn
-declare <2 x half> @llvm.dx.saturate.v2f16(<2 x half>) #1
-
-; CHECK-LABEL: test_saturate_half3
-define noundef <3 x half> @test_saturate_half3(<3 x half> noundef %p0) #0 {
-entry:
-  %p0.addr = alloca <3 x half>, align 8
-  store <3 x half> %p0, ptr %p0.addr, align 8, !tbaa !8
-  %0 = load <3 x half>, ptr %p0.addr, align 8, !tbaa !8
-  ; CHECK: %1 = extractelement <3 x half> %0, i64 0
-  ; CHECK-NEXT: %2 = call half @dx.op.unary.f16(i32 7, half %1)
-  ; CHECK-NEXT: %3 = insertelement <3 x half> %0, half %2, i64 0
-  ; CHECK-NEXT: %4 = extractelement <3 x half> %0, i64 1
-  ; CHECK-NEXT: %5 = call half @dx.op.unary.f16(i32 7, half %4)
-  ; CHECK-NEXT: %6 = insertelement <3 x half> %0, half %5, i64 1
-  ; CHECK-NEXT: %7 = extractelement <3 x half> %0, i64 2
-  ; CHECK-NEXT: %8 = call half @dx.op.unary.f16(i32 7, half %7)
-  ; CHECK-NEXT: %9 = insertelement <3 x half> %0, half %8, i64 2
-  %hlsl.saturate = call <3 x half> @llvm.dx.saturate.v3f16(<3 x half> %0)
-  ; CHECK: ret <3 x half> %9
-  ret <3 x half> %hlsl.saturate
-}
-
-; Function Attrs: nocallback nofree nosync nounwind willreturn
-declare <3 x half> @llvm.dx.saturate.v3f16(<3 x half>) #1
-
-; CHECK-LABEL: test_saturate_half4
-define noundef <4 x half> @test_saturate_half4(<4 x half> noundef %p0) #0 {
-entry:
-  %p0.addr = alloca <4 x half>, align 8
-  store <4 x half> %p0, ptr %p0.addr, align 8, !tbaa !8
-  %0 = load <4 x half>, ptr %p0.addr, align 8, !tbaa !8
-  ; CHECK: %1 = extractelement <4 x half> %0, i64 0
-  ; CHECK-NEXT: %2 = call half @dx.op.unary.f16(i32 7, half %1)
-  ; CHECK-NEXT: %3 = insertelement <4 x half> %0, half %2, i64 0
-  ; CHECK-NEXT: %4 = extractelement <4 x half> %0, i64 1
-  ; CHECK-NEXT: %5 = call half @dx.op.unary.f16(i32 7, half %4)
-  ; CHECK-NEXT: %6 = insertelement <4 x half> %0, half %5, i64 1
-  ; CHECK-NEXT: %7 = extractelement <4 x half> %0, i64 2
-  ; CHECK-NEXT: %8 = call half @dx.op.unary.f16(i32 7, half %7)
-  ; CHECK-NEXT: %9 = insertelement <4 x half> %0, half %8, i64 2
-  ; CHECK-NEXT: %10 = extractelement <4 x half> %0, i64 3
-  ; CHECK-NEXT: %11 = call half @dx.op.unary.f16(i32 7, half %10)
-  ; CHECK-NEXT: %12 = insertelement <4 x half> %0, half %11, i64 3
-  %hlsl.saturate = call <4 x half> @llvm.dx.saturate.v4f16(<4 x half> %0)
-  ; CHECK: ret <4 x half> %12
-  ret <4 x half> %hlsl.saturate
-}
-
-; Function Attrs: nocallback nofree nosync nounwind willreturn
-declare <4 x half> @llvm.dx.saturate.v4f16(<4 x half>) #1
-
 ; CHECK-LABEL: test_saturate_float
 define noundef float @test_saturate_float(float noundef %p0) #0 {
 entry:
@@ -103,75 +34,6 @@ entry:
 ; Function Attrs: nocallback nofree nosync nounwind willreturn
 declare float @llvm.dx.saturate.f32(float) #1
 
-; CHECK-LABEL: test_saturate_float2
-define noundef <2 x float> @test_saturate_float2(<2 x float> noundef %p0) #0 {
-entry:
-  %p0.addr = alloca <2 x float>, align 8
-  store <2 x float> %p0, ptr %p0.addr, align 8, !tbaa !8
-  %0 = load <2 x float>, ptr %p0.addr, align 8, !tbaa !8
-  ; CHECK: %1 = extractelement <2 x float> %0, i64 0
-  ; CHECK-NEXT: %2 = call float @dx.op.unary.f32(i32 7, float %1)
-  ; CHECK-NEXT: %3 = insertelement <2 x float> %0, float %2, i64 0
-  ; CHECK-NEXT: %4 = extractelement <2 x float> %0, i64 1
-  ; CHECK-NEXT: %5 = call float @dx.op.unary.f32(i32 7, float %4)
-  ; CHECK-NEXT: %6 = insertelement <2 x float> %0, float %5, i64 1
-  %hlsl.saturate = call <2 x float> @llvm.dx.saturate.v2f32(<2 x float> %0)
-  ; CHECK: ret <2 x float> %6
-  ret <2 x float> %hlsl.saturate
-}
-
-; Function Attrs: nocallback nofree nosync nounwind willreturn
-declare <2 x float> @llvm.dx.saturate.v2f32(<2 x float>) #1
-
-; CHECK-LABEL: test_saturate_float3
-define noundef <3 x float> @test_saturate_float3(<3 x float> noundef %p0) #0 {
-entry:
-  %p0.addr = alloca <3 x float>, align 16
-  store <3 x float> %p0, ptr %p0.addr, align 16, !tbaa !8
-  %0 = load <3 x float>, ptr %p0.addr, align 16, !tbaa !8
-  ; CHECK: %1 = extractelement <3 x float> %0, i64 0
-  ; CHECK-NEXT: %2 = call float @dx.op.unary.f32(i32 7, float %1)
-  ; CHECK-NEXT: %3 = insertelement <3 x float> %0, float %2, i64 0
-  ; CHECK-NEXT: %4 = extractelement <3 x float> %0, i64 1
-  ; CHECK-NEXT: %5 = call float @dx.op.unary.f32(i32 7, float %4)
-  ; CHECK-NEXT: %6 = insertelement <3 x float> %0, float %5, i64 1
-  ; CHECK-NEXT: %7 = extractelement <3 x float> %0, i64 2
-  ; CHECK-NEXT: %8 = call float @dx.op.unary.f32(i32 7, float %7)
-  ; CHECK-NEXT: %9 = insertelement <3 x float> %0, float %8, i64 2
-  %hlsl.saturate = call <3 x float> @llvm.dx.saturate.v3f32(<3 x float> %0)
-  ; CHECK: ret <3 x float> %9
-  ret <3 x float> %hlsl.saturate
-}
-
-; Function Attrs: nocallback nofree nosync nounwind willreturn
-declare <3 x float> @llvm.dx.saturate.v3f32(<3 x float>) #1
-
-; CHECK-LABEL: test_saturate_float4
-define noundef <4 x float> @test_saturate_float4(<4 x float> noundef %p0) #0 {
-entry:
-  %p0.addr = alloca <4 x float>, align 16
-  store <4 x float> %p0, ptr %p0.addr, align 16, !tbaa !8
-  %0 = load <4 x float>, ptr %p0.addr, align 16, !tbaa !8
-  ; CHECK: %1 = extractelement <4 x float> %0, i64 0
-  ; CHECK-NEXT: %2 = call float @dx.op.unary.f32(i32 7, float %1)
-  ; CHECK-NEXT: %3 = insertelement <4 x float> %0, float %2, i64 0
-  ; CHECK-NEXT: %4 = extractelement <4 x float> %0, i64 1
-  ; CHECK-NEXT: %5 = call float @dx.op.unary.f32(i32 7, float %4)
-  ; CHECK-NEXT: %6 = insertelement <4 x float> %0, float %5, i64 1
-  ; CHECK-NEXT: %7 = extractelement <4 x float> %0, i64 2
-  ; CHECK-NEXT: %8 = call float @dx.op.unary.f32(i32 7, float %7)
-  ; CHECK-NEXT: %9 = insertelement <4 x float> %0, float %8, i64 2
-  ; CHECK-NEXT: %10 = extractelement <4 x float> %0, i64 3
-  ; CHECK-NEXT: %11 = call float @dx.op.unary.f32(i32 7, float %10)
-  ; CHECK-NEXT: %12 = insertelement <4 x float> %0, float %11, i64 3
-  %hlsl.saturate = call <4 x float> @llvm.dx.saturate.v4f32(<4 x float> %0)
-  ; CHECK: ret <4 x float> %12
-  ret <4 x float> %hlsl.saturate
-}
-
-; Function Attrs: nocallback nofree nosync nounwind willreturn
-declare <4 x float> @llvm.dx.saturate.v4f32(<4 x float>) #1
-
 ; CHECK-LABEL: test_saturate_double
 define noundef double @test_saturate_double(double noundef %p0) #0 {
 entry:
@@ -187,75 +49,6 @@ entry:
 ; Function Attrs: nocallback nofree nosync nounwind willreturn
 declare double @llvm.dx.saturate.f64(double) #1
 
-; CHECK-LABEL: test_saturate_double2
-define noundef <2 x double> @test_saturate_double2(<2 x double> noundef %p0) #0 {
-entry:
-  %p0.addr = alloca <2 x double>, align 16
-  store <2 x double> %p0, ptr %p0.addr, align 16, !tbaa !8
-  %0 = load <2 x double>, ptr %p0.addr, align 16, !tbaa !8
-  ; CHECK: %1 = extractelement <2 x double> %0, i64 0
-  ; CHECK-NEXT: %2 = call double @dx.op.unary.f64(i32 7, double %1)
-  ; CHECK-NEXT: %3 = insertelement <2 x double> %0, double %2, i64 0
-  ; CHECK-NEXT: %4 = extractelement <2 x double> %0, i64 1
-  ; CHECK-NEXT: %5 = call double @dx.op.unary.f64(i32 7, double %4)
-  ; CHECK-NEXT: %6 = insertelement <2 x double> %0, double %5, i64 1
-  %hlsl.saturate = call <2 x double> @llvm.dx.saturate.v2f64(<2 x double> %0)
-  ; CHECK: ret <2 x double> %6
-  ret <2 x double> %hlsl.saturate
-}
-
-; Function Attrs: nocallback nofree nosync nounwind willreturn
-declare <2 x double> @llvm.dx.saturate.v2f64(<2 x double>) #1
-
-; CHECK-LABEL: test_saturate_double3
-define noundef <3 x double> @test_saturate_double3(<3 x double> noundef %p0) #0 {
-entry:
-  %p0.addr = alloca <3 x double>, align 32
-  store <3 x double> %p0, ptr %p0.addr, align 32, !tbaa !8
-  %0 = load <3 x double>, ptr %p0.addr, align 32, !tbaa !8
-  ; CHECK: %1 = extractelement <3 x double> %0, i64 0
-  ; CHECK-NEXT: %2 = call double @dx.op.unary.f64(i32 7, double %1)
-  ; CHECK-NEXT: %3 = insertelement <3 x double> %0, double %2, i64 0
-  ; CHECK-NEXT: %4 = extractelement <3 x double> %0, i64 1
-  ; CHECK-NEXT: %5 = call double @dx.op.unary.f64(i32 7, double %4)
-  ; CHECK-NEXT: %6 = insertelement <3 x double> %0, double %5, i64 1
-  ; CHECK-NEXT: %7 = extractelement <3 x double> %0, i64 2
-  ; CHECK-NEXT: %8 = call double @dx.op.unary.f64(i32 7, double %7)
-  ; CHECK-NEXT: %9 = insertelement <3 x double> %0, double %8, i64 2
-  %hlsl.saturate = call <3 x double> @llvm.dx.saturate.v3f64(<3 x double> %0)
-  ; CHECK: ret <3 x double> %9
-  ret <3 x double> %hlsl.saturate
-}
-
-; Function Attrs: nocallback nofree nosync nounwind willreturn
-declare <3 x double> @llvm.dx.saturate.v3f64(<3 x double>) #1
-
-; CHECK-LABEL: test_saturate_double4
-define noundef <4 x double> @test_saturate_double4(<4 x double> noundef %p0) #0 {
-entry:
-  %p0.addr = alloca <4 x double>, align 32
-  store <4 x double> %p0, ptr %p0.addr, align 32, !tbaa !8
-  %0 = load <4 x double>, ptr %p0.addr, align 32, !tbaa !8
-  ; CHECK: %1 = extractelement <4 x double> %0, i64 0
-  ; CHECK-NEXT: %2 = call double @dx.op.unary.f64(i32 7, double %1)
-  ; CHECK-NEXT: %3 = insertelement <4 x double> %0, double %2, i64 0
-  ; CHECK-NEXT: %4 = extractelement <4 x double> %0, i64 1
-  ; CHECK-NEXT: %5 = call double @dx.op.unary.f64(i32 7, double %4)
-  ; CHECK-NEXT: %6 = insertelement <4 x double> %0, double %5, i64 1
-  ; CHECK-NEXT: %7 = extractelement <4 x double> %0, i64 2
-  ; CHECK-NEXT: %8 = call double @dx.op.unary.f64(i32 7, double %7)
-  ; CHECK-NEXT: %9 = insertelement <4 x double> %0, double %8, i64 2
-  ; CHECK-NEXT: %10 = extractelement <4 x double> %0, i64 3
-  ; CHECK-NEXT: %11 = call double @dx.op.unary.f64(i32 7, double %10)
-  ; CHECK-NEXT: %12 = insertelement <4 x double> %0, double %11, i64 3
-  %hlsl.saturate = call <4 x double> @llvm.dx.saturate.v4f64(<4 x double> %0)
-  ; CHECK: ret <4 x double> %12
-  ret <4 x double> %hlsl.saturate
-}
-
-; Function Attrs: nocallback nofree nosync nounwind willreturn
-declare <4 x double> @llvm.dx.saturate.v4f64(<4 x double>) #1
-
 attributes #0 = { convergent nounwind "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" }
 attributes #1 = { nocallback nofree nosync nounwind willreturn }
 

>From 5d625c605985d5e1a2a9195df3c209e1a2b86ee0 Mon Sep 17 00:00:00 2001
From: Bharadwaj Yadavalli <Bharadwaj.Yadavalli at microsoft.com>
Date: Mon, 19 Aug 2024 16:06:26 -0400
Subject: [PATCH 4/4] Cleanup test sources.

---
 .../Target/DirectX/DXILIntrinsicExpansion.cpp |  1 -
 llvm/test/CodeGen/DirectX/saturate.ll         | 48 ++++---------------
 llvm/test/CodeGen/DirectX/saturate_errors.ll  |  4 --
 3 files changed, 9 insertions(+), 44 deletions(-)

diff --git a/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp b/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
index f18adbccac95ee..2c481d15be5bde 100644
--- a/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
+++ b/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
@@ -15,7 +15,6 @@
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/CodeGen/Passes.h"
-#include "llvm/IR/DerivedTypes.h"
 #include "llvm/IR/IRBuilder.h"
 #include "llvm/IR/Instruction.h"
 #include "llvm/IR/Instructions.h"
diff --git a/llvm/test/CodeGen/DirectX/saturate.ll b/llvm/test/CodeGen/DirectX/saturate.ll
index bc1945836f6a4b..a8557351756f2b 100644
--- a/llvm/test/CodeGen/DirectX/saturate.ll
+++ b/llvm/test/CodeGen/DirectX/saturate.ll
@@ -1,18 +1,12 @@
 ; RUN: opt -S -dxil-op-lower -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s
 ; Make sure the intrinsic dx.saturate is to appropriate DXIL op for half/float/double data types.
 
-target datalayout = "e-m:e-p:32:32-i1:32-i8:8-i16:16-i32:32-i64:64-f16:16-f32:32-f64:64-n8:16:32:64"
-target triple = "dxilv1.6-unknown-shadermodel6.6-library"
-
 ; CHECK-LABEL: test_saturate_half
 define noundef half @test_saturate_half(half noundef %p0) #0 {
 entry:
-  %p0.addr = alloca half, align 2
-  store half %p0, ptr %p0.addr, align 2, !tbaa !4
-  %0 = load half, ptr %p0.addr, align 2, !tbaa !4
-  ; CHECK: %1 = call half @dx.op.unary.f16(i32 7, half %0)
-  %hlsl.saturate = call half @llvm.dx.saturate.f16(half %0)
-  ; CHECK: ret half %1
+  ; CHECK: call half @dx.op.unary.f16(i32 7, half %p0)
+  %hlsl.saturate = call half @llvm.dx.saturate.f16(half %p0)
+  ; CHECK: ret half
   ret half %hlsl.saturate
 }
 
@@ -22,12 +16,9 @@ declare half @llvm.dx.saturate.f16(half) #1
 ; CHECK-LABEL: test_saturate_float
 define noundef float @test_saturate_float(float noundef %p0) #0 {
 entry:
-  %p0.addr = alloca float, align 4
-  store float %p0, ptr %p0.addr, align 4, !tbaa !9
-  %0 = load float, ptr %p0.addr, align 4, !tbaa !9
-  ; CHECK: %1 = call float @dx.op.unary.f32(i32 7, float %0)
-  %hlsl.saturate = call float @llvm.dx.saturate.f32(float %0)
-  ; CHECK: ret float %1
+  ; CHECK: call float @dx.op.unary.f32(i32 7, float %p0)
+  %hlsl.saturate = call float @llvm.dx.saturate.f32(float %p0)
+  ; CHECK: ret float
   ret float %hlsl.saturate
 }
 
@@ -37,33 +28,12 @@ declare float @llvm.dx.saturate.f32(float) #1
 ; CHECK-LABEL: test_saturate_double
 define noundef double @test_saturate_double(double noundef %p0) #0 {
 entry:
-  %p0.addr = alloca double, align 8
-  store double %p0, ptr %p0.addr, align 8, !tbaa !11
-  %0 = load double, ptr %p0.addr, align 8, !tbaa !11
-  ; CHECK: %1 = call double @dx.op.unary.f64(i32 7, double %0)
-  %hlsl.saturate = call double @llvm.dx.saturate.f64(double %0)
-  ; CHECK: ret double %1
+  ; CHECK: call double @dx.op.unary.f64(i32 7, double %p0)
+  %hlsl.saturate = call double @llvm.dx.saturate.f64(double %p0)
+  ; CHECK: ret double
   ret double %hlsl.saturate
 }
 
 ; Function Attrs: nocallback nofree nosync nounwind willreturn
 declare double @llvm.dx.saturate.f64(double) #1
 
-attributes #0 = { convergent nounwind "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" }
-attributes #1 = { nocallback nofree nosync nounwind willreturn }
-
-!llvm.module.flags = !{!0, !1}
-!dx.valver = !{!2}
-
-!0 = !{i32 1, !"wchar_size", i32 4}
-!1 = !{i32 7, !"frame-pointer", i32 2}
-!2 = !{i32 1, i32 7}
-!4 = !{!5, !5, i64 0}
-!5 = !{!"half", !6, i64 0}
-!6 = !{!"omnipotent char", !7, i64 0}
-!7 = !{!"Simple C++ TBAA"}
-!8 = !{!6, !6, i64 0}
-!9 = !{!10, !10, i64 0}
-!10 = !{!"float", !6, i64 0}
-!11 = !{!12, !12, i64 0}
-!12 = !{!"double", !6, i64 0}
diff --git a/llvm/test/CodeGen/DirectX/saturate_errors.ll b/llvm/test/CodeGen/DirectX/saturate_errors.ll
index 940843f5e58475..0dd2e04ab56751 100644
--- a/llvm/test/CodeGen/DirectX/saturate_errors.ll
+++ b/llvm/test/CodeGen/DirectX/saturate_errors.ll
@@ -1,8 +1,4 @@
 ; RUN: not opt -S -dxil-op-lower -mtriple=dxil-pc-shadermodel6.3-library %s 2>&1 | FileCheck %s
-; Make sure the intrinsic dx.saturate is to appropriate DXIL op for half/float/double data types.
-
-target datalayout = "e-m:e-p:32:32-i1:32-i8:8-i16:16-i32:32-i64:64-f16:16-f32:32-f64:64-n8:16:32:64"
-target triple = "dxilv1.6-unknown-shadermodel6.6-library"
 
 ; DXIL operation saturate does not support i32 overload
 ; CHECK: invalid intrinsic signature



More information about the llvm-commits mailing list