// RUN: mlir-opt %s -acc-specialize-for-host | FileCheck %s // Recipe definitions acc.private.recipe @privatization_memref_i32 : memref init { ^bb0(%arg0: memref): %0 = memref.alloca() : memref acc.yield %0 : memref } acc.firstprivate.recipe @firstprivatization_memref_i32 : memref init { ^bb0(%arg0: memref): %0 = memref.alloca() : memref acc.yield %0 : memref } copy { ^bb0(%arg0: memref, %arg1: memref): %0 = memref.load %arg0[] : memref memref.store %0, %arg1[] : memref acc.terminator } acc.reduction.recipe @reduction_add_memref_i32 : memref reduction_operator init { ^bb0(%arg0: memref): %c0_i32 = arith.constant 0 : i32 %0 = memref.alloca() : memref memref.store %c0_i32, %0[] : memref acc.yield %0 : memref } combiner { ^bb0(%arg0: memref, %arg1: memref): %0 = memref.load %arg0[] : memref %1 = memref.load %arg1[] : memref %2 = arith.addi %0, %1 : i32 memref.store %2, %arg0[] : memref acc.yield %arg0 : memref } //===----------------------------------------------------------------------===// // Orphan data entry ops - replaced with var //===----------------------------------------------------------------------===// acc.routine @acc_routine_private func(@private) seq // CHECK-LABEL: func.func @private // CHECK-NOT: acc.private func.func @private(%arg0 : memref) attributes {acc.routine_info = #acc.routine_info<[@acc_routine_private]>} { %c0 = arith.constant 0 : i32 %0 = acc.private varPtr(%arg0 : memref) recipe(@privatization_memref_i32) -> memref memref.store %c0, %0[] : memref return } acc.routine @acc_routine_cache func(@cache) seq // CHECK-LABEL: func.func @cache // CHECK-NOT: acc.cache func.func @cache(%arg0 : memref) attributes {acc.routine_info = #acc.routine_info<[@acc_routine_cache]>} { %c0 = arith.constant 0 : i32 %0 = acc.cache varPtr(%arg0 : memref) -> memref memref.store %c0, %0[] : memref return } //===----------------------------------------------------------------------===// // Orphan atomic operations - converted to load/store //===----------------------------------------------------------------------===// acc.routine @acc_routine_atomic func(@orphan_atomic_update) seq // CHECK-LABEL: func.func @orphan_atomic_update // CHECK-NOT: acc.atomic.update // CHECK: memref.load // CHECK: arith.addi // CHECK: memref.store func.func @orphan_atomic_update(%arg0 : memref) attributes {acc.routine_info = #acc.routine_info<[@acc_routine_atomic]>} { acc.atomic.update %arg0 : memref { ^bb0(%arg1: i32): %c1 = arith.constant 1 : i32 %1 = arith.addi %arg1, %c1 : i32 acc.yield %1 : i32 } return } acc.routine @acc_routine_atomic_read func(@orphan_atomic_read) seq // CHECK-LABEL: func.func @orphan_atomic_read // CHECK-NOT: acc.atomic.read // CHECK: memref.copy %arg0, %arg1 func.func @orphan_atomic_read(%arg0 : memref, %arg1 : memref) attributes {acc.routine_info = #acc.routine_info<[@acc_routine_atomic_read]>} { acc.atomic.read %arg1 = %arg0 : memref, memref, i32 return } acc.routine @acc_routine_atomic_write func(@orphan_atomic_write) seq // CHECK-LABEL: func.func @orphan_atomic_write // CHECK-NOT: acc.atomic.write // CHECK: memref.store %arg1, %arg0[] func.func @orphan_atomic_write(%arg0 : memref, %arg1 : i32) attributes {acc.routine_info = #acc.routine_info<[@acc_routine_atomic_write]>} { acc.atomic.write %arg0 = %arg1 : memref, i32 return } acc.routine @acc_routine_atomic_capture func(@orphan_atomic_capture) seq // CHECK-LABEL: func.func @orphan_atomic_capture // CHECK-NOT: acc.atomic.capture // CHECK: memref.copy %arg0, %arg1 // CHECK: [[LOAD:%.*]] = memref.load %arg0[] // CHECK: [[INC:%.*]] = arith.addi [[LOAD]] // CHECK: memref.store [[INC]], %arg0[] func.func @orphan_atomic_capture(%arg0 : memref, %arg1 : memref) attributes {acc.routine_info = #acc.routine_info<[@acc_routine_atomic_capture]>} { %c1_i32 = arith.constant 1 : i32 acc.atomic.capture { acc.atomic.read %arg1 = %arg0 : memref, memref, i32 acc.atomic.update %arg0 : memref { ^bb0(%v: i32): %r = arith.addi %v, %c1_i32 : i32 acc.yield %r : i32 } acc.terminator } return } //===----------------------------------------------------------------------===// // Negative tests - ops that should NOT be converted //===----------------------------------------------------------------------===// // acc.private attached to acc.parallel should NOT be removed acc.routine @acc_routine_private_parallel func(@private_attached_to_parallel) seq // CHECK-LABEL: func.func @private_attached_to_parallel // CHECK: acc.private // CHECK: acc.parallel func.func @private_attached_to_parallel(%arg0 : memref) attributes {acc.routine_info = #acc.routine_info<[@acc_routine_private_parallel]>} { %0 = acc.private varPtr(%arg0 : memref) recipe(@privatization_memref_i32) -> memref acc.parallel private(%0 : memref) { %c1 = arith.constant 1 : i32 memref.store %c1, %0[] : memref acc.yield } return } // acc.atomic.update inside acc.parallel should NOT be converted acc.routine @acc_routine_atomic_parallel func(@atomic_inside_parallel) seq // CHECK-LABEL: func.func @atomic_inside_parallel // CHECK: acc.parallel // CHECK: acc.atomic.update func.func @atomic_inside_parallel(%arg0 : memref) attributes {acc.routine_info = #acc.routine_info<[@acc_routine_atomic_parallel]>} { acc.parallel { acc.atomic.update %arg0 : memref { ^bb0(%arg1: i32): %c1 = arith.constant 1 : i32 %1 = arith.addi %arg1, %c1 : i32 acc.yield %1 : i32 } acc.yield } return } // acc.loop inside acc.parallel should NOT be converted acc.routine @acc_routine_loop_parallel func(@loop_inside_parallel) seq // CHECK-LABEL: func.func @loop_inside_parallel // CHECK: acc.parallel // CHECK: acc.loop func.func @loop_inside_parallel(%arg0 : memref) attributes {acc.routine_info = #acc.routine_info<[@acc_routine_loop_parallel]>} { %c0 = arith.constant 0 : index %c10 = arith.constant 10 : index %c1 = arith.constant 1 : index acc.parallel { acc.loop control(%iv : index) = (%c0 : index) to (%c10 : index) step (%c1 : index) { %c5 = arith.constant 5 : i32 memref.store %c5, %arg0[] : memref acc.yield } attributes {inclusiveUpperbound = array, seq = [#acc.device_type]} acc.yield } return } //===----------------------------------------------------------------------===// // Positive tests - orphan ops attached to orphan loop (both should convert) //===----------------------------------------------------------------------===// // acc.private attached to orphan acc.loop - BOTH should be removed acc.routine @acc_routine_private_loop func(@private_attached_to_loop) seq // CHECK-LABEL: func.func @private_attached_to_loop // CHECK-NOT: acc.private // CHECK-NOT: acc.loop // CHECK: scf.for func.func @private_attached_to_loop(%arg0 : memref) attributes {acc.routine_info = #acc.routine_info<[@acc_routine_private_loop]>} { %c0 = arith.constant 0 : i32 %c10 = arith.constant 10 : i32 %c1 = arith.constant 1 : i32 %0 = acc.private varPtr(%arg0 : memref) recipe(@privatization_memref_i32) -> memref acc.loop private(%0 : memref) control(%iv : i32) = (%c0 : i32) to (%c10 : i32) step (%c1 : i32) { %c1_i32 = arith.constant 1 : i32 memref.store %c1_i32, %0[] : memref acc.yield } attributes {inclusiveUpperbound = array, seq = [#acc.device_type]} return } //===----------------------------------------------------------------------===// // Orphan loop conversion tests //===----------------------------------------------------------------------===// // Orphan acc.loop should be converted to scf.for acc.routine @acc_routine_loop func(@orphan_loop) seq // CHECK-LABEL: func.func @orphan_loop // CHECK-NOT: acc.loop // CHECK: scf.for func.func @orphan_loop(%arg0 : memref) attributes {acc.routine_info = #acc.routine_info<[@acc_routine_loop]>} { %c0 = arith.constant 0 : i32 %c10 = arith.constant 10 : i32 %c1 = arith.constant 1 : i32 acc.loop control(%iv : i32) = (%c0 : i32) to (%c10 : i32) step (%c1 : i32) { memref.store %iv, %arg0[] : memref acc.yield } attributes {inclusiveUpperbound = array, seq = [#acc.device_type]} return } // Nested orphan acc.loop should be converted to nested scf.for acc.routine @acc_routine_nested_loop func(@nested_orphan_loop) seq // CHECK-LABEL: func.func @nested_orphan_loop // CHECK-NOT: acc.loop // CHECK: scf.for // CHECK: scf.for func.func @nested_orphan_loop(%arg0 : memref) attributes {acc.routine_info = #acc.routine_info<[@acc_routine_nested_loop]>} { %c0 = arith.constant 0 : i32 %c10 = arith.constant 10 : i32 %c1 = arith.constant 1 : i32 acc.loop control(%iv0 : i32, %iv1 : i32) = (%c0, %c0 : i32, i32) to (%c10, %c10 : i32, i32) step (%c1, %c1 : i32, i32) { %sum = arith.addi %iv0, %iv1 : i32 memref.store %sum, %arg0[] : memref acc.yield } attributes {inclusiveUpperbound = array, seq = [#acc.device_type]} return } //===----------------------------------------------------------------------===// // Unstructured orphan loop - converted to scf.execute_region //===----------------------------------------------------------------------===// acc.routine @acc_routine_unstructured func(@orphan_unstructured_loop) seq // CHECK-LABEL: func.func @orphan_unstructured_loop // CHECK-NOT: acc.loop // CHECK-NOT: acc.private // CHECK: scf.execute_region // CHECK: ^bb{{[0-9]+}}: // CHECK: cf.cond_br // CHECK: scf.yield func.func @orphan_unstructured_loop(%arg0 : memref<32xi32>) attributes {acc.routine_info = #acc.routine_info<[@acc_routine_unstructured]>} { %c32_i32 = arith.constant 32 : i32 %c2_i32 = arith.constant 2 : i32 %c0_i32 = arith.constant 0 : i32 %c1_i32 = arith.constant 1 : i32 %iter_var = memref.alloca() : memref %priv = acc.private varPtr(%iter_var : memref) recipe(@privatization_memref_i32) -> memref acc.loop private(%priv : memref) { %limit = memref.alloca() : memref memref.store %c32_i32, %limit[] : memref memref.store %c1_i32, %priv[] : memref cf.br ^bb1 ^bb1: %count = memref.load %limit[] : memref %cond = arith.cmpi sgt, %count, %c0_i32 : i32 cf.cond_br %cond, ^bb2, ^bb3 ^bb2: %idx = memref.load %priv[] : memref %idx_idx = arith.index_cast %idx : i32 to index %val = memref.load %arg0[%idx_idx] : memref<32xi32> %new_val = arith.divsi %val, %c2_i32 : i32 memref.store %new_val, %arg0[%idx_idx] : memref<32xi32> %new_count = arith.subi %count, %c1_i32 : i32 memref.store %new_count, %limit[] : memref %new_idx = arith.addi %idx, %c1_i32 : i32 memref.store %new_idx, %priv[] : memref cf.br ^bb1 ^bb3: acc.yield } attributes {independent = [#acc.device_type], unstructured} return } //===----------------------------------------------------------------------===// // Orphan loop with reduction - both converted //===----------------------------------------------------------------------===// acc.routine @acc_routine_loop_reduction func(@orphan_loop_with_reduction) seq // CHECK-LABEL: func.func @orphan_loop_with_reduction // CHECK-NOT: acc.loop // CHECK-NOT: acc.reduction // CHECK-NOT: acc.private // CHECK: scf.for func.func @orphan_loop_with_reduction(%arg0 : memref, %arg1 : memref<100xi32>) attributes {acc.routine_info = #acc.routine_info<[@acc_routine_loop_reduction]>} { %c100_i32 = arith.constant 100 : i32 %c1_i32 = arith.constant 1 : i32 %iter_var = memref.alloca() : memref %red = acc.reduction varPtr(%arg0 : memref) recipe(@reduction_add_memref_i32) -> memref %priv = acc.private varPtr(%iter_var : memref) recipe(@privatization_memref_i32) -> memref acc.loop vector private(%priv : memref) reduction(%red : memref) control(%arg2 : i32) = (%c1_i32 : i32) to (%c100_i32 : i32) step (%c1_i32 : i32) { memref.store %arg2, %priv[] : memref %idx = memref.load %priv[] : memref %idx_cast = arith.index_cast %idx : i32 to index %elem = memref.load %arg1[%idx_cast] : memref<100xi32> %r_val = memref.load %arg0[] : memref %new_r = arith.addi %r_val, %elem : i32 memref.store %new_r, %arg0[] : memref acc.yield } attributes {inclusiveUpperbound = array, independent = [#acc.device_type]} return } //===----------------------------------------------------------------------===// // Orphan loop with variable bounds //===----------------------------------------------------------------------===// acc.routine @acc_routine_var_bounds func(@orphan_loop_variable_bounds) seq // CHECK-LABEL: func.func @orphan_loop_variable_bounds // CHECK-NOT: acc.loop // CHECK: [[LB:%.*]] = memref.load %arg0[] // CHECK: [[UB:%.*]] = memref.load %arg1[] // CHECK: scf.for func.func @orphan_loop_variable_bounds(%arg0 : memref, %arg1 : memref, %arg2 : memref) attributes {acc.routine_info = #acc.routine_info<[@acc_routine_var_bounds]>} { %c1 = arith.constant 1 : i32 %lb = memref.load %arg0[] : memref %ub = memref.load %arg1[] : memref acc.loop vector control(%iv : i32) = (%lb : i32) to (%ub : i32) step (%c1 : i32) { memref.store %iv, %arg2[] : memref acc.yield } attributes {inclusiveUpperbound = array, independent = [#acc.device_type]} return } //===----------------------------------------------------------------------===// // Orphan loop between compute regions - only orphan converted //===----------------------------------------------------------------------===// acc.reduction.recipe @reduction_mul_memref_i32 : memref reduction_operator init { ^bb0(%arg0: memref): %c1_i32 = arith.constant 1 : i32 %0 = memref.alloca() : memref memref.store %c1_i32, %0[] : memref acc.yield %0 : memref } combiner { ^bb0(%arg0: memref, %arg1: memref): %0 = memref.load %arg0[] : memref %1 = memref.load %arg1[] : memref %2 = arith.muli %0, %1 : i32 memref.store %2, %arg0[] : memref acc.yield %arg0 : memref } // Orphan loop sandwiched between compute regions - only orphan should convert // CHECK-LABEL: func.func @orphan_between_compute_regions // CHECK: acc.parallel // CHECK: acc.yield // CHECK-NOT: acc.private varPtr // CHECK-NOT: acc.reduction varPtr // CHECK: scf.for // CHECK: acc.parallel func.func @orphan_between_compute_regions(%arg0 : memref, %arg1 : memref<8xi32>, %arg2 : memref) { %c2_i32 = arith.constant 2 : i32 %c8_i32 = arith.constant 8 : i32 %c1_i32 = arith.constant 1 : i32 %iter_var = memref.alloca() : memref // First compute region - should NOT be converted acc.parallel combined(loop) { %priv1 = acc.private varPtr(%iter_var : memref) recipe(@privatization_memref_i32) -> memref acc.loop combined(parallel) private(%priv1 : memref) control(%iv : i32) = (%c1_i32 : i32) to (%c8_i32 : i32) step (%c1_i32 : i32) { memref.store %iv, %priv1[] : memref %idx = arith.index_cast %iv : i32 to index memref.store %c1_i32, %arg1[%idx] : memref<8xi32> acc.yield } attributes {inclusiveUpperbound = array, independent = [#acc.device_type]} acc.yield } // Orphan loop - SHOULD be converted %priv_orphan = acc.private varPtr(%arg2 : memref) recipe(@privatization_memref_i32) -> memref %red_orphan = acc.reduction varPtr(%arg0 : memref) recipe(@reduction_mul_memref_i32) -> memref %priv_iv = acc.private varPtr(%iter_var : memref) recipe(@privatization_memref_i32) -> memref acc.loop private(%priv_orphan, %priv_iv : memref, memref) reduction(%red_orphan : memref) control(%iv : i32) = (%c1_i32 : i32) to (%c8_i32 : i32) step (%c1_i32 : i32) { memref.store %iv, %priv_iv[] : memref %idx = arith.index_cast %iv : i32 to index %elem = memref.load %arg1[%idx] : memref<8xi32> memref.store %elem, %priv_orphan[] : memref %t = memref.load %priv_orphan[] : memref %mul = arith.muli %t, %c2_i32 : i32 memref.store %mul, %arg0[] : memref acc.yield } attributes {inclusiveUpperbound = array, independent = [#acc.device_type]} // Second compute region - should NOT be converted acc.parallel combined(loop) { %priv2 = acc.private varPtr(%iter_var : memref) recipe(@privatization_memref_i32) -> memref acc.loop combined(parallel) private(%priv2 : memref) control(%iv : i32) = (%c1_i32 : i32) to (%c8_i32 : i32) step (%c1_i32 : i32) { memref.store %iv, %priv2[] : memref %idx = arith.index_cast %iv : i32 to index memref.store %iv, %arg1[%idx] : memref<8xi32> acc.yield } attributes {inclusiveUpperbound = array, independent = [#acc.device_type]} acc.yield } return }