[SimplifyCFG] Optimize CFG when null is passed to a function with nonnull argument
authorDávid Bolvanský <david.bolvansky@gmail.com>
Fri, 15 Jan 2021 22:07:29 +0000 (23:07 +0100)
committerDávid Bolvanský <david.bolvansky@gmail.com>
Fri, 15 Jan 2021 22:53:43 +0000 (23:53 +0100)
Example:

```
__attribute__((nonnull,noinline)) char * pinc(char *p)  {
  return ++p;
}

char * foo(bool b, char *a) {
       return pinc(b ? 0 : a);

}
```

optimize to

```
char * foo(bool b, char *a) {
       return pinc(a);

}
```

Reviewed By: jdoerfert

Differential Revision: https://reviews.llvm.org/D94180

llvm/lib/Transforms/Utils/SimplifyCFG.cpp
llvm/test/Transforms/SimplifyCFG/UnreachableEliminate.ll

index 559830e..7fe33fd 100644 (file)
@@ -1345,7 +1345,7 @@ static bool isSafeToHoistInvoke(BasicBlock *BB1, BasicBlock *BB2,
   return true;
 }
 
-static bool passingValueIsAlwaysUndefined(Value *V, Instruction *I);
+static bool passingValueIsAlwaysUndefined(Value *V, Instruction *I, bool PtrValueMayBeModified = false);
 
 /// Given a conditional branch that goes to BB1 and BB2, hoist any common code
 /// in the two blocks up into the branch block. The caller of this function
@@ -6545,7 +6545,7 @@ bool SimplifyCFGOpt::simplifyCondBranch(BranchInst *BI, IRBuilder<> &Builder) {
 }
 
 /// Check if passing a value to an instruction will cause undefined behavior.
-static bool passingValueIsAlwaysUndefined(Value *V, Instruction *I) {
+static bool passingValueIsAlwaysUndefined(Value *V, Instruction *I, bool PtrValueMayBeModified) {
   Constant *C = dyn_cast<Constant>(V);
   if (!C)
     return false;
@@ -6568,12 +6568,15 @@ static bool passingValueIsAlwaysUndefined(Value *V, Instruction *I) {
 
     // 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)
-        return passingValueIsAlwaysUndefined(V, GEP);
+      if (GEP->getPointerOperand() == I) {
+        if (!GEP->isInBounds() || !GEP->hasAllZeroIndices())
+          PtrValueMayBeModified = true;
+        return passingValueIsAlwaysUndefined(V, GEP, PtrValueMayBeModified);
+      }
 
     // Look through bitcasts.
     if (BitCastInst *BC = dyn_cast<BitCastInst>(Use))
-      return passingValueIsAlwaysUndefined(V, BC);
+      return passingValueIsAlwaysUndefined(V, BC, PtrValueMayBeModified);
 
     // Load from null is undefined.
     if (LoadInst *LI = dyn_cast<LoadInst>(Use))
@@ -6588,10 +6591,35 @@ static bool passingValueIsAlwaysUndefined(Value *V, Instruction *I) {
                                       SI->getPointerAddressSpace())) &&
                SI->getPointerOperand() == I;
 
-    // A call to null is undefined.
-    if (auto *CB = dyn_cast<CallBase>(Use))
-      return !NullPointerIsDefined(CB->getFunction()) &&
-             CB->getCalledOperand() == I;
+    if (auto *CB = dyn_cast<CallBase>(Use)) {
+      if (C->isNullValue() && NullPointerIsDefined(CB->getFunction()))
+        return false;
+      // A call to null is undefined.
+      if (CB->getCalledOperand() == I)
+        return true;
+
+      if (C->isNullValue()) {
+        for (const llvm::Use &Arg : CB->args())
+          if (Arg == I) {
+            unsigned ArgIdx = CB->getArgOperandNo(&Arg);
+            if (CB->paramHasAttr(ArgIdx, Attribute::NonNull) &&
+                CB->paramHasAttr(ArgIdx, Attribute::NoUndef)) {
+              // Passing null to a nonnnull+noundef argument is undefined.
+              return !PtrValueMayBeModified;
+            }
+          }
+      } else if (isa<UndefValue>(C)) {
+        // Passing undef to a noundef argument is undefined.
+        for (const llvm::Use &Arg : CB->args())
+          if (Arg == I) {
+            unsigned ArgIdx = CB->getArgOperandNo(&Arg);
+            if (CB->paramHasAttr(ArgIdx, Attribute::NoUndef)) {
+              // Passing undef to a noundef argument is undefined.
+              return true;
+            }
+          }
+      }
+    }
   }
   return false;
 }
index b3b7abe..4c98481 100644 (file)
@@ -69,7 +69,6 @@ define void @test5(i1 %cond, i8* %ptr) {
 ; CHECK-NEXT:    store i8 2, i8* [[PTR:%.*]], align 8
 ; CHECK-NEXT:    ret void
 ;
-
 entry:
   br i1 %cond, label %bb1, label %bb3
 
@@ -88,11 +87,10 @@ bb2:
 define void @test5_no_null_opt(i1 %cond, i8* %ptr) #0 {
 ; CHECK-LABEL: @test5_no_null_opt(
 ; CHECK-NEXT:  entry:
-; CHECK-NEXT:    [[DOTPTR:%.*]] = select i1 [[COND:%.*]], i8* null, i8* [[PTR:%.*]]
-; CHECK-NEXT:    store i8 2, i8* [[DOTPTR]], align 8
+; CHECK-NEXT:    [[PTR_2:%.*]] = select i1 [[COND:%.*]], i8* null, i8* [[PTR:%.*]]
+; CHECK-NEXT:    store i8 2, i8* [[PTR_2]], align 8
 ; CHECK-NEXT:    ret void
 ;
-
 entry:
   br i1 %cond, label %bb1, label %bb3
 
@@ -206,4 +204,296 @@ else:
   ret void
 }
 
+declare i8* @fn_nonnull_noundef_arg(i8* nonnull noundef %p)
+declare i8* @fn_nonnull_arg(i8* nonnull %p)
+declare i8* @fn_noundef_arg(i8* noundef %p)
+
+define void @test9(i1 %X, i8* %Y) {
+; CHECK-LABEL: @test9(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[TMP0:%.*]] = xor i1 [[X:%.*]], true
+; CHECK-NEXT:    call void @llvm.assume(i1 [[TMP0]])
+; CHECK-NEXT:    [[TMP1:%.*]] = call i8* @fn_nonnull_noundef_arg(i8* [[Y:%.*]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %X, label %if, label %else
+
+if:
+  br label %else
+
+else:
+  %phi = phi i8* [ %Y, %entry ], [ null, %if ]
+  call i8* @fn_nonnull_noundef_arg(i8* %phi)
+  ret void
+}
+
+define void @test9_undef(i1 %X, i8* %Y) {
+; CHECK-LABEL: @test9_undef(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[TMP0:%.*]] = call i8* @fn_noundef_arg(i8* [[Y:%.*]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %X, label %if, label %else
+
+if:
+  br label %else
+
+else:
+  %phi = phi i8* [ %Y, %entry ], [ undef, %if ]
+  call i8* @fn_noundef_arg(i8* %phi)
+  ret void
+}
+
+define void @test9_undef_null_defined(i1 %X, i8* %Y) #0 {
+; CHECK-LABEL: @test9_undef_null_defined(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[TMP0:%.*]] = call i8* @fn_noundef_arg(i8* [[Y:%.*]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %X, label %if, label %else
+
+if:
+  br label %else
+
+else:
+  %phi = phi i8* [ %Y, %entry ], [ undef, %if ]
+  call i8* @fn_noundef_arg(i8* %phi)
+  ret void
+}
+
+define void @test9_null_callsite(i1 %X, i8* %Y) {
+; CHECK-LABEL: @test9_null_callsite(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[TMP0:%.*]] = xor i1 [[X:%.*]], true
+; CHECK-NEXT:    call void @llvm.assume(i1 [[TMP0]])
+; CHECK-NEXT:    [[TMP1:%.*]] = call i8* @fn_nonnull_arg(i8* noundef nonnull [[Y:%.*]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %X, label %if, label %else
+
+if:
+  br label %else
+
+else:
+  %phi = phi i8* [ %Y, %entry ], [ null, %if ]
+  call i8* @fn_nonnull_arg(i8* nonnull noundef %phi)
+  ret void
+}
+
+define void @test9_gep_mismatch(i1 %X, i8* %Y,  i8* %P) {
+; CHECK-LABEL: @test9_gep_mismatch(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 [[X:%.*]], i8* null, i8* [[Y:%.*]]
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr inbounds i8, i8* [[P:%.*]], i64 0
+; CHECK-NEXT:    [[TMP0:%.*]] = call i8* @fn_nonnull_noundef_arg(i8* [[GEP]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %X, label %if, label %else
+
+if:
+  br label %else
+
+else:
+  %phi = phi i8* [ %Y, %entry ], [ null, %if ]
+  %gep = getelementptr inbounds i8, i8* %P, i64 0
+  call i8* @fn_nonnull_noundef_arg(i8* %gep)
+  ret void
+}
+
+define void @test9_gep_zero(i1 %X, i8* %Y) {
+; CHECK-LABEL: @test9_gep_zero(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[TMP0:%.*]] = xor i1 [[X:%.*]], true
+; CHECK-NEXT:    call void @llvm.assume(i1 [[TMP0]])
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr inbounds i8, i8* [[Y:%.*]], i64 0
+; CHECK-NEXT:    [[TMP1:%.*]] = call i8* @fn_nonnull_noundef_arg(i8* [[GEP]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %X, label %if, label %else
+
+if:
+  br label %else
+
+else:
+  %phi = phi i8* [ %Y, %entry ], [ null, %if ]
+  %gep = getelementptr inbounds i8, i8* %phi, i64 0
+  call i8* @fn_nonnull_noundef_arg(i8* %gep)
+  ret void
+}
+
+define void @test9_gep_bitcast(i1 %X, i32* %Y) {
+; CHECK-LABEL: @test9_gep_bitcast(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[TMP0:%.*]] = xor i1 [[X:%.*]], true
+; CHECK-NEXT:    call void @llvm.assume(i1 [[TMP0]])
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr inbounds i32, i32* [[Y:%.*]], i64 0
+; CHECK-NEXT:    [[BC:%.*]] = bitcast i32* [[GEP]] to i8*
+; CHECK-NEXT:    [[TMP1:%.*]] = call i8* @fn_nonnull_noundef_arg(i8* [[BC]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %X, label %if, label %else
+
+if:
+  br label %else
+
+else:
+  %phi = phi i32* [ %Y, %entry ], [ null, %if ]
+  %gep = getelementptr inbounds i32, i32* %phi, i64 0
+  %bc = bitcast i32* %gep to i8*
+  call i8* @fn_nonnull_noundef_arg(i8* %bc)
+  ret void
+}
+
+define void @test9_gep_nonzero(i1 %X, i8* %Y) {
+; CHECK-LABEL: @test9_gep_nonzero(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 [[X:%.*]], i8* null, i8* [[Y:%.*]]
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr i8, i8* [[SPEC_SELECT]], i64 12
+; CHECK-NEXT:    [[TMP0:%.*]] = call i8* @fn_nonnull_noundef_arg(i8* [[GEP]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %X, label %if, label %else
+
+if:
+  br label %else
+
+else:
+  %phi = phi i8* [ %Y, %entry ], [ null, %if ]
+  %gep = getelementptr i8, i8* %phi, i64 12
+  call i8* @fn_nonnull_noundef_arg(i8* %gep)
+  ret void
+}
+
+define void @test9_gep_inbounds_nonzero(i1 %X, i8* %Y) {
+; CHECK-LABEL: @test9_gep_inbounds_nonzero(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 [[X:%.*]], i8* null, i8* [[Y:%.*]]
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr inbounds i8, i8* [[SPEC_SELECT]], i64 12
+; CHECK-NEXT:    [[TMP0:%.*]] = call i8* @fn_nonnull_noundef_arg(i8* [[GEP]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %X, label %if, label %else
+
+if:
+  br label %else
+
+else:
+  %phi = phi i8* [ %Y, %entry ], [ null, %if ]
+  %gep = getelementptr inbounds i8, i8* %phi, i64 12
+  call i8* @fn_nonnull_noundef_arg(i8* %gep)
+  ret void
+}
+
+
+define void @test9_gep_inbouds_unknown_null(i1 %X, i8* %Y, i64 %I) {
+; CHECK-LABEL: @test9_gep_inbouds_unknown_null(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 [[X:%.*]], i8* null, i8* [[Y:%.*]]
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr inbounds i8, i8* [[SPEC_SELECT]], i64 [[I:%.*]]
+; CHECK-NEXT:    [[TMP0:%.*]] = call i8* @fn_nonnull_noundef_arg(i8* [[GEP]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %X, label %if, label %else
+
+if:
+  br label %else
+
+else:
+  %phi = phi i8* [ %Y, %entry ], [ null, %if ]
+  %gep = getelementptr inbounds i8, i8* %phi, i64 %I
+  call i8* @fn_nonnull_noundef_arg(i8* %gep)
+  ret void
+}
+
+define void @test9_gep_unknown_null(i1 %X, i8* %Y, i64 %I) {
+; CHECK-LABEL: @test9_gep_unknown_null(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 [[X:%.*]], i8* null, i8* [[Y:%.*]]
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr i8, i8* [[SPEC_SELECT]], i64 [[I:%.*]]
+; CHECK-NEXT:    [[TMP0:%.*]] = call i8* @fn_nonnull_noundef_arg(i8* [[GEP]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %X, label %if, label %else
+
+if:
+  br label %else
+
+else:
+  %phi = phi i8* [ %Y, %entry ], [ null, %if ]
+  %gep = getelementptr i8, i8* %phi, i64 %I
+  call i8* @fn_nonnull_noundef_arg(i8* %gep)
+  ret void
+}
+
+define void @test9_gep_unknown_undef(i1 %X, i8* %Y, i64 %I) {
+; CHECK-LABEL: @test9_gep_unknown_undef(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr i8, i8* [[Y:%.*]], i64 [[I:%.*]]
+; CHECK-NEXT:    [[TMP0:%.*]] = call i8* @fn_noundef_arg(i8* [[GEP]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %X, label %if, label %else
+
+if:
+  br label %else
+
+else:
+  %phi = phi i8* [ %Y, %entry ], [ undef, %if ]
+  %gep = getelementptr i8, i8* %phi, i64 %I
+  call i8* @fn_noundef_arg(i8* %gep)
+  ret void
+}
+
+define void @test9_missing_noundef(i1 %X, i8* %Y) {
+; CHECK-LABEL: @test9_missing_noundef(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 [[X:%.*]], i8* null, i8* [[Y:%.*]]
+; CHECK-NEXT:    [[TMP0:%.*]] = call i8* @fn_nonnull_arg(i8* [[SPEC_SELECT]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %X, label %if, label %else
+
+if:
+  br label %else
+
+else:
+  %phi = phi i8* [ %Y, %entry ], [ null, %if ]
+  call i8* @fn_nonnull_arg(i8* %phi)
+  ret void
+}
+
+define void @test9_null_defined(i1 %X, i8* %Y) #0 {
+; CHECK-LABEL: @test9_null_defined(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 [[X:%.*]], i8* null, i8* [[Y:%.*]]
+; CHECK-NEXT:    [[TMP0:%.*]] = call i8* @fn_nonnull_noundef_arg(i8* [[SPEC_SELECT]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %X, label %if, label %else
+
+if:
+  br label %else
+
+else:
+  %phi = phi i8* [ %Y, %entry ], [ null, %if ]
+  call i8* @fn_nonnull_noundef_arg(i8* %phi)
+  ret void
+}
+
+
+
 attributes #0 = { null_pointer_is_valid }