[llvm] [SimplifyCFG] Fix stale call-site nonnull causing false UB on PHI args (PR #222004)

Saiyedul Islam via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 8 06:51:25 PDT 2026


https://github.com/saiislam updated https://github.com/llvm/llvm-project/pull/222004

>From aa3229d7c0961e1dce0ae1c4a2cafded2db085b7 Mon Sep 17 00:00:00 2001
From: Saiyedul Islam <Saiyedul.Islam at amd.com>
Date: Thu, 3 Sep 2026 10:39:11 -0500
Subject: [PATCH] [SimplifyCFG] Fix stale call-site nonnull causing false UB on
 PHI args

Inlining can leave a stale call-site nonnull on a PHI that has a null
incoming. passingValueIsAlwaysUndefined() treated that as UB and dropped
the predecessor; #200164 made those call uses reliably visible.

Only treat null as UB when the callee declares nonnull or
dereferenceable (including after looking through GEPs). Call-site-only
attributes must not be used to eliminate predecessors.
---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp     | 24 +++++-
 .../SimplifyCFG/UnreachableEliminate.ll       | 85 +++++++++++++++++++
 2 files changed, 107 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index ca96f2e70d810..871a8dafc72cf 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -9150,10 +9150,30 @@ static bool passingValueIsAlwaysUndefined(Value *V, Instruction *I, bool PtrValu
 
       if (CB->isArgOperand(&Use)) {
         unsigned ArgIdx = CB->getArgOperandNo(&Use);
-        // Passing null to a nonnnull+noundef argument is undefined.
+        // Passing null to a nonnull+noundef argument is undefined.
         if (isa<ConstantPointerNull>(C) && C->getType()->isPointerTy() &&
-            CB->paramHasNonNullAttr(ArgIdx, /*AllowUndefOrPoison=*/false))
+            CB->paramHasNonNullAttr(ArgIdx, /*AllowUndefOrPoison=*/false)) {
+          // Call-site nonnull/dereferenceable is not a stable ABI contract:
+          // it may have been inferred when the argument was provably
+          // non-null and then gone stale (e.g. after inlining introduced a
+          // PHI with a null incoming). Treat nonnull as UB only when the
+          // callee itself declares it:
+          //   (a) an explicit 'nonnull' parameter attribute, or
+          //   (b) 'dereferenceable' (implies nonnull unless null pointers
+          //       are defined in the callee's address space).
+          const Function *Callee = CB->getCalledFunction();
+          if (!Callee || ArgIdx >= Callee->arg_size())
+            return false;
+          unsigned ArgAS =
+              CB->getArgOperand(ArgIdx)->getType()->getPointerAddressSpace();
+          bool CalleeImpliesNonNull =
+              Callee->hasParamAttribute(ArgIdx, Attribute::NonNull) ||
+              (Callee->hasParamAttribute(ArgIdx, Attribute::Dereferenceable) &&
+               !NullPointerIsDefined(Callee, ArgAS));
+          if (!CalleeImpliesNonNull)
+            return false;
           return !PtrValueMayBeModified;
+        }
         // Passing undef to a noundef argument is undefined.
         if (isa<UndefValue>(C) && CB->isPassingUndefUB(ArgIdx))
           return true;
diff --git a/llvm/test/Transforms/SimplifyCFG/UnreachableEliminate.ll b/llvm/test/Transforms/SimplifyCFG/UnreachableEliminate.ll
index b89619d89f014..77be787d0feec 100644
--- a/llvm/test/Transforms/SimplifyCFG/UnreachableEliminate.ll
+++ b/llvm/test/Transforms/SimplifyCFG/UnreachableEliminate.ll
@@ -363,6 +363,91 @@ else:
   ret void
 }
 
+; Call-site-only nonnull+noundef on a PHI argument is not a stable ABI
+; contract (it can go stale after inlining) and must not be treated as UB.
+; The callee does not declare the parameter nonnull.
+define void @test9_stale_callsite_nonnull(i1 %X, ptr %Y) {
+; CHECK-LABEL: @test9_stale_callsite_nonnull(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 [[X:%.*]], ptr null, ptr [[Y:%.*]]
+; CHECK-NEXT:    [[TMP0:%.*]] = call ptr @fn_ptr_arg(ptr noundef nonnull [[SPEC_SELECT]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %X, label %if, label %else
+
+if:
+  br label %else
+
+else:
+  %phi = phi ptr [ %Y, %entry ], [ null, %if ]
+  call ptr @fn_ptr_arg(ptr nonnull noundef %phi)
+  ret void
+}
+
+; Same as above for call-site-only dereferenceable, which paramHasNonNullAttr
+; treats as implying nonnull.
+define void @test9_stale_callsite_deref(i1 %X, ptr %Y) {
+; CHECK-LABEL: @test9_stale_callsite_deref(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 [[X:%.*]], ptr null, ptr [[Y:%.*]]
+; CHECK-NEXT:    [[TMP0:%.*]] = call ptr @fn_ptr_arg(ptr dereferenceable(4) [[SPEC_SELECT]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %X, label %if, label %else
+
+if:
+  br label %else
+
+else:
+  %phi = phi ptr [ %Y, %entry ], [ null, %if ]
+  call ptr @fn_ptr_arg(ptr dereferenceable(4) %phi)
+  ret void
+}
+
+; Indirect call: there is no callee declaration to consult.
+define void @test9_stale_callsite_nonnull_indirect(i1 %X, ptr %Y, ptr %fn) {
+; CHECK-LABEL: @test9_stale_callsite_nonnull_indirect(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 [[X:%.*]], ptr null, ptr [[Y:%.*]]
+; CHECK-NEXT:    [[TMP0:%.*]] = call ptr [[FN:%.*]](ptr noundef nonnull [[SPEC_SELECT]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %X, label %if, label %else
+
+if:
+  br label %else
+
+else:
+  %phi = phi ptr [ %Y, %entry ], [ null, %if ]
+  call ptr %fn(ptr nonnull noundef %phi)
+  ret void
+}
+
+; Looking through a zero-offset GEP of the PHI must use the same rule.
+define void @test9_stale_callsite_nonnull_gep(i1 %X, ptr %Y) {
+; CHECK-LABEL: @test9_stale_callsite_nonnull_gep(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 [[X:%.*]], ptr null, ptr [[Y:%.*]]
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr i8, ptr [[SPEC_SELECT]], i64 0
+; CHECK-NEXT:    [[TMP0:%.*]] = call ptr @fn_ptr_arg(ptr noundef nonnull [[GEP]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %X, label %if, label %else
+
+if:
+  br label %else
+
+else:
+  %phi = phi ptr [ %Y, %entry ], [ null, %if ]
+  %gep = getelementptr i8, ptr %phi, i64 0
+  call ptr @fn_ptr_arg(ptr nonnull noundef %gep)
+  ret void
+}
+
 define void @test9_gep_mismatch(i1 %X, ptr %Y,  ptr %P) {
 ; CHECK-LABEL: @test9_gep_mismatch(
 ; CHECK-NEXT:  entry:



More information about the llvm-commits mailing list