Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reland "[SimplifyCFG] Check if the return instruction causes undefined behavior" #76656

Merged
merged 2 commits into from
Jan 24, 2024

Conversation

DianQK
Copy link
Member

@DianQK DianQK commented Dec 31, 2023

Fixes #60717.
#69498 has been merged.

@DianQK DianQK marked this pull request as ready for review January 23, 2024 05:13
@llvmbot
Copy link
Collaborator

llvmbot commented Jan 23, 2024

@llvm/pr-subscribers-llvm-transforms

Author: Quentin Dian (DianQK)

Changes

Fixes #60717.
#69498 has been merged.


Full diff: https://github.com/llvm/llvm-project/pull/76656.diff

3 Files Affected:

  • (modified) llvm/lib/Transforms/Utils/SimplifyCFG.cpp (+24-1)
  • (modified) llvm/test/Transforms/SimplifyCFG/UnreachableEliminate.ll (+66-2)
  • (added) llvm/test/Transforms/SimplifyCFG/unreachable-eliminate-on-ret.ll (+141)
diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index f3994b6cc39fef..e6593075142803 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7386,11 +7386,34 @@ static bool passingValueIsAlwaysUndefined(Value *V, Instruction *I, bool PtrValu
     // Look through GEPs. A load from a GEP derived from NULL is still undefined
     if (GetElementPtrInst *GEP = dyn_cast<GetElementPtrInst>(Use))
       if (GEP->getPointerOperand() == I) {
-        if (!GEP->isInBounds() || !GEP->hasAllZeroIndices())
+        // The current base address is null, there are four cases to consider:
+        // getelementptr (TY, null, 0)                 -> null
+        // getelementptr (TY, null, not zero)          -> may be modified
+        // getelementptr inbounds (TY, null, 0)        -> null
+        // getelementptr inbounds (TY, null, not zero) -> poison iff null is
+        // undefined?
+        if (!GEP->hasAllZeroIndices() &&
+            (!GEP->isInBounds() ||
+             NullPointerIsDefined(GEP->getFunction(),
+                                  GEP->getPointerAddressSpace())))
           PtrValueMayBeModified = true;
         return passingValueIsAlwaysUndefined(V, GEP, PtrValueMayBeModified);
       }
 
+    // Look through return.
+    if (ReturnInst *Ret = dyn_cast<ReturnInst>(Use)) {
+      bool HasNoUndefAttr =
+          Ret->getFunction()->hasRetAttribute(Attribute::NoUndef);
+      // Return undefined to a noundef return value is undefined.
+      if (isa<UndefValue>(C) && HasNoUndefAttr)
+        return true;
+      // Return null to a nonnull+noundef return value is undefined.
+      if (C->isNullValue() && HasNoUndefAttr &&
+          Ret->getFunction()->hasRetAttribute(Attribute::NonNull)) {
+        return !PtrValueMayBeModified;
+      }
+    }
+
     // Look through bitcasts.
     if (BitCastInst *BC = dyn_cast<BitCastInst>(Use))
       return passingValueIsAlwaysUndefined(V, BC, PtrValueMayBeModified);
diff --git a/llvm/test/Transforms/SimplifyCFG/UnreachableEliminate.ll b/llvm/test/Transforms/SimplifyCFG/UnreachableEliminate.ll
index 5ba43c055f9be1..757340527ec030 100644
--- a/llvm/test/Transforms/SimplifyCFG/UnreachableEliminate.ll
+++ b/llvm/test/Transforms/SimplifyCFG/UnreachableEliminate.ll
@@ -444,6 +444,28 @@ else:
 define void @test9_gep_inbounds_nonzero(i1 %X, ptr %Y) {
 ; CHECK-LABEL: @test9_gep_inbounds_nonzero(
 ; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[TMP0:%.*]] = xor i1 [[X:%.*]], true
+; CHECK-NEXT:    call void @llvm.assume(i1 [[TMP0]])
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr inbounds i8, ptr [[Y:%.*]], i64 12
+; CHECK-NEXT:    [[TMP1:%.*]] = call ptr @fn_nonnull_noundef_arg(ptr [[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 inbounds i8, ptr %phi, i64 12
+  call ptr @fn_nonnull_noundef_arg(ptr %gep)
+  ret void
+}
+
+define void @test9_gep_inbounds_nonzero_null_defined(i1 %X, ptr %Y) #0 {
+; CHECK-LABEL: @test9_gep_inbounds_nonzero_null_defined(
+; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 [[X:%.*]], ptr null, ptr [[Y:%.*]]
 ; CHECK-NEXT:    [[GEP:%.*]] = getelementptr inbounds i8, ptr [[SPEC_SELECT]], i64 12
 ; CHECK-NEXT:    [[TMP0:%.*]] = call ptr @fn_nonnull_noundef_arg(ptr [[GEP]])
@@ -462,9 +484,30 @@ else:
   ret void
 }
 
+define void @test9_gep_inbounds_unknown_null(i1 %X, ptr %Y, i64 %I) {
+; CHECK-LABEL: @test9_gep_inbounds_unknown_null(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[TMP0:%.*]] = xor i1 [[X:%.*]], true
+; CHECK-NEXT:    call void @llvm.assume(i1 [[TMP0]])
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr inbounds i8, ptr [[Y:%.*]], i64 [[I:%.*]]
+; CHECK-NEXT:    [[TMP1:%.*]] = call ptr @fn_nonnull_noundef_arg(ptr [[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 inbounds i8, ptr %phi, i64 %I
+  call ptr @fn_nonnull_noundef_arg(ptr %gep)
+  ret void
+}
 
-define void @test9_gep_inbouds_unknown_null(i1 %X, ptr %Y, i64 %I) {
-; CHECK-LABEL: @test9_gep_inbouds_unknown_null(
+define void @test9_gep_inbounds_unknown_null_defined(i1 %X, ptr %Y, i64 %I) #0 {
+; CHECK-LABEL: @test9_gep_inbounds_unknown_null_defined(
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 [[X:%.*]], ptr null, ptr [[Y:%.*]]
 ; CHECK-NEXT:    [[GEP:%.*]] = getelementptr inbounds i8, ptr [[SPEC_SELECT]], i64 [[I:%.*]]
@@ -484,6 +527,27 @@ else:
   ret void
 }
 
+define void @test9_gep_inbounds_unknown_null_call_noundef(i1 %X, ptr %Y, i64 %I) {
+; CHECK-LABEL: @test9_gep_inbounds_unknown_null_call_noundef(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 [[X:%.*]], ptr null, ptr [[Y:%.*]]
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr inbounds i8, ptr [[SPEC_SELECT]], i64 [[I:%.*]]
+; CHECK-NEXT:    [[TMP0:%.*]] = call ptr @fn_noundef_arg(ptr [[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 inbounds i8, ptr %phi, i64 %I
+  call ptr @fn_noundef_arg(ptr %gep)
+  ret void
+}
+
 define void @test9_gep_unknown_null(i1 %X, ptr %Y, i64 %I) {
 ; CHECK-LABEL: @test9_gep_unknown_null(
 ; CHECK-NEXT:  entry:
diff --git a/llvm/test/Transforms/SimplifyCFG/unreachable-eliminate-on-ret.ll b/llvm/test/Transforms/SimplifyCFG/unreachable-eliminate-on-ret.ll
new file mode 100644
index 00000000000000..924f98a46ab541
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/unreachable-eliminate-on-ret.ll
@@ -0,0 +1,141 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -passes=simplifycfg -simplifycfg-require-and-preserve-domtree=1 -S < %s | FileCheck %s
+
+define noundef i32 @test_ret_noundef(i1 %cond) {
+; CHECK-LABEL: @test_ret_noundef(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    ret i32 1
+;
+entry:
+  br i1 %cond, label %bb1, label %bb2
+
+bb1:
+  br label %bb2
+
+bb2:
+  %r = phi i32 [ undef, %entry ], [ 1, %bb1 ]
+  ret i32 %r
+}
+
+define i32 @test_ret(i1 %cond) {
+; CHECK-LABEL: @test_ret(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 [[COND:%.*]], i32 1, i32 undef
+; CHECK-NEXT:    ret i32 [[SPEC_SELECT]]
+;
+entry:
+  br i1 %cond, label %bb1, label %bb2
+
+bb1:
+  br label %bb2
+
+bb2:
+  %r = phi i32 [ undef, %entry ], [ 1, %bb1 ]
+  ret i32 %r
+}
+
+define nonnull noundef ptr @test_ret_ptr_nonnull_noundef(i1 %cond, ptr %x) {
+; CHECK-LABEL: @test_ret_ptr_nonnull_noundef(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    call void @llvm.assume(i1 [[COND:%.*]])
+; CHECK-NEXT:    ret ptr [[X:%.*]]
+;
+entry:
+  br i1 %cond, label %bb1, label %bb2
+
+bb1:
+  br label %bb2
+
+bb2:
+  %r = phi ptr [ null, %entry ], [ %x, %bb1 ]
+  ret ptr %r
+}
+
+define nonnull noundef ptr @test_ret_ptr_nonnull_noundef_gep_nonzero(i1 %cond, ptr %x) {
+; CHECK-LABEL: @test_ret_ptr_nonnull_noundef_gep_nonzero(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 [[COND:%.*]], ptr [[X:%.*]], ptr null
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr ptr, ptr [[SPEC_SELECT]], i64 12
+; CHECK-NEXT:    ret ptr [[GEP]]
+;
+entry:
+  br i1 %cond, label %bb1, label %bb2
+
+bb1:
+  br label %bb2
+
+bb2:
+  %phi = phi ptr [ null, %entry ], [ %x, %bb1 ]
+  %gep = getelementptr ptr, ptr %phi, i64 12
+  ret ptr %gep
+}
+
+define nonnull noundef ptr @test_ret_ptr_nonnull_noundef_gep_inbounds_nonzero(i1 %cond, ptr %x) {
+; CHECK-LABEL: @test_ret_ptr_nonnull_noundef_gep_inbounds_nonzero(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    call void @llvm.assume(i1 [[COND:%.*]])
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr inbounds ptr, ptr [[X:%.*]], i64 12
+; CHECK-NEXT:    ret ptr [[GEP]]
+;
+entry:
+  br i1 %cond, label %bb1, label %bb2
+
+bb1:
+  br label %bb2
+
+bb2:
+  %phi = phi ptr [ null, %entry ], [ %x, %bb1 ]
+  %gep = getelementptr inbounds ptr, ptr %phi, i64 12
+  ret ptr %gep
+}
+
+define nonnull ptr @test_ret_ptr_nonnull(i1 %cond, ptr %x) {
+; CHECK-LABEL: @test_ret_ptr_nonnull(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 [[COND:%.*]], ptr [[X:%.*]], ptr null
+; CHECK-NEXT:    ret ptr [[SPEC_SELECT]]
+;
+entry:
+  br i1 %cond, label %bb1, label %bb2
+
+bb1:
+  br label %bb2
+
+bb2:
+  %r = phi ptr [ null, %entry ], [ %x, %bb1 ]
+  ret ptr %r
+}
+
+define noundef ptr @test_ret_ptr_noundef(i1 %cond, ptr %x) {
+; CHECK-LABEL: @test_ret_ptr_noundef(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 [[COND:%.*]], ptr [[X:%.*]], ptr null
+; CHECK-NEXT:    ret ptr [[SPEC_SELECT]]
+;
+entry:
+  br i1 %cond, label %bb1, label %bb2
+
+bb1:
+  br label %bb2
+
+bb2:
+  %r = phi ptr [ null, %entry ], [ %x, %bb1 ]
+  ret ptr %r
+}
+
+define ptr @test_ret_ptr(i1 %cond, ptr %x) {
+; CHECK-LABEL: @test_ret_ptr(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 [[COND:%.*]], ptr [[X:%.*]], ptr null
+; CHECK-NEXT:    ret ptr [[SPEC_SELECT]]
+;
+entry:
+  br i1 %cond, label %bb1, label %bb2
+
+bb1:
+  br label %bb2
+
+bb2:
+  %r = phi ptr [ null, %entry ], [ %x, %bb1 ]
+  ret ptr %r
+}

@DianQK
Copy link
Member Author

DianQK commented Jan 23, 2024

Hmm, it looks like we're going to create release/18, so let me merge it first. If we find new issues, we can revert it.

Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but please wait until after LLVM 18 has branched to merge it. Please also perform the merge manually to avoid squashing the commits.

@DianQK
Copy link
Member Author

DianQK commented Jan 23, 2024

LGTM, but please wait until after LLVM 18 has branched to merge it. Please also perform the merge manually to avoid squashing the commits.

Okay, waiting until LLVM 19 is fine. Yes, I'm going to rebase and push.

…d behavior"

This relands commit b6a0be8.

Return undefined to a noundef return value is undefined.

Example:

```
define noundef i32 @test_ret_noundef(i1 %cond) {
entry:
  br i1 %cond, label %bb1, label %bb2
bb1:
  br label %bb2
bb2:
  %r = phi i32 [ undef, %entry ], [ 1, %bb1 ]
  ret i32 %r
}
```
This relands commit f890f01.

The result value of `getelementptr inbounds (TY, null, not zero)` is a poison value.
We can think of it as undefined behavior.
@DianQK DianQK merged commit a58dcc5 into llvm:main Jan 24, 2024
2 of 4 checks passed
@DianQK DianQK deleted the reland-ret-undef branch January 24, 2024 22:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

In a function with a noundef return value, replace ret undef with unreachable
3 participants