Skip to content

[InstCombine] Simplify nonnull pointers #128111

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

Merged
merged 6 commits into from
Feb 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3993,10 +3993,19 @@ Instruction *InstCombinerImpl::visitCallBase(CallBase &Call) {
unsigned ArgNo = 0;

for (Value *V : Call.args()) {
if (V->getType()->isPointerTy() &&
!Call.paramHasAttr(ArgNo, Attribute::NonNull) &&
isKnownNonZero(V, getSimplifyQuery().getWithInstruction(&Call)))
ArgNos.push_back(ArgNo);
if (V->getType()->isPointerTy()) {
// Simplify the nonnull operand if the parameter is known to be nonnull.
// Otherwise, try to infer nonnull for it.
if (Call.paramHasNonNullAttr(ArgNo, /*AllowUndefOrPoison=*/true)) {
if (Value *Res = simplifyNonNullOperand(V)) {
replaceOperand(Call, ArgNo, Res);
Changed = true;
}
} else if (isKnownNonZero(V,
getSimplifyQuery().getWithInstruction(&Call))) {
ArgNos.push_back(ArgNo);
}
}
ArgNo++;
}

Expand Down
4 changes: 4 additions & 0 deletions llvm/lib/Transforms/InstCombine/InstCombineInternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,10 @@ class LLVM_LIBRARY_VISIBILITY InstCombinerImpl final

Instruction *hoistFNegAboveFMulFDiv(Value *FNegOp, Instruction &FMFSource);

/// Simplify \p V given that it is known to be non-null.
/// Returns the simplified value if possible, otherwise returns nullptr.
Value *simplifyNonNullOperand(Value *V);

public:
/// Create and insert the idiom we use to indicate a block is unreachable
/// without having to rewrite the CFG from within InstCombine.
Expand Down
46 changes: 21 additions & 25 deletions llvm/lib/Transforms/InstCombine/InstCombineLoadStoreAlloca.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,19 @@ static bool canSimplifyNullLoadOrGEP(LoadInst &LI, Value *Op) {
return false;
}

/// TODO: Recursively simplify nonnull value to handle one-use inbounds GEPs.
Value *InstCombinerImpl::simplifyNonNullOperand(Value *V) {
if (auto *Sel = dyn_cast<SelectInst>(V)) {
if (isa<ConstantPointerNull>(Sel->getOperand(1)))
return Sel->getOperand(2);

if (isa<ConstantPointerNull>(Sel->getOperand(2)))
return Sel->getOperand(1);
}

return nullptr;
}

Instruction *InstCombinerImpl::visitLoadInst(LoadInst &LI) {
Value *Op = LI.getOperand(0);
if (Value *Res = simplifyLoadInst(&LI, Op, SQ.getWithInstruction(&LI)))
Expand Down Expand Up @@ -1059,20 +1072,13 @@ Instruction *InstCombinerImpl::visitLoadInst(LoadInst &LI) {
V2->copyMetadata(LI, Metadata::PoisonGeneratingIDs);
return SelectInst::Create(SI->getCondition(), V1, V2);
}

// load (select (cond, null, P)) -> load P
if (isa<ConstantPointerNull>(SI->getOperand(1)) &&
!NullPointerIsDefined(SI->getFunction(),
LI.getPointerAddressSpace()))
return replaceOperand(LI, 0, SI->getOperand(2));

// load (select (cond, P, null)) -> load P
if (isa<ConstantPointerNull>(SI->getOperand(2)) &&
!NullPointerIsDefined(SI->getFunction(),
LI.getPointerAddressSpace()))
return replaceOperand(LI, 0, SI->getOperand(1));
}
}

if (!NullPointerIsDefined(LI.getFunction(), LI.getPointerAddressSpace()))
if (Value *V = simplifyNonNullOperand(Op))
return replaceOperand(LI, 0, V);

return nullptr;
}

Expand Down Expand Up @@ -1437,19 +1443,9 @@ Instruction *InstCombinerImpl::visitStoreInst(StoreInst &SI) {
if (isa<UndefValue>(Val))
return eraseInstFromFunction(SI);

// TODO: Add a helper to simplify the pointer operand for all memory
// instructions.
// store val, (select (cond, null, P)) -> store val, P
// store val, (select (cond, P, null)) -> store val, P
if (!NullPointerIsDefined(SI.getFunction(), SI.getPointerAddressSpace())) {
if (SelectInst *Sel = dyn_cast<SelectInst>(Ptr)) {
if (isa<ConstantPointerNull>(Sel->getOperand(1)))
return replaceOperand(SI, 1, Sel->getOperand(2));

if (isa<ConstantPointerNull>(Sel->getOperand(2)))
return replaceOperand(SI, 1, Sel->getOperand(1));
}
}
if (!NullPointerIsDefined(SI.getFunction(), SI.getPointerAddressSpace()))
if (Value *V = simplifyNonNullOperand(Ptr))
return replaceOperand(SI, 1, V);

return nullptr;
}
Expand Down
15 changes: 14 additions & 1 deletion llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3587,10 +3587,23 @@ Instruction *InstCombinerImpl::visitFree(CallInst &FI, Value *Op) {

Instruction *InstCombinerImpl::visitReturnInst(ReturnInst &RI) {
Value *RetVal = RI.getReturnValue();
if (!RetVal || !AttributeFuncs::isNoFPClassCompatibleType(RetVal->getType()))
if (!RetVal)
return nullptr;

Function *F = RI.getFunction();
Type *RetTy = RetVal->getType();
if (RetTy->isPointerTy()) {
if (F->hasRetAttribute(Attribute::NonNull) ||
(F->getAttributes().getRetDereferenceableBytes() > 0 &&
!NullPointerIsDefined(F, RetTy->getPointerAddressSpace()))) {
if (Value *V = simplifyNonNullOperand(RetVal))
return replaceOperand(RI, 0, V);
}
}

if (!AttributeFuncs::isNoFPClassCompatibleType(RetTy))
return nullptr;

FPClassTest ReturnClass = F->getAttributes().getRetNoFPClass();
if (ReturnClass == fcNone)
return nullptr;
Expand Down
29 changes: 9 additions & 20 deletions llvm/test/Transforms/InstCombine/nonnull-select.ll
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@

define nonnull ptr @pr48975(ptr %.0) {
; CHECK-LABEL: @pr48975(
; CHECK-NEXT: [[DOT1:%.*]] = load ptr, ptr [[DOT0:%.*]], align 8
; CHECK-NEXT: [[DOT2:%.*]] = icmp eq ptr [[DOT1]], null
; CHECK-NEXT: [[DOT4:%.*]] = select i1 [[DOT2]], ptr null, ptr [[DOT0]]
; CHECK-NEXT: ret ptr [[DOT4]]
; CHECK-NEXT: ret ptr [[DOT4:%.*]]
;
%.1 = load ptr, ptr %.0, align 8
%.2 = icmp eq ptr %.1, null
Expand All @@ -18,35 +15,31 @@ define nonnull ptr @pr48975(ptr %.0) {

define nonnull ptr @nonnull_ret(i1 %cond, ptr %p) {
; CHECK-LABEL: @nonnull_ret(
; CHECK-NEXT: [[RES:%.*]] = select i1 [[COND:%.*]], ptr [[P:%.*]], ptr null
; CHECK-NEXT: ret ptr [[RES]]
; CHECK-NEXT: ret ptr [[RES:%.*]]
;
%res = select i1 %cond, ptr %p, ptr null
ret ptr %res
}

define nonnull ptr @nonnull_ret2(i1 %cond, ptr %p) {
; CHECK-LABEL: @nonnull_ret2(
; CHECK-NEXT: [[RES:%.*]] = select i1 [[COND:%.*]], ptr null, ptr [[P:%.*]]
; CHECK-NEXT: ret ptr [[RES]]
; CHECK-NEXT: ret ptr [[RES:%.*]]
;
%res = select i1 %cond, ptr null, ptr %p
ret ptr %res
}

define nonnull noundef ptr @nonnull_noundef_ret(i1 %cond, ptr %p) {
; CHECK-LABEL: @nonnull_noundef_ret(
; CHECK-NEXT: [[RES:%.*]] = select i1 [[COND:%.*]], ptr [[P:%.*]], ptr null
; CHECK-NEXT: ret ptr [[RES]]
; CHECK-NEXT: ret ptr [[RES:%.*]]
;
%res = select i1 %cond, ptr %p, ptr null
ret ptr %res
}

define nonnull noundef ptr @nonnull_noundef_ret2(i1 %cond, ptr %p) {
; CHECK-LABEL: @nonnull_noundef_ret2(
; CHECK-NEXT: [[RES:%.*]] = select i1 [[COND:%.*]], ptr null, ptr [[P:%.*]]
; CHECK-NEXT: ret ptr [[RES]]
; CHECK-NEXT: ret ptr [[RES:%.*]]
;
%res = select i1 %cond, ptr null, ptr %p
ret ptr %res
Expand All @@ -55,8 +48,7 @@ define nonnull noundef ptr @nonnull_noundef_ret2(i1 %cond, ptr %p) {

define void @nonnull_call(i1 %cond, ptr %p) {
; CHECK-LABEL: @nonnull_call(
; CHECK-NEXT: [[RES:%.*]] = select i1 [[COND:%.*]], ptr [[P:%.*]], ptr null
; CHECK-NEXT: call void @f(ptr nonnull [[RES]])
; CHECK-NEXT: call void @f(ptr nonnull [[RES:%.*]])
; CHECK-NEXT: ret void
;
%res = select i1 %cond, ptr %p, ptr null
Expand All @@ -66,8 +58,7 @@ define void @nonnull_call(i1 %cond, ptr %p) {

define void @nonnull_call2(i1 %cond, ptr %p) {
; CHECK-LABEL: @nonnull_call2(
; CHECK-NEXT: [[RES:%.*]] = select i1 [[COND:%.*]], ptr null, ptr [[P:%.*]]
; CHECK-NEXT: call void @f(ptr nonnull [[RES]])
; CHECK-NEXT: call void @f(ptr nonnull [[RES:%.*]])
; CHECK-NEXT: ret void
;
%res = select i1 %cond, ptr null, ptr %p
Expand All @@ -77,8 +68,7 @@ define void @nonnull_call2(i1 %cond, ptr %p) {

define void @nonnull_noundef_call(i1 %cond, ptr %p) {
; CHECK-LABEL: @nonnull_noundef_call(
; CHECK-NEXT: [[RES:%.*]] = select i1 [[COND:%.*]], ptr [[P:%.*]], ptr null
; CHECK-NEXT: call void @f(ptr noundef nonnull [[RES]])
; CHECK-NEXT: call void @f(ptr noundef nonnull [[RES:%.*]])
; CHECK-NEXT: ret void
;
%res = select i1 %cond, ptr %p, ptr null
Expand All @@ -88,8 +78,7 @@ define void @nonnull_noundef_call(i1 %cond, ptr %p) {

define void @nonnull_noundef_call2(i1 %cond, ptr %p) {
; CHECK-LABEL: @nonnull_noundef_call2(
; CHECK-NEXT: [[RES:%.*]] = select i1 [[COND:%.*]], ptr null, ptr [[P:%.*]]
; CHECK-NEXT: call void @f(ptr noundef nonnull [[RES]])
; CHECK-NEXT: call void @f(ptr noundef nonnull [[RES:%.*]])
; CHECK-NEXT: ret void
;
%res = select i1 %cond, ptr null, ptr %p
Expand Down
7 changes: 2 additions & 5 deletions llvm/test/Transforms/PhaseOrdering/load-store-sameval.ll
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
; RUN: opt -passes='instcombine,early-cse<memssa>' -S %s | FileCheck %s

; FIXME: We can remove the store instruction in the exit block
define i32 @load_store_sameval(ptr %p, i1 %cond1, i1 %cond2) {
; CHECK-LABEL: define i32 @load_store_sameval(
; CHECK-SAME: ptr [[P:%.*]], i1 [[COND1:%.*]], i1 [[COND2:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*:]]
; CHECK-NEXT: [[SPEC_SELECT:%.*]] = select i1 [[COND1]], ptr null, ptr [[P]]
; CHECK-NEXT: [[PRE:%.*]] = load i32, ptr [[SPEC_SELECT]], align 4
; CHECK-NEXT: [[PRE:%.*]] = load i32, ptr [[P]], align 4
; CHECK-NEXT: br label %[[BLOCK:.*]]
; CHECK: [[BLOCK]]:
; CHECK-NEXT: br label %[[BLOCK2:.*]]
; CHECK: [[BLOCK2]]:
; CHECK-NEXT: br i1 [[COND2]], label %[[BLOCK3:.*]], label %[[EXIT:.*]]
; CHECK: [[BLOCK3]]:
; CHECK-NEXT: [[LOAD:%.*]] = load double, ptr [[SPEC_SELECT]], align 8
; CHECK-NEXT: [[LOAD:%.*]] = load double, ptr [[P]], align 8
; CHECK-NEXT: [[CMP:%.*]] = fcmp une double [[LOAD]], 0.000000e+00
; CHECK-NEXT: br i1 [[CMP]], label %[[BLOCK]], label %[[BLOCK2]]
; CHECK: [[EXIT]]:
; CHECK-NEXT: store i32 [[PRE]], ptr [[P]], align 4
; CHECK-NEXT: ret i32 0
;
entry:
Expand Down
20 changes: 20 additions & 0 deletions llvm/test/Transforms/PhaseOrdering/memset-combine.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5

; RUN: opt < %s -passes=instcombine,memcpyopt -S | FileCheck %s

; FIXME: These two memset calls should be merged into a single one.
define void @merge_memset(ptr %p, i1 %cond) {
; CHECK-LABEL: define void @merge_memset(
; CHECK-SAME: ptr [[P:%.*]], i1 [[COND:%.*]]) {
; CHECK-NEXT: [[SEL:%.*]] = select i1 [[COND]], ptr null, ptr [[P]]
; CHECK-NEXT: tail call void @llvm.memset.p0.i64(ptr noundef nonnull align 1 dereferenceable(4096) [[P]], i8 0, i64 4096, i1 false)
; CHECK-NEXT: [[OFF:%.*]] = getelementptr inbounds nuw i8, ptr [[SEL]], i64 4096
; CHECK-NEXT: tail call void @llvm.memset.p0.i64(ptr noundef nonnull align 1 dereferenceable(768) [[OFF]], i8 0, i64 768, i1 false)
; CHECK-NEXT: ret void
;
%sel = select i1 %cond, ptr null, ptr %p
tail call void @llvm.memset.p0.i64(ptr noundef nonnull %sel, i8 0, i64 4096, i1 false)
%off = getelementptr inbounds nuw i8, ptr %sel, i64 4096
tail call void @llvm.memset.p0.i64(ptr noundef nonnull %off, i8 0, i64 768, i1 false)
ret void
}