[llvm-branch-commits] [flang] [flang][hlfir] optimize hlfir.eval_in_mem bufferization (PR #118069)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Fri Nov 29 00:46:11 PST 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-flang-fir-hlfir
Author: None (jeanPerier)
<details>
<summary>Changes</summary>
This patch extends the optimize bufferization to deal with the new hlfir.eval_in_mem and move the evaluation contained in its body to operate directly over the LHS when it can prove there are no access to the LHS inside the region (and that the LHS is contiguous).
This will allow the array function call optimization when lowering is changed to produce an hlfir.eval_in_mem in the next patch.
---
Full diff: https://github.com/llvm/llvm-project/pull/118069.diff
3 Files Affected:
- (modified) flang/lib/Optimizer/Analysis/AliasAnalysis.cpp (+13-1)
- (modified) flang/lib/Optimizer/HLFIR/Transforms/OptimizedBufferization.cpp (+108)
- (added) flang/test/HLFIR/opt-bufferization-eval_in_mem.fir (+67)
``````````diff
diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index 2b24791d6c7c52..c561285b9feef5 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -91,6 +91,13 @@ bool AliasAnalysis::Source::isDummyArgument() const {
return false;
}
+static bool isEvaluateInMemoryBlockArg(mlir::Value v) {
+ if (auto evalInMem = llvm::dyn_cast_or_null<hlfir::EvaluateInMemoryOp>(
+ v.getParentRegion()->getParentOp()))
+ return evalInMem.getMemory() == v;
+ return false;
+}
+
bool AliasAnalysis::Source::isData() const { return origin.isData; }
bool AliasAnalysis::Source::isBoxData() const {
return mlir::isa<fir::BaseBoxType>(fir::unwrapRefType(valueType)) &&
@@ -698,7 +705,7 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
breakFromLoop = true;
});
}
- if (!defOp && type == SourceKind::Unknown)
+ if (!defOp && type == SourceKind::Unknown) {
// Check if the memory source is coming through a dummy argument.
if (isDummyArgument(v)) {
type = SourceKind::Argument;
@@ -708,7 +715,12 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
if (isPointerReference(ty))
attributes.set(Attribute::Pointer);
+ } else if (isEvaluateInMemoryBlockArg(v)) {
+ // hlfir.eval_in_mem block operands is allocated by the operation.
+ type = SourceKind::Allocate;
+ ty = v.getType();
}
+ }
if (type == SourceKind::Global) {
return {{global, instantiationPoint, followingData},
diff --git a/flang/lib/Optimizer/HLFIR/Transforms/OptimizedBufferization.cpp b/flang/lib/Optimizer/HLFIR/Transforms/OptimizedBufferization.cpp
index a0160b233e3cd1..e8c15a256b9da0 100644
--- a/flang/lib/Optimizer/HLFIR/Transforms/OptimizedBufferization.cpp
+++ b/flang/lib/Optimizer/HLFIR/Transforms/OptimizedBufferization.cpp
@@ -1108,6 +1108,113 @@ class ReductionMaskConversion : public mlir::OpRewritePattern<Op> {
}
};
+class EvaluateIntoMemoryAssignBufferization
+ : public mlir::OpRewritePattern<hlfir::EvaluateInMemoryOp> {
+
+public:
+ using mlir::OpRewritePattern<hlfir::EvaluateInMemoryOp>::OpRewritePattern;
+
+ llvm::LogicalResult
+ matchAndRewrite(hlfir::EvaluateInMemoryOp,
+ mlir::PatternRewriter &rewriter) const override;
+};
+
+static bool mayReadOrWrite(mlir::Region ®ion, mlir::Value var) {
+ fir::AliasAnalysis aliasAnalysis;
+ for (mlir::Operation &op : region.getOps()) {
+ if (op.hasTrait<mlir::OpTrait::HasRecursiveMemoryEffects>()) {
+ for (mlir::Region &subRegion : op.getRegions())
+ if (mayReadOrWrite(subRegion, var))
+ return true;
+ // In MLIR, RecursiveMemoryEffects can be combined with
+ // MemoryEffectOpInterface to describe extra effects on top of the
+ // effects of the nested operations. However, the presence of
+ // RecursiveMemoryEffects and the absence of MemoryEffectOpInterface
+ // implies the operation has no other memory effects than the one of its
+ // nested operations.
+ if (!mlir::isa<mlir::MemoryEffectOpInterface>(op))
+ continue;
+ }
+ if (!aliasAnalysis.getModRef(&op, var).isNoModRef())
+ return true;
+ }
+ return false;
+}
+
+static llvm::LogicalResult
+tryUsingAssignLhsDirectly(hlfir::EvaluateInMemoryOp evalInMem,
+ mlir::PatternRewriter &rewriter) {
+ mlir::Location loc = evalInMem.getLoc();
+ hlfir::DestroyOp destroy;
+ hlfir::AssignOp assign;
+ for (auto user : llvm::enumerate(evalInMem->getUsers())) {
+ if (user.index() > 2)
+ return mlir::failure();
+ mlir::TypeSwitch<mlir::Operation *, void>(user.value())
+ .Case([&](hlfir::AssignOp op) { assign = op; })
+ .Case([&](hlfir::DestroyOp op) { destroy = op; });
+ }
+ if (!assign || !destroy || destroy.mustFinalizeExpr() ||
+ assign.isAllocatableAssignment())
+ return mlir::failure();
+
+ hlfir::Entity lhs{assign.getLhs()};
+ // EvaluateInMemoryOp memory is contiguous, so in general, it can only be
+ // replace by the LHS if the LHS is contiguous.
+ if (!lhs.isSimplyContiguous())
+ return mlir::failure();
+ // Character assignment may involves truncation/padding, so the LHS
+ // cannot be used to evaluate RHS in place without proving the LHS and
+ // RHS lengths are the same.
+ if (lhs.isCharacter())
+ return mlir::failure();
+
+ // The region must not read or write the LHS.
+ if (mayReadOrWrite(evalInMem.getBody(), lhs))
+ return mlir::failure();
+ // Any variables affected between the hlfir.evalInMem and assignment must not
+ // be read or written inside the region since it will be moved at the
+ // assignment insertion point.
+ auto effects = getEffectsBetween(evalInMem->getNextNode(), assign);
+ if (!effects) {
+ LLVM_DEBUG(
+ llvm::dbgs()
+ << "operation with unknown effects between eval_in_mem and assign\n");
+ return mlir::failure();
+ }
+ for (const mlir::MemoryEffects::EffectInstance &effect : *effects) {
+ mlir::Value affected = effect.getValue();
+ if (!affected || mayReadOrWrite(evalInMem.getBody(), affected))
+ return mlir::failure();
+ }
+
+ rewriter.setInsertionPoint(assign);
+ fir::FirOpBuilder builder(rewriter, evalInMem.getOperation());
+ mlir::Value rawLhs = hlfir::genVariableRawAddress(loc, builder, lhs);
+ hlfir::computeEvaluateOpIn(loc, builder, evalInMem, rawLhs);
+ rewriter.eraseOp(assign);
+ rewriter.eraseOp(destroy);
+ rewriter.eraseOp(evalInMem);
+ return mlir::success();
+}
+
+llvm::LogicalResult EvaluateIntoMemoryAssignBufferization::matchAndRewrite(
+ hlfir::EvaluateInMemoryOp evalInMem,
+ mlir::PatternRewriter &rewriter) const {
+ if (mlir::succeeded(tryUsingAssignLhsDirectly(evalInMem, rewriter)))
+ return mlir::success();
+ // Rewrite to temp + as_expr here so that the assign + as_expr pattern can
+ // kick-in for simple types and at least implement the assignment inline
+ // instead of call Assign runtime.
+ fir::FirOpBuilder builder(rewriter, evalInMem.getOperation());
+ mlir::Location loc = evalInMem.getLoc();
+ auto [temp, isHeapAllocated] = hlfir::computeEvaluateOpInNewTemp(
+ loc, builder, evalInMem, evalInMem.getShape(), evalInMem.getTypeparams());
+ rewriter.replaceOpWithNewOp<hlfir::AsExprOp>(
+ evalInMem, temp, /*mustFree=*/builder.createBool(loc, isHeapAllocated));
+ return mlir::success();
+}
+
class OptimizedBufferizationPass
: public hlfir::impl::OptimizedBufferizationBase<
OptimizedBufferizationPass> {
@@ -1130,6 +1237,7 @@ class OptimizedBufferizationPass
patterns.insert<ElementalAssignBufferization>(context);
patterns.insert<BroadcastAssignBufferization>(context);
patterns.insert<VariableAssignBufferization>(context);
+ patterns.insert<EvaluateIntoMemoryAssignBufferization>(context);
patterns.insert<ReductionConversion<hlfir::CountOp>>(context);
patterns.insert<ReductionConversion<hlfir::AnyOp>>(context);
patterns.insert<ReductionConversion<hlfir::AllOp>>(context);
diff --git a/flang/test/HLFIR/opt-bufferization-eval_in_mem.fir b/flang/test/HLFIR/opt-bufferization-eval_in_mem.fir
new file mode 100644
index 00000000000000..984c0bcbaddcc3
--- /dev/null
+++ b/flang/test/HLFIR/opt-bufferization-eval_in_mem.fir
@@ -0,0 +1,67 @@
+// RUN: fir-opt --opt-bufferization %s | FileCheck %s
+
+// Fortran F2023 15.5.2.14 point 4. ensures that _QPfoo cannot access _QFtestEx
+// and the temporary storage for the result can be avoided.
+func.func @_QPtest(%arg0: !fir.ref<!fir.array<10xf32>> {fir.bindc_name = "x"}) {
+ %c10 = arith.constant 10 : index
+ %0 = fir.dummy_scope : !fir.dscope
+ %1 = fir.shape %c10 : (index) -> !fir.shape<1>
+ %2:2 = hlfir.declare %arg0(%1) dummy_scope %0 {uniq_name = "_QFtestEx"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>, !fir.dscope) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>)
+ %3 = hlfir.eval_in_mem shape %1 : (!fir.shape<1>) -> !hlfir.expr<10xf32> {
+ ^bb0(%arg1: !fir.ref<!fir.array<10xf32>>):
+ %4 = fir.call @_QPfoo() fastmath<contract> : () -> !fir.array<10xf32>
+ fir.save_result %4 to %arg1(%1) : !fir.array<10xf32>, !fir.ref<!fir.array<10xf32>>, !fir.shape<1>
+ }
+ hlfir.assign %3 to %2#0 : !hlfir.expr<10xf32>, !fir.ref<!fir.array<10xf32>>
+ hlfir.destroy %3 : !hlfir.expr<10xf32>
+ return
+}
+func.func private @_QPfoo() -> !fir.array<10xf32>
+
+// CHECK-LABEL: func.func @_QPtest(
+// CHECK-SAME: %[[VAL_0:.*]]: !fir.ref<!fir.array<10xf32>> {fir.bindc_name = "x"}) {
+// CHECK: %[[VAL_1:.*]] = arith.constant 10 : index
+// CHECK: %[[VAL_2:.*]] = fir.dummy_scope : !fir.dscope
+// CHECK: %[[VAL_3:.*]] = fir.shape %[[VAL_1]] : (index) -> !fir.shape<1>
+// CHECK: %[[VAL_4:.*]]:2 = hlfir.declare %[[VAL_0]](%[[VAL_3]]) dummy_scope %[[VAL_2]] {uniq_name = "_QFtestEx"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>, !fir.dscope) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>)
+// CHECK: %[[VAL_5:.*]] = fir.call @_QPfoo() fastmath<contract> : () -> !fir.array<10xf32>
+// CHECK: fir.save_result %[[VAL_5]] to %[[VAL_4]]#1(%[[VAL_3]]) : !fir.array<10xf32>, !fir.ref<!fir.array<10xf32>>, !fir.shape<1>
+// CHECK: return
+// CHECK: }
+
+
+// Temporary storage cannot be avoided in this case since
+// _QFnegative_test_is_targetEx has the TARGET attribute.
+func.func @_QPnegative_test_is_target(%arg0: !fir.ref<!fir.array<10xf32>> {fir.bindc_name = "x", fir.target}) {
+ %c10 = arith.constant 10 : index
+ %0 = fir.dummy_scope : !fir.dscope
+ %1 = fir.shape %c10 : (index) -> !fir.shape<1>
+ %2:2 = hlfir.declare %arg0(%1) dummy_scope %0 {fortran_attrs = #fir.var_attrs<target>, uniq_name = "_QFnegative_test_is_targetEx"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>, !fir.dscope) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>)
+ %3 = hlfir.eval_in_mem shape %1 : (!fir.shape<1>) -> !hlfir.expr<10xf32> {
+ ^bb0(%arg1: !fir.ref<!fir.array<10xf32>>):
+ %4 = fir.call @_QPfoo() fastmath<contract> : () -> !fir.array<10xf32>
+ fir.save_result %4 to %arg1(%1) : !fir.array<10xf32>, !fir.ref<!fir.array<10xf32>>, !fir.shape<1>
+ }
+ hlfir.assign %3 to %2#0 : !hlfir.expr<10xf32>, !fir.ref<!fir.array<10xf32>>
+ hlfir.destroy %3 : !hlfir.expr<10xf32>
+ return
+}
+// CHECK-LABEL: func.func @_QPnegative_test_is_target(
+// CHECK-SAME: %[[VAL_0:.*]]: !fir.ref<!fir.array<10xf32>> {fir.bindc_name = "x", fir.target}) {
+// CHECK: %[[VAL_1:.*]] = arith.constant 1 : index
+// CHECK: %[[VAL_2:.*]] = arith.constant false
+// CHECK: %[[VAL_3:.*]] = arith.constant 10 : index
+// CHECK: %[[VAL_4:.*]] = fir.alloca !fir.array<10xf32>
+// CHECK: %[[VAL_7:.*]]:2 = hlfir.declare %[[VAL_0]]{{.*}}
+// CHECK: %[[VAL_8:.*]]:2 = hlfir.declare %[[VAL_4]]{{.*}}
+// CHECK: %[[VAL_9:.*]] = fir.call @_QPfoo() fastmath<contract> : () -> !fir.array<10xf32>
+// CHECK: fir.save_result %[[VAL_9]] to %[[VAL_8]]#1{{.*}}
+// CHECK: %[[VAL_10:.*]] = hlfir.as_expr %[[VAL_8]]#0 move %[[VAL_2]] : (!fir.ref<!fir.array<10xf32>>, i1) -> !hlfir.expr<10xf32>
+// CHECK: fir.do_loop %[[VAL_11:.*]] = %[[VAL_1]] to %[[VAL_3]] step %[[VAL_1]] unordered {
+// CHECK: %[[VAL_12:.*]] = hlfir.apply %[[VAL_10]], %[[VAL_11]] : (!hlfir.expr<10xf32>, index) -> f32
+// CHECK: %[[VAL_13:.*]] = hlfir.designate %[[VAL_7]]#0 (%[[VAL_11]]) : (!fir.ref<!fir.array<10xf32>>, index) -> !fir.ref<f32>
+// CHECK: hlfir.assign %[[VAL_12]] to %[[VAL_13]] : f32, !fir.ref<f32>
+// CHECK: }
+// CHECK: hlfir.destroy %[[VAL_10]] : !hlfir.expr<10xf32>
+// CHECK: return
+// CHECK: }
``````````
</details>
https://github.com/llvm/llvm-project/pull/118069
More information about the llvm-branch-commits
mailing list