From dce50397b52e33e927ffb1d1c40765109abf40f0 Mon Sep 17 00:00:00 2001 From: Tim Gymnich Date: Tue, 10 Sep 2024 05:35:45 +0200 Subject: [PATCH] [clang][HLSL] Add sign intrinsic part 3 (#101989) partially fixes #70078 ### Changes - Implemented `sign` clang builtin - Linked `sign` clang builtin with `hlsl_intrinsics.h` - Added sema checks for `sign` to `CheckHLSLBuiltinFunctionCall` in `SemaChecking.cpp` - Add codegen for `sign` to `EmitHLSLBuiltinExpr` in `CGBuiltin.cpp` - Add codegen tests to `clang/test/CodeGenHLSL/builtins/sign.hlsl` - Add sema tests to `clang/test/SemaHLSL/BuiltIns/sign-errors.hlsl` ### Related PRs - https://github.com/llvm/llvm-project/pull/101987 - https://github.com/llvm/llvm-project/pull/101988 ### Discussion - Should there be a `usign` intrinsic that handles the unsigned cases? --- clang/include/clang/Basic/Builtins.td | 6 + clang/lib/CodeGen/CGBuiltin.cpp | 17 ++ clang/lib/CodeGen/CGHLSLRuntime.h | 1 + clang/lib/Headers/hlsl/hlsl_intrinsics.h | 71 ++++++++ clang/lib/Sema/SemaHLSL.cpp | 17 ++ clang/test/CodeGenHLSL/builtins/sign.hlsl | 157 ++++++++++++++++++ clang/test/SemaHLSL/BuiltIns/sign-errors.hlsl | 16 ++ 7 files changed, 285 insertions(+) create mode 100644 clang/test/CodeGenHLSL/builtins/sign.hlsl create mode 100644 clang/test/SemaHLSL/BuiltIns/sign-errors.hlsl diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index d9833b6559eab39..3dc04f68b3172ac 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -4769,6 +4769,12 @@ def HLSLSelect : LangBuiltin<"HLSL_LANG"> { let Prototype = "void(...)"; } +def HLSLSign : LangBuiltin<"HLSL_LANG"> { + let Spellings = ["__builtin_hlsl_elementwise_sign"]; + 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 0078ceb7e892afb..9950c06a0b9a6bc 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -18870,6 +18870,23 @@ case Builtin::BI__builtin_hlsl_elementwise_isinf: { Intrinsic::ID ID = CGM.getHLSLRuntime().getWaveIsFirstLaneIntrinsic(); return EmitRuntimeCall(Intrinsic::getDeclaration(&CGM.getModule(), ID)); } + case Builtin::BI__builtin_hlsl_elementwise_sign: { + Value *Op0 = EmitScalarExpr(E->getArg(0)); + llvm::Type *Xty = Op0->getType(); + llvm::Type *retType = llvm::Type::getInt32Ty(this->getLLVMContext()); + if (Xty->isVectorTy()) { + auto *XVecTy = E->getArg(0)->getType()->getAs(); + retType = llvm::VectorType::get( + retType, ElementCount::getFixed(XVecTy->getNumElements())); + } + assert((E->getArg(0)->getType()->hasFloatingRepresentation() || + E->getArg(0)->getType()->hasSignedIntegerRepresentation()) && + "sign operand must have a float or int representation"); + + return Builder.CreateIntrinsic( + retType, CGM.getHLSLRuntime().getSignIntrinsic(), + ArrayRef{Op0}, nullptr, "hlsl.sign"); + } } return nullptr; } diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h index 40dcd74b7dd2417..6e226808fcbaf5f 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.h +++ b/clang/lib/CodeGen/CGHLSLRuntime.h @@ -80,6 +80,7 @@ class CGHLSLRuntime { GENERATE_HLSL_INTRINSIC_FUNCTION(Normalize, normalize) GENERATE_HLSL_INTRINSIC_FUNCTION(Rsqrt, rsqrt) GENERATE_HLSL_INTRINSIC_FUNCTION(Saturate, saturate) + GENERATE_HLSL_INTRINSIC_FUNCTION(Sign, sign) GENERATE_HLSL_INTRINSIC_FUNCTION(ThreadId, thread_id) GENERATE_HLSL_INTRINSIC_FUNCTION(FDot, fdot) GENERATE_HLSL_INTRINSIC_FUNCTION(SDot, sdot) diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsics.h b/clang/lib/Headers/hlsl/hlsl_intrinsics.h index 2ac18056b0fc3df..7a1edd93984de78 100644 --- a/clang/lib/Headers/hlsl/hlsl_intrinsics.h +++ b/clang/lib/Headers/hlsl/hlsl_intrinsics.h @@ -1826,5 +1826,76 @@ _HLSL_AVAILABILITY(shadermodel, 6.0) _HLSL_BUILTIN_ALIAS(__builtin_hlsl_wave_is_first_lane) __attribute__((convergent)) bool WaveIsFirstLane(); +//===----------------------------------------------------------------------===// +// sign builtins +//===----------------------------------------------------------------------===// + +/// \fn T sign(T Val) +/// \brief Returns -1 if \a Val is less than zero; 0 if \a Val equals zero; and +/// 1 if \a Val is greater than zero. \param Val The input value. + +#ifdef __HLSL_ENABLE_16_BIT +_HLSL_AVAILABILITY(shadermodel, 6.2) +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int sign(int16_t); +_HLSL_AVAILABILITY(shadermodel, 6.2) +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int2 sign(int16_t2); +_HLSL_AVAILABILITY(shadermodel, 6.2) +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int3 sign(int16_t3); +_HLSL_AVAILABILITY(shadermodel, 6.2) +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int4 sign(int16_t4); +#endif + +_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2) +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int sign(half); +_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2) +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int2 sign(half2); +_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2) +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int3 sign(half3); +_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2) +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int4 sign(half4); + +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int sign(int); +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int2 sign(int2); +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int3 sign(int3); +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int4 sign(int4); + +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int sign(float); +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int2 sign(float2); +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int3 sign(float3); +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int4 sign(float4); + +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int sign(int64_t); +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int2 sign(int64_t2); +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int3 sign(int64_t3); +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int4 sign(int64_t4); + +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int sign(double); +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int2 sign(double2); +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int3 sign(double3); +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_sign) +int4 sign(double4); } // namespace hlsl #endif //_HLSL_HLSL_INTRINSICS_H_ diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index 3b91303ac8cb8a4..f2158226e6ca7ac 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -16,6 +16,7 @@ #include "clang/AST/Expr.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/Type.h" +#include "clang/Basic/Builtins.h" #include "clang/Basic/DiagnosticSema.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/SourceLocation.h" @@ -1513,6 +1514,14 @@ bool CheckNoDoubleVectors(Sema *S, CallExpr *TheCall) { return CheckArgsTypesAreCorrect(S, TheCall, S->Context.FloatTy, checkDoubleVector); } +bool CheckFloatingOrSignedIntRepresentation(Sema *S, CallExpr *TheCall) { + auto checkAllSignedTypes = [](clang::QualType PassedType) -> bool { + return !PassedType->hasSignedIntegerRepresentation() && + !PassedType->hasFloatingRepresentation(); + }; + return CheckArgsTypesAreCorrect(S, TheCall, S->Context.IntTy, + checkAllSignedTypes); +} bool CheckUnsignedIntRepresentation(Sema *S, CallExpr *TheCall) { auto checkAllUnsignedTypes = [](clang::QualType PassedType) -> bool { @@ -1726,6 +1735,14 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) { TheCall->setType(ArgTyA); break; } + case Builtin::BI__builtin_hlsl_elementwise_sign: { + if (CheckFloatingOrSignedIntRepresentation(&SemaRef, TheCall)) + return true; + if (SemaRef.PrepareBuiltinElementwiseMathOneArgCall(TheCall)) + return true; + SetElementTypeAsReturnType(&SemaRef, TheCall, getASTContext().IntTy); + break; + } // Note these are llvm builtins that we want to catch invalid intrinsic // generation. Normal handling of these builitns will occur elsewhere. case Builtin::BI__builtin_elementwise_bitreverse: { diff --git a/clang/test/CodeGenHLSL/builtins/sign.hlsl b/clang/test/CodeGenHLSL/builtins/sign.hlsl new file mode 100644 index 000000000000000..4bb239fb009e45d --- /dev/null +++ b/clang/test/CodeGenHLSL/builtins/sign.hlsl @@ -0,0 +1,157 @@ +// RUN: %clang_cc1 -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: -DTARGET=dx -DFNATTRS=noundef +// RUN: %clang_cc1 -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 \ +// RUN: -DTARGET=dx -DFNATTRS=noundef +// RUN: %clang_cc1 -finclude-default-header -x hlsl -triple \ +// RUN: spirv-unknown-vulkan-compute %s -fnative-half-type \ +// RUN: -emit-llvm -disable-llvm-passes -o - | FileCheck %s \ +// RUN: --check-prefixes=CHECK,NATIVE_HALF \ +// RUN: -DTARGET=spv -DFNATTRS="spir_func noundef" +// RUN: %clang_cc1 -finclude-default-header -x hlsl -triple \ +// RUN: spirv-unknown-vulkan-compute %s -emit-llvm -disable-llvm-passes \ +// RUN: -o - | FileCheck %s --check-prefixes=CHECK,NO_HALF \ +// RUN: -DTARGET=spv -DFNATTRS="spir_func noundef" + +// NATIVE_HALF: define [[FNATTRS]] i32 @ +// NATIVE_HALF: %hlsl.sign = call i32 @llvm.[[TARGET]].sign.f16( +// NATIVE_HALF: ret i32 %hlsl.sign +// NO_HALF: define [[FNATTRS]] i32 @ +// NO_HALF: %hlsl.sign = call i32 @llvm.[[TARGET]].sign.f32( +// NO_HALF: ret i32 %hlsl.sign +int test_sign_half(half p0) { return sign(p0); } + +// NATIVE_HALF: define [[FNATTRS]] <2 x i32> @ +// NATIVE_HALF: %hlsl.sign = call <2 x i32> @llvm.[[TARGET]].sign.v2f16( +// NATIVE_HALF: ret <2 x i32> %hlsl.sign +// NO_HALF: define [[FNATTRS]] <2 x i32> @ +// NO_HALF: %hlsl.sign = call <2 x i32> @llvm.[[TARGET]].sign.v2f32( +// NO_HALF: ret <2 x i32> %hlsl.sign +int2 test_sign_half2(half2 p0) { return sign(p0); } + +// NATIVE_HALF: define [[FNATTRS]] <3 x i32> @ +// NATIVE_HALF: %hlsl.sign = call <3 x i32> @llvm.[[TARGET]].sign.v3f16( +// NATIVE_HALF: ret <3 x i32> %hlsl.sign +// NO_HALF: define [[FNATTRS]] <3 x i32> @ +// NO_HALF: %hlsl.sign = call <3 x i32> @llvm.[[TARGET]].sign.v3f32( +// NO_HALF: ret <3 x i32> %hlsl.sign +int3 test_sign_half3(half3 p0) { return sign(p0); } + +// NATIVE_HALF: define [[FNATTRS]] <4 x i32> @ +// NATIVE_HALF: %hlsl.sign = call <4 x i32> @llvm.[[TARGET]].sign.v4f16( +// NATIVE_HALF: ret <4 x i32> %hlsl.sign +// NO_HALF: define [[FNATTRS]] <4 x i32> @ +// NO_HALF: %hlsl.sign = call <4 x i32> @llvm.[[TARGET]].sign.v4f32( +// NO_HALF: ret <4 x i32> %hlsl.sign +int4 test_sign_half4(half4 p0) { return sign(p0); } + + +// CHECK: define [[FNATTRS]] i32 @ +// CHECK: %hlsl.sign = call i32 @llvm.[[TARGET]].sign.f32( +// CHECK: ret i32 %hlsl.sign +int test_sign_float(float p0) { return sign(p0); } + +// CHECK: define [[FNATTRS]] <2 x i32> @ +// CHECK: %hlsl.sign = call <2 x i32> @llvm.[[TARGET]].sign.v2f32( +// CHECK: ret <2 x i32> %hlsl.sign +int2 test_sign_float2(float2 p0) { return sign(p0); } + +// CHECK: define [[FNATTRS]] <3 x i32> @ +// CHECK: %hlsl.sign = call <3 x i32> @llvm.[[TARGET]].sign.v3f32( +// CHECK: ret <3 x i32> %hlsl.sign +int3 test_sign_float3(float3 p0) { return sign(p0); } + +// CHECK: define [[FNATTRS]] <4 x i32> @ +// CHECK: %hlsl.sign = call <4 x i32> @llvm.[[TARGET]].sign.v4f32( +// CHECK: ret <4 x i32> %hlsl.sign +int4 test_sign_float4(float4 p0) { return sign(p0); } + + +// CHECK: define [[FNATTRS]] i32 @ +// CHECK: %hlsl.sign = call i32 @llvm.[[TARGET]].sign.f64( +// CHECK: ret i32 %hlsl.sign +int test_sign_double(double p0) { return sign(p0); } + +// CHECK: define [[FNATTRS]] <2 x i32> @ +// CHECK: %hlsl.sign = call <2 x i32> @llvm.[[TARGET]].sign.v2f64( +// CHECK: ret <2 x i32> %hlsl.sign +int2 test_sign_double2(double2 p0) { return sign(p0); } + +// CHECK: define [[FNATTRS]] <3 x i32> @ +// CHECK: %hlsl.sign = call <3 x i32> @llvm.[[TARGET]].sign.v3f64( +// CHECK: ret <3 x i32> %hlsl.sign +int3 test_sign_double3(double3 p0) { return sign(p0); } + +// CHECK: define [[FNATTRS]] <4 x i32> @ +// CHECK: %hlsl.sign = call <4 x i32> @llvm.[[TARGET]].sign.v4f64( +// CHECK: ret <4 x i32> %hlsl.sign +int4 test_sign_double4(double4 p0) { return sign(p0); } + + +#ifdef __HLSL_ENABLE_16_BIT +// NATIVE_HALF: define [[FNATTRS]] i32 @ +// NATIVE_HALF: %hlsl.sign = call i32 @llvm.[[TARGET]].sign.i16( +// NATIVE_HALF: ret i32 %hlsl.sign +int test_sign_int16_t(int16_t p0) { return sign(p0); } + +// NATIVE_HALF: define [[FNATTRS]] <2 x i32> @ +// NATIVE_HALF: %hlsl.sign = call <2 x i32> @llvm.[[TARGET]].sign.v2i16( +// NATIVE_HALF: ret <2 x i32> %hlsl.sign +int2 test_sign_int16_t2(int16_t2 p0) { return sign(p0); } + +// NATIVE_HALF: define [[FNATTRS]] <3 x i32> @ +// NATIVE_HALF: %hlsl.sign = call <3 x i32> @llvm.[[TARGET]].sign.v3i16( +// NATIVE_HALF: ret <3 x i32> %hlsl.sign +int3 test_sign_int16_t3(int16_t3 p0) { return sign(p0); } + +// NATIVE_HALF: define [[FNATTRS]] <4 x i32> @ +// NATIVE_HALF: %hlsl.sign = call <4 x i32> @llvm.[[TARGET]].sign.v4i16( +// NATIVE_HALF: ret <4 x i32> %hlsl.sign +int4 test_sign_int16_t4(int16_t4 p0) { return sign(p0); } +#endif // __HLSL_ENABLE_16_BIT + + +// CHECK: define [[FNATTRS]] i32 @ +// CHECK: %hlsl.sign = call i32 @llvm.[[TARGET]].sign.i32( +// CHECK: ret i32 %hlsl.sign +int test_sign_int(int p0) { return sign(p0); } + +// CHECK: define [[FNATTRS]] <2 x i32> @ +// CHECK: %hlsl.sign = call <2 x i32> @llvm.[[TARGET]].sign.v2i32( +// CHECK: ret <2 x i32> %hlsl.sign +int2 test_sign_int2(int2 p0) { return sign(p0); } + +// CHECK: define [[FNATTRS]] <3 x i32> @ +// CHECK: %hlsl.sign = call <3 x i32> @llvm.[[TARGET]].sign.v3i32( +// CHECK: ret <3 x i32> %hlsl.sign +int3 test_sign_int3(int3 p0) { return sign(p0); } + +// CHECK: define [[FNATTRS]] <4 x i32> @ +// CHECK: %hlsl.sign = call <4 x i32> @llvm.[[TARGET]].sign.v4i32( +// CHECK: ret <4 x i32> %hlsl.sign +int4 test_sign_int4(int4 p0) { return sign(p0); } + + +// CHECK: define [[FNATTRS]] i32 @ +// CHECK: %hlsl.sign = call i32 @llvm.[[TARGET]].sign.i64( +// CHECK: ret i32 %hlsl.sign +int test_sign_int64_t(int64_t p0) { return sign(p0); } + +// CHECK: define [[FNATTRS]] <2 x i32> @ +// CHECK: %hlsl.sign = call <2 x i32> @llvm.[[TARGET]].sign.v2i64( +// CHECK: ret <2 x i32> %hlsl.sign +int2 test_sign_int64_t2(int64_t2 p0) { return sign(p0); } + +// CHECK: define [[FNATTRS]] <3 x i32> @ +// CHECK: %hlsl.sign = call <3 x i32> @llvm.[[TARGET]].sign.v3i64( +// CHECK: ret <3 x i32> %hlsl.sign +int3 test_sign_int64_t3(int64_t3 p0) { return sign(p0); } + +// CHECK: define [[FNATTRS]] <4 x i32> @ +// CHECK: %hlsl.sign = call <4 x i32> @llvm.[[TARGET]].sign.v4i64( +// CHECK: ret <4 x i32> %hlsl.sign +int4 test_sign_int64_t4(int64_t4 p0) { return sign(p0); } diff --git a/clang/test/SemaHLSL/BuiltIns/sign-errors.hlsl b/clang/test/SemaHLSL/BuiltIns/sign-errors.hlsl new file mode 100644 index 000000000000000..b67725fc77e52ec --- /dev/null +++ b/clang/test/SemaHLSL/BuiltIns/sign-errors.hlsl @@ -0,0 +1,16 @@ +// 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 + +bool test_too_few_arg() { + return __builtin_hlsl_elementwise_sign(); + // expected-error@-1 {{too few arguments to function call, expected 1, have 0}} +} + +bool2 test_too_many_arg(float2 p0) { + return __builtin_hlsl_elementwise_sign(p0, p0); + // expected-error@-1 {{too many arguments to function call, expected 1, have 2}} +} + +bool builtin_bool_to_float_type_promotion(bool p1) { + return __builtin_hlsl_elementwise_sign(p1); + // expected-error@-1 {passing 'bool' to parameter of incompatible type 'float'}} +}