Skip to content

Commit 6a38e19

Browse files
authored
[HLSL] Implement support for HLSL intrinsic - saturate (#104619)
Implement support for HLSL intrinsic saturate. Implement DXIL codegen for the intrinsic saturate by lowering it to DXIL Op dx.saturate. Implement SPIRV codegen by transforming saturate(x) to clamp(x, 0.0f, 1.0f). Add tests for DXIL and SPIRV CodeGen.
1 parent 5144817 commit 6a38e19

File tree

14 files changed

+395
-17
lines changed

14 files changed

+395
-17
lines changed

clang/include/clang/Basic/Builtins.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4745,6 +4745,12 @@ def HLSLRSqrt : LangBuiltin<"HLSL_LANG"> {
47454745
let Prototype = "void(...)";
47464746
}
47474747

4748+
def HLSLSaturate : LangBuiltin<"HLSL_LANG"> {
4749+
let Spellings = ["__builtin_hlsl_elementwise_saturate"];
4750+
let Attributes = [NoThrow, Const];
4751+
let Prototype = "void(...)";
4752+
}
4753+
47484754
// Builtins for XRay.
47494755
def XRayCustomEvent : Builtin {
47504756
let Spellings = ["__xray_customevent"];

clang/lib/CodeGen/CGBuiltin.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18667,6 +18667,15 @@ case Builtin::BI__builtin_hlsl_elementwise_isinf: {
1866718667
/*ReturnType=*/Op0->getType(), CGM.getHLSLRuntime().getRsqrtIntrinsic(),
1866818668
ArrayRef<Value *>{Op0}, nullptr, "hlsl.rsqrt");
1866918669
}
18670+
case Builtin::BI__builtin_hlsl_elementwise_saturate: {
18671+
Value *Op0 = EmitScalarExpr(E->getArg(0));
18672+
assert(E->getArg(0)->getType()->hasFloatingRepresentation() &&
18673+
"saturate operand must have a float representation");
18674+
return Builder.CreateIntrinsic(
18675+
/*ReturnType=*/Op0->getType(),
18676+
CGM.getHLSLRuntime().getSaturateIntrinsic(), ArrayRef<Value *>{Op0},
18677+
nullptr, "hlsl.saturate");
18678+
}
1867018679
case Builtin::BI__builtin_hlsl_wave_get_lane_index: {
1867118680
return EmitRuntimeCall(CGM.CreateRuntimeFunction(
1867218681
llvm::FunctionType::get(IntTy, {}, false), "__hlsl_wave_get_lane_index",

clang/lib/CodeGen/CGHLSLRuntime.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ class CGHLSLRuntime {
7979
GENERATE_HLSL_INTRINSIC_FUNCTION(Lerp, lerp)
8080
GENERATE_HLSL_INTRINSIC_FUNCTION(Normalize, normalize)
8181
GENERATE_HLSL_INTRINSIC_FUNCTION(Rsqrt, rsqrt)
82+
GENERATE_HLSL_INTRINSIC_FUNCTION(Saturate, saturate)
8283
GENERATE_HLSL_INTRINSIC_FUNCTION(ThreadId, thread_id)
8384

8485
//===----------------------------------------------------------------------===//

clang/lib/Headers/hlsl/hlsl_intrinsics.h

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -916,7 +916,7 @@ float4 lerp(float4, float4, float4);
916916
/// \brief Returns the length of the specified floating-point vector.
917917
/// \param x [in] The vector of floats, or a scalar float.
918918
///
919-
/// Length is based on the following formula: sqrt(x[0]^2 + x[1]^2 + …).
919+
/// Length is based on the following formula: sqrt(x[0]^2 + x[1]^2 + ...).
920920

921921
_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
922922
_HLSL_BUILTIN_ALIAS(__builtin_hlsl_length)
@@ -1564,6 +1564,45 @@ float3 round(float3);
15641564
_HLSL_BUILTIN_ALIAS(__builtin_elementwise_roundeven)
15651565
float4 round(float4);
15661566

1567+
//===----------------------------------------------------------------------===//
1568+
// saturate builtins
1569+
//===----------------------------------------------------------------------===//
1570+
1571+
/// \fn T saturate(T Val)
1572+
/// \brief Returns input value, \a Val, clamped within the range of 0.0f
1573+
/// to 1.0f. \param Val The input value.
1574+
1575+
_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
1576+
_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
1577+
half saturate(half);
1578+
_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
1579+
_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
1580+
half2 saturate(half2);
1581+
_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
1582+
_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
1583+
half3 saturate(half3);
1584+
_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
1585+
_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
1586+
half4 saturate(half4);
1587+
1588+
_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
1589+
float saturate(float);
1590+
_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
1591+
float2 saturate(float2);
1592+
_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
1593+
float3 saturate(float3);
1594+
_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
1595+
float4 saturate(float4);
1596+
1597+
_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
1598+
double saturate(double);
1599+
_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
1600+
double2 saturate(double2);
1601+
_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
1602+
double3 saturate(double3);
1603+
_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_saturate)
1604+
double4 saturate(double4);
1605+
15671606
//===----------------------------------------------------------------------===//
15681607
// sin builtins
15691608
//===----------------------------------------------------------------------===//

clang/lib/Sema/SemaHLSL.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ static bool isLegalTypeForHLSLSV_DispatchThreadID(QualType T) {
356356
return true;
357357
}
358358

359-
void SemaHLSL::handleSV_DispatchThreadIDAttr(Decl *D, const ParsedAttr &AL) {
359+
void SemaHLSL::handleSV_DispatchThreadIDAttr(Decl *D, const ParsedAttr &AL) {
360360
auto *VD = cast<ValueDecl>(D);
361361
if (!isLegalTypeForHLSLSV_DispatchThreadID(VD->getType())) {
362362
Diag(AL.getLoc(), diag::err_hlsl_attr_invalid_type)
@@ -1045,6 +1045,7 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) {
10451045
return true;
10461046
break;
10471047
}
1048+
case Builtin::BI__builtin_hlsl_elementwise_saturate:
10481049
case Builtin::BI__builtin_hlsl_elementwise_rcp: {
10491050
if (CheckAllArgsHaveFloatRepresentation(&SemaRef, TheCall))
10501051
return true;
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -x hlsl -triple \
2+
// RUN: dxil-pc-shadermodel6.3-library %s -fnative-half-type \
3+
// RUN: -emit-llvm -disable-llvm-passes -o - | FileCheck %s \
4+
// RUN: --check-prefixes=CHECK,NATIVE_HALF
5+
// RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -x hlsl -triple \
6+
// RUN: dxil-pc-shadermodel6.3-library %s -emit-llvm -disable-llvm-passes \
7+
// RUN: -o - | FileCheck %s --check-prefixes=CHECK,NO_HALF
8+
9+
// RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -x hlsl -triple \
10+
// RUN: spirv-unknown-vulkan-library %s -fnative-half-type \
11+
// RUN: -emit-llvm -disable-llvm-passes -o - | FileCheck %s \
12+
// RUN: --check-prefixes=SPIRV,SPIRV_HALF
13+
// RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -x hlsl -triple \
14+
// RUN: spirv-unknown-vulkan-library %s \
15+
// RUN: -emit-llvm -disable-llvm-passes -o - | FileCheck %s \
16+
// RUN: --check-prefixes=SPIRV,SPIRV_NO_HALF
17+
18+
// NATIVE_HALF: define noundef half @
19+
// NATIVE_HALF: call half @llvm.dx.saturate.f16(
20+
// NO_HALF: define noundef float @"?test_saturate_half
21+
// NO_HALF: call float @llvm.dx.saturate.f32(
22+
// SPIRV_HALF: define spir_func noundef half @_Z18test_saturate_halfDh(half
23+
// SPIRV_HALF: call half @llvm.spv.saturate.f16(half
24+
// SPIRV_NO_HALF: define spir_func noundef float @_Z18test_saturate_halfDh(float
25+
// SPIRV_NO_HALF: call float @llvm.spv.saturate.f32(float
26+
half test_saturate_half(half p0) { return saturate(p0); }
27+
// NATIVE_HALF: define noundef <2 x half> @
28+
// NATIVE_HALF: call <2 x half> @llvm.dx.saturate.v2f16
29+
// NO_HALF: define noundef <2 x float> @"?test_saturate_half2
30+
// NO_HALF: call <2 x float> @llvm.dx.saturate.v2f32(
31+
// SPIRV_HALF: define spir_func noundef <2 x half> @_Z19test_saturate_half2Dv2_Dh(
32+
// SPIRV_HALF: call <2 x half> @llvm.spv.saturate.v2f16(<2 x half>
33+
// SPIRV_NO_HALF: define spir_func noundef <2 x float> @_Z19test_saturate_half2Dv2_Dh(<2 x float>
34+
// SPIRV_NO_HALF: call <2 x float> @llvm.spv.saturate.v2f32(<2 x float>
35+
half2 test_saturate_half2(half2 p0) { return saturate(p0); }
36+
// NATIVE_HALF: define noundef <3 x half> @
37+
// NATIVE_HALF: call <3 x half> @llvm.dx.saturate.v3f16
38+
// NO_HALF: define noundef <3 x float> @"?test_saturate_half3
39+
// NO_HALF: call <3 x float> @llvm.dx.saturate.v3f32(
40+
// SPIRV_HALF: define spir_func noundef <3 x half> @_Z19test_saturate_half3Dv3_Dh(
41+
// SPIRV_HALF: call <3 x half> @llvm.spv.saturate.v3f16(<3 x half>
42+
// SPIRV_NO_HALF: define spir_func noundef <3 x float> @_Z19test_saturate_half3Dv3_Dh(<3 x float>
43+
// SPIRV_NO_HALF: call <3 x float> @llvm.spv.saturate.v3f32(<3 x float>
44+
half3 test_saturate_half3(half3 p0) { return saturate(p0); }
45+
// NATIVE_HALF: define noundef <4 x half> @
46+
// NATIVE_HALF: call <4 x half> @llvm.dx.saturate.v4f16
47+
// NO_HALF: define noundef <4 x float> @"?test_saturate_half4
48+
// NO_HALF: call <4 x float> @llvm.dx.saturate.v4f32(
49+
// SPIRV_HALF: define spir_func noundef <4 x half> @_Z19test_saturate_half4Dv4_Dh(
50+
// SPIRV_HALF: call <4 x half> @llvm.spv.saturate.v4f16(<4 x half>
51+
// SPIRV_NO_HALF: define spir_func noundef <4 x float> @_Z19test_saturate_half4Dv4_Dh(<4 x float>
52+
// SPIRV_NO_HALF: call <4 x float> @llvm.spv.saturate.v4f32(<4 x float>
53+
half4 test_saturate_half4(half4 p0) { return saturate(p0); }
54+
55+
// CHECK: define noundef float @"?test_saturate_float
56+
// CHECK: call float @llvm.dx.saturate.f32(
57+
// SPIRV: define spir_func noundef float @_Z19test_saturate_floatf(float
58+
// SPIRV: call float @llvm.spv.saturate.f32(float
59+
float test_saturate_float(float p0) { return saturate(p0); }
60+
// CHECK: define noundef <2 x float> @"?test_saturate_float2
61+
// CHECK: call <2 x float> @llvm.dx.saturate.v2f32
62+
// SPIRV: define spir_func noundef <2 x float> @_Z20test_saturate_float2Dv2_f(<2 x float>
63+
// SPIRV: call <2 x float> @llvm.spv.saturate.v2f32(<2 x float>
64+
float2 test_saturate_float2(float2 p0) { return saturate(p0); }
65+
// CHECK: define noundef <3 x float> @"?test_saturate_float3
66+
// CHECK: call <3 x float> @llvm.dx.saturate.v3f32
67+
// SPIRV: define spir_func noundef <3 x float> @_Z20test_saturate_float3Dv3_f(<3 x float>
68+
// SPIRV: call <3 x float> @llvm.spv.saturate.v3f32(<3 x float>
69+
float3 test_saturate_float3(float3 p0) { return saturate(p0); }
70+
// CHECK: define noundef <4 x float> @"?test_saturate_float4
71+
// CHECK: call <4 x float> @llvm.dx.saturate.v4f32
72+
// SPIRV: define spir_func noundef <4 x float> @_Z20test_saturate_float4Dv4_f(<4 x float>
73+
// SPIRV: call <4 x float> @llvm.spv.saturate.v4f32(<4 x float>
74+
float4 test_saturate_float4(float4 p0) { return saturate(p0); }
75+
76+
// CHECK: define noundef double @
77+
// CHECK: call double @llvm.dx.saturate.f64(
78+
// SPIRV: define spir_func noundef double @_Z20test_saturate_doubled(double
79+
// SPIRV: call double @llvm.spv.saturate.f64(double
80+
double test_saturate_double(double p0) { return saturate(p0); }
81+
// CHECK: define noundef <2 x double> @
82+
// CHECK: call <2 x double> @llvm.dx.saturate.v2f64
83+
// SPIRV: define spir_func noundef <2 x double> @_Z21test_saturate_double2Dv2_d(<2 x double>
84+
// SPIRV: call <2 x double> @llvm.spv.saturate.v2f64(<2 x double>
85+
double2 test_saturate_double2(double2 p0) { return saturate(p0); }
86+
// CHECK: define noundef <3 x double> @
87+
// CHECK: call <3 x double> @llvm.dx.saturate.v3f64
88+
// SPIRV: define spir_func noundef <3 x double> @_Z21test_saturate_double3Dv3_d(<3 x double>
89+
// SPIRV: call <3 x double> @llvm.spv.saturate.v3f64(<3 x double>
90+
double3 test_saturate_double3(double3 p0) { return saturate(p0); }
91+
// CHECK: define noundef <4 x double> @
92+
// CHECK: call <4 x double> @llvm.dx.saturate.v4f64
93+
// SPIRV: define spir_func noundef <4 x double> @_Z21test_saturate_double4Dv4_d(<4 x double>
94+
// SPIRV: call <4 x double> @llvm.spv.saturate.v4f64(<4 x double>
95+
double4 test_saturate_double4(double4 p0) { return saturate(p0); }
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// 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
2+
3+
float2 test_no_arg() {
4+
return saturate();
5+
// expected-error@-1 {{no matching function for call to 'saturate'}}
6+
}
7+
8+
float2 test_too_many_arg(float2 p0) {
9+
return saturate(p0, p0, p0, p0);
10+
// expected-error@-1 {{no matching function for call to 'saturate'}}
11+
}
12+
13+
float2 test_saturate_vector_size_mismatch(float3 p0) {
14+
return saturate(p0);
15+
// expected-error@-1 {{implicit conversion truncates vector: 'float3' (aka 'vector<float, 3>') to 'vector<float, 2>'}}
16+
}
17+
18+
float2 test_saturate_float2_int_splat(int p0) {
19+
return saturate(p0);
20+
// expected-error@-1 {{call to 'saturate' is ambiguous}}
21+
}
22+
23+
float2 test_saturate_int_vect_to_float_vec_promotion(int2 p0) {
24+
return saturate(p0);
25+
// expected-error@-1 {{call to 'saturate' is ambiguous}}
26+
}
27+
28+
float test_saturate_bool_type_promotion(bool p0) {
29+
return saturate(p0);
30+
// expected-error@-1 {{call to 'saturate' is ambiguous}}
31+
}

llvm/include/llvm/IR/IntrinsicsDirectX.td

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def int_dx_all : DefaultAttrsIntrinsic<[llvm_i1_ty], [llvm_any_ty]>;
3434
def int_dx_any : DefaultAttrsIntrinsic<[llvm_i1_ty], [llvm_any_ty]>;
3535
def int_dx_clamp : DefaultAttrsIntrinsic<[llvm_any_ty], [LLVMMatchType<0>, LLVMMatchType<0>, LLVMMatchType<0>]>;
3636
def int_dx_uclamp : DefaultAttrsIntrinsic<[llvm_anyint_ty], [LLVMMatchType<0>, LLVMMatchType<0>, LLVMMatchType<0>]>;
37+
def int_dx_saturate : DefaultAttrsIntrinsic<[llvm_anyfloat_ty], [LLVMMatchType<0>]>;
3738

3839
def int_dx_dot2 :
3940
Intrinsic<[LLVMVectorElementType<0>],

llvm/include/llvm/IR/IntrinsicsSPIRV.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,10 @@ let TargetPrefix = "spv" in {
6161
def int_spv_all : DefaultAttrsIntrinsic<[llvm_i1_ty], [llvm_any_ty]>;
6262
def int_spv_any : DefaultAttrsIntrinsic<[llvm_i1_ty], [llvm_any_ty]>;
6363
def int_spv_frac : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty]>;
64-
def int_spv_lerp : Intrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty, LLVMMatchType<0>,LLVMMatchType<0>],
64+
def int_spv_lerp : Intrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty, LLVMMatchType<0>,LLVMMatchType<0>],
6565
[IntrNoMem, IntrWillReturn] >;
6666
def int_spv_length : DefaultAttrsIntrinsic<[LLVMVectorElementType<0>], [llvm_anyfloat_ty]>;
6767
def int_spv_normalize : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty]>;
6868
def int_spv_rsqrt : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty]>;
69+
def int_spv_saturate : DefaultAttrsIntrinsic<[llvm_anyfloat_ty], [LLVMMatchType<0>]>;
6970
}

llvm/lib/Target/DirectX/DXIL.td

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,16 @@ def Abs : DXILOp<6, unary> {
330330
let attributes = [Attributes<DXIL1_0, [ReadNone]>];
331331
}
332332

333+
def Saturate : DXILOp<7, unary> {
334+
let Doc = "Clamps a single or double precision floating point value to [0.0f...1.0f].";
335+
let LLVMIntrinsic = int_dx_saturate;
336+
let arguments = [overloadTy];
337+
let result = overloadTy;
338+
let overloads = [Overloads<DXIL1_0, [halfTy, floatTy, doubleTy]>];
339+
let stages = [Stages<DXIL1_0, [all_stages]>];
340+
let attributes = [Attributes<DXIL1_0, [ReadNone]>];
341+
}
342+
333343
def IsInf : DXILOp<9, isSpecialFloat> {
334344
let Doc = "Determines if the specified value is infinite.";
335345
let LLVMIntrinsic = int_dx_isinf;

0 commit comments

Comments
 (0)