[llvm] [AMDGPU] Fix fmed3 constant-fold sign-of-zero miscompile (PR #201896)

Wooseok Lee via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 8 06:07:26 PDT 2026


https://github.com/wooseoklee updated https://github.com/llvm/llvm-project/pull/201896

>From 0fa80eb1391863ef66457ba93d747370a260dee0 Mon Sep 17 00:00:00 2001
From: wooseoklee <wolee at amd.com>
Date: Fri, 5 Jun 2026 12:26:58 -0500
Subject: [PATCH] [AMDGPU] Fix fmed3 constant-fold sign-of-zero miscompile

fmed3AMDGCN identifies the maximum of three operands via APFloat::compare,
then returns maxnum of the remaining two as the median. APFloat::compare
treats +0 and -0 as equal (cmpEqual), so for inputs like fmed3(-0, -0, +0)
Max3=+0 incorrectly compares equal to Src0=-0, causing the wrong arm to
fire and returning +0 instead of the correct median -0.

Hardware v_med3_f32 sorts with -0 < +0 uniformly across all generations,
so fmed3(-0, -0, +0) must return -0.

Fix by replacing APFloat::compare equality checks with APFloat::bitwiseIsEqual,
which distinguishes +0 from -0 by bit pattern. This is strictly correct:
the only case where compare returns cmpEqual but bitwiseIsEqual returns false
is the +0/-0 pair, which is exactly the misidentification being fixed. All
three arms of the helper are covered.

Affected inputs (all returning wrong +0 before the fix):
  fmed3(-0, -0, +0), fmed3(-0, +0, -0), fmed3(+0, -0, -0)
  fmed3(N, -0, +0), fmed3(-0, N, +0), fmed3(-0, +0, N)  where N < 0
---
 .../AMDGPU/AMDGPUInstCombineIntrinsic.cpp     | 12 ++--
 .../Transforms/InstCombine/AMDGPU/fmed3.ll    | 60 +++++++++++++++++++
 2 files changed, 66 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Target/AMDGPU/AMDGPUInstCombineIntrinsic.cpp b/llvm/lib/Target/AMDGPU/AMDGPUInstCombineIntrinsic.cpp
index eaa3a5c61d0cd..072603f9705c4 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPUInstCombineIntrinsic.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPUInstCombineIntrinsic.cpp
@@ -54,14 +54,14 @@ static APFloat fmed3AMDGCN(const APFloat &Src0, const APFloat &Src1,
                            const APFloat &Src2) {
   APFloat Max3 = maxnum(maxnum(Src0, Src1), Src2);
 
-  APFloat::cmpResult Cmp0 = Max3.compare(Src0);
-  assert(Cmp0 != APFloat::cmpUnordered && "nans handled separately");
-  if (Cmp0 == APFloat::cmpEqual)
+  assert(Max3.compare(Src0) != APFloat::cmpUnordered &&
+         "nans handled separately");
+  if (Max3.bitwiseIsEqual(Src0))
     return maxnum(Src1, Src2);
 
-  APFloat::cmpResult Cmp1 = Max3.compare(Src1);
-  assert(Cmp1 != APFloat::cmpUnordered && "nans handled separately");
-  if (Cmp1 == APFloat::cmpEqual)
+  assert(Max3.compare(Src1) != APFloat::cmpUnordered &&
+         "nans handled separately");
+  if (Max3.bitwiseIsEqual(Src1))
     return maxnum(Src0, Src2);
 
   return maxnum(Src0, Src1);
diff --git a/llvm/test/Transforms/InstCombine/AMDGPU/fmed3.ll b/llvm/test/Transforms/InstCombine/AMDGPU/fmed3.ll
index 252e78fd179f7..86b78ea5770de 100644
--- a/llvm/test/Transforms/InstCombine/AMDGPU/fmed3.ll
+++ b/llvm/test/Transforms/InstCombine/AMDGPU/fmed3.ll
@@ -268,6 +268,66 @@ define float @fmed3_constant_src2_1_f32(float %x, float %y) #1 {
   ret float %med3
 }
 
+; fmed3(-0, -0, +0) should match hardware median, -0 (sorted: {-0, -0, +0}, middle = -0).
+define float @fmed3_neg0_neg0_pos0_f32() #1 {
+; CHECK-LABEL: define float @fmed3_neg0_neg0_pos0_f32(
+; CHECK-SAME: ) #[[ATTR1]] {
+; CHECK-NEXT:    ret float -0.000000e+00
+;
+  %med3 = call float @llvm.amdgcn.fmed3.f32(float -0.0, float -0.0, float 0.0)
+  ret float %med3
+}
+
+; fmed3(-0, +0, -0): exercises the second arm of fmed3AMDGCN (Src1 is max).
+define float @fmed3_neg0_pos0_neg0_f32() #1 {
+; CHECK-LABEL: define float @fmed3_neg0_pos0_neg0_f32(
+; CHECK-SAME: ) #[[ATTR1]] {
+; CHECK-NEXT:    ret float -0.000000e+00
+;
+  %med3 = call float @llvm.amdgcn.fmed3.f32(float -0.0, float 0.0, float -0.0)
+  ret float %med3
+}
+
+; fmed3(+0, -0, -0): exercises the third arm of fmed3AMDGCN (Src2 is max).
+define float @fmed3_pos0_neg0_neg0_f32() #1 {
+; CHECK-LABEL: define float @fmed3_pos0_neg0_neg0_f32(
+; CHECK-SAME: ) #[[ATTR1]] {
+; CHECK-NEXT:    ret float -0.000000e+00
+;
+  %med3 = call float @llvm.amdgcn.fmed3.f32(float 0.0, float -0.0, float -0.0)
+  ret float %med3
+}
+
+; fmed3(N, -0, +0) sorted: {N, -0, +0}, median = -0.
+define float @fmed3_negone_neg0_pos0_f32() #1 {
+; CHECK-LABEL: define float @fmed3_negone_neg0_pos0_f32(
+; CHECK-SAME: ) #[[ATTR1]] {
+; CHECK-NEXT:    ret float -0.000000e+00
+;
+  %med3 = call float @llvm.amdgcn.fmed3.f32(float -1.0, float -0.0, float 0.0)
+  ret float %med3
+}
+
+; fmed3(-0, N, +0) sorted: {N, -0, +0}, median = -0.
+define float @fmed3_neg0_negone_pos0_f32() #1 {
+; CHECK-LABEL: define float @fmed3_neg0_negone_pos0_f32(
+; CHECK-SAME: ) #[[ATTR1]] {
+; CHECK-NEXT:    ret float -0.000000e+00
+;
+  %med3 = call float @llvm.amdgcn.fmed3.f32(float -0.0, float -1.0, float 0.0)
+  ret float %med3
+}
+
+; fmed3(-0, +0, N) sorted: {N, -0, +0}, median = -0.
+define float @fmed3_neg0_pos0_negone_f32() #1 {
+; CHECK-LABEL: define float @fmed3_neg0_pos0_negone_f32(
+; CHECK-SAME: ) #[[ATTR1]] {
+; CHECK-NEXT:    ret float -0.000000e+00
+;
+  %med3 = call float @llvm.amdgcn.fmed3.f32(float -0.0, float 0.0, float -1.0)
+  ret float %med3
+}
+
 define float @fmed3_x_qnan0_qnan1_f32(float %x) #1 {
 ; CHECK-LABEL: define float @fmed3_x_qnan0_qnan1_f32(
 ; CHECK-SAME: float [[X:%.*]]) #[[ATTR1]] {



More information about the llvm-commits mailing list