[flang-commits] [flang] 1bc1609 - [mlir][acc] Declare the memory effects of acc.atomic.write (#230029)
via flang-commits
flang-commits at lists.llvm.org
Fri Oct 9 01:38:29 PDT 2026
Author: Kareem Ergawy
Date: 2026-10-09T10:38:24+02:00
New Revision: 1bc1609a565c65a5f28c2c49a5ebe9c74e13c1d8
URL: https://github.com/llvm/llvm-project/commit/1bc1609a565c65a5f28c2c49a5ebe9c74e13c1d8
DIFF: https://github.com/llvm/llvm-project/commit/1bc1609a565c65a5f28c2c49a5ebe9c74e13c1d8.diff
LOG: [mlir][acc] Declare the memory effects of acc.atomic.write (#230029)
The operation wrote the location designated by `x` but declared no
memory effects, so it did not implement MemoryEffectOpInterface at all
and every memory analysis had to treat it as unknown.
fir::AliasAnalysis::getModRef returns ModAndRef for such an operation,
so a loop containing one blocked the hoisting of any load, however
unrelated.
Declare the write on `x`. `expr` is a value rather than a location and
carries no effect.
This completes the set: acc.atomic.update already declared MemRead and
MemWrite on `x`, acc.atomic.read declares its effects since #228899, and
acc.atomic.capture derives its effects from its body.
The relaxed-ordering contract is stated in the operation description and
tested the same way as for acc.atomic.read: an unrelated load is hoisted
out of a loop across the write, and a load of `x` is not.
Added:
Modified:
flang/test/Transforms/licm-acc-atomic.fir
mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td
mlir/test/Dialect/OpenACC/side-effects.mlir
Removed:
################################################################################
diff --git a/flang/test/Transforms/licm-acc-atomic.fir b/flang/test/Transforms/licm-acc-atomic.fir
index 041c6c50995fb3..fd52d4991c1e65 100644
--- a/flang/test/Transforms/licm-acc-atomic.fir
+++ b/flang/test/Transforms/licm-acc-atomic.fir
@@ -93,3 +93,161 @@ func.func @cse_keeps_both_atomic_reads(%x: !fir.ref<i32>, %v: !fir.ref<i32>) {
acc.atomic.read %v = %x : !fir.ref<i32>, !fir.ref<i32>, i32
return
}
+
+// The same contract for acc.atomic.write: it names only `x`, so an unrelated
+// load is free to leave the loop, and a load of `x` is not.
+
+// CHECK-LABEL: func.func @write_hoist_unrelated_load
+// CHECK: fir.load
+// CHECK: scf.for
+// CHECK-NOT: fir.load
+// CHECK: acc.atomic.write
+func.func @write_hoist_unrelated_load(
+ %x: !fir.ref<i32>, %y: !fir.ref<i32>, %val: i32, %n: index) {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ scf.for %i = %c0 to %n step %c1 {
+ %0 = fir.load %y : !fir.ref<i32>
+ acc.atomic.write %x = %val : !fir.ref<i32>, i32
+ }
+ return
+}
+
+// CHECK-LABEL: func.func @write_no_hoist_load_of_x
+// CHECK: scf.for
+// CHECK: fir.load
+// CHECK: acc.atomic.write
+func.func @write_no_hoist_load_of_x(
+ %x: !fir.ref<i32>, %val: i32, %n: index) {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ scf.for %i = %c0 to %n step %c1 {
+ %0 = fir.load %x : !fir.ref<i32>
+ acc.atomic.write %x = %val : !fir.ref<i32>, i32
+ }
+ return
+}
+
+//--- acc.atomic.capture ------------------------------------------------------
+
+// acc.atomic.capture carries RecursiveMemoryEffects and declares no effects of
+// its own, so fir::AliasAnalysis::getModRef derives them by recursing into the
+// region. That is only precise once every member declares its own effects: an
+// operation without MemoryEffectOpInterface makes the recursion fall back to
+// ModRef, which is the whole region, and nothing in the loop can be hoisted.
+
+// Capture of the read+update form. The unrelated load leaves the loop; `v` and
+// `x` are both written, so loads of those stay.
+
+// CHECK-LABEL: func.func @capture_hoist_unrelated_load
+// CHECK: fir.load
+// CHECK: scf.for
+// CHECK-NOT: fir.load
+// CHECK: acc.atomic.capture
+func.func @capture_hoist_unrelated_load(
+ %x: !fir.ref<i32>, %v: !fir.ref<i32>, %y: !fir.ref<i32>, %n: index) {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %one = arith.constant 1 : i32
+ scf.for %i = %c0 to %n step %c1 {
+ %0 = fir.load %y : !fir.ref<i32>
+ acc.atomic.capture {
+ acc.atomic.read %v = %x : !fir.ref<i32>, !fir.ref<i32>, i32
+ acc.atomic.update %x : !fir.ref<i32> {
+ ^bb0(%old: i32):
+ %r = arith.addi %old, %one : i32
+ acc.yield %r : i32
+ }
+ }
+ }
+ return
+}
+
+// CHECK-LABEL: func.func @capture_no_hoist_load_of_v
+// CHECK: scf.for
+// CHECK: fir.load
+// CHECK: acc.atomic.capture
+func.func @capture_no_hoist_load_of_v(
+ %x: !fir.ref<i32>, %v: !fir.ref<i32>, %n: index) {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %one = arith.constant 1 : i32
+ scf.for %i = %c0 to %n step %c1 {
+ %0 = fir.load %v : !fir.ref<i32>
+ acc.atomic.capture {
+ acc.atomic.read %v = %x : !fir.ref<i32>, !fir.ref<i32>, i32
+ acc.atomic.update %x : !fir.ref<i32> {
+ ^bb0(%old: i32):
+ %r = arith.addi %old, %one : i32
+ acc.yield %r : i32
+ }
+ }
+ }
+ return
+}
+
+// CHECK-LABEL: func.func @capture_no_hoist_load_of_x
+// CHECK: scf.for
+// CHECK: fir.load
+// CHECK: acc.atomic.capture
+func.func @capture_no_hoist_load_of_x(
+ %x: !fir.ref<i32>, %v: !fir.ref<i32>, %n: index) {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %one = arith.constant 1 : i32
+ scf.for %i = %c0 to %n step %c1 {
+ %0 = fir.load %x : !fir.ref<i32>
+ acc.atomic.capture {
+ acc.atomic.read %v = %x : !fir.ref<i32>, !fir.ref<i32>, i32
+ acc.atomic.update %x : !fir.ref<i32> {
+ ^bb0(%old: i32):
+ %r = arith.addi %old, %one : i32
+ acc.yield %r : i32
+ }
+ }
+ }
+ return
+}
+
+// Capture of the read+write form. This is the one the write effect above
+// unlocks: with the write opaque the whole capture read as ModRef and the
+// unrelated load stayed in the loop.
+
+// CHECK-LABEL: func.func @capture_write_hoist_unrelated_load
+// CHECK: fir.load
+// CHECK: scf.for
+// CHECK-NOT: fir.load
+// CHECK: acc.atomic.capture
+func.func @capture_write_hoist_unrelated_load(
+ %x: !fir.ref<i32>, %v: !fir.ref<i32>, %y: !fir.ref<i32>, %val: i32,
+ %n: index) {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ scf.for %i = %c0 to %n step %c1 {
+ %0 = fir.load %y : !fir.ref<i32>
+ acc.atomic.capture {
+ acc.atomic.read %v = %x : !fir.ref<i32>, !fir.ref<i32>, i32
+ acc.atomic.write %x = %val : !fir.ref<i32>, i32
+ }
+ }
+ return
+}
+
+// CHECK-LABEL: func.func @capture_write_no_hoist_load_of_x
+// CHECK: scf.for
+// CHECK: fir.load
+// CHECK: acc.atomic.capture
+func.func @capture_write_no_hoist_load_of_x(
+ %x: !fir.ref<i32>, %v: !fir.ref<i32>, %val: i32, %n: index) {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ scf.for %i = %c0 to %n step %c1 {
+ %0 = fir.load %x : !fir.ref<i32>
+ acc.atomic.capture {
+ acc.atomic.read %v = %x : !fir.ref<i32>, !fir.ref<i32>, i32
+ acc.atomic.write %x = %val : !fir.ref<i32>, i32
+ }
+ }
+ return
+}
+
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td
index 2e8cfc5f68ef2e..70f8ee5c381721 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td
@@ -3079,9 +3079,19 @@ def AtomicWriteOp : OpenACC_Op<"atomic.write",[AtomicWriteOpInterface]> {
written w.r.t. multiple threads. The evaluation of `expr` need not be
atomic w.r.t. the write to address. In general, the type(x) must
dereference to type(expr).
+
+ The operation declares its memory effects per operand: it writes the
+ location designated by `x`. `expr` is a value rather than a location, so
+ it carries no effect. No other effect is declared, which models relaxed
+ ordering: the access to the location is atomic, but the operation creates
+ no ordering relationship with accesses to any other location, so those may
+ be moved across it. OpenACC leaves the ordering of the `atomic` construct
+ unspecified, so relaxed is the weakest reading consistent with the
+ specification and the one that permits the most optimization.
}];
- let arguments = (ins OpenACC_PointerLikeType:$x,
+ let arguments = (ins Arg<OpenACC_PointerLikeType,
+ "address atomically written to", [MemWrite]>:$x,
AnyType:$expr,
Optional<I1>:$ifCond);
let assemblyFormat = [{
diff --git a/mlir/test/Dialect/OpenACC/side-effects.mlir b/mlir/test/Dialect/OpenACC/side-effects.mlir
index 1a7f98cefba8c3..59b138c72ee539 100644
--- a/mlir/test/Dialect/OpenACC/side-effects.mlir
+++ b/mlir/test/Dialect/OpenACC/side-effects.mlir
@@ -40,3 +40,15 @@ func.func @atomic_capture(%x: memref<i32>, %v: memref<i32>) {
}
return
}
+
+// -----
+
+// acc.atomic.write declares a write on the location designated by `x`. `expr`
+// is a value rather than a location, so it carries no effect.
+
+func.func @atomic_write(%x: memref<i32>, %v: i32) {
+ // expected-remark @below {{found an instance of 'write' on op operand 0, on resource '<Default>'}}
+ acc.atomic.write %x = %v : memref<i32>, i32
+ return
+}
+
More information about the flang-commits
mailing list