[llvm] [AMDGPU] Fix miscompile folding math calls with poison/undef vector lanes (PR #216971)

Arseniy Obolenskiy via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 18 03:03:05 PDT 2026


https://github.com/aobolensk updated https://github.com/llvm/llvm-project/pull/216971

>From 09b624829804d2fcf1b14fd58983a96a74e241c5 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Tue, 18 Aug 2026 11:46:22 +0200
Subject: [PATCH] [AMDGPU] Fix miscompile folding math calls with poison/undef
 vector lanes

A poison or undef lane made evaluateCall ConstantDataVector cast fail, and evaluateScalarMathFunc silently treated the missing operand as 0.0, producing wrong results instead of bailing out
---
 llvm/lib/Target/AMDGPU/AMDGPULibCalls.cpp     | 43 ++++-----
 .../amdgpu-simplify-libcall-constant-fold.ll  | 88 +++++++++++++++++++
 2 files changed, 111 insertions(+), 20 deletions(-)
 create mode 100644 llvm/test/CodeGen/AMDGPU/amdgpu-simplify-libcall-constant-fold.ll

diff --git a/llvm/lib/Target/AMDGPU/AMDGPULibCalls.cpp b/llvm/lib/Target/AMDGPU/AMDGPULibCalls.cpp
index 7291fdaca0d69..9524b8d306555 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPULibCalls.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPULibCalls.cpp
@@ -1820,23 +1820,16 @@ bool AMDGPULibCalls::fold_sincos(FPMathOperator *FPOp, IRBuilder<> &B,
 bool AMDGPULibCalls::evaluateScalarMathFunc(const FuncInfo &FInfo,
                                             APFloat &Res0, APFloat &Res1,
                                             Constant *copr0, Constant *copr1) {
-  // By default, opr0/opr1/opr3 holds values of float/double type.
-  // If they are not float/double, each function has to its
-  // operand separately.
-  double opr0 = 0.0, opr1 = 0.0;
+  // Every function handled below reads its first operand as a floating-point
+  // value. Refuse anything else, e.g. a poison vector lane: silently treating
+  // it as 0.0 misfolds the whole call.
   ConstantFP *fpopr0 = dyn_cast_or_null<ConstantFP>(copr0);
-  ConstantFP *fpopr1 = dyn_cast_or_null<ConstantFP>(copr1);
-  if (fpopr0) {
-    opr0 = (getArgType(FInfo) == AMDGPULibFunc::F64)
-             ? fpopr0->getValueAPF().convertToDouble()
-             : (double)fpopr0->getValueAPF().convertToFloat();
-  }
+  if (!fpopr0)
+    return false;
 
-  if (fpopr1) {
-    opr1 = (getArgType(FInfo) == AMDGPULibFunc::F64)
-             ? fpopr1->getValueAPF().convertToDouble()
-             : (double)fpopr1->getValueAPF().convertToFloat();
-  }
+  double opr0 = (getArgType(FInfo) == AMDGPULibFunc::F64)
+                    ? fpopr0->getValueAPF().convertToDouble()
+                    : (double)fpopr0->getValueAPF().convertToFloat();
 
   switch (FInfo.getId()) {
   default:
@@ -1952,9 +1945,16 @@ bool AMDGPULibCalls::evaluateScalarMathFunc(const FuncInfo &FInfo,
 
   // two-arg functions
   case AMDGPULibFunc::EI_POW:
-  case AMDGPULibFunc::EI_POWR:
+  case AMDGPULibFunc::EI_POWR: {
+    ConstantFP *fpopr1 = dyn_cast_or_null<ConstantFP>(copr1);
+    if (!fpopr1)
+      return false;
+    double opr1 = (getArgType(FInfo) == AMDGPULibFunc::F64)
+                      ? fpopr1->getValueAPF().convertToDouble()
+                      : (double)fpopr1->getValueAPF().convertToFloat();
     Res0 = APFloat{pow(opr0, opr1)};
     return true;
+  }
 
   case AMDGPULibFunc::EI_POWN: {
     if (ConstantInt *iopr1 = dyn_cast_or_null<ConstantInt>(copr1)) {
@@ -2014,11 +2014,14 @@ bool AMDGPULibCalls::evaluateCall(CallInst *aCI, const FuncInfo &FInfo) {
       return false;
     }
   } else {
-    ConstantDataVector *CDV0 = dyn_cast_or_null<ConstantDataVector>(copr0);
-    ConstantDataVector *CDV1 = dyn_cast_or_null<ConstantDataVector>(copr1);
+    // An operand of a vector variant is not necessarily a vector: sincos takes
+    // a pointer as its second operand, and fmin/fmax/ldexp accept an
+    // implicitly splatted scalar. Only index into actual vectors.
+    Constant *CV0 = copr0 && copr0->getType()->isVectorTy() ? copr0 : nullptr;
+    Constant *CV1 = copr1 && copr1->getType()->isVectorTy() ? copr1 : nullptr;
     for (int i = 0; i < FuncVecSize; ++i) {
-      Constant *celt0 = CDV0 ? CDV0->getElementAsConstant(i) : nullptr;
-      Constant *celt1 = CDV1 ? CDV1->getElementAsConstant(i) : nullptr;
+      Constant *celt0 = CV0 ? CV0->getAggregateElement((unsigned)i) : nullptr;
+      Constant *celt1 = CV1 ? CV1->getAggregateElement((unsigned)i) : nullptr;
       if (!evaluateScalarMathFunc(FInfo, Val0.emplace_back(0.0),
                                   Val1.emplace_back(0.0), celt0, celt1)) {
         return false;
diff --git a/llvm/test/CodeGen/AMDGPU/amdgpu-simplify-libcall-constant-fold.ll b/llvm/test/CodeGen/AMDGPU/amdgpu-simplify-libcall-constant-fold.ll
new file mode 100644
index 0000000000000..468c11f95e9a6
--- /dev/null
+++ b/llvm/test/CodeGen/AMDGPU/amdgpu-simplify-libcall-constant-fold.ll
@@ -0,0 +1,88 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -mtriple=amdgpu-amd-amdhsa -passes=amdgpu-simplifylib %s | FileCheck %s
+
+; Constant folding of fast-math library calls (evaluateCall).
+
+declare float @_Z3sinf(float)
+declare <2 x float> @_Z3cosDv2_f(<2 x float>)
+declare <2 x float> @_Z3powDv2_fS_(<2 x float>, <2 x float>)
+declare <2 x float> @_Z4fminDv2_ff(<2 x float>, float)
+declare <2 x float> @_Z4fminDv2_fS_(float, <2 x float>)
+declare <2 x float> @_Z6sincosDv2_fPU3AS5S_(<2 x float>, ptr addrspace(5))
+
+define <2 x float> @fold_cos_v2f32() {
+; CHECK-LABEL: define <2 x float> @fold_cos_v2f32() {
+; CHECK-NEXT:    ret <2 x float> <float f0xBF7D7026, float f0xBF275530>
+;
+  %r = call fast <2 x float> @_Z3cosDv2_f(<2 x float> <float 3.0, float 4.0>)
+  ret <2 x float> %r
+}
+
+define <2 x float> @fold_cos_v2f32_zeroinit() {
+; CHECK-LABEL: define <2 x float> @fold_cos_v2f32_zeroinit() {
+; CHECK-NEXT:    ret <2 x float> splat (float 1.000000e+00)
+;
+  %r = call fast <2 x float> @_Z3cosDv2_f(<2 x float> zeroinitializer)
+  ret <2 x float> %r
+}
+
+; A poison lane must not turn the defined lanes into an evaluation at 0.0;
+; cos(3.0) is about -0.99, not cos(0.0) == 1.0.
+define <2 x float> @no_fold_cos_v2f32_poison_lane() {
+; CHECK-LABEL: define <2 x float> @no_fold_cos_v2f32_poison_lane() {
+; CHECK-NEXT:    [[R:%.*]] = call fast <2 x float> @_Z3cosDv2_f(<2 x float> <float 3.000000e+00, float poison>)
+; CHECK-NEXT:    ret <2 x float> [[R]]
+;
+  %r = call fast <2 x float> @_Z3cosDv2_f(<2 x float> <float 3.0, float poison>)
+  ret <2 x float> %r
+}
+
+; Likewise for the second operand: a poison exponent lane must not be
+; evaluated as pow(x, 0.0) == 1.0. (The defined lanes are still folded, by the
+; pow(x, 2) => x*x expansion.)
+define <2 x float> @pow_v2f32_poison_exp_lane() {
+; CHECK-LABEL: define <2 x float> @pow_v2f32_poison_exp_lane() {
+; CHECK-NEXT:    ret <2 x float> <float 9.000000e+00, float 1.600000e+01>
+;
+  %r = call fast <2 x float> @_Z3powDv2_fS_(<2 x float> <float 3.0, float 4.0>, <2 x float> <float 2.0, float poison>)
+  ret <2 x float> %r
+}
+
+define float @no_fold_sin_f32_poison() {
+; CHECK-LABEL: define float @no_fold_sin_f32_poison() {
+; CHECK-NEXT:    [[R:%.*]] = call fast float @_Z3sinf(float poison)
+; CHECK-NEXT:    ret float [[R]]
+;
+  %r = call fast float @_Z3sinf(float poison)
+  ret float %r
+}
+
+; fmin/fmax/ldexp signatures accept an implicitly splatted scalar operand, and
+; sincos takes a pointer; the vector evaluation loop must not index into them.
+define <2 x float> @fmin_v2f32_scalar_second_arg() {
+; CHECK-LABEL: define <2 x float> @fmin_v2f32_scalar_second_arg() {
+; CHECK-NEXT:    [[R:%.*]] = call fast <2 x float> @llvm.minnum.v2f32(<2 x float> <float 1.000000e+00, float 2.000000e+00>, <2 x float> splat (float 1.500000e+00))
+; CHECK-NEXT:    ret <2 x float> [[R]]
+;
+  %r = call fast <2 x float> @_Z4fminDv2_ff(<2 x float> <float 1.0, float 2.0>, float 1.5)
+  ret <2 x float> %r
+}
+
+define <2 x float> @fmin_v2f32_scalar_first_arg() {
+; CHECK-LABEL: define <2 x float> @fmin_v2f32_scalar_first_arg() {
+; CHECK-NEXT:    [[R:%.*]] = call fast <2 x float> @llvm.minnum.v2f32(<2 x float> splat (float 1.500000e+00), <2 x float> <float 1.000000e+00, float 2.000000e+00>)
+; CHECK-NEXT:    ret <2 x float> [[R]]
+;
+  %r = call fast <2 x float> @_Z4fminDv2_fS_(float 1.5, <2 x float> <float 1.0, float 2.0>)
+  ret <2 x float> %r
+}
+
+define <2 x float> @sincos_v2f32(ptr addrspace(5) %p) {
+; CHECK-LABEL: define <2 x float> @sincos_v2f32(
+; CHECK-SAME: ptr addrspace(5) [[P:%.*]]) {
+; CHECK-NEXT:    store <2 x float> <float f0x3F0A5140, float f0xBED51133>, ptr addrspace(5) [[P]], align 8
+; CHECK-NEXT:    ret <2 x float> <float f0x3F576AA4, float f0x3F68C7B7>
+;
+  %r = call fast <2 x float> @_Z6sincosDv2_fPU3AS5S_(<2 x float> <float 1.0, float 2.0>, ptr addrspace(5) %p)
+  ret <2 x float> %r
+}



More information about the llvm-commits mailing list