[clang] [llvm] [HLSL] Implement frexp() (PR #235154)

Dan Brown via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 23 16:56:05 PDT 2026


https://github.com/danbrown-amd created https://github.com/llvm/llvm-project/pull/235154

Resolves #99119, #220654

Assisted-by: Claude Opus 5

>From add0bdfe458adfc94e552287b7b4778c2abd4422 Mon Sep 17 00:00:00 2001
From: danbrown-amd <danbrown at amd.com>
Date: Fri, 16 Oct 2026 18:15:51 -0600
Subject: [PATCH] [HLSL] Implement frexp()

Resolves #99119, #220654

Assisted-by: Claude Opus 5
---
 clang/include/clang/Basic/Builtins.td         |   6 +
 clang/lib/CodeGen/CGHLSLBuiltins.cpp          |  31 ++++
 clang/lib/Headers/hlsl/hlsl_intrinsics.h      |  30 ++++
 clang/lib/Sema/SemaHLSL.cpp                   |  17 ++
 clang/test/CodeGenHLSL/builtins/frexp.hlsl    |  78 +++++++++
 .../test/CodeGenHLSL/builtins/frexp_mat.hlsl  | 155 ++++++++++++++++++
 .../test/SemaHLSL/BuiltIns/frexp-errors.hlsl  |  65 ++++++++
 .../SemaHLSL/BuiltIns/frexp_mat-errors.hlsl   |  53 ++++++
 .../Target/DirectX/DXILIntrinsicExpansion.cpp |  47 ++++++
 .../Target/SPIRV/SPIRVInstructionSelector.cpp |  21 ++-
 llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp  |  10 +-
 llvm/test/CodeGen/DirectX/LongVector/frexp.ll |  17 ++
 llvm/test/CodeGen/DirectX/frexp.ll            |  52 ++++++
 llvm/test/CodeGen/DirectX/frexp_error.ll      |  11 ++
 .../extensions/SPV_EXT_long_vector/frexp.ll   |  40 +++++
 .../CodeGen/SPIRV/hlsl-intrinsics/frexp.ll    |  61 +++++++
 .../CodeGen/SPIRV/llvm-intrinsics/frexp.ll    |  12 ++
 17 files changed, 698 insertions(+), 8 deletions(-)
 create mode 100644 clang/test/CodeGenHLSL/builtins/frexp.hlsl
 create mode 100644 clang/test/CodeGenHLSL/builtins/frexp_mat.hlsl
 create mode 100644 clang/test/SemaHLSL/BuiltIns/frexp-errors.hlsl
 create mode 100644 clang/test/SemaHLSL/BuiltIns/frexp_mat-errors.hlsl
 create mode 100644 llvm/test/CodeGen/DirectX/LongVector/frexp.ll
 create mode 100644 llvm/test/CodeGen/DirectX/frexp.ll
 create mode 100644 llvm/test/CodeGen/DirectX/frexp_error.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/extensions/SPV_EXT_long_vector/frexp.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-intrinsics/frexp.ll

diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index ad9a58bb2ad2d3..b740af3f611a26 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -5882,6 +5882,12 @@ def HLSLBufferUpdateCounter : LangBuiltin<"HLSL_LANG"> {
   let Prototype = "uint32_t(__hlsl_resource_t, int)";
 }
 
+def HLSLFrexp : LangBuiltin<"HLSL_LANG"> {
+  let Spellings = ["__builtin_hlsl_elementwise_frexp"];
+  let Attributes = [NoThrow, Const, CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
 def HLSLSplitDouble: LangBuiltin<"HLSL_LANG"> {
   let Spellings = ["__builtin_hlsl_elementwise_splitdouble"];
   let Attributes = [NoThrow, Const];
diff --git a/clang/lib/CodeGen/CGHLSLBuiltins.cpp b/clang/lib/CodeGen/CGHLSLBuiltins.cpp
index fc422918499ed2..211fd919ab6433 100644
--- a/clang/lib/CodeGen/CGHLSLBuiltins.cpp
+++ b/clang/lib/CodeGen/CGHLSLBuiltins.cpp
@@ -119,6 +119,32 @@ static Value *handleHlslClip(const CallExpr *E, CodeGenFunction *CGF) {
   return LastInstr;
 }
 
+static Value *handleHlslFrexp(const CallExpr *E, CodeGenFunction *CGF) {
+  Value *X = CGF->EmitScalarExpr(E->getArg(0));
+
+  // Calls through the frexp overloads wrap the exponent in an HLSLOutArgExpr;
+  // direct calls to the builtin pass a plain lvalue.
+  CallArgList Args;
+  LValue ExpLValue;
+  if (const auto *OutArg = dyn_cast<HLSLOutArgExpr>(E->getArg(1)))
+    ExpLValue = CGF->EmitHLSLOutArgExpr(OutArg, Args, OutArg->getType());
+  else
+    ExpLValue = CGF->EmitLValue(E->getArg(1));
+
+  // llvm.frexp returns {mantissa, integer exponent}; HLSL wants the exponent
+  // as a float of the same shape as X.
+  llvm::Type *ExpIntTy = X->getType()->getWithNewType(CGF->Int32Ty);
+  Value *Frexp = CGF->Builder.CreateIntrinsic(
+      Intrinsic::frexp, {X->getType(), ExpIntTy}, {X}, nullptr, "hlsl.frexp");
+  Value *Mantissa = CGF->Builder.CreateExtractValue(Frexp, 0);
+  Value *Exp = CGF->Builder.CreateSIToFP(
+      CGF->Builder.CreateExtractValue(Frexp, 1), X->getType());
+
+  CGF->EmitStoreThroughLValue(RValue::get(Exp), ExpLValue);
+  CGF->EmitWritebacks(Args);
+  return Mantissa;
+}
+
 static Value *handleHlslSplitdouble(const CallExpr *E, CodeGenFunction *CGF) {
   Value *Op0 = CGF->EmitScalarExpr(E->getArg(0));
   const auto *OutArg1 = dyn_cast<HLSLOutArgExpr>(E->getArg(1));
@@ -1780,6 +1806,11 @@ Value *CodeGenFunction::EmitHLSLBuiltinExpr(unsigned BuiltinID,
         CGM.getHLSLRuntime().getBufferUpdateCounterIntrinsic(),
         ArrayRef<Value *>{ResHandle, OffsetI8}, nullptr);
   }
+  case Builtin::BI__builtin_hlsl_elementwise_frexp:
+    assert(E->getArg(0)->getType()->hasFloatingRepresentation() &&
+           E->getArg(1)->getType()->hasFloatingRepresentation() &&
+           "frexp operands types mismatch");
+    return handleHlslFrexp(E, this);
   case Builtin::BI__builtin_hlsl_elementwise_splitdouble: {
 
     assert((E->getArg(0)->getType()->hasFloatingRepresentation() &&
diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsics.h b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
index a86fc7dc0988bb..94cdd5632f2538 100644
--- a/clang/lib/Headers/hlsl/hlsl_intrinsics.h
+++ b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
@@ -220,6 +220,36 @@ firstbithigh(vector<T, N> X) {
   return __detail::firstbithigh_impl<vector<uint, N>, vector<T, N>, 64>(X);
 }
 
+//===----------------------------------------------------------------------===//
+// frexp builtins
+//===----------------------------------------------------------------------===//
+
+/// \fn T frexp(T X, out T Exp)
+/// \brief Returns the mantissa and exponent of the specified floating-point
+/// value.
+/// \param X The specified floating-point value.
+/// \param Exp The returned exponent of \a X.
+///
+/// The mantissa has the same sign as \a X and an absolute value in [0.5, 1),
+/// such that X = mantissa * 2^Exp. If \a X is 0, both the mantissa and the
+/// exponent are 0.
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_frexp)
+float frexp(float, out float);
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_frexp)
+float2 frexp(float2, out float2);
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_frexp)
+float3 frexp(float3, out float3);
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_frexp)
+float4 frexp(float4, out float4);
+template <int N>
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_frexp)
+vector<__detail::enable_if_t<(N > 4), float>, N> frexp(
+    vector<__detail::enable_if_t<(N > 4), float>, N>,
+    out vector<__detail::enable_if_t<(N > 4), float>, N>);
+template <int R, int C>
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_frexp)
+matrix<float, R, C> frexp(matrix<float, R, C>, out matrix<float, R, C>);
+
 //===----------------------------------------------------------------------===//
 // mul builtins
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 4fe7ed151e9180..745c4feea7448d 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -5314,6 +5314,23 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) {
     TheCall->setType(ArgTyExpr);
     break;
   }
+  case Builtin::BI__builtin_hlsl_elementwise_frexp: {
+    if (SemaRef.checkArgCount(TheCall, 2))
+      return true;
+
+    if (CheckScalarOrVectorOrMatrix(&SemaRef, TheCall, SemaRef.Context.FloatTy,
+                                    0) ||
+        CheckScalarOrVectorOrMatrix(&SemaRef, TheCall, SemaRef.Context.FloatTy,
+                                    1) ||
+        CheckAllArgsHaveSameType(&SemaRef, TheCall))
+      return true;
+
+    if (CheckModifiableLValue(&SemaRef, TheCall, 1))
+      return true;
+
+    TheCall->setType(TheCall->getArg(0)->getType());
+    break;
+  }
   case Builtin::BI__builtin_hlsl_elementwise_splitdouble: {
     if (SemaRef.checkArgCount(TheCall, 3))
       return true;
diff --git a/clang/test/CodeGenHLSL/builtins/frexp.hlsl b/clang/test/CodeGenHLSL/builtins/frexp.hlsl
new file mode 100644
index 00000000000000..fee76c7a827efa
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/frexp.hlsl
@@ -0,0 +1,78 @@
+// 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,DXCHECK
+// RUN: %clang_cc1 -finclude-default-header -x hlsl -triple \
+// RUN:   spirv-unknown-vulkan-library %s -emit-llvm -disable-llvm-passes \
+// RUN:   -o - | FileCheck %s --check-prefixes=CHECK,SPVCHECK
+
+// DXCHECK: define hidden noundef nofpclass(nan inf) float @_Z12test_frexp_ffRf(
+// SPVCHECK: define hidden spir_func noundef nofpclass(nan inf) float @_Z12test_frexp_ffRf(
+// CHECK:      [[X:%.*]] = load float, ptr %{{.*}}
+// CHECK:      [[FREXP:%.*]] = call { float, i32 } @llvm.frexp.f32.i32(float [[X]])
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { float, i32 } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { float, i32 } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}i32 [[EXPI]] to float
+// CHECK-NEXT: store float [[EXPF]], ptr [[TMP:%.*]], align 4
+// CHECK-NEXT: [[WB:%.*]] = load float, ptr [[TMP]], align 4
+// CHECK-NEXT: store float [[WB]], ptr %{{.*}}, align 4
+// CHECK:      ret float [[MANT]]
+float test_frexp_f(float X, out float E) { return frexp(X, E); }
+
+// DXCHECK: define hidden noundef nofpclass(nan inf) <2 x float> @_Z13test_frexp_f2Dv2_fRS_(
+// SPVCHECK: define hidden spir_func noundef nofpclass(nan inf) <2 x float> @_Z13test_frexp_f2Dv2_fRS_(
+// CHECK:      [[FREXP:%.*]] = call { <2 x float>, <2 x i32> } @llvm.frexp.v2f32.v2i32(<2 x float> %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { <2 x float>, <2 x i32> } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { <2 x float>, <2 x i32> } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}<2 x i32> [[EXPI]] to <2 x float>
+// CHECK-NEXT: store <2 x float> [[EXPF]], ptr [[TMP:%.*]], align 4
+// CHECK:      ret <2 x float> [[MANT]]
+float2 test_frexp_f2(float2 X, out float2 E) { return frexp(X, E); }
+
+// DXCHECK: define hidden noundef nofpclass(nan inf) <3 x float> @_Z13test_frexp_f3Dv3_fRS_(
+// SPVCHECK: define hidden spir_func noundef nofpclass(nan inf) <3 x float> @_Z13test_frexp_f3Dv3_fRS_(
+// CHECK:      [[FREXP:%.*]] = call { <3 x float>, <3 x i32> } @llvm.frexp.v3f32.v3i32(<3 x float> %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { <3 x float>, <3 x i32> } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { <3 x float>, <3 x i32> } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}<3 x i32> [[EXPI]] to <3 x float>
+// CHECK-NEXT: store <3 x float> [[EXPF]], ptr [[TMP:%.*]]
+// CHECK:      ret <3 x float> [[MANT]]
+float3 test_frexp_f3(float3 X, out float3 E) { return frexp(X, E); }
+
+// DXCHECK: define hidden noundef nofpclass(nan inf) <4 x float> @_Z13test_frexp_f4Dv4_fRS_(
+// SPVCHECK: define hidden spir_func noundef nofpclass(nan inf) <4 x float> @_Z13test_frexp_f4Dv4_fRS_(
+// CHECK:      [[FREXP:%.*]] = call { <4 x float>, <4 x i32> } @llvm.frexp.v4f32.v4i32(<4 x float> %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { <4 x float>, <4 x i32> } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { <4 x float>, <4 x i32> } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}<4 x i32> [[EXPI]] to <4 x float>
+// CHECK-NEXT: store <4 x float> [[EXPF]], ptr [[TMP:%.*]], align 4
+// CHECK:      ret <4 x float> [[MANT]]
+float4 test_frexp_f4(float4 X, out float4 E) { return frexp(X, E); }
+
+// DXCHECK: define hidden noundef nofpclass(nan inf) <5 x float> @_Z13test_frexp_f5Dv5_fRS_(
+// SPVCHECK: define hidden spir_func noundef nofpclass(nan inf) <5 x float> @_Z13test_frexp_f5Dv5_fRS_(
+// CHECK:      [[FREXP:%.*]] = call { <5 x float>, <5 x i32> } @llvm.frexp.v5f32.v5i32(<5 x float> %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { <5 x float>, <5 x i32> } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { <5 x float>, <5 x i32> } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}<5 x i32> [[EXPI]] to <5 x float>
+// CHECK-NEXT: store <5 x float> [[EXPF]], ptr [[TMP:%.*]], align 4
+// CHECK:      ret <5 x float> [[MANT]]
+vector<float, 5> test_frexp_f5(vector<float, 5> X, out vector<float, 5> E) {
+  return frexp(X, E);
+}
+
+// Direct calls to the builtin store the exponent straight to the lvalue.
+// CHECK-LABEL: test_frexp_builtin
+// CHECK:      [[FREXP:%.*]] = call { float, i32 } @llvm.frexp.f32.i32(float %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { float, i32 } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { float, i32 } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}i32 [[EXPI]] to float
+// CHECK-NEXT: store float [[EXPF]], ptr [[E:%.*]], align 4
+// CHECK-NEXT: store float [[MANT]], ptr [[M:%.*]], align 4
+// CHECK-NEXT: [[MLOAD:%.*]] = load float, ptr [[M]], align 4
+// CHECK-NEXT: [[ELOAD:%.*]] = load float, ptr [[E]], align 4
+// CHECK-NEXT: fadd {{.*}}float [[MLOAD]], [[ELOAD]]
+float test_frexp_builtin(float X) {
+  float E;
+  float M = __builtin_hlsl_elementwise_frexp(X, E);
+  return M + E;
+}
diff --git a/clang/test/CodeGenHLSL/builtins/frexp_mat.hlsl b/clang/test/CodeGenHLSL/builtins/frexp_mat.hlsl
new file mode 100644
index 00000000000000..9f989b6dc1e2f2
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/frexp_mat.hlsl
@@ -0,0 +1,155 @@
+// 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,DXCHECK
+// RUN: %clang_cc1 -finclude-default-header -x hlsl -triple \
+// RUN:   spirv-unknown-vulkan-library %s -emit-llvm -disable-llvm-passes \
+// RUN:   -o - | FileCheck %s --check-prefixes=CHECK,SPVCHECK
+
+// DXCHECK: define hidden [[FN_TYPE:]]noundef nofpclass(nan inf) <2 x float> @{{.*}}test_frexp_f1x2{{.*}}(
+// SPVCHECK: define hidden [[FN_TYPE:spir_func ]]noundef nofpclass(nan inf) <2 x float> @{{.*}}test_frexp_f1x2{{.*}}(
+// CHECK:      [[FREXP:%.*]] = call { <2 x float>, <2 x i32> } @llvm.frexp.v2f32.v2i32(<2 x float> %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { <2 x float>, <2 x i32> } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { <2 x float>, <2 x i32> } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}<2 x i32> [[EXPI]] to <2 x float>
+// CHECK-NEXT: store <2 x float> [[EXPF]], ptr [[TMP:%.*]], align 4
+// CHECK:      ret <2 x float> [[MANT]]
+float1x2 test_frexp_f1x2(float1x2 X, out float1x2 E) { return frexp(X, E); }
+
+// CHECK: define hidden [[FN_TYPE]]noundef nofpclass(nan inf) <3 x float> @{{.*}}test_frexp_f1x3{{.*}}(
+// CHECK:      [[FREXP:%.*]] = call { <3 x float>, <3 x i32> } @llvm.frexp.v3f32.v3i32(<3 x float> %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { <3 x float>, <3 x i32> } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { <3 x float>, <3 x i32> } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}<3 x i32> [[EXPI]] to <3 x float>
+// CHECK-NEXT: store <3 x float> [[EXPF]], ptr [[TMP:%.*]], align 4
+// CHECK:      ret <3 x float> [[MANT]]
+float1x3 test_frexp_f1x3(float1x3 X, out float1x3 E) { return frexp(X, E); }
+
+// CHECK: define hidden [[FN_TYPE]]noundef nofpclass(nan inf) <4 x float> @{{.*}}test_frexp_f1x4{{.*}}(
+// CHECK:      [[FREXP:%.*]] = call { <4 x float>, <4 x i32> } @llvm.frexp.v4f32.v4i32(<4 x float> %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { <4 x float>, <4 x i32> } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { <4 x float>, <4 x i32> } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}<4 x i32> [[EXPI]] to <4 x float>
+// CHECK-NEXT: store <4 x float> [[EXPF]], ptr [[TMP:%.*]], align 4
+// CHECK:      ret <4 x float> [[MANT]]
+float1x4 test_frexp_f1x4(float1x4 X, out float1x4 E) { return frexp(X, E); }
+
+// CHECK: define hidden [[FN_TYPE]]noundef nofpclass(nan inf) <2 x float> @{{.*}}test_frexp_f2x1{{.*}}(
+// CHECK:      [[FREXP:%.*]] = call { <2 x float>, <2 x i32> } @llvm.frexp.v2f32.v2i32(<2 x float> %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { <2 x float>, <2 x i32> } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { <2 x float>, <2 x i32> } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}<2 x i32> [[EXPI]] to <2 x float>
+// CHECK-NEXT: store <2 x float> [[EXPF]], ptr [[TMP:%.*]], align 4
+// CHECK:      ret <2 x float> [[MANT]]
+float2x1 test_frexp_f2x1(float2x1 X, out float2x1 E) { return frexp(X, E); }
+
+// CHECK: define hidden [[FN_TYPE]]noundef nofpclass(nan inf) <4 x float> @{{.*}}test_frexp_f2x2{{.*}}(
+// CHECK:      [[FREXP:%.*]] = call { <4 x float>, <4 x i32> } @llvm.frexp.v4f32.v4i32(<4 x float> %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { <4 x float>, <4 x i32> } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { <4 x float>, <4 x i32> } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}<4 x i32> [[EXPI]] to <4 x float>
+// CHECK-NEXT: store <4 x float> [[EXPF]], ptr [[TMP:%.*]], align 4
+// CHECK:      ret <4 x float> [[MANT]]
+float2x2 test_frexp_f2x2(float2x2 X, out float2x2 E) { return frexp(X, E); }
+
+// CHECK: define hidden [[FN_TYPE]]noundef nofpclass(nan inf) <6 x float> @{{.*}}test_frexp_f2x3{{.*}}(
+// CHECK:      [[FREXP:%.*]] = call { <6 x float>, <6 x i32> } @llvm.frexp.v6f32.v6i32(<6 x float> %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { <6 x float>, <6 x i32> } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { <6 x float>, <6 x i32> } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}<6 x i32> [[EXPI]] to <6 x float>
+// CHECK-NEXT: store <6 x float> [[EXPF]], ptr [[TMP:%.*]], align 4
+// CHECK:      ret <6 x float> [[MANT]]
+float2x3 test_frexp_f2x3(float2x3 X, out float2x3 E) { return frexp(X, E); }
+
+// CHECK: define hidden [[FN_TYPE]]noundef nofpclass(nan inf) <8 x float> @{{.*}}test_frexp_f2x4{{.*}}(
+// CHECK:      [[FREXP:%.*]] = call { <8 x float>, <8 x i32> } @llvm.frexp.v8f32.v8i32(<8 x float> %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { <8 x float>, <8 x i32> } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { <8 x float>, <8 x i32> } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}<8 x i32> [[EXPI]] to <8 x float>
+// CHECK-NEXT: store <8 x float> [[EXPF]], ptr [[TMP:%.*]], align 4
+// CHECK:      ret <8 x float> [[MANT]]
+float2x4 test_frexp_f2x4(float2x4 X, out float2x4 E) { return frexp(X, E); }
+
+// CHECK: define hidden [[FN_TYPE]]noundef nofpclass(nan inf) <3 x float> @{{.*}}test_frexp_f3x1{{.*}}(
+// CHECK:      [[FREXP:%.*]] = call { <3 x float>, <3 x i32> } @llvm.frexp.v3f32.v3i32(<3 x float> %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { <3 x float>, <3 x i32> } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { <3 x float>, <3 x i32> } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}<3 x i32> [[EXPI]] to <3 x float>
+// CHECK-NEXT: store <3 x float> [[EXPF]], ptr [[TMP:%.*]], align 4
+// CHECK:      ret <3 x float> [[MANT]]
+float3x1 test_frexp_f3x1(float3x1 X, out float3x1 E) { return frexp(X, E); }
+
+// CHECK: define hidden [[FN_TYPE]]noundef nofpclass(nan inf) <6 x float> @{{.*}}test_frexp_f3x2{{.*}}(
+// CHECK:      [[FREXP:%.*]] = call { <6 x float>, <6 x i32> } @llvm.frexp.v6f32.v6i32(<6 x float> %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { <6 x float>, <6 x i32> } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { <6 x float>, <6 x i32> } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}<6 x i32> [[EXPI]] to <6 x float>
+// CHECK-NEXT: store <6 x float> [[EXPF]], ptr [[TMP:%.*]], align 4
+// CHECK:      ret <6 x float> [[MANT]]
+float3x2 test_frexp_f3x2(float3x2 X, out float3x2 E) { return frexp(X, E); }
+
+// CHECK: define hidden [[FN_TYPE]]noundef nofpclass(nan inf) <9 x float> @{{.*}}test_frexp_f3x3{{.*}}(
+// CHECK:      [[FREXP:%.*]] = call { <9 x float>, <9 x i32> } @llvm.frexp.v9f32.v9i32(<9 x float> %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { <9 x float>, <9 x i32> } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { <9 x float>, <9 x i32> } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}<9 x i32> [[EXPI]] to <9 x float>
+// CHECK-NEXT: store <9 x float> [[EXPF]], ptr [[TMP:%.*]], align 4
+// CHECK:      ret <9 x float> [[MANT]]
+float3x3 test_frexp_f3x3(float3x3 X, out float3x3 E) { return frexp(X, E); }
+
+// CHECK: define hidden [[FN_TYPE]]noundef nofpclass(nan inf) <12 x float> @{{.*}}test_frexp_f3x4{{.*}}(
+// CHECK:      [[FREXP:%.*]] = call { <12 x float>, <12 x i32> } @llvm.frexp.v12f32.v12i32(<12 x float> %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { <12 x float>, <12 x i32> } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { <12 x float>, <12 x i32> } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}<12 x i32> [[EXPI]] to <12 x float>
+// CHECK-NEXT: store <12 x float> [[EXPF]], ptr [[TMP:%.*]], align 4
+// CHECK:      ret <12 x float> [[MANT]]
+float3x4 test_frexp_f3x4(float3x4 X, out float3x4 E) { return frexp(X, E); }
+
+// CHECK: define hidden [[FN_TYPE]]noundef nofpclass(nan inf) <4 x float> @{{.*}}test_frexp_f4x1{{.*}}(
+// CHECK:      [[FREXP:%.*]] = call { <4 x float>, <4 x i32> } @llvm.frexp.v4f32.v4i32(<4 x float> %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { <4 x float>, <4 x i32> } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { <4 x float>, <4 x i32> } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}<4 x i32> [[EXPI]] to <4 x float>
+// CHECK-NEXT: store <4 x float> [[EXPF]], ptr [[TMP:%.*]], align 4
+// CHECK:      ret <4 x float> [[MANT]]
+float4x1 test_frexp_f4x1(float4x1 X, out float4x1 E) { return frexp(X, E); }
+
+// CHECK: define hidden [[FN_TYPE]]noundef nofpclass(nan inf) <8 x float> @{{.*}}test_frexp_f4x2{{.*}}(
+// CHECK:      [[FREXP:%.*]] = call { <8 x float>, <8 x i32> } @llvm.frexp.v8f32.v8i32(<8 x float> %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { <8 x float>, <8 x i32> } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { <8 x float>, <8 x i32> } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}<8 x i32> [[EXPI]] to <8 x float>
+// CHECK-NEXT: store <8 x float> [[EXPF]], ptr [[TMP:%.*]], align 4
+// CHECK:      ret <8 x float> [[MANT]]
+float4x2 test_frexp_f4x2(float4x2 X, out float4x2 E) { return frexp(X, E); }
+
+// CHECK: define hidden [[FN_TYPE]]noundef nofpclass(nan inf) <12 x float> @{{.*}}test_frexp_f4x3{{.*}}(
+// CHECK:      [[FREXP:%.*]] = call { <12 x float>, <12 x i32> } @llvm.frexp.v12f32.v12i32(<12 x float> %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { <12 x float>, <12 x i32> } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { <12 x float>, <12 x i32> } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}<12 x i32> [[EXPI]] to <12 x float>
+// CHECK-NEXT: store <12 x float> [[EXPF]], ptr [[TMP:%.*]], align 4
+// CHECK:      ret <12 x float> [[MANT]]
+float4x3 test_frexp_f4x3(float4x3 X, out float4x3 E) { return frexp(X, E); }
+
+// CHECK: define hidden [[FN_TYPE]]noundef nofpclass(nan inf) <16 x float> @{{.*}}test_frexp_f4x4{{.*}}(
+// CHECK:      [[FREXP:%.*]] = call { <16 x float>, <16 x i32> } @llvm.frexp.v16f32.v16i32(<16 x float> %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { <16 x float>, <16 x i32> } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { <16 x float>, <16 x i32> } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}<16 x i32> [[EXPI]] to <16 x float>
+// CHECK-NEXT: store <16 x float> [[EXPF]], ptr [[TMP:%.*]], align 4
+// CHECK:      ret <16 x float> [[MANT]]
+float4x4 test_frexp_f4x4(float4x4 X, out float4x4 E) { return frexp(X, E); }
+
+// Direct calls to the builtin store the exponent straight to the lvalue.
+// CHECK-LABEL: test_frexp_builtin_f3x3
+// CHECK:      [[FREXP:%.*]] = call { <9 x float>, <9 x i32> } @llvm.frexp.v9f32.v9i32(<9 x float> %{{.*}})
+// CHECK-NEXT: [[MANT:%.*]] = extractvalue { <9 x float>, <9 x i32> } [[FREXP]], 0
+// CHECK-NEXT: [[EXPI:%.*]] = extractvalue { <9 x float>, <9 x i32> } [[FREXP]], 1
+// CHECK-NEXT: [[EXPF:%.*]] = sitofp {{.*}}<9 x i32> [[EXPI]] to <9 x float>
+// CHECK-NEXT: store <9 x float> [[EXPF]], ptr [[E:%.*]], align 4
+// CHECK:      ret <9 x float> [[MANT]]
+float3x3 test_frexp_builtin_f3x3(float3x3 X) {
+  float3x3 E;
+  return __builtin_hlsl_elementwise_frexp(X, E);
+}
diff --git a/clang/test/SemaHLSL/BuiltIns/frexp-errors.hlsl b/clang/test/SemaHLSL/BuiltIns/frexp-errors.hlsl
new file mode 100644
index 00000000000000..be0f75d21643f7
--- /dev/null
+++ b/clang/test/SemaHLSL/BuiltIns/frexp-errors.hlsl
@@ -0,0 +1,65 @@
+// RUN: %clang_cc1 -finclude-default-header -triple dxil-pc-shadermodel6.6-library %s -fnative-half-type -fnative-int16-type -verify
+// RUN: %clang_cc1 -finclude-default-header -triple dxil-pc-shadermodel6.6-library %s -verify
+
+float test_no_second_arg(float X) {
+  return __builtin_hlsl_elementwise_frexp(X);
+  // expected-error at -1 {{too few arguments to function call, expected 2, have 1}}
+}
+
+float test_too_many_arg(float X) {
+  float E, F;
+  return __builtin_hlsl_elementwise_frexp(X, E, F);
+  // expected-error at -1 {{too many arguments to function call, expected 2, have 3}}
+}
+
+float test_first_arg_double(double X) {
+  float E;
+  return __builtin_hlsl_elementwise_frexp(X, E);
+  // expected-error at -1 {{invalid operand of type 'double' where 'float' or a vector or matrix of such type is required}}
+}
+
+float test_first_arg_int(int X) {
+  float E;
+  return __builtin_hlsl_elementwise_frexp(X, E);
+  // expected-error at -1 {{invalid operand of type 'int' where 'float' or a vector or matrix of such type is required}}
+}
+
+float test_first_arg_bool(bool X) {
+  float E;
+  return __builtin_hlsl_elementwise_frexp(X, E);
+  // expected-error at -1 {{invalid operand of type 'bool' where 'float' or a vector or matrix of such type is required}}
+}
+
+float test_second_arg_int(float X) {
+  int E;
+  return __builtin_hlsl_elementwise_frexp(X, E);
+  // expected-error at -1 {{invalid operand of type 'int' where 'float' or a vector or matrix of such type is required}}
+}
+
+float2 test_shape_mismatch(float2 X) {
+  float3 E;
+  return __builtin_hlsl_elementwise_frexp(X, E);
+  // expected-error at -1 {{all arguments to '__builtin_hlsl_elementwise_frexp' must have the same type}}
+}
+
+float test_second_arg_rvalue(float X) {
+  return __builtin_hlsl_elementwise_frexp(X, 1.0f);
+  // expected-error at -1 {{cannot bind non-lvalue argument '1.F' to out parameter}}
+}
+
+float test_second_arg_const(float X) {
+  const float E = 0;
+  return __builtin_hlsl_elementwise_frexp(X, E);
+  // expected-error at -1 {{cannot bind non-lvalue argument 'E' to out parameter}}
+}
+
+float test_overload_rvalue(float X) {
+  return frexp(X, 1.0f);
+  // expected-error at -1 {{cannot bind non-lvalue argument '1.F' to out parameter}}
+}
+
+half test_builtin_half(half X) {
+  half E;
+  return __builtin_hlsl_elementwise_frexp(X, E);
+  // expected-error at -1 {{invalid operand of type 'half' where 'float' or a vector or matrix of such type is required}}
+}
diff --git a/clang/test/SemaHLSL/BuiltIns/frexp_mat-errors.hlsl b/clang/test/SemaHLSL/BuiltIns/frexp_mat-errors.hlsl
new file mode 100644
index 00000000000000..3b6a013ff7c03b
--- /dev/null
+++ b/clang/test/SemaHLSL/BuiltIns/frexp_mat-errors.hlsl
@@ -0,0 +1,53 @@
+// RUN: %clang_cc1 -finclude-default-header -triple dxil-pc-shadermodel6.6-library %s -fnative-half-type -fnative-int16-type -emit-llvm-only -disable-llvm-passes -verify
+
+float2x2 test_builtin_int_matrix(int2x2 X) {
+  float2x2 E;
+  return __builtin_hlsl_elementwise_frexp(X, E);
+  // expected-error at -1 {{invalid operand of type 'int2x2' (aka 'matrix<int, 2, 2>') where 'float' or a vector or matrix of such type is required}}
+}
+
+float2x2 test_builtin_bool_matrix(bool2x2 X) {
+  float2x2 E;
+  return __builtin_hlsl_elementwise_frexp(X, E);
+  // expected-error at -1 {{invalid operand of type 'bool2x2' (aka 'matrix<bool, 2, 2>') where 'float' or a vector or matrix of such type is required}}
+}
+
+float2x2 test_builtin_double_matrix(double2x2 X) {
+  float2x2 E;
+  return __builtin_hlsl_elementwise_frexp(X, E);
+  // expected-error at -1 {{invalid operand of type 'double2x2' (aka 'matrix<double, 2, 2>') where 'float' or a vector or matrix of such type is required}}
+}
+
+half2x2 test_builtin_half_matrix(half2x2 X) {
+  half2x2 E;
+  return __builtin_hlsl_elementwise_frexp(X, E);
+  // expected-error at -1 {{invalid operand of type 'half2x2' (aka 'matrix<half, 2, 2>') where 'float' or a vector or matrix of such type is required}}
+}
+
+float2x2 test_builtin_exp_int_matrix(float2x2 X) {
+  int2x2 E;
+  return __builtin_hlsl_elementwise_frexp(X, E);
+  // expected-error at -1 {{invalid operand of type 'int2x2' (aka 'matrix<int, 2, 2>') where 'float' or a vector or matrix of such type is required}}
+}
+
+float2x2 test_builtin_dim_mismatch(float2x2 X) {
+  float2x3 E;
+  return __builtin_hlsl_elementwise_frexp(X, E);
+  // expected-error at -1 {{all arguments to '__builtin_hlsl_elementwise_frexp' must have the same type}}
+}
+
+float2x2 test_builtin_matrix_vector_mismatch(float2x2 X) {
+  float4 E;
+  return __builtin_hlsl_elementwise_frexp(X, E);
+  // expected-error at -1 {{all arguments to '__builtin_hlsl_elementwise_frexp' must have the same type}}
+}
+
+float2x2 test_builtin_exp_not_lvalue(float2x2 X) {
+  return __builtin_hlsl_elementwise_frexp(X, X * 2);
+  // expected-error at -1 {{cannot bind non-lvalue argument 'X * 2' to out parameter}}
+}
+
+float2x2 test_overload_exp_not_lvalue(float2x2 X) {
+  return frexp(X, X * 2);
+  // expected-error at -1 {{cannot bind non-lvalue argument 'X * 2' to out parameter}}
+}
diff --git a/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp b/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
index e7aac17b47cb79..1b2e321b5a3eaa 100644
--- a/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
+++ b/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
@@ -212,6 +212,7 @@ static bool isIntrinsicExpansion(Function &F) {
   case Intrinsic::abs:
   case Intrinsic::atan2:
   case Intrinsic::copysign:
+  case Intrinsic::frexp:
   case Intrinsic::fshl:
   case Intrinsic::fshr:
   case Intrinsic::exp:
@@ -1104,6 +1105,49 @@ static Value *expandCopySignIntrinsic(CallInst *Orig) {
   return Builder.CreateBitCast(CombinedInt, Ty);
 }
 
+// Expand llvm.frexp for 32-bit floats by splitting the IEEE bit pattern. The
+// mantissa keeps the sign of X and has its exponent field set to that of 0.5,
+// so it lies in [0.5, 1). Zeros, denormals, infinities and NaNs return X as the
+// mantissa with an exponent of 0.
+static Value *expandFrexpIntrinsic(CallInst *Orig) {
+  Value *X = Orig->getArgOperand(0);
+  Type *Ty = X->getType();
+  auto *RetTy = cast<StructType>(Orig->getType());
+  Type *ExpTy = RetTy->getElementType(1);
+
+  if (!Ty->getScalarType()->isFloatTy())
+    reportFatalUsageError("frexp is only supported for 32-bit floats in DXIL");
+
+  IRBuilder<> Builder(Orig);
+  Type *IntTy = Ty->getWithNewType(Builder.getInt32Ty());
+
+  // `ConstantInt::get` broadcasts to a splat when `IntTy` is a vector.
+  Constant *ExpMask = ConstantInt::get(IntTy, 0x7f800000);
+  Constant *MantSignMask = ConstantInt::get(IntTy, 0x807fffff);
+  Constant *HalfExpBits = ConstantInt::get(IntTy, 0x3f000000);
+  Constant *ExpShift = ConstantInt::get(IntTy, 23);
+  Constant *ExpBias = ConstantInt::get(IntTy, 126);
+  Constant *Zero = ConstantInt::get(IntTy, 0);
+
+  Value *Bits = Builder.CreateBitCast(X, IntTy);
+  Value *ExpBits = Builder.CreateAnd(Bits, ExpMask);
+  Value *IsSpecial = Builder.CreateOr(Builder.CreateICmpEQ(ExpBits, Zero),
+                                      Builder.CreateICmpEQ(ExpBits, ExpMask));
+
+  Value *Mant =
+      Builder.CreateOr(Builder.CreateAnd(Bits, MantSignMask), HalfExpBits);
+  Mant = Builder.CreateSelect(IsSpecial, X, Builder.CreateBitCast(Mant, Ty));
+
+  Value *Exp =
+      Builder.CreateSub(Builder.CreateLShr(ExpBits, ExpShift), ExpBias);
+  Exp = Builder.CreateSelect(IsSpecial, Zero, Exp);
+  Exp = Builder.CreateSExtOrTrunc(Exp, ExpTy);
+
+  Value *Agg = PoisonValue::get(RetTy);
+  Agg = Builder.CreateInsertValue(Agg, Mant, 0);
+  return Builder.CreateInsertValue(Agg, Exp, 1);
+}
+
 // Expand llvm.matrix.multiply by extracting row/column vectors and computing
 // dot products.
 // Result[r,c] = dot(row_r(LHS), col_c(RHS))
@@ -1310,6 +1354,9 @@ static bool expandIntrinsic(Function &F, CallInst *Orig) {
   case Intrinsic::exp:
     Result = expandExpIntrinsic(Orig);
     break;
+  case Intrinsic::frexp:
+    Result = expandFrexpIntrinsic(Orig);
+    break;
   case Intrinsic::is_fpclass:
     Result = expandIsFPClass(Orig);
     break;
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index fbbf00dbb6c05d..4a47aeec516ade 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -1625,15 +1625,25 @@ bool SPIRVInstructionSelector::selectFrexp(Register ResVReg,
       continue;
 
     MachineIRBuilder MIRBuilder(I);
-    SPIRVTypeInst PointeeTy = GR.getSPIRVTypeForVReg(I.getOperand(1).getReg());
+    Register ExpResReg = I.getOperand(1).getReg();
+    SPIRVTypeInst PointeeTy = GR.getSPIRVTypeForVReg(ExpResReg);
+    if (!PointeeTy) {
+      // An unused exponent result may have no SPIR-V type assigned. Both
+      // extended instruction sets require a 32-bit integer exponent with the
+      // same number of components as the input.
+      PointeeTy = GR.getOrCreateSPIRVIntegerType(32, I, TII);
+      unsigned NumComponents =
+          GR.getScalarOrVectorComponentCount(I.getOperand(2).getReg());
+      if (NumComponents > 1)
+        PointeeTy =
+            GR.getOrCreateSPIRVVectorType(PointeeTy, NumComponents, I, TII);
+    }
     const SPIRVTypeInst PointerType = GR.getOrCreateSPIRVPointerType(
         PointeeTy, MIRBuilder, SPIRV::StorageClass::Function);
     Register PointerVReg =
         createVirtualRegister(PointerType, &GR, MRI, MRI->getMF());
 
     auto It = getOpVariableMBBIt(*I.getMF());
-    // An untyped pointer result type is only legal on OpUntypedVariableKHR,
-    // but not on OpVariable.
     const bool IsUntyped =
         PointerType->getOpcode() == SPIRV::OpTypeUntypedPointerKHR;
     auto VarMIB =
@@ -1644,7 +1654,7 @@ bool SPIRVInstructionSelector::selectFrexp(Register ResVReg,
             .addUse(GR.getSPIRVTypeID(PointerType))
             .addImm(static_cast<uint32_t>(SPIRV::StorageClass::Function));
     if (IsUntyped)
-      VarMIB.addUse(GR.getSPIRVTypeID(PointeeTy)); // Data Type
+      VarMIB.addUse(GR.getSPIRVTypeID(PointeeTy));
     VarMIB.constrainAllUses(TII, TRI, RBI);
 
     SPIRVTypeInst MantissaTy = GR.getSPIRVTypeForVReg(I.getOperand(2).getReg());
@@ -1657,7 +1667,6 @@ bool SPIRVInstructionSelector::selectFrexp(Register ResVReg,
         .addUse(PointerVReg)
         .constrainAllUses(TII, TRI, RBI);
 
-    Register ExpResReg = I.getOperand(1).getReg();
     if (!MRI->use_nodbg_empty(ExpResReg))
       BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpLoad))
           .addDef(ExpResReg)
@@ -1666,7 +1675,7 @@ bool SPIRVInstructionSelector::selectFrexp(Register ResVReg,
           .constrainAllUses(TII, TRI, RBI);
     return true;
   }
-  return false;
+  llvm_unreachable("no extended instruction set available for frexp");
 }
 
 bool SPIRVInstructionSelector::selectLdexp(Register ResVReg,
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
index e9e02c7f2ab280..29f88a13668fac 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
@@ -572,8 +572,14 @@ SPIRVLegalizerInfo::SPIRVLegalizerInfo(const SPIRVSubtarget &ST) {
   getActionDefinitionsBuilder(G_BR).alwaysLegal();
   getActionDefinitionsBuilder(G_BRCOND).legalFor({s1, s32});
 
-  getActionDefinitionsBuilder(G_FFREXP).legalForCartesianProduct(
-      allFloatScalarsAndVectors, {s32, v2s32, v3s32, v4s32, v8s32, v16s32});
+  getActionDefinitionsBuilder(G_FFREXP)
+      .legalForCartesianProduct(allFloatScalarsAndVectors,
+                                {s32, v2s32, v3s32, v4s32, v8s32, v16s32})
+      .fewerElementsIf(vectorElementCountIsGreaterThan(0, MaxVectorSize),
+                       LegalizeMutations::changeElementCountTo(
+                           0, ElementCount::getFixed(MaxVectorSize)))
+      // LegalizerHelper::moreElementsVector does not support G_FFREXP.
+      .scalarizeIf(numElementsNotPow2(0), 0);
 
   // TODO: Review the target OpenCL and GLSL Extended Instruction Set specs to
   // tighten these requirements. Many of these math functions are only legal on
diff --git a/llvm/test/CodeGen/DirectX/LongVector/frexp.ll b/llvm/test/CodeGen/DirectX/LongVector/frexp.ll
new file mode 100644
index 00000000000000..7639fe7b4ed731
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/LongVector/frexp.ll
@@ -0,0 +1,17 @@
+; RUN: llc -mtriple=dxil-pc-shadermodel6.8-library -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-SCALAR
+; RUN: llc -mtriple=dxil-pc-shadermodel6.9-library -stop-before=dxil-op-lower -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-VECTOR
+
+; llvm.frexp is expanded into integer operations on the float bit pattern.
+
+; CHECK-LABEL: define <17 x float> @test_frexp(
+; CHECK-SCALAR-NOT: @llvm.frexp
+; CHECK-SCALAR-COUNT-17: and i32 {{.*}}, 2139095040
+; CHECK-VECTOR-NOT: @llvm.frexp
+; CHECK-VECTOR: bitcast <17 x float> %a to <17 x i32>
+; CHECK-VECTOR: and <17 x i32> {{.*}}, splat (i32 2139095040)
+; CHECK-VECTOR: lshr <17 x i32> {{.*}}, splat (i32 23)
+define <17 x float> @test_frexp(<17 x float> %a) {
+  %r = call { <17 x float>, <17 x i32> } @llvm.frexp.v17f32.v17i32(<17 x float> %a)
+  %m = extractvalue { <17 x float>, <17 x i32> } %r, 0
+  ret <17 x float> %m
+}
diff --git a/llvm/test/CodeGen/DirectX/frexp.ll b/llvm/test/CodeGen/DirectX/frexp.ll
new file mode 100644
index 00000000000000..29fba05deff903
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/frexp.ll
@@ -0,0 +1,52 @@
+; RUN: opt -S -dxil-intrinsic-expansion -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s --check-prefix=EXPCHECK
+; RUN: opt -S -dxil-intrinsic-expansion -scalarizer -dxil-op-lower -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s --check-prefix=DOPCHECK
+
+; Make sure llvm.frexp is expanded into bit operations for DXIL.
+
+define noundef float @frexp_float(float noundef %a, ptr %e) {
+entry:
+; EXPCHECK-LABEL: define noundef float @frexp_float(
+; EXPCHECK:      [[BITS:%.*]] = bitcast float %a to i32
+; EXPCHECK-NEXT: [[EXPBITS:%.*]] = and i32 [[BITS]], 2139095040
+; EXPCHECK-NEXT: [[ISINFNAN:%.*]] = icmp eq i32 [[EXPBITS]], 2139095040
+; EXPCHECK-NEXT: [[ISZERO:%.*]] = icmp eq i32 [[EXPBITS]], 0
+; EXPCHECK-NEXT: [[SPECIAL:%.*]] = or i1 [[ISZERO]], [[ISINFNAN]]
+; EXPCHECK-NEXT: [[SIGNMANT:%.*]] = and i32 [[BITS]], -2139095041
+; EXPCHECK-NEXT: [[MANTBITS:%.*]] = or i32 [[SIGNMANT]], 1056964608
+; EXPCHECK-NEXT: [[MANTF:%.*]] = bitcast i32 [[MANTBITS]] to float
+; EXPCHECK-NEXT: [[MANT:%.*]] = select i1 [[SPECIAL]], float %a, float [[MANTF]]
+; EXPCHECK-NEXT: [[SHR:%.*]] = lshr i32 [[EXPBITS]], 23
+; EXPCHECK-NEXT: [[UNBIASED:%.*]] = sub i32 [[SHR]], 126
+; EXPCHECK-NEXT: [[EXP:%.*]] = select i1 [[SPECIAL]], i32 0, i32 [[UNBIASED]]
+; EXPCHECK-NEXT: [[AGG0:%.*]] = insertvalue { float, i32 } poison, float [[MANT]], 0
+; EXPCHECK-NEXT: [[AGG1:%.*]] = insertvalue { float, i32 } [[AGG0]], i32 [[EXP]], 1
+; EXPCHECK-NOT:  call {{.*}} @llvm.frexp
+; DOPCHECK-LABEL: define noundef float @frexp_float(
+; DOPCHECK-NOT:  @llvm.frexp
+  %r = call { float, i32 } @llvm.frexp.f32.i32(float %a)
+  %m = extractvalue { float, i32 } %r, 0
+  %x = extractvalue { float, i32 } %r, 1
+  %xf = sitofp i32 %x to float
+  store float %xf, ptr %e
+  ret float %m
+}
+
+define noundef <4 x float> @frexp_float4(<4 x float> noundef %a, ptr %e) {
+entry:
+; EXPCHECK-LABEL: define noundef <4 x float> @frexp_float4(
+; EXPCHECK:      [[BITS:%.*]] = bitcast <4 x float> %a to <4 x i32>
+; EXPCHECK-NEXT: [[EXPBITS:%.*]] = and <4 x i32> [[BITS]], splat (i32 2139095040)
+; EXPCHECK:      [[MANT:%.*]] = select <4 x i1> [[SPECIAL:%.*]], <4 x float> %a, <4 x float> {{%.*}}
+; EXPCHECK:      [[EXP:%.*]] = select <4 x i1> [[SPECIAL]], <4 x i32> zeroinitializer, <4 x i32> {{%.*}}
+; EXPCHECK-NEXT: [[AGG0:%.*]] = insertvalue { <4 x float>, <4 x i32> } poison, <4 x float> [[MANT]], 0
+; EXPCHECK-NEXT: [[AGG1:%.*]] = insertvalue { <4 x float>, <4 x i32> } [[AGG0]], <4 x i32> [[EXP]], 1
+; EXPCHECK-NOT:  call {{.*}} @llvm.frexp
+; DOPCHECK-LABEL: define noundef <4 x float> @frexp_float4(
+; DOPCHECK-NOT:  @llvm.frexp
+  %r = call { <4 x float>, <4 x i32> } @llvm.frexp.v4f32.v4i32(<4 x float> %a)
+  %m = extractvalue { <4 x float>, <4 x i32> } %r, 0
+  %x = extractvalue { <4 x float>, <4 x i32> } %r, 1
+  %xf = sitofp <4 x i32> %x to <4 x float>
+  store <4 x float> %xf, ptr %e
+  ret <4 x float> %m
+}
diff --git a/llvm/test/CodeGen/DirectX/frexp_error.ll b/llvm/test/CodeGen/DirectX/frexp_error.ll
new file mode 100644
index 00000000000000..6d571153f61b7c
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/frexp_error.ll
@@ -0,0 +1,11 @@
+; RUN: not opt -S -dxil-intrinsic-expansion -mtriple=dxil-pc-shadermodel6.3-library %s 2>&1 | FileCheck %s
+
+; DXIL only supports frexp on 32-bit floats.
+; CHECK: LLVM ERROR: frexp is only supported for 32-bit floats in DXIL
+
+define noundef half @frexp_half(half noundef %a) {
+entry:
+  %r = call { half, i32 } @llvm.frexp.f16.i32(half %a)
+  %m = extractvalue { half, i32 } %r, 0
+  ret half %m
+}
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_EXT_long_vector/frexp.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_EXT_long_vector/frexp.ll
new file mode 100644
index 00000000000000..9abe4932ba1425
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_EXT_long_vector/frexp.ll
@@ -0,0 +1,40 @@
+; RUN: llc -verify-machineinstrs -O0 --spirv-ext=+SPV_EXT_long_vector -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 --spirv-ext=+SPV_EXT_long_vector -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; CHECK-DAG: %[[#F32:]] = OpTypeFloat 32
+; CHECK-DAG: %[[#V8F32:]] = OpTypeVector %[[#F32]] 8
+; CHECK-DAG: %[[#V16F32:]] = OpTypeVector %[[#F32]] 16
+
+; Power-of-two vectors up to <16 x float> are legal as is.
+; CHECK-LABEL: Begin function frexp_v8f32
+; CHECK: OpExtInst %[[#V8F32]] %[[#]] frexp
+; CHECK-NOT: OpExtInst
+; CHECK: OpFunctionEnd
+define <8 x float> @frexp_v8f32(<8 x float> %a) {
+  %r = call { <8 x float>, <8 x i32> } @llvm.frexp.v8f32.v8i32(<8 x float> %a)
+  %m = extractvalue { <8 x float>, <8 x i32> } %r, 0
+  ret <8 x float> %m
+}
+
+; Vectors with a non-power-of-two element count are scalarized.
+; CHECK-LABEL: Begin function frexp_v5f32
+; CHECK-COUNT-5: OpExtInst %[[#F32]] %[[#]] frexp
+; CHECK-NOT: OpExtInst
+; CHECK: OpFunctionEnd
+define <5 x float> @frexp_v5f32(<5 x float> %a) {
+  %r = call { <5 x float>, <5 x i32> } @llvm.frexp.v5f32.v5i32(<5 x float> %a)
+  %m = extractvalue { <5 x float>, <5 x i32> } %r, 0
+  ret <5 x float> %m
+}
+
+; Vectors larger than <16 x float> are split.
+; CHECK-LABEL: Begin function frexp_v17f32
+; CHECK: OpExtInst %[[#V16F32]] %[[#]] frexp
+; CHECK: OpExtInst %[[#F32]] %[[#]] frexp
+; CHECK-NOT: OpExtInst
+; CHECK: OpFunctionEnd
+define <17 x float> @frexp_v17f32(<17 x float> %a) {
+  %r = call { <17 x float>, <17 x i32> } @llvm.frexp.v17f32.v17i32(<17 x float> %a)
+  %m = extractvalue { <17 x float>, <17 x i32> } %r, 0
+  ret <17 x float> %m
+}
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/frexp.ll b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/frexp.ll
new file mode 100644
index 00000000000000..b10c917a8888bd
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/frexp.ll
@@ -0,0 +1,61 @@
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv-unknown-vulkan-library %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-vulkan-library %s -o - -filetype=obj | spirv-val --target-env vulkan1.3 %}
+
+; Make sure llvm.frexp is lowered to GLSL.std.450 Frexp for Vulkan.
+
+; CHECK-DAG: %[[#glsl:]] = OpExtInstImport "GLSL.std.450"
+; CHECK-DAG: %[[#float:]] = OpTypeFloat 32
+; CHECK-DAG: %[[#int:]] = OpTypeInt 32 0
+; CHECK-DAG: %[[#vec4_float:]] = OpTypeVector %[[#float]] 4
+; CHECK-DAG: %[[#vec4_int:]] = OpTypeVector %[[#int]] 4
+; CHECK-DAG: %[[#ptr_int:]] = OpTypePointer Function %[[#int]]
+; CHECK-DAG: %[[#ptr_vec4_int:]] = OpTypePointer Function %[[#vec4_int]]
+
+define noundef float @frexp_float(float noundef %a) {
+entry:
+  ; CHECK: %[[#]] = OpFunction %[[#float]] None %[[#]]
+  ; CHECK: %[[#arg0:]] = OpFunctionParameter %[[#float]]
+  ; CHECK: %[[#var:]] = OpVariable %[[#ptr_int]] Function
+  ; CHECK: %[[#mant:]] = OpExtInst %[[#float]] %[[#glsl]] Frexp %[[#arg0]] %[[#var]]
+  ; CHECK: %[[#exp:]] = OpLoad %[[#int]] %[[#var]]
+  ; CHECK: %[[#expf:]] = OpConvertSToF %[[#float]] %[[#exp]]
+  ; CHECK: %[[#]] = OpFAdd %[[#float]] %[[#mant]] %[[#expf]]
+  %r = call { float, i32 } @llvm.frexp.f32.i32(float %a)
+  %m = extractvalue { float, i32 } %r, 0
+  %x = extractvalue { float, i32 } %r, 1
+  %xf = sitofp i32 %x to float
+  %s = fadd float %m, %xf
+  ret float %s
+}
+
+define noundef <4 x float> @frexp_float4(<4 x float> noundef %a) {
+entry:
+  ; CHECK: %[[#]] = OpFunction %[[#vec4_float]] None %[[#]]
+  ; CHECK: %[[#arg0:]] = OpFunctionParameter %[[#vec4_float]]
+  ; CHECK: %[[#var:]] = OpVariable %[[#ptr_vec4_int]] Function
+  ; CHECK: %[[#mant:]] = OpExtInst %[[#vec4_float]] %[[#glsl]] Frexp %[[#arg0]] %[[#var]]
+  ; CHECK: %[[#exp:]] = OpLoad %[[#vec4_int]] %[[#var]]
+  ; CHECK: %[[#expf:]] = OpConvertSToF %[[#vec4_float]] %[[#exp]]
+  ; CHECK: %[[#]] = OpFAdd %[[#vec4_float]] %[[#mant]] %[[#expf]]
+  %r = call { <4 x float>, <4 x i32> } @llvm.frexp.v4f32.v4i32(<4 x float> %a)
+  %m = extractvalue { <4 x float>, <4 x i32> } %r, 0
+  %x = extractvalue { <4 x float>, <4 x i32> } %r, 1
+  %xf = sitofp <4 x i32> %x to <4 x float>
+  %s = fadd <4 x float> %m, %xf
+  ret <4 x float> %s
+}
+
+; The exponent result is never extracted, e.g. when an HLSL out argument is
+; dead, so its register has no SPIR-V type.
+define noundef <4 x float> @frexp_exp_not_extracted(<4 x float> noundef %a) {
+entry:
+  ; CHECK: %[[#]] = OpFunction %[[#vec4_float]] None %[[#]]
+  ; CHECK: %[[#arg0:]] = OpFunctionParameter %[[#vec4_float]]
+  ; CHECK: %[[#var:]] = OpVariable %[[#ptr_vec4_int]] Function
+  ; CHECK: %[[#mant:]] = OpExtInst %[[#vec4_float]] %[[#glsl]] Frexp %[[#arg0]] %[[#var]]
+  ; CHECK-NOT: OpLoad %[[#vec4_int]] %[[#var]]
+  ; CHECK: OpReturnValue %[[#mant]]
+  %r = call { <4 x float>, <4 x i32> } @llvm.frexp.v4f32.v4i32(<4 x float> %a)
+  %m = extractvalue { <4 x float>, <4 x i32> } %r, 0
+  ret <4 x float> %m
+}
diff --git a/llvm/test/CodeGen/SPIRV/llvm-intrinsics/frexp.ll b/llvm/test/CodeGen/SPIRV/llvm-intrinsics/frexp.ll
index 8b02dbd32bc63c..4c5dc59975f1cd 100644
--- a/llvm/test/CodeGen/SPIRV/llvm-intrinsics/frexp.ll
+++ b/llvm/test/CodeGen/SPIRV/llvm-intrinsics/frexp.ll
@@ -187,3 +187,15 @@ define void @frexp_f64(ptr %out, double %x) {
   store i32 %exp, ptr %exp.ptr
   ret void
 }
+
+; The exponent result is never extracted, so its register has no SPIR-V type.
+; CHECK: %[[#x_var5:]] = OpFunctionParameter %[[#vec4_float_type]]
+; CHECK: %[[#var11:]] = OpVariable %[[#fn_ptr_type_vec4_i32]] Function
+; CHECK: %[[#extinst11:]] = OpExtInst %[[#vec4_float_type]] %[[#extinst_id]] frexp %[[#x_var5]] %[[#var11]]
+; CHECK-NOT: OpLoad %[[#vec4_int_type]] %[[#var11]]
+; CHECK: OpReturnValue %[[#extinst11]]
+define <4 x float> @frexp_exp_not_extracted(<4 x float> %x) {
+  %ret = call { <4 x float>, <4 x i32> } @llvm.frexp.v4f32.v4i32(<4 x float> %x)
+  %mantissa = extractvalue { <4 x float>, <4 x i32> } %ret, 0
+  ret <4 x float> %mantissa
+}



More information about the cfe-commits mailing list