| // 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 |
| } |
| |