blob: 6f135043ba6dcd5e0a744cc5756c0cacff9a7639 [file] [log] [blame]
// RUN: %eopt --print-activity-analysis='relative verbose' --split-input-file %s | FileCheck %s
// CHECK-LABEL: processing function @sparse_callee
// CHECK: "fadd"(#0)
// CHECK: sources: [#enzyme.argorigin<@sparse_callee(0)>]
// CHECK: sinks: [#enzyme.retorigin<@sparse_callee(1)>]
func.func @sparse_callee(%arg0: f64) -> (f64, f64) {
%zero = llvm.mlir.constant (0.0) : f64
%0 = llvm.fadd %arg0, %arg0 {tag = "fadd"} : f64
return %zero, %0 : f64, f64
}
// CHECK-LABEL: processing function @sparse_caller
// CHECK: "fmul"(#0)
// CHECK: sources: [#enzyme.argorigin<@sparse_caller(1)>]
// CHECK: sinks: [#enzyme.retorigin<@sparse_caller(0)>]
func.func @sparse_caller(%unused: i64, %arg0: f64) -> f64 {
%0 = llvm.fmul %arg0, %arg0 {tag = "fmul"} : f64
%zero, %1 = call @sparse_callee(%0) : (f64) -> (f64, f64)
return %1 : f64
}
// -----
func.func @aliased_callee(%arg0: !llvm.ptr) -> !llvm.ptr {
%c0 = llvm.mlir.constant (0) : i64
%0 = llvm.getelementptr inbounds %arg0[%c0] : (!llvm.ptr, i64) -> !llvm.ptr, f64
return %0 : !llvm.ptr
}
// Test propagation of aliasing through function calls
// CHECK-LABEL: processing function @loadstore
// CHECK: "alloca"(#0)
// CHECK: sources: [#enzyme.argorigin<@loadstore(0)>]
// CHECK: sinks: [#enzyme.retorigin<@loadstore(0)>]
func.func @loadstore(%arg0: f64) -> f64 {
%c1 = llvm.mlir.constant (1) : i64
%ptr = llvm.alloca %c1 x f64 {tag = "alloca"} : (i64) -> !llvm.ptr
%ptr2 = call @aliased_callee(%ptr) : (!llvm.ptr) -> !llvm.ptr
llvm.store %arg0, %ptr2 : f64, !llvm.ptr
%0 = llvm.load %ptr : !llvm.ptr -> f64
return %0 : f64
}
// -----
llvm.func local_unnamed_addr @malloc(i64 {llvm.noundef}) -> (!llvm.ptr {llvm.noalias, llvm.noundef}) attributes {memory = #llvm.memory_effects<other = none, argMem = none, inaccessibleMem = readwrite>}
func.func @returnptr(%arg0: f64) -> !llvm.ptr {
%c8 = llvm.mlir.constant (8) : i64
%ptr = llvm.call @malloc(%c8) {tag = "malloc"} : (i64) -> !llvm.ptr
llvm.store %arg0, %ptr : f64, !llvm.ptr
return %ptr : !llvm.ptr
}
// CHECK-LABEL: processing function @loadstore
// CHECK: "loaded"(#0)
// CHECK: sources: [#enzyme.argorigin<@loadstore(0)>]
// CHECK: sinks: [#enzyme.retorigin<@loadstore(0)>]
func.func @loadstore(%arg0: f64) -> f64 {
%ptr = call @returnptr(%arg0) : (f64) -> !llvm.ptr
%val = llvm.load %ptr {tag = "loaded"} : !llvm.ptr -> f64
return %val : f64
}
// -----
// CHECK-LABEL: processing function @load_nested
// CHECK: forward value origins:
// CHECK: distinct[0]<#enzyme.pseudoclass<@load_nested(1, 0)>> originates from [#enzyme.argorigin<@load_nested(0)>, #enzyme.argorigin<@load_nested(1)>]
func.func @load_nested(%arg0: !llvm.ptr, %arg1: !llvm.ptr) {
%data = llvm.load %arg0 : !llvm.ptr -> !llvm.ptr
%val = llvm.load %data : !llvm.ptr -> f64
llvm.store %val, %arg1 : f64, !llvm.ptr
return
}
// Since we're not keeping track of origin depth, just passing %alloc to @load_nested
// means we need to union the origins of %alloc with everything %alloc points to
// (%inner in this case)
// CHECK-LABEL: processing function @pass_pointer_to
// CHECK: "val"(#0)
// CHECK: sources: [#enzyme.argorigin<@pass_pointer_to(0)>]
// CHECK: sinks: [#enzyme.argorigin<@pass_pointer_to(1)>, #enzyme.argorigin<@pass_pointer_to(2)>]
// CHECK: "inner"(#0)
// CHECK: sources: [#enzyme.argorigin<@pass_pointer_to(0)>, #enzyme.argorigin<@pass_pointer_to(1)>]
// CHECK: sinks: [#enzyme.argorigin<@pass_pointer_to(1)>, #enzyme.argorigin<@pass_pointer_to(2)>]
func.func @pass_pointer_to(%arg0: f64, %alloc: !llvm.ptr, %out: !llvm.ptr) {
%one = llvm.mlir.constant (1) : i64
%val = llvm.fmul %arg0, %arg0 {tag = "val"} : f64
%inner = llvm.load %alloc {tag = "inner"} : !llvm.ptr -> !llvm.ptr
llvm.store %val, %inner : f64, !llvm.ptr
func.call @load_nested(%alloc, %out) : (!llvm.ptr, !llvm.ptr) -> ()
return
}
// -----
func.func @callee(%val: f64, %out: !llvm.ptr) {
llvm.store %val, %out : f64, !llvm.ptr
return
}
// CHECK-LABEL: processing function @caller
// CHECK: "square"(#0)
// CHECK: sources: [#enzyme.argorigin<@caller(1)>]
// CHECK: sinks: [#enzyme.argorigin<@caller(2)>]
func.func @caller(%unused: i32, %val: f64, %out: !llvm.ptr) {
%square = llvm.fmul %val, %val {tag = "square"} : f64
call @callee(%square, %out) : (f64, !llvm.ptr) -> ()
return
}
// -----
#alias_scope_domain4 = #llvm.alias_scope_domain<id = distinct[4]<>, description = "mat_mult">
#alias_scope13 = #llvm.alias_scope<id = distinct[20]<>, domain = #alias_scope_domain4, description = "mat_mult: %out">
// Make sure that known inactive operations don't cause worst-case pessimistic results
// CHECK-LABEL: processing function @with_noop
// CHECK: "alloca"(#0)
// CHECK: sources: [#enzyme.argorigin<@with_noop(0)>]
// CHECK: sinks: [#enzyme.retorigin<@with_noop(0)>]
func.func @with_noop(%arg0: f64) -> f64 {
%c1 = llvm.mlir.constant (1) : i64
%ptr = llvm.alloca %c1 x f64 {tag = "alloca"} : (i64) -> !llvm.ptr
llvm.store %arg0, %ptr : f64, !llvm.ptr
llvm.intr.experimental.noalias.scope.decl #alias_scope13
%0 = llvm.load %ptr : !llvm.ptr -> f64
return %0 : f64
}