[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