Skip to content

Commit

Permalink
Annotate return values of allocation functions with dereferenceable_o…
Browse files Browse the repository at this point in the history
…r_null

Summary:
Example
define dso_local noalias i8* @_Z6maixxnv() local_unnamed_addr #0 {
entry:
  %call = tail call noalias dereferenceable_or_null(64) i8* @malloc(i64 64) #6
  ret i8* %call
}


Reviewers: jdoerfert

Reviewed By: jdoerfert

Subscribers: aaron.ballman, llvm-commits

Tags: #llvm

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

llvm-svn: 370168
  • Loading branch information
davidbolvansky committed Aug 28, 2019
1 parent b9d87b9 commit 05bda8b
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 105 deletions.
5 changes: 5 additions & 0 deletions llvm/include/llvm/Analysis/MemoryBuiltins.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ bool isReallocLikeFn(const Value *V, const TargetLibraryInfo *TLI,
/// reallocates memory (e.g., realloc).
bool isReallocLikeFn(const Function *F, const TargetLibraryInfo *TLI);

/// Tests if a value is a call or invoke to a library function that
/// allocates memory and throws if an allocation failed (e.g., new).
bool isOpNewLikeFn(const Value *V, const TargetLibraryInfo *TLI,
bool LookThroughBitCast = false);

//===----------------------------------------------------------------------===//
// malloc Call Utility Functions.
//
Expand Down
7 changes: 7 additions & 0 deletions llvm/lib/Analysis/MemoryBuiltins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,13 @@ bool llvm::isReallocLikeFn(const Function *F, const TargetLibraryInfo *TLI) {
return getAllocationDataForFunction(F, ReallocLike, TLI).hasValue();
}

/// Tests if a value is a call or invoke to a library function that
/// allocates memory and throws if an allocation failed (e.g., new).
bool llvm::isOpNewLikeFn(const Value *V, const TargetLibraryInfo *TLI,
bool LookThroughBitCast) {
return getAllocationData(V, OpNewLike, TLI, LookThroughBitCast).hasValue();
}

/// extractMallocCall - Returns the corresponding CallInst if the instruction
/// is a malloc call. Since CallInst::CreateMalloc() only creates calls, we
/// ignore InvokeInst here.
Expand Down
33 changes: 33 additions & 0 deletions llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4178,8 +4178,41 @@ static IntrinsicInst *findInitTrampoline(Value *Callee) {
return nullptr;
}

static void annotateAnyAllocSite(CallBase &Call, const TargetLibraryInfo *TLI) {
ConstantInt *Op0C = dyn_cast<ConstantInt>(Call.getOperand(0));
ConstantInt *Op1C = (Call.getNumArgOperands() == 1)
? nullptr
: dyn_cast<ConstantInt>(Call.getOperand(1));
if ((Op0C && Op0C->isNullValue()) || (Op1C && Op1C->isNullValue()))
return;
if (isMallocLikeFn(&Call, TLI) && Op0C) {
Call.addAttribute(AttributeList::ReturnIndex,
Attribute::getWithDereferenceableOrNullBytes(
Call.getContext(), Op0C->getZExtValue()));
} else if (isOpNewLikeFn(&Call, TLI) && Op0C) {
Call.addAttribute(AttributeList::ReturnIndex,
Attribute::getWithDereferenceableBytes(
Call.getContext(), Op0C->getZExtValue()));
} else if (isReallocLikeFn(&Call, TLI) && Op1C) {
Call.addAttribute(AttributeList::ReturnIndex,
Attribute::getWithDereferenceableOrNullBytes(
Call.getContext(), Op1C->getZExtValue()));
} else if (isCallocLikeFn(&Call, TLI) && Op0C && Op1C) {
bool Overflow;
const APInt &N = Op0C->getValue();
APInt Size = N.umul_ov(Op1C->getValue(), Overflow);
if (!Overflow)
Call.addAttribute(AttributeList::ReturnIndex,
Attribute::getWithDereferenceableOrNullBytes(
Call.getContext(), Size.getZExtValue()));
}
}

/// Improvements for call, callbr and invoke instructions.
Instruction *InstCombiner::visitCallBase(CallBase &Call) {
if (isAllocationFn(&Call, &TLI))
annotateAnyAllocSite(Call, &TLI);

if (isAllocLikeFn(&Call, &TLI))
return visitAllocSite(Call);

Expand Down
108 changes: 75 additions & 33 deletions llvm/test/Transforms/InstCombine/compare-unescaped.ll
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; RUN: opt -instcombine -S < %s | FileCheck %s

@gp = global i32* null, align 8

declare i8* @malloc(i64) #1

define i1 @compare_global_trivialeq() {
; CHECK-LABEL: @compare_global_trivialeq(
; CHECK-NEXT: ret i1 false
;
%m = call i8* @malloc(i64 4)
%bc = bitcast i8* %m to i32*
%lgp = load i32*, i32** @gp, align 8
%cmp = icmp eq i32* %bc, %lgp
ret i1 %cmp
; CHECK-LABEL: compare_global_trivialeq
; CHECK: ret i1 false
}

define i1 @compare_global_trivialne() {
; CHECK-LABEL: @compare_global_trivialne(
; CHECK-NEXT: ret i1 true
;
%m = call i8* @malloc(i64 4)
%bc = bitcast i8* %m to i32*
%lgp = load i32*, i32** @gp, align 8
%cmp = icmp ne i32* %bc, %lgp
ret i1 %cmp
; CHECK-LABEL: compare_global_trivialne
; CHECK: ret i1 true
}


Expand All @@ -30,102 +33,143 @@ define i1 @compare_global_trivialne() {
; The comparison should fold to false irrespective of whether the call to malloc can be elided or not
declare void @f()
define i1 @compare_and_call_with_deopt() {
; CHECK-LABEL: compare_and_call_with_deopt
; CHECK-LABEL: @compare_and_call_with_deopt(
; CHECK-NEXT: [[M:%.*]] = call dereferenceable_or_null(24) i8* @malloc(i64 24)
; CHECK-NEXT: tail call void @f() [ "deopt"(i8* [[M]]) ]
; CHECK-NEXT: ret i1 false
;
%m = call i8* @malloc(i64 24)
%bc = bitcast i8* %m to i32*
%lgp = load i32*, i32** @gp, align 8, !nonnull !0
%cmp = icmp eq i32* %lgp, %bc
tail call void @f() [ "deopt"(i8* %m) ]
ret i1 %cmp
; CHECK: ret i1 false
}

; Same functon as above with deopt operand in function f, but comparison is NE
define i1 @compare_ne_and_call_with_deopt() {
; CHECK-LABEL: compare_ne_and_call_with_deopt
; CHECK-LABEL: @compare_ne_and_call_with_deopt(
; CHECK-NEXT: [[M:%.*]] = call dereferenceable_or_null(24) i8* @malloc(i64 24)
; CHECK-NEXT: tail call void @f() [ "deopt"(i8* [[M]]) ]
; CHECK-NEXT: ret i1 true
;
%m = call i8* @malloc(i64 24)
%bc = bitcast i8* %m to i32*
%lgp = load i32*, i32** @gp, align 8, !nonnull !0
%cmp = icmp ne i32* %lgp, %bc
tail call void @f() [ "deopt"(i8* %m) ]
ret i1 %cmp
; CHECK: ret i1 true
}

; Same function as above, but global not marked nonnull, and we cannot fold the comparison
define i1 @compare_ne_global_maybe_null() {
; CHECK-LABEL: compare_ne_global_maybe_null
; CHECK-LABEL: @compare_ne_global_maybe_null(
; CHECK-NEXT: [[M:%.*]] = call dereferenceable_or_null(24) i8* @malloc(i64 24)
; CHECK-NEXT: [[BC:%.*]] = bitcast i8* [[M]] to i32*
; CHECK-NEXT: [[LGP:%.*]] = load i32*, i32** @gp, align 8
; CHECK-NEXT: [[CMP:%.*]] = icmp ne i32* [[LGP]], [[BC]]
; CHECK-NEXT: tail call void @f() [ "deopt"(i8* [[M]]) ]
; CHECK-NEXT: ret i1 [[CMP]]
;
%m = call i8* @malloc(i64 24)
%bc = bitcast i8* %m to i32*
%lgp = load i32*, i32** @gp
%cmp = icmp ne i32* %lgp, %bc
tail call void @f() [ "deopt"(i8* %m) ]
ret i1 %cmp
; CHECK: ret i1 %cmp
}

; FIXME: The comparison should fold to false since %m escapes (call to function escape)
; after the comparison.
declare void @escape(i8*)
define i1 @compare_and_call_after() {
; CHECK-LABEL: compare_and_call_after
; CHECK-LABEL: @compare_and_call_after(
; CHECK-NEXT: [[M:%.*]] = call dereferenceable_or_null(24) i8* @malloc(i64 24)
; CHECK-NEXT: [[BC:%.*]] = bitcast i8* [[M]] to i32*
; CHECK-NEXT: [[LGP:%.*]] = load i32*, i32** @gp, align 8, !nonnull !0
; CHECK-NEXT: [[CMP:%.*]] = icmp eq i32* [[LGP]], [[BC]]
; CHECK-NEXT: br i1 [[CMP]], label [[ESCAPE_CALL:%.*]], label [[JUST_RETURN:%.*]]
; CHECK: escape_call:
; CHECK-NEXT: call void @escape(i8* [[M]])
; CHECK-NEXT: ret i1 true
; CHECK: just_return:
; CHECK-NEXT: ret i1 [[CMP]]
;
%m = call i8* @malloc(i64 24)
%bc = bitcast i8* %m to i32*
%lgp = load i32*, i32** @gp, align 8, !nonnull !0
%cmp = icmp eq i32* %bc, %lgp
br i1 %cmp, label %escape_call, label %just_return

escape_call:
call void @escape(i8* %m)
ret i1 true
call void @escape(i8* %m)
ret i1 true

just_return:
ret i1 %cmp
ret i1 %cmp
}

define i1 @compare_distinct_mallocs() {
; CHECK-LABEL: @compare_distinct_mallocs(
; CHECK-NEXT: ret i1 false
;
%m = call i8* @malloc(i64 4)
%n = call i8* @malloc(i64 4)
%cmp = icmp eq i8* %m, %n
ret i1 %cmp
; CHECK-LABEL: compare_distinct_mallocs
; CHECK: ret i1 false
}

; the compare is folded to true since the folding compare looks through bitcasts.
; call to malloc and the bitcast instructions are elided after that since there are no uses of the malloc
; the compare is folded to true since the folding compare looks through bitcasts.
; call to malloc and the bitcast instructions are elided after that since there are no uses of the malloc
define i1 @compare_samepointer_under_bitcast() {
; CHECK-LABEL: @compare_samepointer_under_bitcast(
; CHECK-NEXT: ret i1 true
;
%m = call i8* @malloc(i64 4)
%bc = bitcast i8* %m to i32*
%bcback = bitcast i32* %bc to i8*
%cmp = icmp eq i8* %m, %bcback
ret i1 %cmp
; CHECK-LABEL: compare_samepointer_under_bitcast
; CHECK: ret i1 true
}

; the compare is folded to true since the folding compare looks through bitcasts.
; the compare is folded to true since the folding compare looks through bitcasts.
; The malloc call for %m cannot be elided since it is used in the call to function f.
define i1 @compare_samepointer_escaped() {
; CHECK-LABEL: @compare_samepointer_escaped(
; CHECK-NEXT: [[M:%.*]] = call dereferenceable_or_null(4) i8* @malloc(i64 4)
; CHECK-NEXT: call void @f() [ "deopt"(i8* [[M]]) ]
; CHECK-NEXT: ret i1 true
;
%m = call i8* @malloc(i64 4)
%bc = bitcast i8* %m to i32*
%bcback = bitcast i32* %bc to i8*
%cmp = icmp eq i8* %m, %bcback
call void @f() [ "deopt"(i8* %m) ]
ret i1 %cmp
; CHECK-LABEL: compare_samepointer_escaped
; CHECK-NEXT: %m = call i8* @malloc(i64 4)
; CHECK-NEXT: call void @f() [ "deopt"(i8* %m) ]
; CHECK: ret i1 true
}

; Technically, we can fold the %cmp2 comparison, even though %m escapes through
; the ret statement since `ret` terminates the function and we cannot reach from
; the ret to cmp.
; the ret to cmp.
; FIXME: Folding this %cmp2 when %m escapes through ret could be an issue with
; cross-threading data dependencies since we do not make the distinction between
; atomic and non-atomic loads in capture tracking.
define i8* @compare_ret_escape(i8* %c) {
; CHECK-LABEL: @compare_ret_escape(
; CHECK-NEXT: [[M:%.*]] = call dereferenceable_or_null(4) i8* @malloc(i64 4)
; CHECK-NEXT: [[N:%.*]] = call dereferenceable_or_null(4) i8* @malloc(i64 4)
; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8* [[N]], [[C:%.*]]
; CHECK-NEXT: br i1 [[CMP]], label [[RETST:%.*]], label [[CHK:%.*]]
; CHECK: retst:
; CHECK-NEXT: ret i8* [[M]]
; CHECK: chk:
; CHECK-NEXT: [[BC:%.*]] = bitcast i8* [[M]] to i32*
; CHECK-NEXT: [[LGP:%.*]] = load i32*, i32** @gp, align 8, !nonnull !0
; CHECK-NEXT: [[CMP2:%.*]] = icmp eq i32* [[LGP]], [[BC]]
; CHECK-NEXT: br i1 [[CMP2]], label [[RETST]], label [[CHK2:%.*]]
; CHECK: chk2:
; CHECK-NEXT: ret i8* [[N]]
;
%m = call i8* @malloc(i64 4)
%n = call i8* @malloc(i64 4)
%cmp = icmp eq i8* %n, %c
Expand All @@ -142,23 +186,21 @@ chk:

chk2:
ret i8* %n
; CHECK-LABEL: compare_ret_escape
; CHECK: %cmp = icmp eq i8* %n, %c
; CHECK: %cmp2 = icmp eq i32* %lgp, %bc
}

; The malloc call for %m cannot be elided since it is used in the call to function f.
; However, the cmp can be folded to true as %n doesnt escape and %m, %n are distinct allocations
define i1 @compare_distinct_pointer_escape() {
; CHECK-LABEL: @compare_distinct_pointer_escape(
; CHECK-NEXT: [[M:%.*]] = call dereferenceable_or_null(4) i8* @malloc(i64 4)
; CHECK-NEXT: tail call void @f() [ "deopt"(i8* [[M]]) ]
; CHECK-NEXT: ret i1 true
;
%m = call i8* @malloc(i64 4)
%n = call i8* @malloc(i64 4)
tail call void @f() [ "deopt"(i8* %m) ]
%cmp = icmp ne i8* %m, %n
ret i1 %cmp
; CHECK-LABEL: compare_distinct_pointer_escape
; CHECK-NEXT: %m = call i8* @malloc(i64 4)
; CHECK-NEXT: tail call void @f() [ "deopt"(i8* %m) ]
; CHECK-NEXT: ret i1 true
}

!0 = !{}
19 changes: 9 additions & 10 deletions llvm/test/Transforms/InstCombine/deref-alloc-fns.ll
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ define noalias i8* @malloc_nonconstant_size(i64 %n) {

define noalias i8* @malloc_constant_size() {
; CHECK-LABEL: @malloc_constant_size(
; CHECK-NEXT: [[CALL:%.*]] = tail call noalias i8* @malloc(i64 40)
; CHECK-NEXT: [[CALL:%.*]] = tail call noalias dereferenceable_or_null(40) i8* @malloc(i64 40)
; CHECK-NEXT: ret i8* [[CALL]]
;
%call = tail call noalias i8* @malloc(i64 40)
Expand All @@ -35,7 +35,7 @@ define noalias i8* @malloc_constant_size2() {

define noalias i8* @malloc_constant_size3() {
; CHECK-LABEL: @malloc_constant_size3(
; CHECK-NEXT: [[CALL:%.*]] = tail call noalias dereferenceable(80) i8* @malloc(i64 40)
; CHECK-NEXT: [[CALL:%.*]] = tail call noalias dereferenceable(80) dereferenceable_or_null(40) i8* @malloc(i64 40)
; CHECK-NEXT: ret i8* [[CALL]]
;
%call = tail call noalias dereferenceable(80) i8* @malloc(i64 40)
Expand Down Expand Up @@ -72,7 +72,7 @@ define noalias i8* @realloc_constant_zero_size(i8* %p) {

define noalias i8* @realloc_constant_size(i8* %p) {
; CHECK-LABEL: @realloc_constant_size(
; CHECK-NEXT: [[CALL:%.*]] = tail call noalias i8* @realloc(i8* [[P:%.*]], i64 40)
; CHECK-NEXT: [[CALL:%.*]] = tail call noalias dereferenceable_or_null(40) i8* @realloc(i8* [[P:%.*]], i64 40)
; CHECK-NEXT: ret i8* [[CALL]]
;
%call = tail call noalias i8* @realloc(i8* %p, i64 40)
Expand Down Expand Up @@ -136,7 +136,7 @@ define noalias i8* @calloc_constant_zero_size3(i64 %n) {

define noalias i8* @calloc_constant_size() {
; CHECK-LABEL: @calloc_constant_size(
; CHECK-NEXT: [[CALL:%.*]] = tail call noalias i8* @calloc(i64 16, i64 8)
; CHECK-NEXT: [[CALL:%.*]] = tail call noalias dereferenceable_or_null(128) i8* @calloc(i64 16, i64 8)
; CHECK-NEXT: ret i8* [[CALL]]
;
%call = tail call noalias i8* @calloc(i64 16, i64 8)
Expand All @@ -152,7 +152,6 @@ define noalias i8* @calloc_constant_size_overflow() {
ret i8* %call
}


define noalias i8* @op_new_nonconstant_size(i64 %n) {
; CHECK-LABEL: @op_new_nonconstant_size(
; CHECK-NEXT: [[CALL:%.*]] = tail call i8* @_Znam(i64 [[N:%.*]])
Expand All @@ -162,17 +161,17 @@ define noalias i8* @op_new_nonconstant_size(i64 %n) {
ret i8* %call
}

define noalias i8* @op_new_constant_zero_size() {
; CHECK-LABEL: @op_new_constant_zero_size(
; CHECK-NEXT: [[CALL:%.*]] = tail call i8* @_Znam(i64 40)
define noalias i8* @op_new_constant_size() {
; CHECK-LABEL: @op_new_constant_size(
; CHECK-NEXT: [[CALL:%.*]] = tail call dereferenceable_or_null(40) i8* @_Znam(i64 40)
; CHECK-NEXT: ret i8* [[CALL]]
;
%call = tail call i8* @_Znam(i64 40)
ret i8* %call
}

define noalias i8* @op_new_constant_size() {
; CHECK-LABEL: @op_new_constant_size(
define noalias i8* @op_new_constant_zero_size() {
; CHECK-LABEL: @op_new_constant_zero_size(
; CHECK-NEXT: [[CALL:%.*]] = tail call i8* @_Znam(i64 0)
; CHECK-NEXT: ret i8* [[CALL]]
;
Expand Down
Loading

0 comments on commit 05bda8b

Please sign in to comment.