[flang-commits] [flang] [flang] implements a rewrite pattern to constant fold fir::BoxEleSizeOp (PR #192320)
Moazin K. via flang-commits
flang-commits at lists.llvm.org
Wed Apr 15 12:15:18 PDT 2026
https://github.com/moazin created https://github.com/llvm/llvm-project/pull/192320
Implements a rewrite pattern to constant fold an `fir::BoxEleSizeOp` when possible.
>From 3099c582c11564cc5273e119bbc1889dcb00b18e Mon Sep 17 00:00:00 2001
From: Moazin Khatti <mkhatti at nvidia.com>
Date: Tue, 7 Apr 2026 16:46:39 -0700
Subject: [PATCH] [flang] implements a rewrite pattern to constant fold
fir::BoxEleSizeOp
---
.../include/flang/Optimizer/Dialect/FIROps.td | 2 +
flang/lib/Optimizer/Dialect/FIROps.cpp | 61 ++++++++++
flang/test/Fir/box-elesize-canonicalize.fir | 105 ++++++++++++++++++
flang/test/Lower/volatile-string.f90 | 3 +-
4 files changed, 169 insertions(+), 2 deletions(-)
create mode 100644 flang/test/Fir/box-elesize-canonicalize.fir
diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td
index f134866e2415d..d6fe214e0e9a0 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.td
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.td
@@ -1252,6 +1252,8 @@ def fir_BoxEleSizeOp : fir_SimpleOneResultOp<"box_elesize", [NoMemoryEffect]> {
let arguments = (ins BoxOrClassType:$val);
let results = (outs AnyIntegerLike);
+
+ let hasCanonicalizer = 1;
}
def fir_BoxTypeCodeOp : fir_SimpleOneResultOp<"box_typecode", [NoMemoryEffect]>
diff --git a/flang/lib/Optimizer/Dialect/FIROps.cpp b/flang/lib/Optimizer/Dialect/FIROps.cpp
index 92e8c462417d4..405ada36a0037 100644
--- a/flang/lib/Optimizer/Dialect/FIROps.cpp
+++ b/flang/lib/Optimizer/Dialect/FIROps.cpp
@@ -31,6 +31,7 @@
#include "mlir/IR/OpDefinition.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/IR/TypeRange.h"
+#include "mlir/Interfaces/DataLayoutInterfaces.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/TypeSwitch.h"
@@ -1218,6 +1219,66 @@ mlir::OpFoldResult fir::BoxCharLenOp::fold(FoldAdaptor adaptor) {
return {};
}
+//===----------------------------------------------------------------------===//
+// BoxEleSizeOp
+//===----------------------------------------------------------------------===//
+
+namespace {
+/// Canonicalize fir.box_elesize when the element type is statically known.
+struct FoldBoxEleSize : public mlir::OpRewritePattern<fir::BoxEleSizeOp> {
+ using mlir::OpRewritePattern<fir::BoxEleSizeOp>::OpRewritePattern;
+
+ mlir::LogicalResult
+ matchAndRewrite(fir::BoxEleSizeOp op,
+ mlir::PatternRewriter &rewriter) const override {
+ auto boxTy = mlir::cast<fir::BaseBoxType>(op.getVal().getType());
+
+ // ClassType is polymorphic — the actual runtime type may differ, size
+ // not statically known.
+ if (mlir::isa<fir::ClassType>(boxTy))
+ return mlir::failure();
+
+ // unwrapInnerType peels through any ptr/heap/array wrapper inside the box
+ // to reach the scalar element type.
+ mlir::Type eleTy = boxTy.unwrapInnerType();
+
+ // NoneType is the element type for TYPE(*) assumed-type boxes — no static
+ // element type.
+ if (mlir::isa<mlir::NoneType>(eleTy))
+ return mlir::failure();
+
+ // Bail out for types whose size is only known at runtime as a string whose
+ // length is unknown or a record type with fields with a dynamic size
+ if (fir::hasDynamicSize(eleTy))
+ return mlir::failure();
+
+ mlir::DataLayout dl = mlir::DataLayout::closest(op);
+ fir::KindMapping kindMap = fir::getKindMapping(op.getOperation());
+
+ auto sizeAndAlign =
+ fir::getTypeSizeAndAlignment(op.getLoc(), eleTy, dl, kindMap);
+ if (!sizeAndAlign)
+ return mlir::failure();
+
+ // The descriptor stores the byte stride between elements (not the raw
+ // natural size), so we must round up to alignment just as
+ // fir::computeElementDistance does.
+ auto [size, alignment] = *sizeAndAlign;
+ std::int64_t distance = llvm::alignTo(size, alignment);
+
+ mlir::Type resultTy = op.getType();
+ rewriter.replaceOpWithNewOp<mlir::arith::ConstantOp>(
+ op, resultTy, rewriter.getIntegerAttr(resultTy, distance));
+ return mlir::success();
+ }
+};
+} // namespace
+
+void fir::BoxEleSizeOp::getCanonicalizationPatterns(
+ mlir::RewritePatternSet &results, mlir::MLIRContext *context) {
+ results.insert<FoldBoxEleSize>(context);
+}
+
//===----------------------------------------------------------------------===//
// BoxDimsOp
//===----------------------------------------------------------------------===//
diff --git a/flang/test/Fir/box-elesize-canonicalize.fir b/flang/test/Fir/box-elesize-canonicalize.fir
new file mode 100644
index 0000000000000..dd2b06ed3ef77
--- /dev/null
+++ b/flang/test/Fir/box-elesize-canonicalize.fir
@@ -0,0 +1,105 @@
+// RUN: fir-opt --canonicalize %s | FileCheck %s
+
+// Fold: plain array box with dynamic extent — element type (f32) is statically known.
+func.func @fold_array_f32(%arg0: !fir.box<!fir.array<?xf32>>) -> index {
+ %0 = fir.box_elesize %arg0 : (!fir.box<!fir.array<?xf32>>) -> index
+ return %0 : index
+}
+// CHECK-LABEL: func.func @fold_array_f32(
+// CHECK: %[[C:.*]] = arith.constant 4 : index
+// CHECK: return %[[C]]
+// CHECK-NOT: fir.box_elesize
+
+// Fold: plain array box with static extent — element type (f64) is statically known.
+func.func @fold_array_f64(%arg0: !fir.box<!fir.array<10xf64>>) -> index {
+ %0 = fir.box_elesize %arg0 : (!fir.box<!fir.array<10xf64>>) -> index
+ return %0 : index
+}
+// CHECK-LABEL: func.func @fold_array_f64(
+// CHECK: %[[C:.*]] = arith.constant 8 : index
+// CHECK: return %[[C]]
+// CHECK-NOT: fir.box_elesize
+
+// Fold: scalar (non-array) box.
+func.func @fold_scalar_i32(%arg0: !fir.box<i32>) -> index {
+ %0 = fir.box_elesize %arg0 : (!fir.box<i32>) -> index
+ return %0 : index
+}
+// CHECK-LABEL: func.func @fold_scalar_i32(
+// CHECK: %[[C:.*]] = arith.constant 4 : index
+// CHECK: return %[[C]]
+// CHECK-NOT: fir.box_elesize
+
+// No fold: ClassType is polymorphic — the actual element type is only known at runtime.
+func.func @no_fold_class(%arg0: !fir.class<!fir.type<sometype{i:i32}>>) -> index {
+ %0 = fir.box_elesize %arg0 : (!fir.class<!fir.type<sometype{i:i32}>>) -> index
+ return %0 : index
+}
+// CHECK-LABEL: func.func @no_fold_class(
+// CHECK: %[[V:.*]] = fir.box_elesize %{{.*}} : (!fir.class<!fir.type<sometype{i:i32}>>) -> index
+// CHECK: return %[[V]]
+
+// No fold: !fir.box<none> is an assumed-type (TYPE(*)) — no static element type.
+func.func @no_fold_none(%arg0: !fir.box<none>) -> index {
+ %0 = fir.box_elesize %arg0 : (!fir.box<none>) -> index
+ return %0 : index
+}
+// CHECK-LABEL: func.func @no_fold_none(
+// CHECK: %[[V:.*]] = fir.box_elesize %{{.*}} : (!fir.box<none>) -> index
+// CHECK: return %[[V]]
+
+// Fold: allocatable array (!fir.box<!fir.heap<!fir.array<...>>>)
+func.func @fold_heap_array(%arg0: !fir.box<!fir.heap<!fir.array<?xf32>>>) -> index {
+ %0 = fir.box_elesize %arg0 : (!fir.box<!fir.heap<!fir.array<?xf32>>>) -> index
+ return %0 : index
+}
+// CHECK-LABEL: func.func @fold_heap_array(
+// CHECK: %[[C:.*]] = arith.constant 4 : index
+// CHECK: return %[[C]]
+// CHECK-NOT: fir.box_elesize
+
+// Fold: Fortran pointer array (!fir.box<!fir.ptr<!fir.array<...>>>)
+func.func @fold_ptr_array(%arg0: !fir.box<!fir.ptr<!fir.array<?xf64>>>) -> index {
+ %0 = fir.box_elesize %arg0 : (!fir.box<!fir.ptr<!fir.array<?xf64>>>) -> index
+ return %0 : index
+}
+// CHECK-LABEL: func.func @fold_ptr_array(
+// CHECK: %[[C:.*]] = arith.constant 8 : index
+// CHECK: return %[[C]]
+// CHECK-NOT: fir.box_elesize
+
+// No fold: dynamic-length CHARACTER array
+func.func @no_fold_char_dynamic(%arg0: !fir.box<!fir.array<?x!fir.char<1,?>>>) -> index {
+ %0 = fir.box_elesize %arg0 : (!fir.box<!fir.array<?x!fir.char<1,?>>>) -> index
+ return %0 : index
+}
+// CHECK-LABEL: func.func @no_fold_char_dynamic(
+// CHECK: %[[V:.*]] = fir.box_elesize %{{.*}} : (!fir.box<!fir.array<?x!fir.char<1,?>>>) -> index
+// CHECK: return %[[V]]
+
+// Fold: fixed-length CHARACTER array. Element size is statically known (5 bytes).
+func.func @fold_char_static(%arg0: !fir.box<!fir.array<?x!fir.char<1,5>>>) -> index {
+ %0 = fir.box_elesize %arg0 : (!fir.box<!fir.array<?x!fir.char<1,5>>>) -> index
+ return %0 : index
+}
+// CHECK-LABEL: func.func @fold_char_static(
+// CHECK: %[[C:.*]] = arith.constant 5 : index
+// CHECK: return %[[C]]
+// CHECK-NOT: fir.box_elesize
+
+// Tests inside a module with an explicit x86-64 DLTI data-layout spec.
+module attributes { dlti.dl_spec = #dlti.dl_spec< i1 = dense<8> : vector<2xi64>, i8 = dense<8> : vector<2xi64>, i16 = dense<16> : vector<2xi64>, i32 = dense<32> : vector<2xi64>, i64 = dense<64> : vector<2xi64>, i128 = dense<128> : vector<2xi64>, f16 = dense<16> : vector<2xi64>, f64 = dense<64> : vector<2xi64>, f80 = dense<128> : vector<2xi64>, f128 = dense<128> : vector<2xi64>, !llvm.ptr = dense<64> : vector<4xi64>, !llvm.ptr<270> = dense<32> : vector<4xi64>, !llvm.ptr<271> = dense<32> : vector<4xi64>, !llvm.ptr<272> = dense<64> : vector<4xi64>, "dlti.endianness" = "little", "dlti.stack_alignment" = 128 : i64>, fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", llvm.data_layout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"} {
+
+ // fp80 edge case: x87 80-bit extended precision (Fortran REAL(10) on x86-64).
+ // MLIR's DataLayout derives the natural byte size from the bit-width
+ // (80 bits = 10 bytes) and the DLTI entry "f80 = dense<128> : vector<2xi64>"
+ // gives ABI alignment = 16 bytes.
+ func.func @fold_f80_with_dlti(%arg0: !fir.box<f80>) -> index {
+ %0 = fir.box_elesize %arg0 : (!fir.box<f80>) -> index
+ return %0 : index
+ }
+ // CHECK-LABEL: func.func @fold_f80_with_dlti(
+ // CHECK: %[[C:.*]] = arith.constant 16 : index
+ // CHECK: return %[[C]]
+ // CHECK-NOT: fir.box_elesize
+}
diff --git a/flang/test/Lower/volatile-string.f90 b/flang/test/Lower/volatile-string.f90
index f3e291dd19182..2d8bb94c3388f 100644
--- a/flang/test/Lower/volatile-string.f90
+++ b/flang/test/Lower/volatile-string.f90
@@ -56,9 +56,8 @@ subroutine assign_different_length(string)
! CHECK: %[[VAL_32:.*]] = fir.convert %[[VAL_28]] : (!fir.ref<!fir.char<1,{{.*}}>>) -> !fir.ref<i8>
! CHECK: fir.call @_FortranAAdjustl(%[[VAL_29]], %[[VAL_31]], %[[VAL_32]], %[[VAL_1]]) fastmath<contract> : (!fir.ref<!fir.box<none>>, !fir.box<none>, !fir.ref<i8>, i32) -> ()
! CHECK: %[[VAL_33:.*]] = fir.load %[[VAL_6]] : !fir.ref<!fir.box<!fir.heap<!fir.char<1,3>>>>
-! CHECK: %[[VAL_34:.*]] = fir.box_elesize %[[VAL_33]] : (!fir.box<!fir.heap<!fir.char<1,3>>>) -> index
! CHECK: %[[VAL_35:.*]] = fir.box_addr %[[VAL_33]] : (!fir.box<!fir.heap<!fir.char<1,3>>>) -> !fir.heap<!fir.char<1,3>>
-! CHECK: %[[VAL_36:.*]]:2 = hlfir.declare %[[VAL_35]] typeparams %[[VAL_34]] {uniq_name = ".tmp.intrinsic_result"} : (!fir.heap<!fir.char<1,3>>, index) -> (!fir.heap<!fir.char<1,3>>, !fir.heap<!fir.char<1,3>>)
+! CHECK: %[[VAL_36:.*]]:2 = hlfir.declare %[[VAL_35]] typeparams %[[VAL_5]] {uniq_name = ".tmp.intrinsic_result"} : (!fir.heap<!fir.char<1,3>>, index) -> (!fir.heap<!fir.char<1,3>>, !fir.heap<!fir.char<1,3>>)
! CHECK: %[[VAL_37:.*]] = hlfir.as_expr %[[VAL_36]]#0 move %[[VAL_0]] : (!fir.heap<!fir.char<1,3>>, i1) -> !hlfir.expr<!fir.char<1,3>>
! CHECK: hlfir.assign %[[VAL_37]] to %[[VAL_14]]#0 : !hlfir.expr<!fir.char<1,3>>, !fir.ref<!fir.char<1,3>, volatile>
! CHECK: hlfir.destroy %[[VAL_37]] : !hlfir.expr<!fir.char<1,3>>
More information about the flang-commits
mailing list