[flang-commits] [flang] 7bbed79 - [flang] Fold reads of fir.create_box components (#210388)
via flang-commits
flang-commits at lists.llvm.org
Mon Jul 20 08:11:02 PDT 2026
Author: Slava Zakharin
Date: 2026-07-20T08:10:57-07:00
New Revision: 7bbed7999de1a60c790a3b0f01d965c18fd832e8
URL: https://github.com/llvm/llvm-project/commit/7bbed7999de1a60c790a3b0f01d965c18fd832e8
DIFF: https://github.com/llvm/llvm-project/commit/7bbed7999de1a60c790a3b0f01d965c18fd832e8.diff
LOG: [flang] Fold reads of fir.create_box components (#210388)
Fold fir.box_addr and constant-dimension fir.box_dims of a descriptor
built by fir.create_box back to the operands that built it, so the
create_box can die when nothing consumes it as a real descriptor.
fir.array_coor is intentionally not folded through create_box, since
that would drop its explicit byte strides.
Assisted-by: Cursor
Added:
flang/test/Fir/box-dims-folding.fir
Modified:
flang/include/flang/Optimizer/Dialect/FIROps.td
flang/lib/Optimizer/Dialect/FIROps.cpp
flang/test/Fir/array-coor-canonicalization.fir
flang/test/Fir/boxaddr-folding.fir
Removed:
################################################################################
diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td
index 78be358d0f97f..ae7b8796f8957 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.td
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.td
@@ -1314,6 +1314,8 @@ def fir_BoxDimsOp
$val `,` $dim attr-dict `:` functional-type(operands, results)
}];
+ let hasCanonicalizer = 1;
+
let extraClassDeclaration = [{
mlir::Type getTupleType();
mlir::Value getLowerBound() {return getResult(0);};
diff --git a/flang/lib/Optimizer/Dialect/FIROps.cpp b/flang/lib/Optimizer/Dialect/FIROps.cpp
index 83495912f06c2..b9968897ade6d 100644
--- a/flang/lib/Optimizer/Dialect/FIROps.cpp
+++ b/flang/lib/Optimizer/Dialect/FIROps.cpp
@@ -1505,6 +1505,15 @@ mlir::OpFoldResult fir::BoxAddrOp::fold(FoldAdaptor adaptor) {
return box.getMemref();
}
}
+ // fir.create_box always describes the whole array (no slice), so the
+ // base address is exactly its memref operand. The verifier guarantees the
+ // result box preserves the memref storage kind, so the types match.
+ if (auto box = mlir::dyn_cast<fir::CreateBoxOp>(v)) {
+ if (box.getMemref().getType() == getType()) {
+ propagateAttributes(getOperation(), box.getMemref().getDefiningOp());
+ return box.getMemref();
+ }
+ }
if (auto box = mlir::dyn_cast<fir::EmboxCharOp>(v))
if (box.getMemref().getType() == getType())
return box.getMemref();
@@ -1604,6 +1613,47 @@ void fir::BoxEleSizeOp::getCanonicalizationPatterns(
// BoxDimsOp
//===----------------------------------------------------------------------===//
+namespace {
+/// fir.box_dims of a descriptor built by fir.create_box with a statically known
+/// dimension forwards the lower bound, extent, and byte stride operands that
+/// built that dimension. These are plain SSA operands of the create_box, so
+/// replacing the box_dims results with them is a value forwarding; the
+/// create_box dominates the box_dims, so the operands are already available.
+struct SimplifyBoxDimsFromCreateBox
+ : public mlir::OpRewritePattern<fir::BoxDimsOp> {
+ using mlir::OpRewritePattern<fir::BoxDimsOp>::OpRewritePattern;
+
+ mlir::LogicalResult
+ matchAndRewrite(fir::BoxDimsOp op,
+ mlir::PatternRewriter &rewriter) const override {
+ auto createBox = op.getVal().getDefiningOp<fir::CreateBoxOp>();
+ if (!createBox)
+ return mlir::failure();
+
+ std::optional<std::int64_t> dim = fir::getIntIfConstant(op.getDim());
+ if (!dim || *dim < 0 || *dim >= createBox.getRank())
+ return mlir::failure();
+
+ // The create_box operands are AnyIntegerType and may
diff er from the
+ // index-typed box_dims results, so convert them.
+ mlir::Location loc = op.getLoc();
+ rewriter.replaceOp(op,
+ {rewriter.createOrFold<fir::ConvertOp>(
+ loc, op.getType(0), createBox.getLbounds()[*dim]),
+ rewriter.createOrFold<fir::ConvertOp>(
+ loc, op.getType(1), createBox.getExtents()[*dim]),
+ rewriter.createOrFold<fir::ConvertOp>(
+ loc, op.getType(2), createBox.getStrides()[*dim])});
+ return mlir::success();
+ }
+};
+} // namespace
+
+void fir::BoxDimsOp::getCanonicalizationPatterns(
+ mlir::RewritePatternSet &results, mlir::MLIRContext *context) {
+ results.insert<SimplifyBoxDimsFromCreateBox>(context);
+}
+
/// Get the result types packed in a tuple tuple
mlir::Type fir::BoxDimsOp::getTupleType() {
// note: triple, but 4 is nearest power of 2
diff --git a/flang/test/Fir/array-coor-canonicalization.fir b/flang/test/Fir/array-coor-canonicalization.fir
index cb2efa007294f..d2b8bc06a0c76 100644
--- a/flang/test/Fir/array-coor-canonicalization.fir
+++ b/flang/test/Fir/array-coor-canonicalization.fir
@@ -1059,3 +1059,18 @@ func.func @test37_merge_no_fold_inner_rank_too_large(%base: !fir.ref<!fir.array<
%inner = fir.array_coor %view(%inner_shape) %i, %j : (!fir.ref<!fir.array<3x4xf64>>, !fir.shape<2>, index, index) -> !fir.ref<f64>
return %inner : !fir.ref<f64>
}
+
+// No fold: a fir.array_coor on a descriptor built by fir.create_box must not be
+// rewritten into an array_coor on the base pointer. create_box carries
+// arbitrary byte strides that an array_coor on a ref+shape cannot express, so
+// keeping the array_coor on the box is what preserves those strides.
+// CHECK-LABEL: func.func @test38_no_fold_array_coor_create_box(
+// CHECK: %[[BOX:.*]] = fir.create_box
+// CHECK: fir.array_coor %[[BOX]]
+func.func @test38_no_fold_array_coor_create_box(%arg0: !fir.ref<!fir.array<?xf32>>,
+ %lb: index, %ext: index,
+ %sm: index, %idx: index) -> !fir.ref<f32> {
+ %0 = fir.create_box %arg0 lbs(%lb) extents(%ext) strides(%sm) : (!fir.ref<!fir.array<?xf32>>, index, index, index) -> !fir.box<!fir.array<?xf32>>
+ %1 = fir.array_coor %0 %idx : (!fir.box<!fir.array<?xf32>>, index) -> !fir.ref<f32>
+ return %1 : !fir.ref<f32>
+}
diff --git a/flang/test/Fir/box-dims-folding.fir b/flang/test/Fir/box-dims-folding.fir
new file mode 100644
index 0000000000000..e566df81c3f9a
--- /dev/null
+++ b/flang/test/Fir/box-dims-folding.fir
@@ -0,0 +1,60 @@
+// RUN: fir-opt --canonicalize %s -split-input-file | FileCheck %s
+
+// fir.box_dims of a fir.create_box with a constant dimension forwards the lower
+// bound, extent, and byte stride operands that built that dimension.
+// CHECK-LABEL: func.func @fold_box_dims_create_box(
+// CHECK-SAME: %[[BASE:[^:]+]]: !fir.ref<!fir.array<?xf32>>, %[[LB:[^:]+]]: index, %[[EXT:[^:]+]]: index, %[[SM:[^:]+]]: index)
+// CHECK-NOT: fir.box_dims
+// CHECK: return %[[LB]], %[[EXT]], %[[SM]]
+func.func @fold_box_dims_create_box(%base: !fir.ref<!fir.array<?xf32>>, %lb: index, %ext: index, %sm: index) -> (index, index, index) {
+ %c0 = arith.constant 0 : index
+ %0 = fir.create_box %base lbs(%lb) extents(%ext) strides(%sm) : (!fir.ref<!fir.array<?xf32>>, index, index, index) -> !fir.box<!fir.array<?xf32>>
+ %1:3 = fir.box_dims %0, %c0 : (!fir.box<!fir.array<?xf32>>, index) -> (index, index, index)
+ return %1#0, %1#1, %1#2 : index, index, index
+}
+
+// -----
+
+// The forwarded operands are selected per dimension: dim 1 forwards the second
+// lower bound / extent / stride triple.
+// CHECK-LABEL: func.func @fold_box_dims_create_box_2d(
+// CHECK-SAME: %[[BASE:[^:]+]]: !fir.ref<!fir.array<?x?xf32>>, %[[LB0:[^:]+]]: index, %[[EXT0:[^:]+]]: index, %[[SM0:[^:]+]]: index, %[[LB1:[^:]+]]: index, %[[EXT1:[^:]+]]: index, %[[SM1:[^:]+]]: index)
+// CHECK-NOT: fir.box_dims
+// CHECK: return %[[LB1]], %[[EXT1]], %[[SM1]]
+func.func @fold_box_dims_create_box_2d(%base: !fir.ref<!fir.array<?x?xf32>>, %lb0: index, %ext0: index, %sm0: index, %lb1: index, %ext1: index, %sm1: index) -> (index, index, index) {
+ %c1 = arith.constant 1 : index
+ %0 = fir.create_box %base lbs(%lb0, %lb1) extents(%ext0, %ext1) strides(%sm0, %sm1) : (!fir.ref<!fir.array<?x?xf32>>, index, index, index, index, index, index) -> !fir.box<!fir.array<?x?xf32>>
+ %1:3 = fir.box_dims %0, %c1 : (!fir.box<!fir.array<?x?xf32>>, index) -> (index, index, index)
+ return %1#0, %1#1, %1#2 : index, index, index
+}
+
+// -----
+
+// The create_box operands are AnyIntegerType; when they are not index-typed the
+// fold inserts fir.convert so the forwarded values match the box_dims results.
+// CHECK-LABEL: func.func @fold_box_dims_create_box_i64_operands(
+// CHECK-SAME: %[[BASE:[^:]+]]: !fir.ref<!fir.array<?xf32>>, %[[LB:[^:]+]]: i64, %[[EXT:[^:]+]]: i64, %[[SM:[^:]+]]: i64)
+// CHECK-NOT: fir.box_dims
+// CHECK-DAG: %[[LBC:.*]] = fir.convert %[[LB]] : (i64) -> index
+// CHECK-DAG: %[[EXTC:.*]] = fir.convert %[[EXT]] : (i64) -> index
+// CHECK-DAG: %[[SMC:.*]] = fir.convert %[[SM]] : (i64) -> index
+// CHECK: return %[[LBC]], %[[EXTC]], %[[SMC]]
+func.func @fold_box_dims_create_box_i64_operands(%base: !fir.ref<!fir.array<?xf32>>, %lb: i64, %ext: i64, %sm: i64) -> (index, index, index) {
+ %c0 = arith.constant 0 : index
+ %0 = fir.create_box %base lbs(%lb) extents(%ext) strides(%sm) : (!fir.ref<!fir.array<?xf32>>, i64, i64, i64) -> !fir.box<!fir.array<?xf32>>
+ %1:3 = fir.box_dims %0, %c0 : (!fir.box<!fir.array<?xf32>>, index) -> (index, index, index)
+ return %1#0, %1#1, %1#2 : index, index, index
+}
+
+// -----
+
+// No fold: the queried dimension is not a compile-time constant, so the right
+// operand triple cannot be selected.
+// CHECK-LABEL: func.func @no_fold_box_dims_create_box_dynamic_dim(
+// CHECK: %[[BOX:.*]] = fir.create_box
+// CHECK: fir.box_dims %[[BOX]]
+func.func @no_fold_box_dims_create_box_dynamic_dim(%base: !fir.ref<!fir.array<?x?xf32>>, %lb0: index, %ext0: index, %sm0: index, %lb1: index, %ext1: index, %sm1: index, %d: index) -> (index, index, index) {
+ %0 = fir.create_box %base lbs(%lb0, %lb1) extents(%ext0, %ext1) strides(%sm0, %sm1) : (!fir.ref<!fir.array<?x?xf32>>, index, index, index, index, index, index) -> !fir.box<!fir.array<?x?xf32>>
+ %1:3 = fir.box_dims %0, %d : (!fir.box<!fir.array<?x?xf32>>, index) -> (index, index, index)
+ return %1#0, %1#1, %1#2 : index, index, index
+}
diff --git a/flang/test/Fir/boxaddr-folding.fir b/flang/test/Fir/boxaddr-folding.fir
index 4fd23864ae49c..f7bbbdb71556a 100644
--- a/flang/test/Fir/boxaddr-folding.fir
+++ b/flang/test/Fir/boxaddr-folding.fir
@@ -44,3 +44,40 @@ func.func @check_folding(%arg0 : !fir.ref<!fir.array<?xi32>>) {
func.func @check(%arg0: !fir.ref<!fir.array<?xi32>>) {
return
}
+
+// -----
+
+// The base address of a descriptor built by fir.create_box is exactly its
+// memref operand (create_box never slices), so box_addr folds to that operand
+// when the result type preserves the memref storage kind.
+// CHECK-LABEL: func @check_create_box_folding
+func.func @check_create_box_folding(%arg0: !fir.ref<!fir.array<?xf32>>, %lb: index, %ext: index, %sm: index) {
+ %0 = fir.create_box %arg0 lbs(%lb) extents(%ext) strides(%sm) : (!fir.ref<!fir.array<?xf32>>, index, index, index) -> !fir.box<!fir.array<?xf32>>
+ %1 = fir.box_addr %0 : (!fir.box<!fir.array<?xf32>>) -> !fir.ref<!fir.array<?xf32>>
+ // CHECK-NOT: fir.box_addr
+ // CHECK: fir.call @check_ref(%arg0)
+ fir.call @check_ref(%1) : (!fir.ref<!fir.array<?xf32>>) -> ()
+ return
+}
+
+func.func @check_ref(%arg0: !fir.ref<!fir.array<?xf32>>) {
+ return
+}
+
+// -----
+
+// When the box_addr result type does not match the create_box memref type the
+// storage kind would change, so the fold must not fire.
+// CHECK-LABEL: func @check_create_box_no_folding
+func.func @check_create_box_no_folding(%arg0: !fir.heap<!fir.array<?xf32>>, %lb: index, %ext: index, %sm: index) {
+ %0 = fir.create_box %arg0 lbs(%lb) extents(%ext) strides(%sm) : (!fir.heap<!fir.array<?xf32>>, index, index, index) -> !fir.box<!fir.heap<!fir.array<?xf32>>>
+ %1 = fir.box_addr %0 : (!fir.box<!fir.heap<!fir.array<?xf32>>>) -> !fir.ref<!fir.array<?xf32>>
+ // CHECK: %[[ADDR:.*]] = fir.box_addr
+ // CHECK: fir.call @check_ref(%[[ADDR]])
+ fir.call @check_ref(%1) : (!fir.ref<!fir.array<?xf32>>) -> ()
+ return
+}
+
+func.func @check_ref(%arg0: !fir.ref<!fir.array<?xf32>>) {
+ return
+}
More information about the flang-commits
mailing list