[flang-commits] [flang] [flang] Simplify the comparison of characters (PR #154593)
Leandro Lupori via flang-commits
flang-commits at lists.llvm.org
Wed Sep 24 07:26:25 PDT 2025
https://github.com/luporl updated https://github.com/llvm/llvm-project/pull/154593
>From 3e6b04051cb381af85854f86d90d8111d7a292e9 Mon Sep 17 00:00:00 2001
From: Leandro Lupori <leandro.lupori at linaro.org>
Date: Wed, 20 Aug 2025 14:44:35 -0300
Subject: [PATCH 1/4] [flang] Simplify the comparison of characters
Because character comparison appends spaces to the shorter character,
calls to trim() that are used only in the comparison can be eliminated.
Example:
`trim(x) == trim(y)`
can be simplified to
`x == y`
This makes 527.cam4_r about 3% faster, measured on Neoverse V2.
This patch implements the optimization above in a new pass:
ExpressionSimplification. Although no other expression simplifications
are planned at the moment, they could be easily added to the new pass.
The ExpressionSimplification pass runs early in the HLFIR pipeline, to
make it easy to identify expressions before any transformations occur.
---
flang/include/flang/Optimizer/HLFIR/Passes.td | 5 +
.../Optimizer/HLFIR/Transforms/CMakeLists.txt | 1 +
.../Transforms/ExpressionSimplification.cpp | 294 ++++++++++++++++++
flang/lib/Optimizer/Passes/Pipelines.cpp | 3 +
flang/test/Driver/mlir-pass-pipeline.f90 | 1 +
.../test/HLFIR/expression-simplification.fir | 173 +++++++++++
6 files changed, 477 insertions(+)
create mode 100644 flang/lib/Optimizer/HLFIR/Transforms/ExpressionSimplification.cpp
create mode 100644 flang/test/HLFIR/expression-simplification.fir
diff --git a/flang/include/flang/Optimizer/HLFIR/Passes.td b/flang/include/flang/Optimizer/HLFIR/Passes.td
index 04d7aec5fe489..43646c6c05ead 100644
--- a/flang/include/flang/Optimizer/HLFIR/Passes.td
+++ b/flang/include/flang/Optimizer/HLFIR/Passes.td
@@ -61,6 +61,11 @@ def SimplifyHLFIRIntrinsics : Pass<"simplify-hlfir-intrinsics"> {
"the hlfir.matmul.">];
}
+def ExpressionSimplification
+ : Pass<"expression-simplification", "::mlir::ModuleOp"> {
+ let summary = "Simplify Fortran expressions";
+}
+
def InlineElementals : Pass<"inline-elementals"> {
let summary = "Inline chained hlfir.elemental operations";
}
diff --git a/flang/lib/Optimizer/HLFIR/Transforms/CMakeLists.txt b/flang/lib/Optimizer/HLFIR/Transforms/CMakeLists.txt
index cc74273d9c5d9..28153076df0fb 100644
--- a/flang/lib/Optimizer/HLFIR/Transforms/CMakeLists.txt
+++ b/flang/lib/Optimizer/HLFIR/Transforms/CMakeLists.txt
@@ -3,6 +3,7 @@ get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS)
add_flang_library(HLFIRTransforms
BufferizeHLFIR.cpp
ConvertToFIR.cpp
+ ExpressionSimplification.cpp
InlineElementals.cpp
InlineHLFIRAssign.cpp
InlineHLFIRCopyIn.cpp
diff --git a/flang/lib/Optimizer/HLFIR/Transforms/ExpressionSimplification.cpp b/flang/lib/Optimizer/HLFIR/Transforms/ExpressionSimplification.cpp
new file mode 100644
index 0000000000000..d9e73482020f8
--- /dev/null
+++ b/flang/lib/Optimizer/HLFIR/Transforms/ExpressionSimplification.cpp
@@ -0,0 +1,294 @@
+#include "flang/Optimizer/Builder/FIRBuilder.h"
+#include "flang/Optimizer/HLFIR/HLFIROps.h"
+#include "flang/Optimizer/HLFIR/Passes.h"
+#include "flang/Runtime/entry-names.h"
+#include "llvm/Support/DebugLog.h"
+
+namespace hlfir {
+#define GEN_PASS_DEF_EXPRESSIONSIMPLIFICATION
+#include "flang/Optimizer/HLFIR/Passes.h.inc"
+} // namespace hlfir
+
+#define DEBUG_TYPE "expression-simplification"
+
+#define INDENT(n) std::string((n) * 2, ' ')
+
+static void removeOperands(mlir::Operation *op, int nestLevel);
+
+static void removeOp(mlir::Operation *op, int parentUses, int nestLevel) {
+ int opUses = std::distance(op->getUses().begin(), op->getUses().end());
+
+ if (opUses <= parentUses) {
+ LDBG() << INDENT(nestLevel) << "remove: " << *op;
+ removeOperands(op, nestLevel);
+ op->dropAllReferences();
+ op->dropAllUses();
+ op->erase();
+ }
+}
+
+static void removeOp(mlir::Operation *op) {
+ removeOp(op, /*parentUses=*/0, /*nestLevel=*/0);
+ LDBG();
+}
+
+static void removeOperands(mlir::Operation *op, int nestLevel) {
+ for (mlir::Value operand : op->getOperands()) {
+ if (!operand)
+ // Already removed.
+ continue;
+ if (mlir::Operation *operandOp = operand.getDefiningOp()) {
+ int uses = 0;
+ for (mlir::Operation *user : operandOp->getUsers())
+ if (user == op)
+ ++uses;
+ removeOp(operandOp, uses, nestLevel + 1);
+ }
+ }
+}
+
+template <typename UserOp>
+static UserOp getFirstUser(mlir::Operation *op) {
+ auto it = op->user_begin(), end = op->user_end(), prev = it;
+ for (; it != end; prev = it++)
+ ;
+ if (prev != end)
+ if (auto userOp = mlir::dyn_cast<UserOp>(*prev))
+ return userOp;
+ return {};
+}
+
+template <typename UserOp>
+static UserOp getLastUser(mlir::Operation *op) {
+ if (!op->getUsers().empty())
+ if (auto userOp = mlir::dyn_cast<UserOp>(*op->user_begin()))
+ return userOp;
+ return {};
+}
+
+template <typename UserOp>
+static UserOp getPreviousUser(mlir::Operation *op, mlir::Operation *curUser) {
+ for (auto user = op->user_begin(), end = op->user_end(); user != end;
+ ++user) {
+ if (*user == curUser) {
+ if (++user == end)
+ break;
+ if (auto userOp = mlir::dyn_cast<UserOp>(*user))
+ return userOp;
+ break;
+ }
+ }
+ return {};
+}
+
+// Check if operation has the expected number of uses.
+static bool expectUses(mlir::Operation *op, int expUses) {
+ int uses = std::distance(op->use_begin(), op->use_end());
+ if (uses != expUses) {
+ LDBG() << "expectUses: expected " << expUses << ", got " << uses;
+ for (mlir::Operation *user : op->getUsers())
+ LDBG() << "\t" << *user;
+ }
+ return uses == expUses;
+}
+
+template <typename Op>
+static Op expectOp(mlir::Value val) {
+ if (Op op = mlir::dyn_cast<Op>(val.getDefiningOp())) {
+ LDBG() << op;
+ return op;
+ }
+ return {};
+}
+
+static mlir::Value findBoxDef(mlir::Value val) {
+ if (auto op = expectOp<fir::ConvertOp>(val)) {
+ assert(op->getOperands().size() != 0);
+ if (auto boxOp = expectOp<fir::EmboxOp>(op->getOperand(0)))
+ return boxOp.getResult();
+ }
+ return {};
+}
+
+namespace {
+
+// This class analyzes a trimmed character and removes the call to trim() (and
+// its dependencies) if its result is not used elsewhere.
+class TrimRemover {
+public:
+ TrimRemover(fir::FirOpBuilder &builder, mlir::Value charVal,
+ mlir::Value charLenVal)
+ : builder(builder), charVal(charVal), charLenVal(charLenVal) {}
+ TrimRemover(const TrimRemover &) = delete;
+
+ bool charWasTrimmed();
+ void removeTrim();
+
+private:
+ // Class inputs.
+ fir::FirOpBuilder &builder;
+ mlir::Value charVal;
+ mlir::Value charLenVal;
+ // Operations found while analyzing inputs, that are needed when removing
+ // the trim call.
+ hlfir::DeclareOp charDeclOp; // Trim input character.
+ fir::CallOp trimCallOp; // Trim call.
+ hlfir::EndAssociateOp endAssocOp; // Trim result association.
+ hlfir::DestroyOp destroyExprOp; // Trim result expression.
+ fir::AllocaOp allocaOp; // Trim result alloca.
+};
+
+bool TrimRemover::charWasTrimmed() {
+ LDBG() << "\ncharWasTrimmed: " << charVal;
+
+ // Get the declare and expression operations associated to `charVal`, that
+ // correspond to the result of trim.
+ auto charCvtOp = expectOp<fir::ConvertOp>(charVal);
+ auto charLenCvtOp = expectOp<fir::ConvertOp>(charLenVal);
+ if (!charCvtOp || !charLenCvtOp || !expectUses(charCvtOp, 1) ||
+ !expectUses(charLenCvtOp, 1))
+ return false;
+ auto assocOp = expectOp<hlfir::AssociateOp>(charCvtOp.getOperand());
+ if (!assocOp || !expectUses(assocOp, 3)) // end_associate uses assocOp twice
+ return false;
+ endAssocOp = getLastUser<hlfir::EndAssociateOp>(assocOp);
+ if (!endAssocOp)
+ return false;
+ auto asExprOp = expectOp<hlfir::AsExprOp>(assocOp.getOperand(0));
+ if (!asExprOp || !expectUses(asExprOp, 2))
+ return false;
+ destroyExprOp = getLastUser<hlfir::DestroyOp>(asExprOp);
+ if (!destroyExprOp)
+ return false;
+ auto declOp = expectOp<hlfir::DeclareOp>(asExprOp.getOperand(0));
+ if (!declOp || !expectUses(declOp, 1))
+ return false;
+
+ // Get associated box and alloca.
+ auto boxAddrOp = expectOp<fir::BoxAddrOp>(declOp.getMemref());
+ if (!boxAddrOp || !expectUses(boxAddrOp, 1))
+ return false;
+ auto loadOp = expectOp<fir::LoadOp>(boxAddrOp.getOperand());
+ if (!loadOp || !getFirstUser<fir::BoxEleSizeOp>(loadOp) ||
+ !expectUses(loadOp, 2))
+ return false;
+ // The allocaOp is initialized by a store.
+ // Besides its use by the store and loadOp, it's also converted and used by
+ // the trim call.
+ allocaOp = expectOp<fir::AllocaOp>(loadOp.getMemref());
+ if (!allocaOp || !getFirstUser<fir::StoreOp>(allocaOp) ||
+ !expectUses(allocaOp, 3))
+ return false;
+
+ // Find the trim call that uses the allocaOp.
+ if (auto userOp = getPreviousUser<fir::ConvertOp>(allocaOp, loadOp))
+ if (userOp->hasOneUse())
+ trimCallOp = mlir::dyn_cast<fir::CallOp>(*userOp->user_begin());
+ if (!trimCallOp)
+ return false;
+ LDBG() << "call: " << trimCallOp;
+ llvm::StringRef calleeName =
+ trimCallOp.getCalleeAttr().getLeafReference().getValue();
+ LDBG() << "callee: " << calleeName;
+ if (calleeName != RTNAME_STRING(Trim))
+ return false;
+
+ // Get trim input character.
+ auto chrEmboxOp =
+ expectOp<fir::EmboxOp>(findBoxDef(trimCallOp.getOperand(1)));
+ if (!chrEmboxOp)
+ return false;
+ charDeclOp = expectOp<hlfir::DeclareOp>(chrEmboxOp.getMemref());
+ if (!charDeclOp)
+ return false;
+
+ // Found everything as expected.
+ return true;
+}
+
+void TrimRemover::removeTrim() {
+ LDBG() << "\nremoveTrim:";
+
+ auto charCvtOp = expectOp<fir::ConvertOp>(charVal);
+ auto charLenCvtOp = expectOp<fir::ConvertOp>(charLenVal);
+ assert(charCvtOp && charLenCvtOp);
+
+ // Replace trim output char with its input.
+ mlir::Location loc = charVal.getLoc();
+ auto cvtOp = fir::ConvertOp::create(builder, loc, charCvtOp.getType(),
+ charDeclOp.getOriginalBase());
+ charCvtOp.replaceAllUsesWith(cvtOp.getResult());
+
+ // Replace trim output length with its input.
+ mlir::Value chrLen = charDeclOp.getTypeparams().back();
+ auto cvtLenOp =
+ fir::ConvertOp::create(builder, loc, charLenCvtOp.getType(), chrLen);
+ charLenCvtOp.replaceAllUsesWith(cvtLenOp.getResult());
+
+ // Remove trim call and old conversions.
+ removeOp(charCvtOp);
+ removeOp(charLenCvtOp);
+ removeOp(trimCallOp);
+ // Remove association and expression.
+ removeOp(endAssocOp);
+ removeOp(destroyExprOp);
+ // The only remaining use of allocaOp should be its initialization.
+ // Remove the store and alloca operations.
+ if (auto userOp = getLastUser<fir::StoreOp>(allocaOp))
+ removeOp(userOp);
+}
+
+} // namespace
+
+namespace {
+
+class ExpressionSimplification
+ : public hlfir::impl::ExpressionSimplificationBase<
+ ExpressionSimplification> {
+public:
+ using ExpressionSimplificationBase<
+ ExpressionSimplification>::ExpressionSimplificationBase;
+
+ void runOnOperation() override;
+
+private:
+ // Simplify character comparisons.
+ // Because character comparison appends spaces to the shorter character,
+ // calls to trim() that are used only in the comparison can be eliminated.
+ //
+ // Example:
+ // `trim(x) == trim(y)`
+ // can be simplified to
+ // `x == y`
+ void simplifyCharCompare(fir::CallOp call, const fir::KindMapping &kindMap);
+};
+
+void ExpressionSimplification::simplifyCharCompare(
+ fir::CallOp call, const fir::KindMapping &kindMap) {
+ fir::FirOpBuilder builder{call, kindMap};
+ mlir::Operation::operand_range args = call.getArgs();
+ TrimRemover lhsTrimRem(builder, args[0], args[2]);
+ TrimRemover rhsTrimRem(builder, args[1], args[3]);
+
+ if (lhsTrimRem.charWasTrimmed())
+ lhsTrimRem.removeTrim();
+ if (rhsTrimRem.charWasTrimmed())
+ rhsTrimRem.removeTrim();
+}
+
+void ExpressionSimplification::runOnOperation() {
+ mlir::ModuleOp module = getOperation();
+ fir::KindMapping kindMap = fir::getKindMapping(module);
+ module.walk([&](mlir::Operation *op) {
+ if (auto call = mlir::dyn_cast<fir::CallOp>(op)) {
+ if (mlir::SymbolRefAttr callee = call.getCalleeAttr()) {
+ mlir::StringRef funcName = callee.getLeafReference().getValue();
+ if (funcName.starts_with(RTNAME_STRING(CharacterCompareScalar))) {
+ simplifyCharCompare(call, kindMap);
+ }
+ }
+ }
+ });
+}
+
+} // namespace
diff --git a/flang/lib/Optimizer/Passes/Pipelines.cpp b/flang/lib/Optimizer/Passes/Pipelines.cpp
index ca8e820608688..3b8f3e3add6e9 100644
--- a/flang/lib/Optimizer/Passes/Pipelines.cpp
+++ b/flang/lib/Optimizer/Passes/Pipelines.cpp
@@ -244,6 +244,9 @@ void createDefaultFIROptimizerPassPipeline(mlir::PassManager &pm,
/// passes pipeline
void createHLFIRToFIRPassPipeline(mlir::PassManager &pm, bool enableOpenMP,
llvm::OptimizationLevel optLevel) {
+ if (optLevel.getSizeLevel() > 0 || optLevel.getSpeedupLevel() > 0) {
+ pm.addPass(hlfir::createExpressionSimplification());
+ }
if (optLevel.isOptimizingForSpeed()) {
addCanonicalizerPassWithoutRegionSimplification(pm);
addNestedPassToAllTopLevelOperations<PassConstructor>(
diff --git a/flang/test/Driver/mlir-pass-pipeline.f90 b/flang/test/Driver/mlir-pass-pipeline.f90
index 0bcd055a84b87..3d19a0a5700f5 100644
--- a/flang/test/Driver/mlir-pass-pipeline.f90
+++ b/flang/test/Driver/mlir-pass-pipeline.f90
@@ -15,6 +15,7 @@
! ALL: Pass statistics report
! ALL: Fortran::lower::VerifierPass
+! O2-NEXT: ExpressionSimplification
! O2-NEXT: Canonicalizer
! ALL: Pipeline Collection : ['fir.global', 'func.func', 'omp.declare_reduction', 'omp.private']
! ALL-NEXT:'fir.global' Pipeline
diff --git a/flang/test/HLFIR/expression-simplification.fir b/flang/test/HLFIR/expression-simplification.fir
new file mode 100644
index 0000000000000..28e17de381461
--- /dev/null
+++ b/flang/test/HLFIR/expression-simplification.fir
@@ -0,0 +1,173 @@
+// RUN: fir-opt %s --expression-simplification | FileCheck %s
+
+// Test removal of trim() calls.
+
+// logical function cmp(x, y)
+// character(*) :: x, y
+// cmp = trim(x) == trim(y)
+// end function
+
+func.func @test_char_cmp(%arg0: !fir.boxchar<1> {fir.bindc_name = "x"},
+ %arg1: !fir.boxchar<1> {fir.bindc_name = "y"}) -> !fir.logical<4> {
+ %0 = fir.alloca !fir.box<!fir.heap<!fir.char<1,?>>>
+ %1 = fir.alloca !fir.box<!fir.heap<!fir.char<1,?>>>
+ %2 = fir.dummy_scope : !fir.dscope
+ %3 = fir.alloca !fir.logical<4> {bindc_name = "cmp", uniq_name = "_QFcmpEcmp"}
+ %4:2 = hlfir.declare %3 {uniq_name = "_QFcmpEcmp"} : (!fir.ref<!fir.logical<4>>) -> (!fir.ref<!fir.logical<4>>, !fir.ref<!fir.logical<4>>)
+ %5:2 = fir.unboxchar %arg0 : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
+ %6:2 = hlfir.declare %5#0 typeparams %5#1 dummy_scope %2 {uniq_name = "_QFcmpEx"} : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+ %7:2 = fir.unboxchar %arg1 : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
+ %8:2 = hlfir.declare %7#0 typeparams %7#1 dummy_scope %2 {uniq_name = "_QFcmpEy"} : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+ %9 = fir.embox %6#1 typeparams %5#1 : (!fir.ref<!fir.char<1,?>>, index) -> !fir.box<!fir.char<1,?>>
+ %10 = fir.zero_bits !fir.heap<!fir.char<1,?>>
+ %c0 = arith.constant 0 : index
+ %11 = fir.embox %10 typeparams %c0 : (!fir.heap<!fir.char<1,?>>, index) -> !fir.box<!fir.heap<!fir.char<1,?>>>
+ fir.store %11 to %1 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ %12 = fir.address_of(@_QQclX710b72634dc58109795123e3cbc3b5ed) : !fir.ref<!fir.char<1,65>>
+ %c5_i32 = arith.constant 5 : i32
+ %13 = fir.convert %1 : (!fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>) -> !fir.ref<!fir.box<none>>
+ %14 = fir.convert %9 : (!fir.box<!fir.char<1,?>>) -> !fir.box<none>
+ %15 = fir.convert %12 : (!fir.ref<!fir.char<1,65>>) -> !fir.ref<i8>
+ fir.call @_FortranATrim(%13, %14, %15, %c5_i32) fastmath<contract> : (!fir.ref<!fir.box<none>>, !fir.box<none>, !fir.ref<i8>, i32) -> ()
+ %16 = fir.load %1 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ %17 = fir.box_elesize %16 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> index
+ %18 = fir.box_addr %16 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> !fir.heap<!fir.char<1,?>>
+ %19:2 = hlfir.declare %18 typeparams %17 {uniq_name = ".tmp.intrinsic_result"} : (!fir.heap<!fir.char<1,?>>, index) -> (!fir.boxchar<1>, !fir.heap<!fir.char<1,?>>)
+ %true = arith.constant true
+ %20 = hlfir.as_expr %19#0 move %true : (!fir.boxchar<1>, i1) -> !hlfir.expr<!fir.char<1,?>>
+ %21 = fir.embox %8#1 typeparams %7#1 : (!fir.ref<!fir.char<1,?>>, index) -> !fir.box<!fir.char<1,?>>
+ %22 = fir.zero_bits !fir.heap<!fir.char<1,?>>
+ %c0_0 = arith.constant 0 : index
+ %23 = fir.embox %22 typeparams %c0_0 : (!fir.heap<!fir.char<1,?>>, index) -> !fir.box<!fir.heap<!fir.char<1,?>>>
+ fir.store %23 to %0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ %24 = fir.address_of(@_QQclX710b72634dc58109795123e3cbc3b5ed) : !fir.ref<!fir.char<1,65>>
+ %c5_i32_1 = arith.constant 5 : i32
+ %25 = fir.convert %0 : (!fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>) -> !fir.ref<!fir.box<none>>
+ %26 = fir.convert %21 : (!fir.box<!fir.char<1,?>>) -> !fir.box<none>
+ %27 = fir.convert %24 : (!fir.ref<!fir.char<1,65>>) -> !fir.ref<i8>
+ fir.call @_FortranATrim(%25, %26, %27, %c5_i32_1) fastmath<contract> : (!fir.ref<!fir.box<none>>, !fir.box<none>, !fir.ref<i8>, i32) -> ()
+ %28 = fir.load %0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ %29 = fir.box_elesize %28 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> index
+ %30 = fir.box_addr %28 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> !fir.heap<!fir.char<1,?>>
+ %31:2 = hlfir.declare %30 typeparams %29 {uniq_name = ".tmp.intrinsic_result"} : (!fir.heap<!fir.char<1,?>>, index) -> (!fir.boxchar<1>, !fir.heap<!fir.char<1,?>>)
+ %true_2 = arith.constant true
+ %32 = hlfir.as_expr %31#0 move %true_2 : (!fir.boxchar<1>, i1) -> !hlfir.expr<!fir.char<1,?>>
+ %33:3 = hlfir.associate %20 typeparams %17 {adapt.valuebyref} : (!hlfir.expr<!fir.char<1,?>>, index) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>, i1)
+ %34:3 = hlfir.associate %32 typeparams %29 {adapt.valuebyref} : (!hlfir.expr<!fir.char<1,?>>, index) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>, i1)
+ %35 = fir.convert %33#1 : (!fir.ref<!fir.char<1,?>>) -> !fir.ref<i8>
+ %36 = fir.convert %34#1 : (!fir.ref<!fir.char<1,?>>) -> !fir.ref<i8>
+ %37 = fir.convert %17 : (index) -> i64
+ %38 = fir.convert %29 : (index) -> i64
+ %39 = fir.call @_FortranACharacterCompareScalar1(%35, %36, %37, %38) fastmath<contract> : (!fir.ref<i8>, !fir.ref<i8>, i64, i64) -> i32
+ %c0_i32 = arith.constant 0 : i32
+ %40 = arith.cmpi eq, %39, %c0_i32 : i32
+ hlfir.end_associate %33#1, %33#2 : !fir.ref<!fir.char<1,?>>, i1
+ hlfir.end_associate %34#1, %34#2 : !fir.ref<!fir.char<1,?>>, i1
+ %41 = fir.convert %40 : (i1) -> !fir.logical<4>
+ hlfir.assign %41 to %4#0 : !fir.logical<4>, !fir.ref<!fir.logical<4>>
+ hlfir.destroy %32 : !hlfir.expr<!fir.char<1,?>>
+ hlfir.destroy %20 : !hlfir.expr<!fir.char<1,?>>
+ %42 = fir.load %4#0 : !fir.ref<!fir.logical<4>>
+ return %42 : !fir.logical<4>
+}
+
+// CHECK-LABEL: func.func @test_char_cmp(
+// CHECK-SAME: %[[VAL_4:.*]]: !fir.boxchar<1> {fir.bindc_name = "x"},
+// CHECK-SAME: %[[VAL_7:.*]]: !fir.boxchar<1> {fir.bindc_name = "y"}) -> !fir.logical<4> {
+// CHECK: %[[VAL_0:.*]] = fir.dummy_scope : !fir.dscope
+// CHECK: %[[VAL_1:.*]] = fir.alloca !fir.logical<4> {bindc_name = "cmp", uniq_name = "_QFcmpEcmp"}
+// CHECK: %[[VAL_2:.*]]:2 = hlfir.declare %[[VAL_1]] {uniq_name = "_QFcmpEcmp"} : (!fir.ref<!fir.logical<4>>) -> (!fir.ref<!fir.logical<4>>, !fir.ref<!fir.logical<4>>)
+// CHECK: %[[VAL_3:.*]]:2 = fir.unboxchar %[[VAL_4]] : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
+// CHECK: %[[VAL_5:.*]]:2 = hlfir.declare %[[VAL_3]]#0 typeparams %[[VAL_3]]#1 dummy_scope %[[VAL_0]] {uniq_name = "_QFcmpEx"} : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+// CHECK: %[[VAL_6:.*]]:2 = fir.unboxchar %[[VAL_7]] : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
+// CHECK: %[[VAL_8:.*]]:2 = hlfir.declare %[[VAL_6]]#0 typeparams %[[VAL_6]]#1 dummy_scope %[[VAL_0]] {uniq_name = "_QFcmpEy"} : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+// CHECK: %[[VAL_9:.*]] = fir.convert %[[VAL_5]]#1 : (!fir.ref<!fir.char<1,?>>) -> !fir.ref<i8>
+// CHECK: %[[VAL_10:.*]] = fir.convert %[[VAL_3]]#1 : (index) -> i64
+// CHECK: %[[VAL_11:.*]] = fir.convert %[[VAL_8]]#1 : (!fir.ref<!fir.char<1,?>>) -> !fir.ref<i8>
+// CHECK: %[[VAL_12:.*]] = fir.convert %[[VAL_6]]#1 : (index) -> i64
+// CHECK: %[[VAL_13:.*]] = fir.call @_FortranACharacterCompareScalar1(%[[VAL_9]], %[[VAL_11]], %[[VAL_10]], %[[VAL_12]]) fastmath<contract> : (!fir.ref<i8>, !fir.ref<i8>, i64, i64) -> i32
+// CHECK: %[[VAL_14:.*]] = arith.constant 0 : i32
+// CHECK: %[[VAL_15:.*]] = arith.cmpi eq, %[[VAL_13]], %[[VAL_14]] : i32
+// CHECK: %[[VAL_16:.*]] = fir.convert %[[VAL_15]] : (i1) -> !fir.logical<4>
+// CHECK: hlfir.assign %[[VAL_16]] to %[[VAL_2]]#0 : !fir.logical<4>, !fir.ref<!fir.logical<4>>
+// CHECK: %[[VAL_17:.*]] = fir.load %[[VAL_2]]#0 : !fir.ref<!fir.logical<4>>
+// CHECK: return %[[VAL_17]] : !fir.logical<4>
+// CHECK: }
+
+// Check that trim() is not removed when its result is stored.
+
+// logical function eq_use3(x, y) result(res)
+// character(*) :: x, y
+// character(:), allocatable :: tx
+//
+// tx = trim(x)
+// res = tx == y
+// end function
+
+func.func @test_char_cmp2(%arg0: !fir.boxchar<1> {fir.bindc_name = "x"}, %arg1: !fir.boxchar<1> {fir.bindc_name = "y"}) -> !fir.logical<4> {
+ %0 = fir.alloca !fir.box<!fir.heap<!fir.char<1,?>>>
+ %1 = fir.dummy_scope : !fir.dscope
+ %2 = fir.alloca !fir.logical<4> {bindc_name = "res", uniq_name = "_QFcmpEres"}
+ %3:2 = hlfir.declare %2 {uniq_name = "_QFcmpEres"} : (!fir.ref<!fir.logical<4>>) -> (!fir.ref<!fir.logical<4>>, !fir.ref<!fir.logical<4>>)
+ %4 = fir.alloca !fir.box<!fir.heap<!fir.char<1,?>>> {bindc_name = "tx", uniq_name = "_QFcmpEtx"}
+ %5 = fir.zero_bits !fir.heap<!fir.char<1,?>>
+ %c0 = arith.constant 0 : index
+ %6 = fir.embox %5 typeparams %c0 : (!fir.heap<!fir.char<1,?>>, index) -> !fir.box<!fir.heap<!fir.char<1,?>>>
+ fir.store %6 to %4 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ %7:2 = hlfir.declare %4 {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFcmpEtx"} : (!fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>) -> (!fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>, !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>)
+ %8:2 = fir.unboxchar %arg0 : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
+ %9:2 = hlfir.declare %8#0 typeparams %8#1 dummy_scope %1 {uniq_name = "_QFcmpEx"} : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+ %10:2 = fir.unboxchar %arg1 : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
+ %11:2 = hlfir.declare %10#0 typeparams %10#1 dummy_scope %1 {uniq_name = "_QFcmpEy"} : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+ %12 = fir.embox %9#1 typeparams %8#1 : (!fir.ref<!fir.char<1,?>>, index) -> !fir.box<!fir.char<1,?>>
+ %13 = fir.zero_bits !fir.heap<!fir.char<1,?>>
+ %c0_0 = arith.constant 0 : index
+ %14 = fir.embox %13 typeparams %c0_0 : (!fir.heap<!fir.char<1,?>>, index) -> !fir.box<!fir.heap<!fir.char<1,?>>>
+ fir.store %14 to %0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ %15 = fir.address_of(@_QQclX9c1101346143d1d3654395ce601d7e68) : !fir.ref<!fir.char<1,66>>
+ %c5_i32 = arith.constant 5 : i32
+ %16 = fir.convert %0 : (!fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>) -> !fir.ref<!fir.box<none>>
+ %17 = fir.convert %12 : (!fir.box<!fir.char<1,?>>) -> !fir.box<none>
+ %18 = fir.convert %15 : (!fir.ref<!fir.char<1,66>>) -> !fir.ref<i8>
+ fir.call @_FortranATrim(%16, %17, %18, %c5_i32) fastmath<contract> : (!fir.ref<!fir.box<none>>, !fir.box<none>, !fir.ref<i8>, i32) -> ()
+ %19 = fir.load %0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ %20 = fir.box_elesize %19 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> index
+ %21 = fir.box_addr %19 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> !fir.heap<!fir.char<1,?>>
+ %22:2 = hlfir.declare %21 typeparams %20 {uniq_name = ".tmp.intrinsic_result"} : (!fir.heap<!fir.char<1,?>>, index) -> (!fir.boxchar<1>, !fir.heap<!fir.char<1,?>>)
+ %true = arith.constant true
+ %23 = hlfir.as_expr %22#0 move %true : (!fir.boxchar<1>, i1) -> !hlfir.expr<!fir.char<1,?>>
+ hlfir.assign %23 to %7#0 realloc : !hlfir.expr<!fir.char<1,?>>, !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ hlfir.destroy %23 : !hlfir.expr<!fir.char<1,?>>
+ %24 = fir.load %7#0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ %25 = fir.box_addr %24 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> !fir.heap<!fir.char<1,?>>
+ %26 = fir.load %7#0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ %27 = fir.box_elesize %26 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> index
+ %28 = fir.convert %25 : (!fir.heap<!fir.char<1,?>>) -> !fir.ref<i8>
+ %29 = fir.convert %11#1 : (!fir.ref<!fir.char<1,?>>) -> !fir.ref<i8>
+ %30 = fir.convert %27 : (index) -> i64
+ %31 = fir.convert %10#1 : (index) -> i64
+ %32 = fir.call @_FortranACharacterCompareScalar1(%28, %29, %30, %31) fastmath<contract> : (!fir.ref<i8>, !fir.ref<i8>, i64, i64) -> i32
+ %c0_i32 = arith.constant 0 : i32
+ %33 = arith.cmpi eq, %32, %c0_i32 : i32
+ %34 = fir.convert %33 : (i1) -> !fir.logical<4>
+ hlfir.assign %34 to %3#0 : !fir.logical<4>, !fir.ref<!fir.logical<4>>
+ %35 = fir.load %3#0 : !fir.ref<!fir.logical<4>>
+ %36 = fir.load %7#0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ %37 = fir.box_addr %36 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> !fir.heap<!fir.char<1,?>>
+ %38 = fir.convert %37 : (!fir.heap<!fir.char<1,?>>) -> i64
+ %c0_i64 = arith.constant 0 : i64
+ %39 = arith.cmpi ne, %38, %c0_i64 : i64
+ fir.if %39 {
+ %40 = fir.load %7#0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ %41 = fir.box_addr %40 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> !fir.heap<!fir.char<1,?>>
+ fir.freemem %41 : !fir.heap<!fir.char<1,?>>
+ %42 = fir.zero_bits !fir.heap<!fir.char<1,?>>
+ %c0_1 = arith.constant 0 : index
+ %43 = fir.embox %42 typeparams %c0_1 : (!fir.heap<!fir.char<1,?>>, index) -> !fir.box<!fir.heap<!fir.char<1,?>>>
+ fir.store %43 to %7#0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ }
+ return %35 : !fir.logical<4>
+}
+
+// CHECK-LABEL: func.func @test_char_cmp2(
+// CHECK: fir.call @_FortranATrim
>From e868001dfac93e5114f2f3b962b6be3468ff95e4 Mon Sep 17 00:00:00 2001
From: Leandro Lupori <leandro.lupori at linaro.org>
Date: Fri, 5 Sep 2025 15:04:27 -0300
Subject: [PATCH 2/4] Refactor impl. to use the new HLFIR char_trim and cmpchar
ops
---
.../Transforms/ExpressionSimplification.cpp | 245 +++---------------
.../test/HLFIR/expression-simplification.fir | 220 ++++++----------
2 files changed, 113 insertions(+), 352 deletions(-)
diff --git a/flang/lib/Optimizer/HLFIR/Transforms/ExpressionSimplification.cpp b/flang/lib/Optimizer/HLFIR/Transforms/ExpressionSimplification.cpp
index d9e73482020f8..23d8fa4caeeb7 100644
--- a/flang/lib/Optimizer/HLFIR/Transforms/ExpressionSimplification.cpp
+++ b/flang/lib/Optimizer/HLFIR/Transforms/ExpressionSimplification.cpp
@@ -1,7 +1,14 @@
+//===- ExpressionSimplification.cpp - Simplify HLFIR expressions ----------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
#include "flang/Optimizer/Builder/FIRBuilder.h"
#include "flang/Optimizer/HLFIR/HLFIROps.h"
#include "flang/Optimizer/HLFIR/Passes.h"
-#include "flang/Runtime/entry-names.h"
#include "llvm/Support/DebugLog.h"
namespace hlfir {
@@ -11,42 +18,15 @@ namespace hlfir {
#define DEBUG_TYPE "expression-simplification"
-#define INDENT(n) std::string((n) * 2, ' ')
-
-static void removeOperands(mlir::Operation *op, int nestLevel);
-
-static void removeOp(mlir::Operation *op, int parentUses, int nestLevel) {
- int opUses = std::distance(op->getUses().begin(), op->getUses().end());
-
- if (opUses <= parentUses) {
- LDBG() << INDENT(nestLevel) << "remove: " << *op;
- removeOperands(op, nestLevel);
- op->dropAllReferences();
- op->dropAllUses();
- op->erase();
- }
-}
-
static void removeOp(mlir::Operation *op) {
- removeOp(op, /*parentUses=*/0, /*nestLevel=*/0);
- LDBG();
-}
-
-static void removeOperands(mlir::Operation *op, int nestLevel) {
- for (mlir::Value operand : op->getOperands()) {
- if (!operand)
- // Already removed.
- continue;
- if (mlir::Operation *operandOp = operand.getDefiningOp()) {
- int uses = 0;
- for (mlir::Operation *user : operandOp->getUsers())
- if (user == op)
- ++uses;
- removeOp(operandOp, uses, nestLevel + 1);
- }
- }
+ op->dropAllReferences();
+ op->dropAllUses();
+ op->erase();
}
+// Get the first user of `op`.
+// Note that we consider the first user to be the one on the lowest line of
+// the emitted HLFIR. The user iterator considers the opposite.
template <typename UserOp>
static UserOp getFirstUser(mlir::Operation *op) {
auto it = op->user_begin(), end = op->user_end(), prev = it;
@@ -58,6 +38,9 @@ static UserOp getFirstUser(mlir::Operation *op) {
return {};
}
+// Get the last user of `op`.
+// Note that we consider the last user to be the one on the highest line of
+// the emitted HLFIR. The user iterator considers the opposite.
template <typename UserOp>
static UserOp getLastUser(mlir::Operation *op) {
if (!op->getUsers().empty())
@@ -66,182 +49,45 @@ static UserOp getLastUser(mlir::Operation *op) {
return {};
}
-template <typename UserOp>
-static UserOp getPreviousUser(mlir::Operation *op, mlir::Operation *curUser) {
- for (auto user = op->user_begin(), end = op->user_end(); user != end;
- ++user) {
- if (*user == curUser) {
- if (++user == end)
- break;
- if (auto userOp = mlir::dyn_cast<UserOp>(*user))
- return userOp;
- break;
- }
- }
- return {};
-}
-
-// Check if operation has the expected number of uses.
-static bool expectUses(mlir::Operation *op, int expUses) {
- int uses = std::distance(op->use_begin(), op->use_end());
- if (uses != expUses) {
- LDBG() << "expectUses: expected " << expUses << ", got " << uses;
- for (mlir::Operation *user : op->getUsers())
- LDBG() << "\t" << *user;
- }
- return uses == expUses;
-}
-
-template <typename Op>
-static Op expectOp(mlir::Value val) {
- if (Op op = mlir::dyn_cast<Op>(val.getDefiningOp())) {
- LDBG() << op;
- return op;
- }
- return {};
-}
-
-static mlir::Value findBoxDef(mlir::Value val) {
- if (auto op = expectOp<fir::ConvertOp>(val)) {
- assert(op->getOperands().size() != 0);
- if (auto boxOp = expectOp<fir::EmboxOp>(op->getOperand(0)))
- return boxOp.getResult();
- }
- return {};
-}
-
namespace {
-// This class analyzes a trimmed character and removes the call to trim() (and
-// its dependencies) if its result is not used elsewhere.
+// This class analyzes a trimmed character and removes the trim operation if
+// its result is not used elsewhere.
class TrimRemover {
public:
- TrimRemover(fir::FirOpBuilder &builder, mlir::Value charVal,
- mlir::Value charLenVal)
- : builder(builder), charVal(charVal), charLenVal(charLenVal) {}
+ TrimRemover(mlir::Value charVal) : charVal(charVal) {}
TrimRemover(const TrimRemover &) = delete;
bool charWasTrimmed();
void removeTrim();
private:
- // Class inputs.
- fir::FirOpBuilder &builder;
mlir::Value charVal;
- mlir::Value charLenVal;
- // Operations found while analyzing inputs, that are needed when removing
- // the trim call.
- hlfir::DeclareOp charDeclOp; // Trim input character.
- fir::CallOp trimCallOp; // Trim call.
- hlfir::EndAssociateOp endAssocOp; // Trim result association.
- hlfir::DestroyOp destroyExprOp; // Trim result expression.
- fir::AllocaOp allocaOp; // Trim result alloca.
+ hlfir::CharTrimOp trimOp;
+ hlfir::CmpCharOp cmpCharOp;
+ hlfir::DestroyOp destroyOp;
};
bool TrimRemover::charWasTrimmed() {
- LDBG() << "\ncharWasTrimmed: " << charVal;
+ LDBG() << "charWasTrimmed: " << charVal;
- // Get the declare and expression operations associated to `charVal`, that
- // correspond to the result of trim.
- auto charCvtOp = expectOp<fir::ConvertOp>(charVal);
- auto charLenCvtOp = expectOp<fir::ConvertOp>(charLenVal);
- if (!charCvtOp || !charLenCvtOp || !expectUses(charCvtOp, 1) ||
- !expectUses(charLenCvtOp, 1))
- return false;
- auto assocOp = expectOp<hlfir::AssociateOp>(charCvtOp.getOperand());
- if (!assocOp || !expectUses(assocOp, 3)) // end_associate uses assocOp twice
- return false;
- endAssocOp = getLastUser<hlfir::EndAssociateOp>(assocOp);
- if (!endAssocOp)
+ trimOp = mlir::dyn_cast<hlfir::CharTrimOp>(charVal.getDefiningOp());
+ if (!trimOp)
return false;
- auto asExprOp = expectOp<hlfir::AsExprOp>(assocOp.getOperand(0));
- if (!asExprOp || !expectUses(asExprOp, 2))
- return false;
- destroyExprOp = getLastUser<hlfir::DestroyOp>(asExprOp);
- if (!destroyExprOp)
- return false;
- auto declOp = expectOp<hlfir::DeclareOp>(asExprOp.getOperand(0));
- if (!declOp || !expectUses(declOp, 1))
- return false;
-
- // Get associated box and alloca.
- auto boxAddrOp = expectOp<fir::BoxAddrOp>(declOp.getMemref());
- if (!boxAddrOp || !expectUses(boxAddrOp, 1))
- return false;
- auto loadOp = expectOp<fir::LoadOp>(boxAddrOp.getOperand());
- if (!loadOp || !getFirstUser<fir::BoxEleSizeOp>(loadOp) ||
- !expectUses(loadOp, 2))
- return false;
- // The allocaOp is initialized by a store.
- // Besides its use by the store and loadOp, it's also converted and used by
- // the trim call.
- allocaOp = expectOp<fir::AllocaOp>(loadOp.getMemref());
- if (!allocaOp || !getFirstUser<fir::StoreOp>(allocaOp) ||
- !expectUses(allocaOp, 3))
- return false;
-
- // Find the trim call that uses the allocaOp.
- if (auto userOp = getPreviousUser<fir::ConvertOp>(allocaOp, loadOp))
- if (userOp->hasOneUse())
- trimCallOp = mlir::dyn_cast<fir::CallOp>(*userOp->user_begin());
- if (!trimCallOp)
- return false;
- LDBG() << "call: " << trimCallOp;
- llvm::StringRef calleeName =
- trimCallOp.getCalleeAttr().getLeafReference().getValue();
- LDBG() << "callee: " << calleeName;
- if (calleeName != RTNAME_STRING(Trim))
- return false;
-
- // Get trim input character.
- auto chrEmboxOp =
- expectOp<fir::EmboxOp>(findBoxDef(trimCallOp.getOperand(1)));
- if (!chrEmboxOp)
- return false;
- charDeclOp = expectOp<hlfir::DeclareOp>(chrEmboxOp.getMemref());
- if (!charDeclOp)
- return false;
-
- // Found everything as expected.
- return true;
+ int trimUses = std::distance(trimOp->use_begin(), trimOp->use_end());
+ cmpCharOp = getFirstUser<hlfir::CmpCharOp>(trimOp);
+ destroyOp = getLastUser<hlfir::DestroyOp>(trimOp);
+ return cmpCharOp && destroyOp && trimUses == 2;
}
void TrimRemover::removeTrim() {
- LDBG() << "\nremoveTrim:";
-
- auto charCvtOp = expectOp<fir::ConvertOp>(charVal);
- auto charLenCvtOp = expectOp<fir::ConvertOp>(charLenVal);
- assert(charCvtOp && charLenCvtOp);
-
- // Replace trim output char with its input.
- mlir::Location loc = charVal.getLoc();
- auto cvtOp = fir::ConvertOp::create(builder, loc, charCvtOp.getType(),
- charDeclOp.getOriginalBase());
- charCvtOp.replaceAllUsesWith(cvtOp.getResult());
+ LDBG() << "removeTrim: " << trimOp;
- // Replace trim output length with its input.
- mlir::Value chrLen = charDeclOp.getTypeparams().back();
- auto cvtLenOp =
- fir::ConvertOp::create(builder, loc, charLenCvtOp.getType(), chrLen);
- charLenCvtOp.replaceAllUsesWith(cvtLenOp.getResult());
-
- // Remove trim call and old conversions.
- removeOp(charCvtOp);
- removeOp(charLenCvtOp);
- removeOp(trimCallOp);
- // Remove association and expression.
- removeOp(endAssocOp);
- removeOp(destroyExprOp);
- // The only remaining use of allocaOp should be its initialization.
- // Remove the store and alloca operations.
- if (auto userOp = getLastUser<fir::StoreOp>(allocaOp))
- removeOp(userOp);
+ cmpCharOp->replaceUsesOfWith(trimOp.getResult(), trimOp.getChr());
+ removeOp(destroyOp);
+ removeOp(trimOp);
}
-} // namespace
-
-namespace {
-
class ExpressionSimplification
: public hlfir::impl::ExpressionSimplificationBase<
ExpressionSimplification> {
@@ -260,15 +106,12 @@ class ExpressionSimplification
// `trim(x) == trim(y)`
// can be simplified to
// `x == y`
- void simplifyCharCompare(fir::CallOp call, const fir::KindMapping &kindMap);
+ void simplifyCmpChar(hlfir::CmpCharOp cmpChar);
};
-void ExpressionSimplification::simplifyCharCompare(
- fir::CallOp call, const fir::KindMapping &kindMap) {
- fir::FirOpBuilder builder{call, kindMap};
- mlir::Operation::operand_range args = call.getArgs();
- TrimRemover lhsTrimRem(builder, args[0], args[2]);
- TrimRemover rhsTrimRem(builder, args[1], args[3]);
+void ExpressionSimplification::simplifyCmpChar(hlfir::CmpCharOp cmpChar) {
+ TrimRemover lhsTrimRem(cmpChar.getLchr());
+ TrimRemover rhsTrimRem(cmpChar.getRchr());
if (lhsTrimRem.charWasTrimmed())
lhsTrimRem.removeTrim();
@@ -278,17 +121,7 @@ void ExpressionSimplification::simplifyCharCompare(
void ExpressionSimplification::runOnOperation() {
mlir::ModuleOp module = getOperation();
- fir::KindMapping kindMap = fir::getKindMapping(module);
- module.walk([&](mlir::Operation *op) {
- if (auto call = mlir::dyn_cast<fir::CallOp>(op)) {
- if (mlir::SymbolRefAttr callee = call.getCalleeAttr()) {
- mlir::StringRef funcName = callee.getLeafReference().getValue();
- if (funcName.starts_with(RTNAME_STRING(CharacterCompareScalar))) {
- simplifyCharCompare(call, kindMap);
- }
- }
- }
- });
+ module.walk([&](hlfir::CmpCharOp cmpChar) { simplifyCmpChar(cmpChar); });
}
} // namespace
diff --git a/flang/test/HLFIR/expression-simplification.fir b/flang/test/HLFIR/expression-simplification.fir
index 28e17de381461..b9cd9c4dab6f0 100644
--- a/flang/test/HLFIR/expression-simplification.fir
+++ b/flang/test/HLFIR/expression-simplification.fir
@@ -2,101 +2,51 @@
// Test removal of trim() calls.
-// logical function cmp(x, y)
+// logical function test_char_cmp(x, y) result(cmp)
// character(*) :: x, y
// cmp = trim(x) == trim(y)
// end function
-func.func @test_char_cmp(%arg0: !fir.boxchar<1> {fir.bindc_name = "x"},
- %arg1: !fir.boxchar<1> {fir.bindc_name = "y"}) -> !fir.logical<4> {
- %0 = fir.alloca !fir.box<!fir.heap<!fir.char<1,?>>>
- %1 = fir.alloca !fir.box<!fir.heap<!fir.char<1,?>>>
- %2 = fir.dummy_scope : !fir.dscope
- %3 = fir.alloca !fir.logical<4> {bindc_name = "cmp", uniq_name = "_QFcmpEcmp"}
- %4:2 = hlfir.declare %3 {uniq_name = "_QFcmpEcmp"} : (!fir.ref<!fir.logical<4>>) -> (!fir.ref<!fir.logical<4>>, !fir.ref<!fir.logical<4>>)
- %5:2 = fir.unboxchar %arg0 : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
- %6:2 = hlfir.declare %5#0 typeparams %5#1 dummy_scope %2 {uniq_name = "_QFcmpEx"} : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
- %7:2 = fir.unboxchar %arg1 : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
- %8:2 = hlfir.declare %7#0 typeparams %7#1 dummy_scope %2 {uniq_name = "_QFcmpEy"} : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
- %9 = fir.embox %6#1 typeparams %5#1 : (!fir.ref<!fir.char<1,?>>, index) -> !fir.box<!fir.char<1,?>>
- %10 = fir.zero_bits !fir.heap<!fir.char<1,?>>
- %c0 = arith.constant 0 : index
- %11 = fir.embox %10 typeparams %c0 : (!fir.heap<!fir.char<1,?>>, index) -> !fir.box<!fir.heap<!fir.char<1,?>>>
- fir.store %11 to %1 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
- %12 = fir.address_of(@_QQclX710b72634dc58109795123e3cbc3b5ed) : !fir.ref<!fir.char<1,65>>
- %c5_i32 = arith.constant 5 : i32
- %13 = fir.convert %1 : (!fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>) -> !fir.ref<!fir.box<none>>
- %14 = fir.convert %9 : (!fir.box<!fir.char<1,?>>) -> !fir.box<none>
- %15 = fir.convert %12 : (!fir.ref<!fir.char<1,65>>) -> !fir.ref<i8>
- fir.call @_FortranATrim(%13, %14, %15, %c5_i32) fastmath<contract> : (!fir.ref<!fir.box<none>>, !fir.box<none>, !fir.ref<i8>, i32) -> ()
- %16 = fir.load %1 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
- %17 = fir.box_elesize %16 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> index
- %18 = fir.box_addr %16 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> !fir.heap<!fir.char<1,?>>
- %19:2 = hlfir.declare %18 typeparams %17 {uniq_name = ".tmp.intrinsic_result"} : (!fir.heap<!fir.char<1,?>>, index) -> (!fir.boxchar<1>, !fir.heap<!fir.char<1,?>>)
- %true = arith.constant true
- %20 = hlfir.as_expr %19#0 move %true : (!fir.boxchar<1>, i1) -> !hlfir.expr<!fir.char<1,?>>
- %21 = fir.embox %8#1 typeparams %7#1 : (!fir.ref<!fir.char<1,?>>, index) -> !fir.box<!fir.char<1,?>>
- %22 = fir.zero_bits !fir.heap<!fir.char<1,?>>
- %c0_0 = arith.constant 0 : index
- %23 = fir.embox %22 typeparams %c0_0 : (!fir.heap<!fir.char<1,?>>, index) -> !fir.box<!fir.heap<!fir.char<1,?>>>
- fir.store %23 to %0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
- %24 = fir.address_of(@_QQclX710b72634dc58109795123e3cbc3b5ed) : !fir.ref<!fir.char<1,65>>
- %c5_i32_1 = arith.constant 5 : i32
- %25 = fir.convert %0 : (!fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>) -> !fir.ref<!fir.box<none>>
- %26 = fir.convert %21 : (!fir.box<!fir.char<1,?>>) -> !fir.box<none>
- %27 = fir.convert %24 : (!fir.ref<!fir.char<1,65>>) -> !fir.ref<i8>
- fir.call @_FortranATrim(%25, %26, %27, %c5_i32_1) fastmath<contract> : (!fir.ref<!fir.box<none>>, !fir.box<none>, !fir.ref<i8>, i32) -> ()
- %28 = fir.load %0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
- %29 = fir.box_elesize %28 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> index
- %30 = fir.box_addr %28 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> !fir.heap<!fir.char<1,?>>
- %31:2 = hlfir.declare %30 typeparams %29 {uniq_name = ".tmp.intrinsic_result"} : (!fir.heap<!fir.char<1,?>>, index) -> (!fir.boxchar<1>, !fir.heap<!fir.char<1,?>>)
- %true_2 = arith.constant true
- %32 = hlfir.as_expr %31#0 move %true_2 : (!fir.boxchar<1>, i1) -> !hlfir.expr<!fir.char<1,?>>
- %33:3 = hlfir.associate %20 typeparams %17 {adapt.valuebyref} : (!hlfir.expr<!fir.char<1,?>>, index) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>, i1)
- %34:3 = hlfir.associate %32 typeparams %29 {adapt.valuebyref} : (!hlfir.expr<!fir.char<1,?>>, index) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>, i1)
- %35 = fir.convert %33#1 : (!fir.ref<!fir.char<1,?>>) -> !fir.ref<i8>
- %36 = fir.convert %34#1 : (!fir.ref<!fir.char<1,?>>) -> !fir.ref<i8>
- %37 = fir.convert %17 : (index) -> i64
- %38 = fir.convert %29 : (index) -> i64
- %39 = fir.call @_FortranACharacterCompareScalar1(%35, %36, %37, %38) fastmath<contract> : (!fir.ref<i8>, !fir.ref<i8>, i64, i64) -> i32
- %c0_i32 = arith.constant 0 : i32
- %40 = arith.cmpi eq, %39, %c0_i32 : i32
- hlfir.end_associate %33#1, %33#2 : !fir.ref<!fir.char<1,?>>, i1
- hlfir.end_associate %34#1, %34#2 : !fir.ref<!fir.char<1,?>>, i1
- %41 = fir.convert %40 : (i1) -> !fir.logical<4>
- hlfir.assign %41 to %4#0 : !fir.logical<4>, !fir.ref<!fir.logical<4>>
- hlfir.destroy %32 : !hlfir.expr<!fir.char<1,?>>
- hlfir.destroy %20 : !hlfir.expr<!fir.char<1,?>>
- %42 = fir.load %4#0 : !fir.ref<!fir.logical<4>>
- return %42 : !fir.logical<4>
+func.func @_QPtest_char_cmp(%arg0: !fir.boxchar<1> {fir.bindc_name = "x"},
+ %arg1: !fir.boxchar<1> {fir.bindc_name = "y"}) -> !fir.logical<4> {
+ %0 = fir.dummy_scope : !fir.dscope
+ %1 = fir.alloca !fir.logical<4> {bindc_name = "cmp", uniq_name = "_QFtest_char_cmpEcmp"}
+ %2:2 = hlfir.declare %1 {uniq_name = "_QFtest_char_cmpEcmp"} : (!fir.ref<!fir.logical<4>>) -> (!fir.ref<!fir.logical<4>>, !fir.ref<!fir.logical<4>>)
+ %3:2 = fir.unboxchar %arg0 : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
+ %4:2 = hlfir.declare %3#0 typeparams %3#1 dummy_scope %0 {uniq_name = "_QFtest_char_cmpEx"} : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+ %5:2 = fir.unboxchar %arg1 : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
+ %6:2 = hlfir.declare %5#0 typeparams %5#1 dummy_scope %0 {uniq_name = "_QFtest_char_cmpEy"} : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+ %7 = hlfir.char_trim %4#0 : (!fir.boxchar<1>) -> !hlfir.expr<!fir.char<1,?>>
+ %8 = hlfir.char_trim %6#0 : (!fir.boxchar<1>) -> !hlfir.expr<!fir.char<1,?>>
+ %9 = hlfir.cmpchar eq %7 %8 : (!hlfir.expr<!fir.char<1,?>>, !hlfir.expr<!fir.char<1,?>>) -> i1
+ %10 = fir.convert %9 : (i1) -> !fir.logical<4>
+ hlfir.assign %10 to %2#0 : !fir.logical<4>, !fir.ref<!fir.logical<4>>
+ hlfir.destroy %8 : !hlfir.expr<!fir.char<1,?>>
+ hlfir.destroy %7 : !hlfir.expr<!fir.char<1,?>>
+ %11 = fir.load %2#0 : !fir.ref<!fir.logical<4>>
+ return %11 : !fir.logical<4>
}
-// CHECK-LABEL: func.func @test_char_cmp(
-// CHECK-SAME: %[[VAL_4:.*]]: !fir.boxchar<1> {fir.bindc_name = "x"},
-// CHECK-SAME: %[[VAL_7:.*]]: !fir.boxchar<1> {fir.bindc_name = "y"}) -> !fir.logical<4> {
+// CHECK-LABEL: func.func @_QPtest_char_cmp(
+// CHECK-SAME: %[[ARG_0:.*]]: !fir.boxchar<1> {fir.bindc_name = "x"},
+// CHECK-SAME: %[[ARG_1:.*]]: !fir.boxchar<1> {fir.bindc_name = "y"}) -> !fir.logical<4> {
// CHECK: %[[VAL_0:.*]] = fir.dummy_scope : !fir.dscope
-// CHECK: %[[VAL_1:.*]] = fir.alloca !fir.logical<4> {bindc_name = "cmp", uniq_name = "_QFcmpEcmp"}
-// CHECK: %[[VAL_2:.*]]:2 = hlfir.declare %[[VAL_1]] {uniq_name = "_QFcmpEcmp"} : (!fir.ref<!fir.logical<4>>) -> (!fir.ref<!fir.logical<4>>, !fir.ref<!fir.logical<4>>)
-// CHECK: %[[VAL_3:.*]]:2 = fir.unboxchar %[[VAL_4]] : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
-// CHECK: %[[VAL_5:.*]]:2 = hlfir.declare %[[VAL_3]]#0 typeparams %[[VAL_3]]#1 dummy_scope %[[VAL_0]] {uniq_name = "_QFcmpEx"} : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
-// CHECK: %[[VAL_6:.*]]:2 = fir.unboxchar %[[VAL_7]] : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
-// CHECK: %[[VAL_8:.*]]:2 = hlfir.declare %[[VAL_6]]#0 typeparams %[[VAL_6]]#1 dummy_scope %[[VAL_0]] {uniq_name = "_QFcmpEy"} : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
-// CHECK: %[[VAL_9:.*]] = fir.convert %[[VAL_5]]#1 : (!fir.ref<!fir.char<1,?>>) -> !fir.ref<i8>
-// CHECK: %[[VAL_10:.*]] = fir.convert %[[VAL_3]]#1 : (index) -> i64
-// CHECK: %[[VAL_11:.*]] = fir.convert %[[VAL_8]]#1 : (!fir.ref<!fir.char<1,?>>) -> !fir.ref<i8>
-// CHECK: %[[VAL_12:.*]] = fir.convert %[[VAL_6]]#1 : (index) -> i64
-// CHECK: %[[VAL_13:.*]] = fir.call @_FortranACharacterCompareScalar1(%[[VAL_9]], %[[VAL_11]], %[[VAL_10]], %[[VAL_12]]) fastmath<contract> : (!fir.ref<i8>, !fir.ref<i8>, i64, i64) -> i32
-// CHECK: %[[VAL_14:.*]] = arith.constant 0 : i32
-// CHECK: %[[VAL_15:.*]] = arith.cmpi eq, %[[VAL_13]], %[[VAL_14]] : i32
-// CHECK: %[[VAL_16:.*]] = fir.convert %[[VAL_15]] : (i1) -> !fir.logical<4>
-// CHECK: hlfir.assign %[[VAL_16]] to %[[VAL_2]]#0 : !fir.logical<4>, !fir.ref<!fir.logical<4>>
-// CHECK: %[[VAL_17:.*]] = fir.load %[[VAL_2]]#0 : !fir.ref<!fir.logical<4>>
-// CHECK: return %[[VAL_17]] : !fir.logical<4>
+// CHECK: %[[VAL_1:.*]] = fir.alloca !fir.logical<4> {bindc_name = "cmp", uniq_name = "_QFtest_char_cmpEcmp"}
+// CHECK: %[[VAL_2:.*]]:2 = hlfir.declare %[[VAL_1]] {uniq_name = "_QFtest_char_cmpEcmp"} : (!fir.ref<!fir.logical<4>>) -> (!fir.ref<!fir.logical<4>>, !fir.ref<!fir.logical<4>>)
+// CHECK: %[[VAL_3:.*]]:2 = fir.unboxchar %[[ARG_0]] : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
+// CHECK: %[[VAL_5:.*]]:2 = hlfir.declare %[[VAL_3]]#0 typeparams %[[VAL_3]]#1 dummy_scope %[[VAL_0]] {uniq_name = "_QFtest_char_cmpEx"} : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+// CHECK: %[[VAL_6:.*]]:2 = fir.unboxchar %[[ARG_1]] : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
+// CHECK: %[[VAL_8:.*]]:2 = hlfir.declare %[[VAL_6]]#0 typeparams %[[VAL_6]]#1 dummy_scope %[[VAL_0]] {uniq_name = "_QFtest_char_cmpEy"} : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+// CHECK: %[[VAL_9:.*]] = hlfir.cmpchar eq %[[VAL_5]]#0 %[[VAL_8]]#0 : (!fir.boxchar<1>, !fir.boxchar<1>) -> i1
+// CHECK: %[[VAL_10:.*]] = fir.convert %[[VAL_9]] : (i1) -> !fir.logical<4>
+// CHECK: hlfir.assign %[[VAL_10]] to %[[VAL_2]]#0 : !fir.logical<4>, !fir.ref<!fir.logical<4>>
+// CHECK: %[[VAL_11:.*]] = fir.load %[[VAL_2]]#0 : !fir.ref<!fir.logical<4>>
+// CHECK: return %[[VAL_11]] : !fir.logical<4>
// CHECK: }
// Check that trim() is not removed when its result is stored.
-// logical function eq_use3(x, y) result(res)
+// logical function test_char_cmp2(x, y) result(res)
// character(*) :: x, y
// character(:), allocatable :: tx
//
@@ -104,70 +54,48 @@ func.func @test_char_cmp(%arg0: !fir.boxchar<1> {fir.bindc_name = "x"},
// res = tx == y
// end function
-func.func @test_char_cmp2(%arg0: !fir.boxchar<1> {fir.bindc_name = "x"}, %arg1: !fir.boxchar<1> {fir.bindc_name = "y"}) -> !fir.logical<4> {
- %0 = fir.alloca !fir.box<!fir.heap<!fir.char<1,?>>>
- %1 = fir.dummy_scope : !fir.dscope
- %2 = fir.alloca !fir.logical<4> {bindc_name = "res", uniq_name = "_QFcmpEres"}
- %3:2 = hlfir.declare %2 {uniq_name = "_QFcmpEres"} : (!fir.ref<!fir.logical<4>>) -> (!fir.ref<!fir.logical<4>>, !fir.ref<!fir.logical<4>>)
- %4 = fir.alloca !fir.box<!fir.heap<!fir.char<1,?>>> {bindc_name = "tx", uniq_name = "_QFcmpEtx"}
- %5 = fir.zero_bits !fir.heap<!fir.char<1,?>>
+func.func @_QPtest_char_cmp2(%arg0: !fir.boxchar<1> {fir.bindc_name = "x"}, %arg1: !fir.boxchar<1> {fir.bindc_name = "y"}) -> !fir.logical<4> {
+ %0 = fir.dummy_scope : !fir.dscope
+ %1 = fir.alloca !fir.logical<4> {bindc_name = "res", uniq_name = "_QFtest_char_cmp2Eres"}
+ %2:2 = hlfir.declare %1 {uniq_name = "_QFtest_char_cmp2Eres"} : (!fir.ref<!fir.logical<4>>) -> (!fir.ref<!fir.logical<4>>, !fir.ref<!fir.logical<4>>)
+ %3 = fir.alloca !fir.box<!fir.heap<!fir.char<1,?>>> {bindc_name = "tx", uniq_name = "_QFtest_char_cmp2Etx"}
+ %4 = fir.zero_bits !fir.heap<!fir.char<1,?>>
%c0 = arith.constant 0 : index
- %6 = fir.embox %5 typeparams %c0 : (!fir.heap<!fir.char<1,?>>, index) -> !fir.box<!fir.heap<!fir.char<1,?>>>
- fir.store %6 to %4 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
- %7:2 = hlfir.declare %4 {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFcmpEtx"} : (!fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>) -> (!fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>, !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>)
- %8:2 = fir.unboxchar %arg0 : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
- %9:2 = hlfir.declare %8#0 typeparams %8#1 dummy_scope %1 {uniq_name = "_QFcmpEx"} : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
- %10:2 = fir.unboxchar %arg1 : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
- %11:2 = hlfir.declare %10#0 typeparams %10#1 dummy_scope %1 {uniq_name = "_QFcmpEy"} : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
- %12 = fir.embox %9#1 typeparams %8#1 : (!fir.ref<!fir.char<1,?>>, index) -> !fir.box<!fir.char<1,?>>
- %13 = fir.zero_bits !fir.heap<!fir.char<1,?>>
- %c0_0 = arith.constant 0 : index
- %14 = fir.embox %13 typeparams %c0_0 : (!fir.heap<!fir.char<1,?>>, index) -> !fir.box<!fir.heap<!fir.char<1,?>>>
- fir.store %14 to %0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
- %15 = fir.address_of(@_QQclX9c1101346143d1d3654395ce601d7e68) : !fir.ref<!fir.char<1,66>>
- %c5_i32 = arith.constant 5 : i32
- %16 = fir.convert %0 : (!fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>) -> !fir.ref<!fir.box<none>>
- %17 = fir.convert %12 : (!fir.box<!fir.char<1,?>>) -> !fir.box<none>
- %18 = fir.convert %15 : (!fir.ref<!fir.char<1,66>>) -> !fir.ref<i8>
- fir.call @_FortranATrim(%16, %17, %18, %c5_i32) fastmath<contract> : (!fir.ref<!fir.box<none>>, !fir.box<none>, !fir.ref<i8>, i32) -> ()
- %19 = fir.load %0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
- %20 = fir.box_elesize %19 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> index
- %21 = fir.box_addr %19 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> !fir.heap<!fir.char<1,?>>
- %22:2 = hlfir.declare %21 typeparams %20 {uniq_name = ".tmp.intrinsic_result"} : (!fir.heap<!fir.char<1,?>>, index) -> (!fir.boxchar<1>, !fir.heap<!fir.char<1,?>>)
- %true = arith.constant true
- %23 = hlfir.as_expr %22#0 move %true : (!fir.boxchar<1>, i1) -> !hlfir.expr<!fir.char<1,?>>
- hlfir.assign %23 to %7#0 realloc : !hlfir.expr<!fir.char<1,?>>, !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
- hlfir.destroy %23 : !hlfir.expr<!fir.char<1,?>>
- %24 = fir.load %7#0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
- %25 = fir.box_addr %24 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> !fir.heap<!fir.char<1,?>>
- %26 = fir.load %7#0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
- %27 = fir.box_elesize %26 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> index
- %28 = fir.convert %25 : (!fir.heap<!fir.char<1,?>>) -> !fir.ref<i8>
- %29 = fir.convert %11#1 : (!fir.ref<!fir.char<1,?>>) -> !fir.ref<i8>
- %30 = fir.convert %27 : (index) -> i64
- %31 = fir.convert %10#1 : (index) -> i64
- %32 = fir.call @_FortranACharacterCompareScalar1(%28, %29, %30, %31) fastmath<contract> : (!fir.ref<i8>, !fir.ref<i8>, i64, i64) -> i32
- %c0_i32 = arith.constant 0 : i32
- %33 = arith.cmpi eq, %32, %c0_i32 : i32
- %34 = fir.convert %33 : (i1) -> !fir.logical<4>
- hlfir.assign %34 to %3#0 : !fir.logical<4>, !fir.ref<!fir.logical<4>>
- %35 = fir.load %3#0 : !fir.ref<!fir.logical<4>>
- %36 = fir.load %7#0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
- %37 = fir.box_addr %36 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> !fir.heap<!fir.char<1,?>>
- %38 = fir.convert %37 : (!fir.heap<!fir.char<1,?>>) -> i64
+ %5 = fir.embox %4 typeparams %c0 : (!fir.heap<!fir.char<1,?>>, index) -> !fir.box<!fir.heap<!fir.char<1,?>>>
+ fir.store %5 to %3 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ %6:2 = hlfir.declare %3 {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFtest_char_cmp2Etx"} : (!fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>) -> (!fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>, !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>)
+ %7:2 = fir.unboxchar %arg0 : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
+ %8:2 = hlfir.declare %7#0 typeparams %7#1 dummy_scope %0 {uniq_name = "_QFtest_char_cmp2Ex"} : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+ %9:2 = fir.unboxchar %arg1 : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
+ %10:2 = hlfir.declare %9#0 typeparams %9#1 dummy_scope %0 {uniq_name = "_QFtest_char_cmp2Ey"} : (!fir.ref<!fir.char<1,?>>, index, !fir.dscope) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+ %11 = hlfir.char_trim %8#0 : (!fir.boxchar<1>) -> !hlfir.expr<!fir.char<1,?>>
+ hlfir.assign %11 to %6#0 realloc : !hlfir.expr<!fir.char<1,?>>, !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ hlfir.destroy %11 : !hlfir.expr<!fir.char<1,?>>
+ %12 = fir.load %6#0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ %13 = fir.box_addr %12 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> !fir.heap<!fir.char<1,?>>
+ %14 = fir.load %6#0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ %15 = fir.box_elesize %14 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> index
+ %16 = fir.emboxchar %13, %15 : (!fir.heap<!fir.char<1,?>>, index) -> !fir.boxchar<1>
+ %17 = hlfir.cmpchar eq %16 %10#0 : (!fir.boxchar<1>, !fir.boxchar<1>) -> i1
+ %18 = fir.convert %17 : (i1) -> !fir.logical<4>
+ hlfir.assign %18 to %2#0 : !fir.logical<4>, !fir.ref<!fir.logical<4>>
+ %19 = fir.load %2#0 : !fir.ref<!fir.logical<4>>
+ %20 = fir.load %6#0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ %21 = fir.box_addr %20 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> !fir.heap<!fir.char<1,?>>
+ %22 = fir.convert %21 : (!fir.heap<!fir.char<1,?>>) -> i64
%c0_i64 = arith.constant 0 : i64
- %39 = arith.cmpi ne, %38, %c0_i64 : i64
- fir.if %39 {
- %40 = fir.load %7#0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
- %41 = fir.box_addr %40 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> !fir.heap<!fir.char<1,?>>
- fir.freemem %41 : !fir.heap<!fir.char<1,?>>
- %42 = fir.zero_bits !fir.heap<!fir.char<1,?>>
- %c0_1 = arith.constant 0 : index
- %43 = fir.embox %42 typeparams %c0_1 : (!fir.heap<!fir.char<1,?>>, index) -> !fir.box<!fir.heap<!fir.char<1,?>>>
- fir.store %43 to %7#0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ %23 = arith.cmpi ne, %22, %c0_i64 : i64
+ fir.if %23 {
+ %24 = fir.load %6#0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ %25 = fir.box_addr %24 : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> !fir.heap<!fir.char<1,?>>
+ fir.freemem %25 : !fir.heap<!fir.char<1,?>>
+ %26 = fir.zero_bits !fir.heap<!fir.char<1,?>>
+ %c0_0 = arith.constant 0 : index
+ %27 = fir.embox %26 typeparams %c0_0 : (!fir.heap<!fir.char<1,?>>, index) -> !fir.box<!fir.heap<!fir.char<1,?>>>
+ fir.store %27 to %6#0 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
}
- return %35 : !fir.logical<4>
+ return %19 : !fir.logical<4>
}
-// CHECK-LABEL: func.func @test_char_cmp2(
-// CHECK: fir.call @_FortranATrim
+// CHECK-LABEL: func.func @_QPtest_char_cmp2(
+// CHECK: hlfir.cmpchar eq
>From e5b4d38c6245a6c39652842b7bb412c1d35be2b2 Mon Sep 17 00:00:00 2001
From: Leandro Lupori <leandro.lupori at linaro.org>
Date: Thu, 18 Sep 2025 14:36:51 -0300
Subject: [PATCH 3/4] Address review's comments
---
flang/include/flang/Optimizer/HLFIR/Passes.td | 3 +-
.../Transforms/ExpressionSimplification.cpp | 122 +++++++-----------
flang/lib/Optimizer/Passes/Pipelines.cpp | 3 +-
flang/test/Driver/mlir-pass-pipeline.f90 | 10 +-
.../test/HLFIR/expression-simplification.fir | 2 +-
5 files changed, 60 insertions(+), 80 deletions(-)
diff --git a/flang/include/flang/Optimizer/HLFIR/Passes.td b/flang/include/flang/Optimizer/HLFIR/Passes.td
index 43646c6c05ead..bfff458f7a6c5 100644
--- a/flang/include/flang/Optimizer/HLFIR/Passes.td
+++ b/flang/include/flang/Optimizer/HLFIR/Passes.td
@@ -61,8 +61,7 @@ def SimplifyHLFIRIntrinsics : Pass<"simplify-hlfir-intrinsics"> {
"the hlfir.matmul.">];
}
-def ExpressionSimplification
- : Pass<"expression-simplification", "::mlir::ModuleOp"> {
+def ExpressionSimplification : Pass<"hlfir-expression-simplification"> {
let summary = "Simplify Fortran expressions";
}
diff --git a/flang/lib/Optimizer/HLFIR/Transforms/ExpressionSimplification.cpp b/flang/lib/Optimizer/HLFIR/Transforms/ExpressionSimplification.cpp
index 23d8fa4caeeb7..0559b49d8ecba 100644
--- a/flang/lib/Optimizer/HLFIR/Transforms/ExpressionSimplification.cpp
+++ b/flang/lib/Optimizer/HLFIR/Transforms/ExpressionSimplification.cpp
@@ -9,21 +9,13 @@
#include "flang/Optimizer/Builder/FIRBuilder.h"
#include "flang/Optimizer/HLFIR/HLFIROps.h"
#include "flang/Optimizer/HLFIR/Passes.h"
-#include "llvm/Support/DebugLog.h"
+#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
namespace hlfir {
#define GEN_PASS_DEF_EXPRESSIONSIMPLIFICATION
#include "flang/Optimizer/HLFIR/Passes.h.inc"
} // namespace hlfir
-#define DEBUG_TYPE "expression-simplification"
-
-static void removeOp(mlir::Operation *op) {
- op->dropAllReferences();
- op->dropAllUses();
- op->erase();
-}
-
// Get the first user of `op`.
// Note that we consider the first user to be the one on the lowest line of
// the emitted HLFIR. The user iterator considers the opposite.
@@ -51,77 +43,57 @@ static UserOp getLastUser(mlir::Operation *op) {
namespace {
-// This class analyzes a trimmed character and removes the trim operation if
-// its result is not used elsewhere.
-class TrimRemover {
+// Trim operations can be erased in certain expressions, such as character
+// comparisons.
+// Since a character comparison appends spaces to the shorter character,
+// calls to trim() that are used only in the comparison can be eliminated.
+//
+// Example:
+// `trim(x) == trim(y)`
+// can be simplified to
+// `x == y`
+class EraseTrim : public mlir::OpRewritePattern<hlfir::CharTrimOp> {
public:
- TrimRemover(mlir::Value charVal) : charVal(charVal) {}
- TrimRemover(const TrimRemover &) = delete;
-
- bool charWasTrimmed();
- void removeTrim();
-
-private:
- mlir::Value charVal;
- hlfir::CharTrimOp trimOp;
- hlfir::CmpCharOp cmpCharOp;
- hlfir::DestroyOp destroyOp;
+ using mlir::OpRewritePattern<hlfir::CharTrimOp>::OpRewritePattern;
+
+ llvm::LogicalResult
+ matchAndRewrite(hlfir::CharTrimOp trimOp,
+ mlir::PatternRewriter &rewriter) const override {
+ int trimUses = std::distance(trimOp->use_begin(), trimOp->use_end());
+ auto cmpCharOp = getFirstUser<hlfir::CmpCharOp>(trimOp);
+ auto destroyOp = getLastUser<hlfir::DestroyOp>(trimOp);
+ if (!cmpCharOp || !destroyOp || trimUses != 2)
+ return rewriter.notifyMatchFailure(
+ trimOp, "hlfir.char_trim is not used (only) by hlfir.cmpchar");
+
+ rewriter.eraseOp(destroyOp);
+ rewriter.replaceOp(trimOp, trimOp.getChr());
+ return mlir::success();
+ }
};
-bool TrimRemover::charWasTrimmed() {
- LDBG() << "charWasTrimmed: " << charVal;
-
- trimOp = mlir::dyn_cast<hlfir::CharTrimOp>(charVal.getDefiningOp());
- if (!trimOp)
- return false;
- int trimUses = std::distance(trimOp->use_begin(), trimOp->use_end());
- cmpCharOp = getFirstUser<hlfir::CmpCharOp>(trimOp);
- destroyOp = getLastUser<hlfir::DestroyOp>(trimOp);
- return cmpCharOp && destroyOp && trimUses == 2;
-}
-
-void TrimRemover::removeTrim() {
- LDBG() << "removeTrim: " << trimOp;
-
- cmpCharOp->replaceUsesOfWith(trimOp.getResult(), trimOp.getChr());
- removeOp(destroyOp);
- removeOp(trimOp);
-}
-
-class ExpressionSimplification
+class ExpressionSimplificationPass
: public hlfir::impl::ExpressionSimplificationBase<
- ExpressionSimplification> {
+ ExpressionSimplificationPass> {
public:
- using ExpressionSimplificationBase<
- ExpressionSimplification>::ExpressionSimplificationBase;
-
- void runOnOperation() override;
-
-private:
- // Simplify character comparisons.
- // Because character comparison appends spaces to the shorter character,
- // calls to trim() that are used only in the comparison can be eliminated.
- //
- // Example:
- // `trim(x) == trim(y)`
- // can be simplified to
- // `x == y`
- void simplifyCmpChar(hlfir::CmpCharOp cmpChar);
+ void runOnOperation() override {
+ mlir::MLIRContext *context = &getContext();
+
+ mlir::GreedyRewriteConfig config;
+ // Prevent the pattern driver from merging blocks.
+ config.setRegionSimplificationLevel(
+ mlir::GreedySimplifyRegionLevel::Disabled);
+
+ mlir::RewritePatternSet patterns(context);
+ patterns.insert<EraseTrim>(context);
+
+ if (mlir::failed(mlir::applyPatternsGreedily(
+ getOperation(), std::move(patterns), config))) {
+ mlir::emitError(getOperation()->getLoc(),
+ "failure in HLFIR expression simplification");
+ signalPassFailure();
+ }
+ }
};
-void ExpressionSimplification::simplifyCmpChar(hlfir::CmpCharOp cmpChar) {
- TrimRemover lhsTrimRem(cmpChar.getLchr());
- TrimRemover rhsTrimRem(cmpChar.getRchr());
-
- if (lhsTrimRem.charWasTrimmed())
- lhsTrimRem.removeTrim();
- if (rhsTrimRem.charWasTrimmed())
- rhsTrimRem.removeTrim();
-}
-
-void ExpressionSimplification::runOnOperation() {
- mlir::ModuleOp module = getOperation();
- module.walk([&](hlfir::CmpCharOp cmpChar) { simplifyCmpChar(cmpChar); });
-}
-
} // namespace
diff --git a/flang/lib/Optimizer/Passes/Pipelines.cpp b/flang/lib/Optimizer/Passes/Pipelines.cpp
index 21f7b72b92e9e..fe58f4512871c 100644
--- a/flang/lib/Optimizer/Passes/Pipelines.cpp
+++ b/flang/lib/Optimizer/Passes/Pipelines.cpp
@@ -243,7 +243,8 @@ void createHLFIRToFIRPassPipeline(mlir::PassManager &pm,
EnableOpenMP enableOpenMP,
llvm::OptimizationLevel optLevel) {
if (optLevel.getSizeLevel() > 0 || optLevel.getSpeedupLevel() > 0) {
- pm.addPass(hlfir::createExpressionSimplification());
+ addNestedPassToAllTopLevelOperations<PassConstructor>(
+ pm, hlfir::createExpressionSimplification);
}
if (optLevel.isOptimizingForSpeed()) {
addCanonicalizerPassWithoutRegionSimplification(pm);
diff --git a/flang/test/Driver/mlir-pass-pipeline.f90 b/flang/test/Driver/mlir-pass-pipeline.f90
index 4c854cfab295a..34f319ee444b3 100644
--- a/flang/test/Driver/mlir-pass-pipeline.f90
+++ b/flang/test/Driver/mlir-pass-pipeline.f90
@@ -15,7 +15,15 @@
! ALL: Pass statistics report
! ALL: Fortran::lower::VerifierPass
-! O2-NEXT: ExpressionSimplification
+! O2-NEXT: Pipeline Collection : ['fir.global', 'func.func', 'omp.declare_reduction', 'omp.private']
+! O2-NEXT: 'fir.global' Pipeline
+! O2-NEXT: ExpressionSimplification
+! O2-NEXT: 'func.func' Pipeline
+! O2-NEXT: ExpressionSimplification
+! O2-NEXT: 'omp.declare_reduction' Pipeline
+! O2-NEXT: ExpressionSimplification
+! O2-NEXT: 'omp.private' Pipeline
+! O2-NEXT: ExpressionSimplification
! O2-NEXT: Canonicalizer
! ALL: Pipeline Collection : ['fir.global', 'func.func', 'omp.declare_reduction', 'omp.private']
! ALL-NEXT:'fir.global' Pipeline
diff --git a/flang/test/HLFIR/expression-simplification.fir b/flang/test/HLFIR/expression-simplification.fir
index b9cd9c4dab6f0..07bef0601a36c 100644
--- a/flang/test/HLFIR/expression-simplification.fir
+++ b/flang/test/HLFIR/expression-simplification.fir
@@ -1,4 +1,4 @@
-// RUN: fir-opt %s --expression-simplification | FileCheck %s
+// RUN: fir-opt %s --hlfir-expression-simplification | FileCheck %s
// Test removal of trim() calls.
>From f91664aa1c28bb34dcea9f096b808eafc9877b6a Mon Sep 17 00:00:00 2001
From: Leandro Lupori <leandro.lupori at linaro.org>
Date: Wed, 24 Sep 2025 11:25:39 -0300
Subject: [PATCH 4/4] Fix expression-simplification.fir
---
flang/test/HLFIR/expression-simplification.fir | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/flang/test/HLFIR/expression-simplification.fir b/flang/test/HLFIR/expression-simplification.fir
index 07bef0601a36c..15d1550f1f172 100644
--- a/flang/test/HLFIR/expression-simplification.fir
+++ b/flang/test/HLFIR/expression-simplification.fir
@@ -98,4 +98,4 @@ func.func @_QPtest_char_cmp2(%arg0: !fir.boxchar<1> {fir.bindc_name = "x"}, %arg
}
// CHECK-LABEL: func.func @_QPtest_char_cmp2(
-// CHECK: hlfir.cmpchar eq
+// CHECK: hlfir.char_trim
More information about the flang-commits
mailing list