; RUN: opt -functionattrs --disable-nofree-inference=false -S < %s | FileCheck %s --check-prefix=FNATTR
; RUN: opt -attributor --attributor-disable=false -S < %s | FileCheck %s --check-prefix=ATTRIBUTOR

target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"

; Test cases specifically designed for the "nofree" function attribute.
; We use FIXME's to indicate problems and missing attributes.

; Free functions
declare void @free(i8* nocapture) local_unnamed_addr #1
declare noalias i8* @realloc(i8* nocapture, i64) local_unnamed_addr #0
declare void @_ZdaPv(i8*) local_unnamed_addr #2


; TEST 1 (positive case)
; FNATTR: Function Attrs: noinline norecurse nounwind readnone uwtable
; FNATTR-NEXT: define void @only_return()
; ATTRIBUTOR: Function Attrs: nofree noinline nosync nounwind uwtable
; ATTRIBUTOR-NEXT: define void @only_return()
define void @only_return() #0 {
    ret void
}


; TEST 2 (negative case)
; Only free
; void only_free(char* p) {
;    free(p);
; }

; FNATTR: Function Attrs: noinline nounwind uwtable
; FNATTR-NEXT: define void @only_free(i8* nocapture) local_unnamed_addr
; ATTRIBUTOR: Function Attrs: noinline nounwind uwtable
; ATTRIBUTOR-NOT: nofree
; ATTRIBUTOR-NEXT: define void @only_free(i8* nocapture) local_unnamed_addr #1
define void @only_free(i8* nocapture) local_unnamed_addr #0 {
    tail call void @free(i8* %0) #1
    ret void
}


; TEST 3 (negative case)
; Free occurs in same scc.
; void free_in_scc1(char*p){
;    free_in_scc2(p);
; }
; void free_in_scc2(char*p){
;    free_in_scc1(p);
;    free(p);
; }


; FNATTR: Function Attrs: noinline nounwind uwtable
; FNATTR-NEXT: define void @free_in_scc1(i8* nocapture) local_unnamed_addr
; ATTRIBUTOR: Function Attrs: noinline nounwind uwtable
; ATTRIBUTOR-NOT: nofree
; ATTRIBUTOR-NEXT :define void @free_in_scc1(i8* nocapture) local_unnamed_addr
define void @free_in_scc1(i8* nocapture) local_unnamed_addr #0 {
  tail call void @free_in_scc2(i8* %0) #1
  ret void
}


; FNATTR: Function Attrs: noinline nounwind uwtable
; FNATTR-NEXT: define void @free_in_scc2(i8* nocapture) local_unnamed_addr
; ATTRIBUTOR: Function Attrs: noinline nounwind uwtable
; ATTRIBUTOR-NOT: nofree
; ATTRIBUTOR: define void @free_in_scc2(i8* nocapture) local_unnamed_addr
define void @free_in_scc2(i8* nocapture) local_unnamed_addr #0 {
  tail call void @free_in_scc1(i8* %0)
  tail call void @free(i8* %0) #1
  ret void
}


; TEST 4 (positive case)
; Free doesn't occur.
; void mutual_recursion1(){
;    mutual_recursion2();
; }
; void mutual_recursion2(){
;     mutual_recursion1();
; }


; FNATTR: Function Attrs: noinline nounwind readnone uwtable
; FNATTR-NEXT: define void @mutual_recursion1()
; ATTRIBUTOR: Function Attrs: nofree noinline nosync nounwind uwtable
; ATTRIBUTOR-NEXT: define void @mutual_recursion1()
define void @mutual_recursion1() #0 {
  call void @mutual_recursion2()
  ret void
}

; FNATTR: Function Attrs: noinline nounwind readnone uwtable
; FNATTR-NEXT: define void @mutual_recursion2()
; ATTRIBUTOR: Function Attrs: nofree noinline nosync nounwind uwtable
; ATTRIBUTOR-NEXT: define void @mutual_recursion2()
define void @mutual_recursion2() #0 {
  call void @mutual_recursion1()
  ret void
}


; TEST 5
; C++ delete operation (negative case)
; void delete_op (char p[]){
;     delete [] p;
; }

; FNATTR: Function Attrs: noinline nounwind uwtable
; FNATTR-NEXT: define void @_Z9delete_opPc(i8*) local_unnamed_addr
; ATTRIBUTOR: Function Attrs: noinline nounwind uwtable
; ATTRIBUTOR-NOT: nofree
; ATTRIBUTOR-NEXT: define void @_Z9delete_opPc(i8*) local_unnamed_addr #1
define void @_Z9delete_opPc(i8*) local_unnamed_addr #0 {
  %2 = icmp eq i8* %0, null
  br i1 %2, label %4, label %3

; <label>:3:                                      ; preds = %1
  tail call void @_ZdaPv(i8* nonnull %0) #2
  br label %4

; <label>:4:                                      ; preds = %3, %1
  ret void
}


; TEST 6 (negative case)
; Call realloc
; FNATTR: Function Attrs: noinline nounwind uwtable
; FNATTR-NEXT: define noalias i8* @call_realloc(i8* nocapture, i64) local_unnamed_addr
; ATTRIBUTOR: Function Attrs: noinline nounwind uwtable
; ATTRIBUTOR-NOT: nofree
; ATTRIBUTOR-NEXT: define noalias i8* @call_realloc(i8* nocapture, i64) local_unnamed_addr
define noalias i8* @call_realloc(i8* nocapture, i64) local_unnamed_addr #0 {
    %ret = tail call i8* @realloc(i8* %0, i64 %1) #2
    ret i8* %ret
}


; TEST 7 (positive case)
; Call function declaration with "nofree"


; FNATTR: Function Attrs: nofree noinline nounwind readnone uwtable
; FNATTR-NEXT: declare void @nofree_function()
; ATTRIBUTOR: Function Attrs:  nofree noinline nounwind readnone uwtable 
; ATTRIBUTOR-NEXT: declare void @nofree_function()
declare void @nofree_function() nofree readnone #0

; FNATTR: Function Attrs: noinline nounwind readnone uwtable
; FNATTR-NEXT: define void @call_nofree_function()
; ATTRIBUTOR: Function Attrs: nofree noinline nosync nounwind uwtable
; ATTRIBUTOR-NEXT: define void @call_nofree_function()
define void @call_nofree_function() #0 {
    tail call void @nofree_function()
    ret void
}

; TEST 8 (negative case)
; Call function declaration without "nofree"


; ATTRIBUTOR: Function Attrs: noinline nounwind uwtable
; ATTRIBUTOR-NEXT: declare void @maybe_free()
declare void @maybe_free() #0


; FNATTR: Function Attrs: noinline nounwind uwtable
; FNATTR: define void @call_maybe_free()
; ATTRIBUTOR: Function Attrs: noinline nounwind uwtable
; ATTRIBUTOR-NOT: nofree
; ATTRIBUTOR-NEXT: define void @call_maybe_free()
define void @call_maybe_free() #0 {
    tail call void @maybe_free()
    ret void
}


; TEST 9 (negative case)
; Call both of above functions

; FNATTR: Function Attrs: noinline nounwind uwtable
; FNATTR-NEXT: define void @call_both()
; ATTRIBUTOR: Function Attrs: noinline nounwind uwtable
; ATTRIBUTOR-NOT: nofree
; ATTRIBUTOR-NEXT: define void @call_both()
define void @call_both() #0 {
    tail call void @maybe_free()
    tail call void @nofree_function()
    ret void
}


; TEST 10 (positive case)
; Call intrinsic function
; FNATTRS: Function Attrs: noinline readnone speculatable
; FNATTRS-NEXT: declare float @llvm.floor.f32(float)
; ATTRIBUTOR: Function Attrs: nounwind readnone speculatable
; ATTRIBUTOR-NEXT: declare float @llvm.floor.f32(float)
declare float @llvm.floor.f32(float)

; FNATTRS: Function Attrs: noinline nounwind uwtable
; FNATTRS-NEXT: define void @call_floor(float %a)
; FIXME: missing nofree
; ATTRIBUTOR: Function Attrs: noinline nosync nounwind uwtable
; ATTRIBUTOR-NEXT: define void @call_floor(float %a)

define void @call_floor(float %a) #0 {
    tail call float @llvm.floor.f32(float %a)
    ret void
}

; TEST 11 (positive case)
; Check propagation.

; FNATTRS: Function Attrs: noinline nounwind uwtable
; FNATTRS-NEXT: define void @f1()
; ATTRIBUTOR: Function Attrs: nofree noinline nosync nounwind uwtable
; ATTRIBUTOR-NEXT: define void @f1()
define void @f1() #0 {
    tail call void @nofree_function()
    ret void
}

; FNATTRS: Function Attrs: noinline nounwind uwtable
; FNATTRS-NEXT: define void @f2()
; ATTRIBUTOR: Function Attrs: nofree noinline nosync nounwind uwtable
; ATTRIBUTOR-NEXT: define void @f2()
define void @f2() #0 {
    tail call void @f1()
    ret void
}


attributes #0 = { nounwind uwtable noinline }
attributes #1 = { nounwind }
attributes #2 = { nobuiltin nounwind }
