Skip to content

[HLSL][DXIL] Implement asdouble intrinsic #114847

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/Builtins.td
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
37 changes: 37 additions & 0 deletions clang/lib/CodeGen/CGBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<clang::VectorType>()) {
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<Value *>{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<int> 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,
Expand Down Expand Up @@ -19022,6 +19057,8 @@ Value *CodeGenFunction::EmitHLSLBuiltinExpr(unsigned BuiltinID,
CGM.getHLSLRuntime().getAnyIntrinsic(), ArrayRef<Value *>{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));
Expand Down
18 changes: 18 additions & 0 deletions clang/lib/Headers/hlsl/hlsl_intrinsics.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
//===----------------------------------------------------------------------===//
Expand Down
9 changes: 9 additions & 0 deletions clang/lib/Sema/SemaHLSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
37 changes: 37 additions & 0 deletions clang/test/CodeGenHLSL/builtins/asdouble.hlsl
Original file line number Diff line number Diff line change
@@ -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: {{.*}} <i32 0, i32 1>
// 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: {{.*}} <i32 0, i32 3, i32 1, i32 4, i32 2, i32 5>
// 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
16 changes: 16 additions & 0 deletions clang/test/SemaHLSL/BuiltIns/asdouble-errors.hlsl
Original file line number Diff line number Diff line change
@@ -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}}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like adding some tests to check for type mismatch would be useful

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there a type that you have in mind? Most types will be implicitly converted here, I added the custom struct type to illustrate that.

Copy link
Contributor

Choose a reason for hiding this comment

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

Any type really, what we really care about here is making sure the error is meaningful to the user. I usually write such tests with half, float or bool if the former are implicitly cast.

1 change: 1 addition & 0 deletions llvm/include/llvm/IR/IntrinsicsDirectX.td
Original file line number Diff line number Diff line change
Expand Up @@ -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]>;
Expand Down
9 changes: 9 additions & 0 deletions llvm/lib/Target/DirectX/DXIL.td
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,15 @@ def FlattenedThreadIdInGroup : DXILOp<96, flattenedThreadIdInGroup> {
let attributes = [Attributes<DXIL1_0, [ReadNone]>];
}

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<DXIL1_0, [all_stages]>];
let attributes = [Attributes<DXIL1_0, [ReadNone]>];
}

def SplitDouble : DXILOp<102, splitDouble> {
let Doc = "Splits a double into 2 uints";
let arguments = [OverloadTy];
Expand Down
3 changes: 3 additions & 0 deletions llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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:
Expand Down
22 changes: 22 additions & 0 deletions llvm/test/CodeGen/DirectX/asdouble.ll
Original file line number Diff line number Diff line change
@@ -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>)
Loading