[llvm] [SelectionDAG] Sign-extend sub-int FPOWI/FLDEXP exponent instead of asserting (PR #200621)

Justin Lebar via llvm-commits llvm-commits at lists.llvm.org
Sun May 31 21:57:42 PDT 2026


https://github.com/jlebar updated https://github.com/llvm/llvm-project/pull/200621

>From 933a8ca16426a28b621c9852ac77044fe6050567 Mon Sep 17 00:00:00 2001
From: Justin Lebar <justin.lebar at gmail.com>
Date: Sat, 30 May 2026 13:55:20 -0700
Subject: [PATCH] [SelectionDAG] Don't assert on an illegal FPOWI/FLDEXP
 exponent

PromoteIntOp_ExpOp is reached precisely when the exponent type is
illegal -- typically narrower than int. It asserted the exponent width
already equalled sizeof(int), crashing +asserts builds on that common
case (e.g. @llvm.ldexp.f64.i16). But makeLibCall already sign-extends a
narrower-than-int exponent to int, so release builds lowered it
correctly; only the assert was wrong.

A wider-than-int exponent (e.g. @llvm.ldexp.f64.i48, where i48 is illegal
and promoted through this path) can't be passed in an int, so emit an
error and return poison -- matching the soften/expand/LegalizeDAG paths
-- instead of silently truncating.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply at anthropic.com>
---
 .../SelectionDAG/LegalizeIntegerTypes.cpp     | 17 ++++--
 llvm/test/CodeGen/AArch64/ldexp.ll            | 59 +++++++++++++++++++
 .../powi-ldexp-promote-libcall-error.ll       | 24 ++++++++
 3 files changed, 96 insertions(+), 4 deletions(-)
 create mode 100644 llvm/test/CodeGen/AArch64/powi-ldexp-promote-libcall-error.ll

diff --git a/llvm/lib/CodeGen/SelectionDAG/LegalizeIntegerTypes.cpp b/llvm/lib/CodeGen/SelectionDAG/LegalizeIntegerTypes.cpp
index 86617b6dd33de..17922272a3b19 100644
--- a/llvm/lib/CodeGen/SelectionDAG/LegalizeIntegerTypes.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/LegalizeIntegerTypes.cpp
@@ -2821,10 +2821,19 @@ SDValue DAGTypeLegalizer::PromoteIntOp_ExpOp(SDNode *N) {
   // we rewrite to a libcall here directly, letting makeLibCall handle promotion
   // if the target accepts it according to shouldSignExtendTypeInLibCall.
 
-  // The exponent should fit in a sizeof(int) type for the libcall to be valid.
-  assert(DAG.getLibInfo().getIntSize() ==
-             N->getOperand(1 + OpOffset).getValueType().getSizeInBits() &&
-         "POWI exponent should match with sizeof(int) when doing the libcall.");
+  // A wider-than-int exponent can't be passed in an int (there's no wider
+  // libcall), so bail like the soften/expand paths. A narrower one is
+  // sign-extended to int by the makeLibCall below.
+  if (N->getOperand(1 + OpOffset).getScalarValueSizeInBits() >
+      DAG.getLibInfo().getIntSize()) {
+    DAG.getContext()->emitError(
+        "powi/ldexp exponent does not match sizeof(int)");
+    if (IsStrict)
+      ReplaceValueWith(SDValue(N, 1), Chain);
+    ReplaceValueWith(SDValue(N, 0), DAG.getPOISON(N->getValueType(0)));
+    return SDValue();
+  }
+
   TargetLowering::MakeLibCallOptions CallOptions;
   CallOptions.setIsSigned(true);
   SDValue Ops[2] = {N->getOperand(0 + OpOffset), N->getOperand(1 + OpOffset)};
diff --git a/llvm/test/CodeGen/AArch64/ldexp.ll b/llvm/test/CodeGen/AArch64/ldexp.ll
index 308e861abb6d4..51e85cb82337f 100644
--- a/llvm/test/CodeGen/AArch64/ldexp.ll
+++ b/llvm/test/CodeGen/AArch64/ldexp.ll
@@ -56,6 +56,65 @@ entry:
   ret double %call
 }
 
+; A sub-int exponent must be sign-extended to int on the libcall path;
+; PromoteIntOp_ExpOp must not crash on it.
+define double @testExpIntrinsic_i16(double %val, i16 %a) {
+; SVELINUX-LABEL: testExpIntrinsic_i16:
+; SVELINUX:       // %bb.0: // %entry
+; SVELINUX-NEXT:    str x30, [sp, #-16]! // 8-byte Folded Spill
+; SVELINUX-NEXT:    .cfi_def_cfa_offset 16
+; SVELINUX-NEXT:    .cfi_offset w30, -16
+; SVELINUX-NEXT:    sxth w0, w0
+; SVELINUX-NEXT:    bl ldexp
+; SVELINUX-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; SVELINUX-NEXT:    ret
+;
+; GISEL-LABEL: testExpIntrinsic_i16:
+; GISEL:       // %bb.0: // %entry
+; GISEL-NEXT:    str x30, [sp, #-16]! // 8-byte Folded Spill
+; GISEL-NEXT:    .cfi_def_cfa_offset 16
+; GISEL-NEXT:    .cfi_offset w30, -16
+; GISEL-NEXT:    sxth w0, w0
+; GISEL-NEXT:    bl ldexp
+; GISEL-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; GISEL-NEXT:    ret
+;
+; SVEWINDOWS-LABEL: testExpIntrinsic_i16:
+; SVEWINDOWS:       .seh_proc testExpIntrinsic_i16
+; SVEWINDOWS-NEXT:  // %bb.0: // %entry
+; SVEWINDOWS-NEXT:    str x30, [sp, #-16]! // 8-byte Folded Spill
+; SVEWINDOWS-NEXT:    .seh_save_reg_x x30, 16
+; SVEWINDOWS-NEXT:    .seh_endprologue
+; SVEWINDOWS-NEXT:    sxth w0, w0
+; SVEWINDOWS-NEXT:    bl ldexp
+; SVEWINDOWS-NEXT:    .seh_startepilogue
+; SVEWINDOWS-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; SVEWINDOWS-NEXT:    .seh_save_reg_x x30, 16
+; SVEWINDOWS-NEXT:    .seh_endepilogue
+; SVEWINDOWS-NEXT:    ret
+; SVEWINDOWS-NEXT:    .seh_endfunclet
+; SVEWINDOWS-NEXT:    .seh_endproc
+;
+; WINDOWS-LABEL: testExpIntrinsic_i16:
+; WINDOWS:       .seh_proc testExpIntrinsic_i16
+; WINDOWS-NEXT:  // %bb.0: // %entry
+; WINDOWS-NEXT:    str x30, [sp, #-16]! // 8-byte Folded Spill
+; WINDOWS-NEXT:    .seh_save_reg_x x30, 16
+; WINDOWS-NEXT:    .seh_endprologue
+; WINDOWS-NEXT:    sxth w0, w0
+; WINDOWS-NEXT:    bl ldexp
+; WINDOWS-NEXT:    .seh_startepilogue
+; WINDOWS-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; WINDOWS-NEXT:    .seh_save_reg_x x30, 16
+; WINDOWS-NEXT:    .seh_endepilogue
+; WINDOWS-NEXT:    ret
+; WINDOWS-NEXT:    .seh_endfunclet
+; WINDOWS-NEXT:    .seh_endproc
+entry:
+  %call = tail call fast double @llvm.ldexp.f64.i16(double %val, i16 %a)
+  ret double %call
+}
+
 define float @testExpf(float %val, i32 %a) {
 ; SVELINUX-LABEL: testExpf:
 ; SVELINUX:       // %bb.0: // %entry
diff --git a/llvm/test/CodeGen/AArch64/powi-ldexp-promote-libcall-error.ll b/llvm/test/CodeGen/AArch64/powi-ldexp-promote-libcall-error.ll
new file mode 100644
index 0000000000000..14830bdddcaa5
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/powi-ldexp-promote-libcall-error.ll
@@ -0,0 +1,24 @@
+; RUN: not llc -mtriple=aarch64-linux-gnu -filetype=null %s 2>&1 | FileCheck %s
+
+; A powi/ldexp exponent wider than sizeof(int) can't be passed to the libcall,
+; so PromoteIntOp_ExpOp must emit a clean error rather than crash (assertions)
+; or silently truncate. i48 is illegal and promoted through that path here; i64
+; would be legal and i128 expanded, so neither would reach it.
+
+; CHECK: error: powi/ldexp exponent does not match sizeof(int)
+define double @ldexp_f64_i48(double %val, i48 %a) {
+  %call = call double @llvm.ldexp.f64.i48(double %val, i48 %a)
+  ret double %call
+}
+
+; CHECK: error: powi/ldexp exponent does not match sizeof(int)
+define double @powi_f64_i48(double %val, i48 %a) {
+  %call = call double @llvm.powi.f64.i48(double %val, i48 %a)
+  ret double %call
+}
+
+; CHECK: error: powi/ldexp exponent does not match sizeof(int)
+define double @ldexp_f64_i48_strictfp(double %val, i48 %a) strictfp {
+  %call = call double @llvm.experimental.constrained.ldexp.f64.i48(double %val, i48 %a, metadata !"round.tonearest", metadata !"fpexcept.strict")
+  ret double %call
+}



More information about the llvm-commits mailing list