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

[HLSL] Implement the ldexp intrinsic #138182

merged 4 commits into from
May 3, 2025

Conversation

kmpeng
Copy link
Contributor

@kmpeng kmpeng commented May 1, 2025

Closes #99133.

Implemented ldexp entirely in hlsl_intrinsics.h and hlsl_intrinsic_helpers.h, added coresponding tests in clang/test/CodeGenHLSL/builtins/ldexp.hlsl and clang/test/SemaHLSL/BuiltIns/ldexp-errors.hlsl.

@llvmbot llvmbot added clang Clang issues not falling into any other category backend:X86 clang:headers Headers provided by Clang, e.g. for intrinsics HLSL HLSL Language Support labels May 1, 2025
@llvmbot
Copy link
Member

llvmbot commented May 1, 2025

@llvm/pr-subscribers-hlsl
@llvm/pr-subscribers-clang

@llvm/pr-subscribers-backend-x86

Author: Kaitlin Peng (kmpeng)

Changes

Closes #99133.

Implemented ldexp entirely in hlsl_intrinsics.h and hlsl_intrinsic_helpers.h, added coresponding tests in clang/test/CodeGenHLSL/builtins/ldexp.hlsl and clang/test/SemaHLSL/BuiltIns/ldexp-errors.hlsl.


Full diff: https://github.com/llvm/llvm-project/pull/138182.diff

4 Files Affected:

  • (modified) clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h (+4)
  • (modified) clang/lib/Headers/hlsl/hlsl_intrinsics.h (+41)
  • (added) clang/test/CodeGenHLSL/builtins/ldexp.hlsl (+49)
  • (added) clang/test/SemaHLSL/BuiltIns/ldexp-errors.hlsl (+39)
diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h b/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
index 3180492b7de36..4eb7b8f45c85a 100644
--- a/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
+++ b/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h
@@ -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
 
diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsics.h b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
index 193e7e6e99498..ffeb09bb49bef 100644
--- a/clang/lib/Headers/hlsl/hlsl_intrinsics.h
+++ b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
@@ -303,6 +303,47 @@ 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 \a X by two, raised to the power of \a Exp.
+/// \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
 //===----------------------------------------------------------------------===//
diff --git a/clang/test/CodeGenHLSL/builtins/ldexp.hlsl b/clang/test/CodeGenHLSL/builtins/ldexp.hlsl
new file mode 100644
index 0000000000000..6a6c04318f805
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/ldexp.hlsl
@@ -0,0 +1,49 @@
+// RUN: %clang_cc1 -finclude-default-header -x hlsl -triple dxil-pc-shadermodel6.3-library %s -fnative-half-type -emit-llvm -o - | FileCheck %s
+
+// CHECK-LABEL: test_ldexp_half
+// CHECK: %elt.exp2.i = call reassoc nnan ninf nsz arcp afn half @llvm.exp2.f16(half %{{.*}})
+// CHECK: %mul.i = fmul reassoc nnan ninf nsz arcp afn half %elt.exp2.i, %{{.*}}
+// CHECK: ret half %mul.i
+half test_ldexp_half(half X, half Exp) { return ldexp(X, Exp); }
+
+// CHECK-LABEL: test_ldexp_half2
+// CHECK: %elt.exp2.i = call reassoc nnan ninf nsz arcp afn <2 x half> @llvm.exp2.v2f16(<2 x half> %{{.*}})
+// CHECK: %mul.i = fmul reassoc nnan ninf nsz arcp afn <2 x half> %elt.exp2.i, %{{.*}}
+// CHECK: ret <2 x half> %mul.i
+half2 test_ldexp_half2(half2 X, half2 Exp) { return ldexp(X, Exp); }
+
+// CHECK-LABEL: test_ldexp_half3
+// CHECK: %elt.exp2.i = call reassoc nnan ninf nsz arcp afn <3 x half> @llvm.exp2.v3f16(<3 x half> %{{.*}})
+// CHECK: %mul.i = fmul reassoc nnan ninf nsz arcp afn <3 x half> %elt.exp2.i, %{{.*}}
+// CHECK: ret <3 x half> %mul.i
+half3 test_ldexp_half3(half3 X, half3 Exp) { return ldexp(X, Exp); }
+
+// CHECK-LABEL: test_ldexp_half4
+// CHECK: %elt.exp2.i = call reassoc nnan ninf nsz arcp afn <4 x half> @llvm.exp2.v4f16(<4 x half> %{{.*}})
+// CHECK: %mul.i = fmul reassoc nnan ninf nsz arcp afn <4 x half> %elt.exp2.i, %{{.*}}
+// CHECK: ret <4 x half> %mul.i
+half4 test_ldexp_half4(half4 X, half4 Exp) { return ldexp(X, Exp); }
+
+// CHECK-LABEL: test_ldexp_float
+// CHECK: %elt.exp2.i = call reassoc nnan ninf nsz arcp afn float @llvm.exp2.f32(float %{{.*}})
+// CHECK: %mul.i = fmul reassoc nnan ninf nsz arcp afn float %elt.exp2.i, %{{.*}}
+// CHECK: ret float %mul.i
+float test_ldexp_float(float X, float Exp) { return ldexp(X, Exp); }
+
+// CHECK-LABEL: test_ldexp_float2
+// CHECK: %elt.exp2.i = call reassoc nnan ninf nsz arcp afn <2 x float> @llvm.exp2.v2f32(<2 x float> %{{.*}})
+// CHECK: %mul.i = fmul reassoc nnan ninf nsz arcp afn <2 x float> %elt.exp2.i, %{{.*}}
+// CHECK: ret <2 x float> %mul.i
+float2 test_ldexp_float2(float2 X, float2 Exp) { return ldexp(X, Exp); }
+
+// CHECK-LABEL: test_ldexp_float3
+// CHECK: %elt.exp2.i = call reassoc nnan ninf nsz arcp afn <3 x float> @llvm.exp2.v3f32(<3 x float> %{{.*}})
+// CHECK: %mul.i = fmul reassoc nnan ninf nsz arcp afn <3 x float> %elt.exp2.i, %{{.*}}
+// CHECK: ret <3 x float> %mul.i
+float3 test_ldexp_float3(float3 X, float3 Exp) { return ldexp(X, Exp); }
+
+// CHECK-LABEL: test_ldexp_float4
+// CHECK: %elt.exp2.i = call reassoc nnan ninf nsz arcp afn <4 x float> @llvm.exp2.v4f32(<4 x float> %{{.*}})
+// CHECK: %mul.i = fmul reassoc nnan ninf nsz arcp afn <4 x float> %elt.exp2.i, %{{.*}}
+// CHECK: ret <4 x float> %mul.i
+float4 test_ldexp_float4(float4 X, float4 Exp) { return ldexp(X, Exp); }
diff --git a/clang/test/SemaHLSL/BuiltIns/ldexp-errors.hlsl b/clang/test/SemaHLSL/BuiltIns/ldexp-errors.hlsl
new file mode 100644
index 0000000000000..0bc7f7e40f5d3
--- /dev/null
+++ b/clang/test/SemaHLSL/BuiltIns/ldexp-errors.hlsl
@@ -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}}
+  // 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>'}}
+}

Copy link

github-actions bot commented May 1, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@@ -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.

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.

Copy link
Contributor

@Icohedron Icohedron left a comment

Choose a reason for hiding this comment

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

@spall pointed out some good changes that should be made

Copy link
Member

@farzonl farzonl left a comment

Choose a reason for hiding this comment

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

This looks fine.

I would not like to carry forward native half testing of intrinsics that are written purely in HLSL. I think the builtin ones aren't great either but I understand how they came to exist.

As for looking at notes. its easy to mess up the the vector size enforcements. and we have been using notes to confirm we did them right. I know for some notes we are going to have a bunch of repeated things we don't care about. But there is benefit to keeping them as there is no other way to confirm that the no matching function for call to 'ldexp' is happeing because of vector size out of bounds.

@farzonl
Copy link
Member

farzonl commented May 2, 2025

Might be worth looking at usages of ldexp in https://github.com/microsoft/DirectX-Graphics-Samples/tree/master/MiniEngine/Core/Shaders/PixelPacking_RGBE.hlsli specically lines 52 and 79 and make sure their is no ambiguous cases to answer @V-FEXrt question here: llvm/wg-hlsl#264 (comment)

Then start looking at
llvm/wg-hlsl#242 (llvm-beanz/offload-test-suite#95)

@kmpeng kmpeng merged commit d11df05 into llvm:main May 3, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend:X86 clang:headers Headers provided by Clang, e.g. for intrinsics clang Clang issues not falling into any other category HLSL HLSL Language Support
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement the ldexp HLSL Function
5 participants