[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 ®istry) {
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 ®istry) {
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(®ion.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