diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 290feb58754ad..83c90b3d6e681 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -4750,6 +4750,12 @@ def HLSLAny : LangBuiltin<"HLSL_LANG"> { let Prototype = "bool(...)"; } +def HLSLAsDouble : LangBuiltin<"HLSL_LANG"> { + let Spellings = ["__builtin_hlsl_asdouble"]; + let Attributes = [NoThrow, Const]; + let Prototype = "void(...)"; +} + def HLSLWaveActiveAnyTrue : LangBuiltin<"HLSL_LANG"> { let Spellings = ["__builtin_hlsl_wave_active_any_true"]; let Attributes = [NoThrow, Const]; diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index ff7132fd8bc1e..0bb3ef9dfa2be 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -208,6 +208,41 @@ static Value *handleHlslSplitdouble(const CallExpr *E, CodeGenFunction *CGF) { return LastInst; } +Value *handleAsDoubleBuiltin(CodeGenFunction &CGF, const CallExpr *E) { + assert((E->getArg(0)->getType()->hasUnsignedIntegerRepresentation() && + E->getArg(1)->getType()->hasUnsignedIntegerRepresentation()) && + "asdouble operands types mismatch"); + Value *OpLowBits = CGF.EmitScalarExpr(E->getArg(0)); + Value *OpHighBits = CGF.EmitScalarExpr(E->getArg(1)); + + llvm::Type *ResultType = CGF.DoubleTy; + int N = 1; + if (auto *VTy = E->getArg(0)->getType()->getAs()) { + N = VTy->getNumElements(); + ResultType = llvm::FixedVectorType::get(CGF.DoubleTy, N); + } + + if (CGF.CGM.getTarget().getTriple().isDXIL()) + return CGF.Builder.CreateIntrinsic( + /*ReturnType=*/ResultType, Intrinsic::dx_asdouble, + ArrayRef{OpLowBits, OpHighBits}, nullptr, "hlsl.asdouble"); + + if (!E->getArg(0)->getType()->isVectorType()) { + OpLowBits = CGF.Builder.CreateVectorSplat(1, OpLowBits); + OpHighBits = CGF.Builder.CreateVectorSplat(1, OpHighBits); + } + + llvm::SmallVector Mask; + for (int i = 0; i < N; i++) { + Mask.push_back(i); + Mask.push_back(i + N); + } + + Value *BitVec = CGF.Builder.CreateShuffleVector(OpLowBits, OpHighBits, Mask); + + return CGF.Builder.CreateBitCast(BitVec, ResultType); +} + /// getBuiltinLibFunction - Given a builtin id for a function like /// "__builtin_fabsf", return a Function* for "fabsf". llvm::Constant *CodeGenModule::getBuiltinLibFunction(const FunctionDecl *FD, @@ -19022,6 +19057,8 @@ Value *CodeGenFunction::EmitHLSLBuiltinExpr(unsigned BuiltinID, CGM.getHLSLRuntime().getAnyIntrinsic(), ArrayRef{Op0}, nullptr, "hlsl.any"); } + case Builtin::BI__builtin_hlsl_asdouble: + return handleAsDoubleBuiltin(*this, E); case Builtin::BI__builtin_hlsl_elementwise_clamp: { Value *OpX = EmitScalarExpr(E->getArg(0)); Value *OpMin = EmitScalarExpr(E->getArg(1)); diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsics.h b/clang/lib/Headers/hlsl/hlsl_intrinsics.h index a484d04155d6b..a3e0b5c65a6f5 100644 --- a/clang/lib/Headers/hlsl/hlsl_intrinsics.h +++ b/clang/lib/Headers/hlsl/hlsl_intrinsics.h @@ -361,6 +361,24 @@ bool any(double3); _HLSL_BUILTIN_ALIAS(__builtin_hlsl_any) bool any(double4); +//===----------------------------------------------------------------------===// +// asdouble builtins +//===----------------------------------------------------------------------===// + +/// \fn double asdouble(uint LowBits, uint HighBits) +/// \brief Reinterprets a cast value (two 32-bit values) into a double. +/// \param LowBits The low 32-bit pattern of the input value. +/// \param HighBits The high 32-bit pattern of the input value. + +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_asdouble) +double asdouble(uint, uint); +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_asdouble) +double2 asdouble(uint2, uint2); +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_asdouble) +double3 asdouble(uint3, uint3); +_HLSL_BUILTIN_ALIAS(__builtin_hlsl_asdouble) +double4 asdouble(uint4, uint4); + //===----------------------------------------------------------------------===// // asfloat builtins //===----------------------------------------------------------------------===// diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index f4fc0f2ddc27a..dc3ed8e914331 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -1888,6 +1888,15 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) { return true; break; } + case Builtin::BI__builtin_hlsl_asdouble: { + if (SemaRef.checkArgCount(TheCall, 2)) + return true; + if (CheckUnsignedIntRepresentation(&SemaRef, TheCall)) + return true; + + SetElementTypeAsReturnType(&SemaRef, TheCall, getASTContext().DoubleTy); + break; + } case Builtin::BI__builtin_hlsl_elementwise_clamp: { if (SemaRef.checkArgCount(TheCall, 3)) return true; diff --git a/clang/test/CodeGenHLSL/builtins/asdouble.hlsl b/clang/test/CodeGenHLSL/builtins/asdouble.hlsl new file mode 100644 index 0000000000000..f1c31107cdcad --- /dev/null +++ b/clang/test/CodeGenHLSL/builtins/asdouble.hlsl @@ -0,0 +1,37 @@ +// RUN: %clang_cc1 -finclude-default-header -triple \ +// RUN: dxil-pc-shadermodel6.3-compute %s -emit-llvm -disable-llvm-passes -o - | \ +// RUN: FileCheck %s --check-prefixes=CHECK,CHECK-DXIL +// RUN: %clang_cc1 -finclude-default-header -triple \ +// RUN: spirv-pc-vulkan-compute %s -emit-llvm -disable-llvm-passes -o - | \ +// RUN: FileCheck %s --check-prefixes=CHECK,CHECK-SPV + +// Test lowering of asdouble expansion to shuffle/bitcast and splat when required + +// CHECK-LABEL: test_uint +double test_uint(uint low, uint high) { + // CHECK-SPV: %[[LOW_INSERT:.*]] = insertelement <1 x i32> + // CHECK-SPV: %[[LOW_SHUFFLE:.*]] = shufflevector <1 x i32> %[[LOW_INSERT]], {{.*}} zeroinitializer + // CHECK-SPV: %[[HIGH_INSERT:.*]] = insertelement <1 x i32> + // CHECK-SPV: %[[HIGH_SHUFFLE:.*]] = shufflevector <1 x i32> %[[HIGH_INSERT]], {{.*}} zeroinitializer + + // CHECK-SPV: %[[SHUFFLE0:.*]] = shufflevector <1 x i32> %[[LOW_SHUFFLE]], <1 x i32> %[[HIGH_SHUFFLE]], + // CHECK-SPV-SAME: {{.*}} + // CHECK-SPV: bitcast <2 x i32> %[[SHUFFLE0]] to double + + // CHECK-DXIL: call double @llvm.dx.asdouble.i32 + return asdouble(low, high); +} + +// CHECK-DXIL: declare double @llvm.dx.asdouble.i32 + +// CHECK-LABEL: test_vuint +double3 test_vuint(uint3 low, uint3 high) { + // CHECK-SPV: %[[SHUFFLE1:.*]] = shufflevector + // CHECK-SPV-SAME: {{.*}} + // CHECK-SPV: bitcast <6 x i32> %[[SHUFFLE1]] to <3 x double> + + // CHECK-DXIL: call <3 x double> @llvm.dx.asdouble.v3i32 + return asdouble(low, high); +} + +// CHECK-DXIL: declare <3 x double> @llvm.dx.asdouble.v3i32 diff --git a/clang/test/SemaHLSL/BuiltIns/asdouble-errors.hlsl b/clang/test/SemaHLSL/BuiltIns/asdouble-errors.hlsl new file mode 100644 index 0000000000000..c6b57c76a1e2b --- /dev/null +++ b/clang/test/SemaHLSL/BuiltIns/asdouble-errors.hlsl @@ -0,0 +1,16 @@ +// RUN: %clang_cc1 -finclude-default-header -triple dxil-pc-shadermodel6.6-library %s -emit-llvm-only -disable-llvm-passes -verify + +double test_too_few_arg() { + return __builtin_hlsl_asdouble(); + // expected-error@-1 {{too few arguments to function call, expected 2, have 0}} +} + +double test_too_few_arg_1(uint p0) { + return __builtin_hlsl_asdouble(p0); + // expected-error@-1 {{too few arguments to function call, expected 2, have 1}} +} + +double test_too_many_arg(uint p0) { + return __builtin_hlsl_asdouble(p0, p0, p0); + // expected-error@-1 {{too many arguments to function call, expected 2, have 3}} +} diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td index 62688eae36e9a..dad60a2535cf4 100644 --- a/llvm/include/llvm/IR/IntrinsicsDirectX.td +++ b/llvm/include/llvm/IR/IntrinsicsDirectX.td @@ -46,6 +46,7 @@ def int_dx_cast_handle : Intrinsic<[llvm_any_ty], [llvm_any_ty]>; def int_dx_all : DefaultAttrsIntrinsic<[llvm_i1_ty], [llvm_any_ty], [IntrNoMem]>; def int_dx_any : DefaultAttrsIntrinsic<[llvm_i1_ty], [llvm_any_ty], [IntrNoMem]>; +def int_dx_asdouble : DefaultAttrsIntrinsic<[LLVMScalarOrSameVectorWidth<0, llvm_double_ty>], [llvm_anyint_ty, LLVMMatchType<0>], [IntrNoMem]>; def int_dx_uclamp : DefaultAttrsIntrinsic<[llvm_anyint_ty], [LLVMMatchType<0>, LLVMMatchType<0>, LLVMMatchType<0>], [IntrNoMem]>; def int_dx_sclamp : DefaultAttrsIntrinsic<[llvm_anyint_ty], [LLVMMatchType<0>, LLVMMatchType<0>, LLVMMatchType<0>], [IntrNoMem]>; def int_dx_nclamp : DefaultAttrsIntrinsic<[llvm_anyfloat_ty], [LLVMMatchType<0>, LLVMMatchType<0>, LLVMMatchType<0>], [IntrNoMem]>; diff --git a/llvm/lib/Target/DirectX/DXIL.td b/llvm/lib/Target/DirectX/DXIL.td index 36228a5e0dce1..7cc08b2fe7cc4 100644 --- a/llvm/lib/Target/DirectX/DXIL.td +++ b/llvm/lib/Target/DirectX/DXIL.td @@ -818,6 +818,15 @@ def FlattenedThreadIdInGroup : DXILOp<96, flattenedThreadIdInGroup> { let attributes = [Attributes]; } +def MakeDouble : DXILOp<101, makeDouble> { + let Doc = "creates a double value"; + let LLVMIntrinsic = int_dx_asdouble; + let arguments = [Int32Ty, Int32Ty]; + let result = DoubleTy; + let stages = [Stages]; + let attributes = [Attributes]; +} + def SplitDouble : DXILOp<102, splitDouble> { let Doc = "Splits a double into 2 uints"; let arguments = [OverloadTy]; diff --git a/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp b/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp index 182cdaa4e9a7d..2ca4e23594d56 100644 --- a/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp +++ b/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp @@ -28,6 +28,8 @@ bool DirectXTTIImpl::isTargetIntrinsicWithScalarOpAtArg(Intrinsic::ID ID, bool DirectXTTIImpl::isVectorIntrinsicWithOverloadTypeAtArg(Intrinsic::ID ID, int ScalarOpdIdx) { switch (ID) { + case Intrinsic::dx_asdouble: + return ScalarOpdIdx == 0; default: return ScalarOpdIdx == -1; } @@ -39,6 +41,7 @@ bool DirectXTTIImpl::isTargetIntrinsicTriviallyScalarizable( case Intrinsic::dx_frac: case Intrinsic::dx_rsqrt: case Intrinsic::dx_wave_readlane: + case Intrinsic::dx_asdouble: case Intrinsic::dx_splitdouble: case Intrinsic::dx_firstbituhigh: case Intrinsic::dx_firstbitshigh: diff --git a/llvm/test/CodeGen/DirectX/asdouble.ll b/llvm/test/CodeGen/DirectX/asdouble.ll new file mode 100644 index 0000000000000..6a581d69eb7e9 --- /dev/null +++ b/llvm/test/CodeGen/DirectX/asdouble.ll @@ -0,0 +1,22 @@ +; RUN: opt -S -scalarizer -dxil-op-lower -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s + +; Test that for scalar and vector inputs, asdouble maps down to the makeDouble +; DirectX op + +define noundef double @asdouble_scalar(i32 noundef %low, i32 noundef %high) { +; CHECK: call double @dx.op.makeDouble(i32 101, i32 %low, i32 %high) + %ret = call double @llvm.dx.asdouble.i32(i32 %low, i32 %high) + ret double %ret +} + +declare double @llvm.dx.asdouble.i32(i32, i32) + +define noundef <3 x double> @asdouble_vec(<3 x i32> noundef %low, <3 x i32> noundef %high) { +; CHECK: call double @dx.op.makeDouble(i32 101, i32 %low.i0, i32 %high.i0) +; CHECK: call double @dx.op.makeDouble(i32 101, i32 %low.i1, i32 %high.i1) +; CHECK: call double @dx.op.makeDouble(i32 101, i32 %low.i2, i32 %high.i2) + %ret = call <3 x double> @llvm.dx.asdouble.v3i32(<3 x i32> %low, <3 x i32> %high) + ret <3 x double> %ret +} + +declare <3 x double> @llvm.dx.asdouble.v3i32(<3 x i32>, <3 x i32>)