Skip to content

[HLSL] Implement the ldexp intrinsic #138182

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 4 commits into from
May 3, 2025
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
4 changes: 4 additions & 0 deletions clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ template <typename T> constexpr T faceforward_impl(T N, T I, T Ng) {
#endif
}

template <typename T> constexpr T ldexp_impl(T X, T Exp) {
return exp2(Exp) * X;
}

} // namespace __detail
} // namespace hlsl

Expand Down
42 changes: 42 additions & 0 deletions clang/lib/Headers/hlsl/hlsl_intrinsics.h
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,48 @@ fmod(__detail::HLSL_FIXED_VECTOR<float, N> X,
return __detail::fmod_vec_impl(X, Y);
}

//===----------------------------------------------------------------------===//
// ldexp builtins
//===----------------------------------------------------------------------===//

/// \fn T ldexp(T X, T Exp)
/// \brief Returns the result of multiplying the specified value by two raised
/// to the power of the specified exponent.
/// \param X [in] The specified value.
/// \param Exp [in] The specified exponent.
///
/// This function uses the following formula: X * 2^Exp

template <typename T>
_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
const inline __detail::enable_if_t<__detail::is_arithmetic<T>::Value &&
__detail::is_same<half, T>::value,
T> ldexp(T X, T Exp) {
return __detail::ldexp_impl(X, Exp);
}

template <typename T>
const inline __detail::enable_if_t<
__detail::is_arithmetic<T>::Value && __detail::is_same<float, T>::value, T>
ldexp(T X, T Exp) {
return __detail::ldexp_impl(X, Exp);
}

template <int N>
_HLSL_16BIT_AVAILABILITY(shadermodel, 6.2)
const inline __detail::HLSL_FIXED_VECTOR<half, N> ldexp(
__detail::HLSL_FIXED_VECTOR<half, N> X,
__detail::HLSL_FIXED_VECTOR<half, N> Exp) {
return __detail::ldexp_impl(X, Exp);
}

template <int N>
const inline __detail::HLSL_FIXED_VECTOR<float, N>
ldexp(__detail::HLSL_FIXED_VECTOR<float, N> X,
__detail::HLSL_FIXED_VECTOR<float, N> Exp) {
return __detail::ldexp_impl(X, Exp);
}

//===----------------------------------------------------------------------===//
// length builtins
//===----------------------------------------------------------------------===//
Expand Down
49 changes: 49 additions & 0 deletions clang/test/CodeGenHLSL/builtins/ldexp.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// RUN: %clang_cc1 -finclude-default-header -x hlsl -triple dxil-pc-shadermodel6.3-library %s -fnative-half-type -emit-llvm -disable-llvm-passes -o - | FileCheck %s
Copy link
Contributor

Choose a reason for hiding this comment

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

A lot of the builtins that support half and float have tests with native half and without native half.
See: https://github.com/llvm/llvm-project/blob/main/clang/test/CodeGenHLSL/builtins/cos.hlsl
Should we do that here?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yea, to be consistent if nothing else.

Copy link
Member

Choose a reason for hiding this comment

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

I was hoping we would only do those for intrinsics that require clang builtins since we define most of them as void(...). for anything thats pure HLSL like this one we have extensive testing of native half.


// CHECK-LABEL: define linkonce_odr noundef nofpclass(nan inf) half @_ZN4hlsl8__detail10ldexp_implIDhEET_S2_S2_
// CHECK: %elt.exp2 = call reassoc nnan ninf nsz arcp afn half @llvm.exp2.f16(half %{{.*}})
// CHECK: %mul = fmul reassoc nnan ninf nsz arcp afn half %elt.exp2, %{{.*}}
// CHECK: ret half %mul
half test_ldexp_half(half X, half Exp) { return ldexp(X, Exp); }

// CHECK-LABEL: define linkonce_odr noundef nofpclass(nan inf) <2 x half> @_ZN4hlsl8__detail10ldexp_implIDv2_DhEET_S3_S3_
// CHECK: %elt.exp2 = call reassoc nnan ninf nsz arcp afn <2 x half> @llvm.exp2.v2f16(<2 x half> %{{.*}})
// CHECK: %mul = fmul reassoc nnan ninf nsz arcp afn <2 x half> %elt.exp2, %{{.*}}
// CHECK: ret <2 x half> %mul
half2 test_ldexp_half2(half2 X, half2 Exp) { return ldexp(X, Exp); }

// CHECK-LABEL: define linkonce_odr noundef nofpclass(nan inf) <3 x half> @_ZN4hlsl8__detail10ldexp_implIDv3_DhEET_S3_S3_
// CHECK: %elt.exp2 = call reassoc nnan ninf nsz arcp afn <3 x half> @llvm.exp2.v3f16(<3 x half> %{{.*}})
// CHECK: %mul = fmul reassoc nnan ninf nsz arcp afn <3 x half> %elt.exp2, %{{.*}}
// CHECK: ret <3 x half> %mul
half3 test_ldexp_half3(half3 X, half3 Exp) { return ldexp(X, Exp); }

// CHECK-LABEL: define linkonce_odr noundef nofpclass(nan inf) <4 x half> @_ZN4hlsl8__detail10ldexp_implIDv4_DhEET_S3_S3_
// CHECK: %elt.exp2 = call reassoc nnan ninf nsz arcp afn <4 x half> @llvm.exp2.v4f16(<4 x half> %{{.*}})
// CHECK: %mul = fmul reassoc nnan ninf nsz arcp afn <4 x half> %elt.exp2, %{{.*}}
// CHECK: ret <4 x half> %mul
half4 test_ldexp_half4(half4 X, half4 Exp) { return ldexp(X, Exp); }

// CHECK-LABEL: define linkonce_odr noundef nofpclass(nan inf) float @_ZN4hlsl8__detail10ldexp_implIfEET_S2_S2_
// CHECK: %elt.exp2 = call reassoc nnan ninf nsz arcp afn float @llvm.exp2.f32(float %{{.*}})
// CHECK: %mul = fmul reassoc nnan ninf nsz arcp afn float %elt.exp2, %{{.*}}
// CHECK: ret float %mul
float test_ldexp_float(float X, float Exp) { return ldexp(X, Exp); }

// CHECK-LABEL: define linkonce_odr noundef nofpclass(nan inf) <2 x float> @_ZN4hlsl8__detail10ldexp_implIDv2_fEET_S3_S3_
// CHECK: %elt.exp2 = call reassoc nnan ninf nsz arcp afn <2 x float> @llvm.exp2.v2f32(<2 x float> %{{.*}})
// CHECK: %mul = fmul reassoc nnan ninf nsz arcp afn <2 x float> %elt.exp2, %{{.*}}
// CHECK: ret <2 x float> %mul
float2 test_ldexp_float2(float2 X, float2 Exp) { return ldexp(X, Exp); }

// CHECK-LABEL: define linkonce_odr noundef nofpclass(nan inf) <3 x float> @_ZN4hlsl8__detail10ldexp_implIDv3_fEET_S3_S3_
// CHECK: %elt.exp2 = call reassoc nnan ninf nsz arcp afn <3 x float> @llvm.exp2.v3f32(<3 x float> %{{.*}})
// CHECK: %mul = fmul reassoc nnan ninf nsz arcp afn <3 x float> %elt.exp2, %{{.*}}
// CHECK: ret <3 x float> %mul
float3 test_ldexp_float3(float3 X, float3 Exp) { return ldexp(X, Exp); }

// CHECK-LABEL: define linkonce_odr noundef nofpclass(nan inf) <4 x float> @_ZN4hlsl8__detail10ldexp_implIDv4_fEET_S3_S3_
// CHECK: %elt.exp2 = call reassoc nnan ninf nsz arcp afn <4 x float> @llvm.exp2.v4f32(<4 x float> %{{.*}})
// CHECK: %mul = fmul reassoc nnan ninf nsz arcp afn <4 x float> %elt.exp2, %{{.*}}
// CHECK: ret <4 x float> %mul
float4 test_ldexp_float4(float4 X, float4 Exp) { return ldexp(X, Exp); }
39 changes: 39 additions & 0 deletions clang/test/SemaHLSL/BuiltIns/ldexp-errors.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// RUN: %clang_cc1 -finclude-default-header -triple dxil-pc-shadermodel6.6-library %s -fnative-half-type -emit-llvm-only -disable-llvm-passes -verify

float test_double_inputs(double p0, double p1) {
return ldexp(p0, p1);
// expected-error@-1 {{no matching function for call to 'ldexp'}}
// expected-note@hlsl/hlsl_intrinsics.h:* {{candidate template ignored}}
Copy link
Contributor

Choose a reason for hiding this comment

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

is it worth including these notes in these tests? They don't seem valuable to me, and can be excluded with -verify-ignore-unexpected=note

Copy link
Contributor

Choose a reason for hiding this comment

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

Yea the notes don't seem worthwhile to include. They're more distracting than anything useful. Plus, I don't see any other tests checking for notes.

Copy link
Member

@farzonl farzonl May 2, 2025

Choose a reason for hiding this comment

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

Ignoring notes will also ignore the template substitution notes which explains the no matching function error for vec5 call to 'ldexp'. Thats something I'd like to explicitly test for to make sure the template is behaving the way we want it to.

Plus, I don't see any other tests checking for notes.

git grep for with T = float5 you will find the others that Kaitlin is basing this off of.

// expected-note@hlsl/hlsl_intrinsics.h:* {{candidate template ignored}}
// expected-note@hlsl/hlsl_intrinsics.h:* {{candidate template ignored}}
// expected-note@hlsl/hlsl_intrinsics.h:* {{candidate template ignored}}
}

float test_int_inputs(int p0, int p1, int p2) {
return ldexp(p0, p1);
// expected-error@-1 {{no matching function for call to 'ldexp'}}
// expected-note@hlsl/hlsl_intrinsics.h:* {{candidate template ignored}}
// expected-note@hlsl/hlsl_intrinsics.h:* {{candidate template ignored}}
// expected-note@hlsl/hlsl_intrinsics.h:* {{candidate template ignored}}
// expected-note@hlsl/hlsl_intrinsics.h:* {{candidate template ignored}}
}

float1 test_vec1_inputs(float1 p0, float1 p1) {
return ldexp(p0, p1);
// expected-error@-1 {{no matching function for call to 'ldexp'}}
// expected-note@hlsl/hlsl_intrinsics.h:* {{candidate template ignored: substitution failure [with T = float1]: no type named 'Type' in 'hlsl::__detail::enable_if<false, vector<float, 1>>'}}
// expected-note@hlsl/hlsl_intrinsics.h:* {{candidate template ignored: substitution failure [with T = float1]: no type named 'Type' in 'hlsl::__detail::enable_if<false, vector<float, 1>>'}}
// expected-note@hlsl/hlsl_intrinsics.h:* {{candidate template ignored: substitution failure [with N = 1]: no type named 'Type' in 'hlsl::__detail::enable_if<false, half>'}}
// expected-note@hlsl/hlsl_intrinsics.h:* {{candidate template ignored: substitution failure [with N = 1]: no type named 'Type' in 'hlsl::__detail::enable_if<false, float>'}}
}

typedef float float5 __attribute__((ext_vector_type(5)));

float5 test_vec5_inputs(float5 p0, float5 p1) {
return ldexp(p0, p1);
// expected-error@-1 {{no matching function for call to 'ldexp'}}
// expected-note@hlsl/hlsl_intrinsics.h:* {{candidate template ignored: substitution failure [with T = float5]: no type named 'Type' in 'hlsl::__detail::enable_if<false, vector<float, 5>>'}}
// expected-note@hlsl/hlsl_intrinsics.h:* {{candidate template ignored: substitution failure [with T = float5]: no type named 'Type' in 'hlsl::__detail::enable_if<false, vector<float, 5>>'}}
// expected-note@hlsl/hlsl_intrinsics.h:* {{candidate template ignored: substitution failure [with N = 5]: no type named 'Type' in 'hlsl::__detail::enable_if<false, half>'}}
// expected-note@hlsl/hlsl_intrinsics.h:* {{candidate template ignored: substitution failure [with N = 5]: no type named 'Type' in 'hlsl::__detail::enable_if<false, float>'}}
}