[flang-commits] [flang] 1bd6392 - [flang][acc] Add `OutlineIdentityOperandOpInterface` to hlfir/fir declare (#208091)

via flang-commits flang-commits at lists.llvm.org
Thu Jul 9 11:46:59 PDT 2026


Author: Susan Tan (ス-ザン タン)
Date: 2026-07-09T14:46:53-04:00
New Revision: 1bd63928e4fcf575d93aaef220c19ca41965e015

URL: https://github.com/llvm/llvm-project/commit/1bd63928e4fcf575d93aaef220c19ca41965e015
DIFF: https://github.com/llvm/llvm-project/commit/1bd63928e4fcf575d93aaef220c19ca41965e015.diff

LOG: [flang][acc] Add `OutlineIdentityOperandOpInterface` to hlfir/fir declare (#208091)

Inlining can leave a `fir.declare`'s dummy_scope operand (`!fir.dscope`)
defined outside an OpenACC compute region while the declare itself gets
rematerialized inside, since `fir.dummy_scope` wasn't registered as an
OutlineRematerializationOpInterface candidate.

This dangling, unmappable live-in is illegal for parallelization. 

Fix: Added `OutlineIdentityOperandOpInterface`, implemented by
`fir.declare/hlfir.declare`, so `OffloadLiveInValueCanonicalization`
drops their dummy_scope operand (an identity token that must never be
duplicated) instead of cloning it when sinking/rematerializing them into
offload regions.

Added: 
    

Modified: 
    flang/include/flang/Optimizer/OpenACC/Support/FIROpenACCOpsInterfaces.h
    flang/lib/Optimizer/OpenACC/Support/FIROpenACCOpsInterfaces.cpp
    flang/lib/Optimizer/OpenACC/Support/RegisterOpenACCExtensions.cpp
    flang/test/Fir/OpenACC/offload-livein-value-canonicalization.fir
    mlir/include/mlir/Dialect/OpenACC/OpenACCOpsInterfaces.td
    mlir/lib/Dialect/OpenACC/Transforms/OffloadLiveInValueCanonicalization.cpp

Removed: 
    


################################################################################
diff  --git a/flang/include/flang/Optimizer/OpenACC/Support/FIROpenACCOpsInterfaces.h b/flang/include/flang/Optimizer/OpenACC/Support/FIROpenACCOpsInterfaces.h
index a39d517412c07..31e1593e11325 100644
--- a/flang/include/flang/Optimizer/OpenACC/Support/FIROpenACCOpsInterfaces.h
+++ b/flang/include/flang/Optimizer/OpenACC/Support/FIROpenACCOpsInterfaces.h
@@ -99,6 +99,20 @@ struct OutlineRematerializationModel<fir::ConvertOp>
   bool isRematerializationCandidate(mlir::Operation *op) const;
 };
 
+/// External model for OutlineIdentityOperandOpInterface.
+/// Drops the dummy_scope operand (an identity token that must not be
+/// duplicated) from a [hl]fir.declare instance that has been sunk or
+/// rematerialized into an offload region. Alias analysis falls back to a
+/// dominance-based lookup of the enclosing dummy scope when this operand is
+/// absent, so correctness is preserved without needing to keep or clone the
+/// original operand.
+template <typename Op>
+struct OutlineIdentityOperandDeclareModel
+    : public mlir::acc::OutlineIdentityOperandOpInterface::ExternalModel<
+          OutlineIdentityOperandDeclareModel<Op>, Op> {
+  void dropOutlinedIdentityOperands(mlir::Operation *op) const;
+};
+
 /// External model for OffloadRegionOpInterface.
 /// This interface marks operations whose regions are targets for offloading
 /// and outlining.

diff  --git a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCOpsInterfaces.cpp b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCOpsInterfaces.cpp
index 590619a31a33f..8e4b55fe5f4a8 100644
--- a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCOpsInterfaces.cpp
+++ b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCOpsInterfaces.cpp
@@ -143,6 +143,26 @@ bool OutlineRematerializationModel<
          fir::ConvertOp::isIntegerCompatible(outTy);
 }
 
+template <>
+void OutlineIdentityOperandDeclareModel<
+    fir::DeclareOp>::dropOutlinedIdentityOperands(mlir::Operation *op) const {
+  auto declareOp = mlir::cast<fir::DeclareOp>(op);
+  if (declareOp.getDummyScope()) {
+    declareOp.getDummyScopeMutable().clear();
+    declareOp->removeAttr(declareOp.getDummyArgNoAttrName());
+  }
+}
+
+template <>
+void OutlineIdentityOperandDeclareModel<
+    hlfir::DeclareOp>::dropOutlinedIdentityOperands(mlir::Operation *op) const {
+  auto declareOp = mlir::cast<hlfir::DeclareOp>(op);
+  if (declareOp.getDummyScope()) {
+    declareOp.getDummyScopeMutable().clear();
+    declareOp->removeAttr(declareOp.getDummyArgNoAttrName());
+  }
+}
+
 // Helper to recursively process address-of operations in derived type
 // descriptors and collect all needed fir.globals.
 static void processAddrOfOpInDerivedTypeDescriptor(

diff  --git a/flang/lib/Optimizer/OpenACC/Support/RegisterOpenACCExtensions.cpp b/flang/lib/Optimizer/OpenACC/Support/RegisterOpenACCExtensions.cpp
index 070208d22ba05..7e5eeda1f5de9 100644
--- a/flang/lib/Optimizer/OpenACC/Support/RegisterOpenACCExtensions.cpp
+++ b/flang/lib/Optimizer/OpenACC/Support/RegisterOpenACCExtensions.cpp
@@ -53,6 +53,8 @@ void registerOpenACCExtensions(mlir::DialectRegistry &registry) {
         PartialEntityAccessModel<fir::CoordinateOp>>(*ctx);
     fir::DeclareOp::attachInterface<PartialEntityAccessModel<fir::DeclareOp>>(
         *ctx);
+    fir::DeclareOp::attachInterface<
+        OutlineIdentityOperandDeclareModel<fir::DeclareOp>>(*ctx);
 
     fir::AddrOfOp::attachInterface<AddressOfGlobalModel>(*ctx);
     fir::GlobalOp::attachInterface<GlobalVariableModel>(*ctx);
@@ -94,6 +96,8 @@ void registerOpenACCExtensions(mlir::DialectRegistry &registry) {
             PartialEntityAccessModel<hlfir::DesignateOp>>(*ctx);
         hlfir::DeclareOp::attachInterface<
             PartialEntityAccessModel<hlfir::DeclareOp>>(*ctx);
+        hlfir::DeclareOp::attachInterface<
+            OutlineIdentityOperandDeclareModel<hlfir::DeclareOp>>(*ctx);
       });
 
   // Register CUF operation interfaces

diff  --git a/flang/test/Fir/OpenACC/offload-livein-value-canonicalization.fir b/flang/test/Fir/OpenACC/offload-livein-value-canonicalization.fir
index e692e5fb6dc4f..8b1d4cd0b1e16 100644
--- a/flang/test/Fir/OpenACC/offload-livein-value-canonicalization.fir
+++ b/flang/test/Fir/OpenACC/offload-livein-value-canonicalization.fir
@@ -467,3 +467,75 @@ func.func @test_undef_slice_rematerialize(%arg0: !fir.box<!fir.array<?x?xf32>>)
 // CHECK:   %[[UNDEF_INNER:.*]] = fir.undefined index
 // CHECK:   %[[SLICE_INNER:.*]] = fir.slice {{.*}}, %[[UNDEF_INNER]], %[[UNDEF_INNER]]
 // CHECK:   fir.rebox %arg0 [%[[SLICE_INNER]]]
+
+// -----
+
+// Test that fir.declare's dummy_scope operand is dropped, not duplicated,
+// when the declare is sunk into an offload region. Regression test: after
+// inlining, a fir.declare for a dummy argument aliasing a device-valid
+// global (e.g. one with !$ACC DECLARE COPYIN) is sunk/rematerialized into
+// the offload region. Its dummy_scope operand is an identity token (see
+// fir.dummy_scope) that must never be duplicated, so it must be dropped
+// from the sunk/cloned declare instead of being left as an illegal live-in
+// or cloned alongside it.
+
+fir.global @global_for_dummy_scope {acc.declare = #acc.declare<dataClause = acc_copyin>} : i32 {
+  %0 = arith.constant 0 : i32
+  fir.has_value %0 : i32
+}
+
+func.func private @use_ref(!fir.ref<i32>) -> ()
+
+func.func @test_dummy_scope_sink() {
+  %addr = fir.address_of(@global_for_dummy_scope) : !fir.ref<i32>
+  %scope = fir.dummy_scope : !fir.dscope
+  %decl = fir.declare %addr dummy_scope %scope {uniq_name = "global"} : (!fir.ref<i32>, !fir.dscope) -> !fir.ref<i32>
+  acc.serial {
+    fir.call @use_ref(%decl) : (!fir.ref<i32>) -> ()
+    acc.yield
+  }
+  return
+}
+
+// CHECK-LABEL: @test_dummy_scope_sink
+// The dummy scope stays outside, untouched and unduplicated: nothing
+// references it anymore once the declare's operand is dropped.
+// CHECK: fir.dummy_scope
+// CHECK: acc.serial {
+// CHECK-DAG:   fir.address_of(@global_for_dummy_scope)
+// CHECK-DAG:   %[[DECL:.*]] = fir.declare %{{.*}} {uniq_name = "global"} : (!fir.ref<i32>) -> !fir.ref<i32>
+// CHECK:   fir.call @use_ref(%[[DECL]])
+
+// -----
+
+// Test fir.declare rematerialization (used both inside and outside region):
+// the outer declare keeps its dummy_scope untouched, while the clone made
+// for the region drops it rather than duplicating the scope.
+
+fir.global @global_for_dummy_scope_remat {acc.declare = #acc.declare<dataClause = acc_copyin>} : i32 {
+  %0 = arith.constant 0 : i32
+  fir.has_value %0 : i32
+}
+
+func.func private @use_ref(!fir.ref<i32>) -> ()
+
+func.func @test_dummy_scope_rematerialize() {
+  %addr = fir.address_of(@global_for_dummy_scope_remat) : !fir.ref<i32>
+  %scope = fir.dummy_scope : !fir.dscope
+  %decl = fir.declare %addr dummy_scope %scope {uniq_name = "global"} : (!fir.ref<i32>, !fir.dscope) -> !fir.ref<i32>
+  fir.call @use_ref(%decl) : (!fir.ref<i32>) -> ()
+  acc.serial {
+    fir.call @use_ref(%decl) : (!fir.ref<i32>) -> ()
+    acc.yield
+  }
+  return
+}
+
+// CHECK-LABEL: @test_dummy_scope_rematerialize
+// CHECK: %[[SCOPE:.*]] = fir.dummy_scope
+// CHECK: %[[DECL_OUTER:.*]] = fir.declare %{{.*}} dummy_scope %[[SCOPE]] {uniq_name = "global"}
+// CHECK: fir.call @use_ref(%[[DECL_OUTER]])
+// CHECK: acc.serial {
+// CHECK-DAG:   fir.address_of(@global_for_dummy_scope_remat)
+// CHECK-DAG:   %[[DECL_INNER:.*]] = fir.declare %{{.*}} {uniq_name = "global"} : (!fir.ref<i32>) -> !fir.ref<i32>
+// CHECK:   fir.call @use_ref(%[[DECL_INNER]])

diff  --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCOpsInterfaces.td b/mlir/include/mlir/Dialect/OpenACC/OpenACCOpsInterfaces.td
index a42bb7a66aae7..fa44fe7a49158 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACCOpsInterfaces.td
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCOpsInterfaces.td
@@ -141,6 +141,28 @@ def OutlineRematerializationOpInterface : OpInterface<"OutlineRematerializationO
   }];
 }
 
+def OutlineIdentityOperandOpInterface
+    : OpInterface<"OutlineIdentityOperandOpInterface"> {
+  let cppNamespace = "::mlir::acc";
+
+  let description = [{
+    An interface for operations with an "identity" operand (e.g. FIR's
+    dummy scope handle) that must never be duplicated, unlike operands
+    handled by `OutlineRematerializationOpInterface`. When such an
+    operation is sunk/cloned into an offload region and the operand would
+    otherwise reference a value outside it, drop the operand instead of
+    cloning it; implementations must preserve correctness some other way
+    (e.g. FIR's dominance-based scope lookup fallback).
+  }];
+
+  let methods = [
+    InterfaceMethod<"Drop any identity operands from this operation "
+                      "instance that reference values defined outside its "
+                      "containing region.",
+                      "void", "dropOutlinedIdentityOperands", (ins), [{}]>,
+  ];
+}
+
 def OffloadRegionOpInterface : OpInterface<"OffloadRegionOpInterface"> {
   let cppNamespace = "::mlir::acc";
 

diff  --git a/mlir/lib/Dialect/OpenACC/Transforms/OffloadLiveInValueCanonicalization.cpp b/mlir/lib/Dialect/OpenACC/Transforms/OffloadLiveInValueCanonicalization.cpp
index 60285c8a02bb9..4b57354b8b5d8 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/OffloadLiveInValueCanonicalization.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/OffloadLiveInValueCanonicalization.cpp
@@ -246,6 +246,13 @@ class OffloadLiveInValueCanonicalization
       Operation *sinkOp = sinkCandidate.getDefiningOp();
       assert(sinkOp && "must have op to be considered");
       sinkOp->moveBefore(&region.front().front());
+      // Some operations (e.g. fir.declare) may carry identity operands that
+      // must not be left referencing a value defined outside the region.
+      // Unlike other operands, such identity operands cannot simply be
+      // treated as new live-ins to legalize; they must be dropped instead.
+      if (auto identityOp =
+              dyn_cast<acc::OutlineIdentityOperandOpInterface>(sinkOp))
+        identityOp.dropOutlinedIdentityOperands();
       LLVM_DEBUG(llvm::dbgs() << "\t\tSunk: " << *sinkOp << "\n");
     }
 
@@ -262,6 +269,14 @@ class OffloadLiveInValueCanonicalization
     computeTopologicalSorting(opsToRematerialize);
     for (Operation *rematerializationOp : opsToRematerialize) {
       Operation *clonedOp = builder.clone(*rematerializationOp);
+      // See the comment above: identity operands must be dropped from the
+      // clone rather than duplicated, since duplicating them would make the
+      // clone appear to belong to a 
diff erent instantiation than the
+      // original operation (and any of its siblings left outside the
+      // region).
+      if (auto identityOp =
+              dyn_cast<acc::OutlineIdentityOperandOpInterface>(clonedOp))
+        identityOp.dropOutlinedIdentityOperands();
       for (auto [oldResult, newResult] : llvm::zip(
                rematerializationOp->getResults(), clonedOp->getResults())) {
         replaceAllUsesInRegionWith(oldResult, newResult, region);


        


More information about the flang-commits mailing list