// RUN: %eopt --print-activity-analysis --split-input-file %s 2>&1 | FileCheck %s

// CHECK-LABEL: @callgraph_sparse:
// CHECK:         "callres": Active
func.func private @mysquare(%arg0: f64) -> f64 {
    %mul = arith.mulf %arg0, %arg0 : f64
    return %mul : f64
}

func.func @callgraph_sparse(%arg0: f64) -> f64 {
    %squared = call @mysquare(%arg0) {tag = "callres"} : (f64) -> f64
    return %squared : f64
}

// -----

func.func private @noop(%arg0: memref<f64>) {
    return
}

// CHECK-LABEL: @callgraph_dense:
// CHECK:         "sin": Active
func.func @callgraph_dense(%arg0: f64) -> f64 {
    %m = memref.alloc() : memref<f64>
    memref.store %arg0, %m[] : memref<f64>
    call @noop(%m) : (memref<f64>) -> ()
    %loaded = memref.load %m[] : memref<f64>
    %sin = math.sin %loaded {tag = "sin"} : f64
    return %sin : f64
}

// -----

func.func private @identity(%arg0: memref<f64>) -> memref<f64> {
    %space = memref.alloc() : memref<memref<f64>>
    memref.store %arg0, %space[] : memref<memref<f64>>
    %loaded = memref.load %space[] : memref<memref<f64>>
    memref.dealloc %space : memref<memref<f64>>
    return %loaded : memref<f64>
}

// CHECK-LABEL: @aliased_store:
// CHECK:         "retval": Active
func.func @aliased_store(%arg0: f64) -> f64 {
    %ptr = memref.alloc() : memref<f64>
    memref.store %arg0, %ptr[] : memref<f64>
    %new_ptr = call @identity(%ptr) : (memref<f64>) -> memref<f64>
    %val = memref.load %new_ptr[] {tag = "retval"} : memref<f64>
    return %val : f64
}

// -----

func.func private @write(%arg0: f64, %ptr: memref<f64>) {
    memref.store %arg0, %ptr[] : memref<f64>
    return
}

func.func private @read(%ptr: memref<f64>) -> f64 {
    %val = memref.load %ptr[] : memref<f64>
    return %val : f64
}

// CHECK-LABEL: @across_func_boundaries
// CHECK:         "retval": Active
func.func @across_func_boundaries(%arg0: f64) -> f64 {
    %ptr = memref.alloc() : memref<f64>
    call @write(%arg0, %ptr) : (f64, memref<f64>) -> ()
    %val = call @read(%ptr) {tag = "retval"} : (memref<f64>) -> f64
    return %val : f64
}

func.func private @no_write(%arg0: f64, %ptr: memref<f64>) {
    %cst = arith.constant 3.0 : f64
    memref.store %cst, %ptr[] : memref<f64>
    return
}
// CHECK-LABEL: @across_func_boundaries_const
// CHECK:         "retval": Constant
func.func @across_func_boundaries_const(%arg0: f64) -> f64 {
    %ptr = memref.alloc() : memref<f64>
    call @no_write(%arg0, %ptr) : (f64, memref<f64>) -> ()
    %val = call @read(%ptr) {tag = "retval"} : (memref<f64>) -> f64
    return %val : f64
}

// -----

// Like across_func_boundaries_const, but in the LLVM dialect
llvm.func private @identity(%arg0: !llvm.ptr) -> !llvm.ptr attributes {} {
    %c4 = llvm.mlir.constant (4) : i64
    %mem = memref.alloc() : memref<f32>
    %nonaliasidx = memref.extract_aligned_pointer_as_index %mem : memref<f32> -> index
    %nonaliasi64 = arith.index_cast %nonaliasidx : index to i64
    %nonalias = llvm.inttoptr %nonaliasi64 : i64 to !llvm.ptr
    %inner = llvm.mlir.constant (4.5 : f32) : f32
    llvm.store %inner, %nonalias : f32, !llvm.ptr
    llvm.return %nonalias : !llvm.ptr
}

// CHECK-LABEL: @nonaliased_store:
// CHECK:         "retval": Constant
func.func @nonaliased_store(%arg0: f32) -> f32 {
    %c1 = llvm.mlir.constant (1) : i64
    %ptr = llvm.alloca %c1 x f32 : (i64) -> !llvm.ptr
    llvm.store %arg0, %ptr : f32, !llvm.ptr

    %new_ptr = llvm.call @identity(%ptr) : (!llvm.ptr) -> !llvm.ptr
    %val = llvm.load %new_ptr {tag = "retval"} : !llvm.ptr -> f32
    return %val : f32
}
