[flang-commits] [flang] [flang][Transforms] Add `SelectOpsConversion` pass (PR #212977)

Kareem Ergawy via flang-commits flang-commits at lists.llvm.org
Thu Jul 30 02:50:32 PDT 2026


https://github.com/ergawy created https://github.com/llvm/llvm-project/pull/212977

Introduces `--fir-select-ops-conversion`, which lowers `fir.select`, `fir.select_case`, and `fir.select_rank` to the control-flow dialect (`cf.switch` / `cf.cond_br` / `cf.br`) while preserving the CFG shape. `fir.select_case` becomes an if-then-else ladder of `arith.cmpi` + `cf.cond_br`; Fortran `UNSIGNED` selectors use `ule`. Signed / unsigned FIR integer values are normalized to signless via `fir.convert` first.

`fir.select_type` is not handled here — it is already lowered by `--fir-polymorphic-op` (`PolymorphicOpConversion`).

The pass runs in the default FIR optimizer pipeline right after `PolymorphicOpConversion`. Pipeline-check tests are updated to expect `SelectOpsConversion` in the sequence; `Fir/select.fir` and `Lower/volatile3.f90` are relaxed to accept the newly-canonicalized form of the lowered output.

The main purpose of moving these conversion pattern earlier in the MLIR pipeline and target `cf` instead of directly `llvm` is to be able to later on use control-flow to structured-control-flow lifting: https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Conversion/Passes.td#L402.

>From 679cc014983191ba012f3f28288b60692fdfb712 Mon Sep 17 00:00:00 2001
From: ergawy <kareem.ergawy at gmail.com>
Date: Thu, 30 Jul 2026 02:02:01 -0700
Subject: [PATCH] [flang][Transforms] Add SelectOpsConversion pass
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Introduces `--fir-select-ops-conversion`, which lowers `fir.select`,
`fir.select_case`, and `fir.select_rank` to the control-flow dialect
(`cf.switch` / `cf.cond_br` / `cf.br`) while preserving the CFG shape.
`fir.select_case` becomes an if-then-else ladder of `arith.cmpi` +
`cf.cond_br`; Fortran `UNSIGNED` selectors use `ule`. Signed / unsigned
FIR integer values are normalized to signless via `fir.convert` first.

`fir.select_type` is not handled here — it is already lowered by
`--fir-polymorphic-op` (`PolymorphicOpConversion`).

The pass runs in the default FIR optimizer pipeline right after
`PolymorphicOpConversion`. Pipeline-check tests are updated to expect
`SelectOpsConversion` in the sequence; `Fir/select.fir` and
`Lower/volatile3.f90` are relaxed to accept the newly-canonicalized form
of the lowered output.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply at anthropic.com>
---
 .../flang/Optimizer/Transforms/Passes.td      |  20 +-
 flang/lib/Optimizer/Passes/Pipelines.cpp      |   1 +
 flang/lib/Optimizer/Transforms/CMakeLists.txt |   1 +
 .../Transforms/SelectOpsConversion.cpp        | 284 ++++++++++++++++++
 flang/test/Driver/bbc-mlir-pass-pipeline.f90  |   1 +
 .../test/Driver/mlir-debug-pass-pipeline.f90  |   1 +
 flang/test/Driver/mlir-pass-pipeline.f90      |   1 +
 flang/test/Fir/SelectOpsConversion/select.fir | 113 +++++++
 .../Fir/SelectOpsConversion/select_case.fir   | 192 ++++++++++++
 .../Fir/SelectOpsConversion/select_rank.fir   |  46 +++
 flang/test/Fir/basic-program.fir              |   1 +
 flang/test/Fir/select.fir                     |  18 +-
 flang/test/Lower/volatile3.f90                |  18 +-
 13 files changed, 683 insertions(+), 14 deletions(-)
 create mode 100644 flang/lib/Optimizer/Transforms/SelectOpsConversion.cpp
 create mode 100644 flang/test/Fir/SelectOpsConversion/select.fir
 create mode 100644 flang/test/Fir/SelectOpsConversion/select_case.fir
 create mode 100644 flang/test/Fir/SelectOpsConversion/select_rank.fir

diff --git a/flang/include/flang/Optimizer/Transforms/Passes.td b/flang/include/flang/Optimizer/Transforms/Passes.td
index ba29cd6df2eac..7a868852ce5f3 100644
--- a/flang/include/flang/Optimizer/Transforms/Passes.td
+++ b/flang/include/flang/Optimizer/Transforms/Passes.td
@@ -76,6 +76,22 @@ def AffineDialectDemotion : Pass<"demote-affine", "::mlir::func::FuncOp"> {
   ];
 }
 
+def SelectOpsConversion : Pass<"fir-select-ops-conversion"> {
+  let summary = "Lower fir.select / fir.select_case / fir.select_rank to cf.*";
+
+  let description = [{
+    Lowers FIR multi-way branch terminators to control-flow dialect ops
+    (`cf.switch`, `cf.cond_br`, `cf.br`), keeping the same CFG shape (block
+    count, branch directions, destination operand forwarding).
+
+    `fir.select_type` is intentionally NOT handled here since it's already
+    handled by `PolymorphicOpConversion`.
+  }];
+
+  let dependentDialects = ["mlir::arith::ArithDialect",
+      "mlir::cf::ControlFlowDialect", "fir::FIROpsDialect"];
+}
+
 def FIRToSCFPass : Pass<"fir-to-scf"> {
   let summary = "Convert FIR structured control flow ops to SCF dialect.";
   let description = [{
@@ -411,8 +427,8 @@ def PolymorphicOpConversion : Pass<"fir-polymorphic-op", "mlir::ModuleOp"> {
   let summary =
     "Simplify operations on polymorphic types";
   let description = [{
-    This pass breaks up the lowering of operations on polymorphic types by 
-    introducing an intermediate FIR level that simplifies code geneation. 
+    This pass breaks up the lowering of operations on polymorphic types by
+    introducing an intermediate FIR level that simplifies code generation.
   }];
   let dependentDialects = [
     "fir::FIROpsDialect", "mlir::func::FuncDialect"
diff --git a/flang/lib/Optimizer/Passes/Pipelines.cpp b/flang/lib/Optimizer/Passes/Pipelines.cpp
index f40d99aa5a66c..500730ee44f44 100644
--- a/flang/lib/Optimizer/Passes/Pipelines.cpp
+++ b/flang/lib/Optimizer/Passes/Pipelines.cpp
@@ -242,6 +242,7 @@ void createDefaultFIROptimizerPassPipeline(mlir::PassManager &pm,
 
   // Polymorphic types
   pm.addPass(fir::createPolymorphicOpConversion());
+  pm.addPass(fir::createSelectOpsConversion());
   pm.addPass(fir::createAssumedRankOpConversion());
 
   // Optimize redundant array repacking operations,
diff --git a/flang/lib/Optimizer/Transforms/CMakeLists.txt b/flang/lib/Optimizer/Transforms/CMakeLists.txt
index 997dc22063138..3bc2df4c94248 100644
--- a/flang/lib/Optimizer/Transforms/CMakeLists.txt
+++ b/flang/lib/Optimizer/Transforms/CMakeLists.txt
@@ -46,6 +46,7 @@ add_flang_library(FIRTransforms
   GenRuntimeCallsForTest.cpp
   LoopInvariantCodeMotion.cpp
   LoopVersioning.cpp
+  SelectOpsConversion.cpp
   MIFOpConversion.cpp
   MemRefDataFlowOpt.cpp
   MemoryAllocation.cpp
diff --git a/flang/lib/Optimizer/Transforms/SelectOpsConversion.cpp b/flang/lib/Optimizer/Transforms/SelectOpsConversion.cpp
new file mode 100644
index 0000000000000..184961c81c544
--- /dev/null
+++ b/flang/lib/Optimizer/Transforms/SelectOpsConversion.cpp
@@ -0,0 +1,284 @@
+//===- SelectOpsConversion.cpp - Lower fir.select* to cf.* ----------------===//
+//
+// 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/Todo.h"
+#include "flang/Optimizer/Dialect/FIRDialect.h"
+#include "flang/Optimizer/Dialect/FIROps.h"
+#include "flang/Optimizer/Dialect/FIRType.h"
+#include "flang/Optimizer/Transforms/Passes.h"
+#include "mlir/Dialect/Arith/IR/Arith.h"
+#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
+#include "mlir/IR/Builders.h"
+
+namespace fir {
+#define GEN_PASS_DEF_SELECTOPSCONVERSION
+#include "flang/Optimizer/Transforms/Passes.h.inc"
+} // namespace fir
+
+#define DEBUG_TYPE "fir-select-ops-conversion"
+
+namespace {
+using namespace mlir;
+
+template <typename SwitchLike>
+static ValueRange successorOperands(SwitchLike op, unsigned successorIdx) {
+  return op.getSuccessorOperands(successorIdx).getForwardedOperands();
+}
+
+// Bit-cast a signed or unsigned FIR integer value to its signless-integer
+// equivalent. The arith / cf dialects only accept signless integers, so any
+// `ui*` / `si*` value must be normalized before use.
+static Value toSignlessInteger(OpBuilder &b, Location loc, Value v) {
+  auto intTy = dyn_cast<IntegerType>(v.getType());
+  if (!intTy || intTy.isSignless())
+    return v;
+  auto signlessTy = IntegerType::get(v.getContext(), intTy.getWidth());
+  return fir::ConvertOp::create(b, loc, signlessTy, v);
+}
+
+// Widen `v` to a signless i64, the type used for `cf.switch` selectors in
+// this pass. `v` must be integer- or index-typed. Wider-than-64-bit
+// selectors are truncated to i64, matching the behaviour of the FIR-to-LLVM
+// `integerCast` helper this pass replaces.
+static Value toSignlessI64(OpBuilder &b, Location loc, Value v) {
+  v = toSignlessInteger(b, loc, v);
+  auto i64 = IntegerType::get(v.getContext(), 64);
+  Type srcTy = v.getType();
+  if (srcTy == i64)
+    return v;
+  if (isa<IndexType>(srcTy))
+    return arith::IndexCastOp::create(b, loc, i64, v);
+  unsigned srcW = cast<IntegerType>(srcTy).getWidth();
+  if (srcW < 64)
+    return arith::ExtSIOp::create(b, loc, i64, v);
+  return arith::TruncIOp::create(b, loc, i64, v);
+}
+
+//===----------------------------------------------------------------------===//
+// fir.select / fir.select_rank → cf.switch
+//===----------------------------------------------------------------------===//
+
+template <typename SwitchLike>
+static LogicalResult lowerToSwitch(SwitchLike op) {
+  Location loc = op.getLoc();
+  OpBuilder builder(op);
+
+  // The fir.select* selector must be integer or index typed. (fir.select_rank
+  // in particular reaches this pass with an integer selector: flang emits a
+  // `fir.box_rank` earlier that reads the rank field from the CFI descriptor
+  // as an `i8`, and the `fir.select_rank` then dispatches on that.)
+  Type selectorTy = op.getSelector().getType();
+  if (!isa<IntegerType, IndexType>(selectorTy))
+    return op.emitOpError("selector is not an integer/index type");
+
+  // Widen the selector to i64. `cf.switch`'s case-value APInts are also
+  // constructed at 64 bits below, so the case-value / selector element-type
+  // constraint is satisfied. Widening to i64 additionally accommodates
+  // Fortran computed GOTOs whose label values may not fit in the source
+  // selector's width.
+  Value selector = toSignlessI64(builder, loc, op.getSelector());
+
+  SmallVector<int64_t> caseValues;
+  SmallVector<Block *> caseDests;
+  SmallVector<ValueRange> caseOperands;
+  Block *defaultDest = nullptr;
+  ValueRange defaultOperands;
+
+  unsigned numConds = op.getNumConditions();
+  ArrayRef<Attribute> cases = op.getCases().getValue();
+  for (unsigned i = 0; i != numConds; ++i) {
+    Block *dest = op.getSuccessor(i);
+    ValueRange ops = successorOperands(op, i);
+    Attribute attr = cases[i];
+
+    if (auto intAttr = dyn_cast<IntegerAttr>(attr)) {
+      caseValues.push_back(intAttr.getInt());
+      caseDests.push_back(dest);
+      caseOperands.push_back(ops);
+      continue;
+    }
+
+    assert(isa<UnitAttr>(attr) && "unexpected case attribute kind");
+    assert(!defaultDest && "multiple unit (default) entries in fir.select*");
+    defaultDest = dest;
+    defaultOperands = ops;
+  }
+
+  if (!defaultDest)
+    return op.emitOpError("fir.select* verifier requires a unit default, "
+                          "but none was found");
+
+  if (caseValues.empty()) {
+    cf::BranchOp::create(builder, loc, defaultDest, defaultOperands);
+  } else {
+    SmallVector<APInt> caseAPInts;
+    caseAPInts.reserve(caseValues.size());
+    for (int64_t v : caseValues)
+      caseAPInts.emplace_back(64, v, /*isSigned=*/true);
+    cf::SwitchOp::create(builder, loc, selector, defaultDest, defaultOperands,
+                         caseAPInts, caseDests, caseOperands);
+  }
+
+  op.erase();
+  return success();
+}
+
+//===----------------------------------------------------------------------===//
+// fir.select_case → if-then-else ladder of cf.cond_br
+//===----------------------------------------------------------------------===//
+
+// Emit one rung of the fir.select_case if-then-else ladder:
+//
+//   thisBlock:  ...; cf.cond_br %cmp, dest(destOps), nextBlock
+//   nextBlock:  <insertion point at end>
+//
+// Returns the freshly-created nextBlock.
+static Block *genCaseLadderStep(OpBuilder &builder, Location loc, Value cmp,
+                                Block *dest, ValueRange destOps) {
+  Block *thisBlock = builder.getInsertionBlock();
+  Region *region = thisBlock->getParent();
+  Block *nextBlock =
+      builder.createBlock(region, std::next(thisBlock->getIterator()));
+  builder.setInsertionPointToEnd(thisBlock);
+  cf::CondBranchOp::create(builder, loc, cmp, dest, destOps, nextBlock,
+                           ValueRange());
+  builder.setInsertionPointToEnd(nextBlock);
+  return nextBlock;
+}
+
+static LogicalResult lowerSelectCase(fir::SelectCaseOp op) {
+  Location loc = op.getLoc();
+  Value selector = op.getSelector();
+
+  // CHARACTER selectors are not yet supported.
+  if (isa<fir::CharacterType>(selector.getType())) {
+    TODO(op.getLoc(), "fir.select_case codegen with character type");
+    return failure();
+  }
+
+  if (!isa<IntegerType, IndexType>(selector.getType()))
+    return op.emitOpError("non-integer/character selector not supported");
+
+  OpBuilder builder(op);
+  // Fortran `UNSIGNED` selectors have `ui*` type; convert to signless up
+  // front and pick the unsigned compare predicate for range checks. Signed
+  // and signless selectors use the signed predicate.
+  auto origSelTy = dyn_cast<IntegerType>(selector.getType());
+  bool isUnsigned = origSelTy && origSelTy.isUnsigned();
+  arith::CmpIPredicate lePred =
+      isUnsigned ? arith::CmpIPredicate::ule : arith::CmpIPredicate::sle;
+  selector = toSignlessInteger(builder, loc, selector);
+  unsigned numConds = op.getNumConditions();
+  ArrayRef<Attribute> cases = op.getCases().getValue();
+
+  for (unsigned i = 0; i != numConds; ++i) {
+    Block *dest = op.getSuccessor(i);
+    ValueRange destOps = successorOperands(op, i);
+    Attribute attr = cases[i];
+
+    if (isa<UnitAttr>(attr)) {
+      // Default branch — unconditional jump. Must be the last entry.
+      assert(i + 1 == numConds && "fir.select_case unit attr must be last");
+      cf::BranchOp::create(builder, loc, dest, destOps);
+      op.erase();
+      return success();
+    }
+
+    std::optional<OperandRange> cmpOpsOpt = op.getCompareOperands(i);
+    if (!cmpOpsOpt)
+      return op.emitOpError("missing compare operands for case ") << i;
+    OperandRange cmpOps = *cmpOpsOpt;
+
+    if (isa<fir::PointIntervalAttr>(attr)) {
+      Value cmpOp = toSignlessInteger(builder, loc, cmpOps.front());
+      Value cmp = arith::CmpIOp::create(
+          builder, loc, arith::CmpIPredicate::eq, selector, cmpOp);
+      genCaseLadderStep(builder, loc, cmp, dest, destOps);
+      continue;
+    }
+    if (isa<fir::LowerBoundAttr>(attr)) {
+      // case(c:): match when c <= selector.
+      Value cmpOp = toSignlessInteger(builder, loc, cmpOps.front());
+      Value cmp = arith::CmpIOp::create(builder, loc, lePred, cmpOp, selector);
+      genCaseLadderStep(builder, loc, cmp, dest, destOps);
+      continue;
+    }
+    if (isa<fir::UpperBoundAttr>(attr)) {
+      // case(:c): match when selector <= c.
+      Value cmpOp = toSignlessInteger(builder, loc, cmpOps.front());
+      Value cmp = arith::CmpIOp::create(builder, loc, lePred, selector, cmpOp);
+      genCaseLadderStep(builder, loc, cmp, dest, destOps);
+      continue;
+    }
+    if (isa<fir::ClosedIntervalAttr>(attr)) {
+      // case(lo:hi): two-step short-circuit. First check lo <= selector;
+      // if true, branch to a hi-check block; if false, fall through.
+      Value lo = toSignlessInteger(builder, loc, cmpOps[0]);
+      Value hi = toSignlessInteger(builder, loc, cmpOps[1]);
+      Value cmpLo = arith::CmpIOp::create(builder, loc, lePred, lo, selector);
+      // Block layout (in source order):
+      //   thisBlock     -> cond_br cmpLo, hiCheck, fallThrough
+      //   hiCheck       -> cond_br cmpHi, dest, fallThrough
+      //   fallThrough   -> next case
+      Block *thisBlock = builder.getInsertionBlock();
+      Region *region = thisBlock->getParent();
+      auto insertIt = std::next(thisBlock->getIterator());
+      Block *hiCheck = builder.createBlock(region, insertIt);
+      Block *fallThrough =
+          builder.createBlock(region, std::next(hiCheck->getIterator()));
+      builder.setInsertionPointToEnd(thisBlock);
+      cf::CondBranchOp::create(builder, loc, cmpLo, hiCheck, ValueRange(),
+                               fallThrough, ValueRange());
+      builder.setInsertionPointToEnd(hiCheck);
+      Value cmpHi = arith::CmpIOp::create(builder, loc, lePred, selector, hi);
+      cf::CondBranchOp::create(builder, loc, cmpHi, dest, destOps, fallThrough,
+                               ValueRange());
+      builder.setInsertionPointToEnd(fallThrough);
+      continue;
+    }
+    return op.emitOpError("unknown case attribute kind");
+  }
+
+  // The FIR verifier requires a `unit` entry, so we should not reach here.
+  return op.emitOpError("fir.select_case has no unit (default) entry");
+}
+
+//===----------------------------------------------------------------------===//
+// Pass
+//===----------------------------------------------------------------------===//
+
+class SelectOpsConversion
+    : public fir::impl::SelectOpsConversionBase<SelectOpsConversion> {
+public:
+  using SelectOpsConversionBase<SelectOpsConversion>::SelectOpsConversionBase;
+
+  void runOnOperation() override {
+    // Collect first; rewriting mutates blocks and would invalidate a live walk.
+    SmallVector<Operation *> worklist;
+    getOperation()->walk([&](Operation *op) {
+      if (isa<fir::SelectOp, fir::SelectCaseOp, fir::SelectRankOp>(op))
+        worklist.push_back(op);
+    });
+
+    for (Operation *op : worklist) {
+      LogicalResult r = success();
+      if (auto s = dyn_cast<fir::SelectOp>(op))
+        r = lowerToSwitch(s);
+      else if (auto sr = dyn_cast<fir::SelectRankOp>(op))
+        r = lowerToSwitch(sr);
+      else if (auto sc = dyn_cast<fir::SelectCaseOp>(op))
+        r = lowerSelectCase(sc);
+      if (failed(r)) {
+        signalPassFailure();
+        return;
+      }
+    }
+  }
+};
+
+} // namespace
diff --git a/flang/test/Driver/bbc-mlir-pass-pipeline.f90 b/flang/test/Driver/bbc-mlir-pass-pipeline.f90
index 4c98c22b90e33..ad293ac42ef64 100644
--- a/flang/test/Driver/bbc-mlir-pass-pipeline.f90
+++ b/flang/test/Driver/bbc-mlir-pass-pipeline.f90
@@ -47,6 +47,7 @@
 ! CHECK-NEXT:   (S) 0 num-dce'd - Number of operations DCE'd
 
 ! CHECK-NEXT: PolymorphicOpConversion
+! CHECK-NEXT: SelectOpsConversion
 ! CHECK-NEXT: AssumedRankOpConversion
 ! CHECK-NEXT: 'func.func' Pipeline
 ! CHECK-NEXT:   OptimizeArrayRepacking
diff --git a/flang/test/Driver/mlir-debug-pass-pipeline.f90 b/flang/test/Driver/mlir-debug-pass-pipeline.f90
index 93c227ebfde74..b1d0b01037fa4 100644
--- a/flang/test/Driver/mlir-debug-pass-pipeline.f90
+++ b/flang/test/Driver/mlir-debug-pass-pipeline.f90
@@ -84,6 +84,7 @@
 ! ALL-NEXT:   (S) 0 num-dce'd - Number of operations DCE'd
 
 ! ALL-NEXT: PolymorphicOpConversion
+! ALL-NEXT: SelectOpsConversion
 ! ALL-NEXT: AssumedRankOpConversion
 ! ALL-NEXT: LowerRepackArraysPass
 ! ALL-NEXT: SimplifyFIROperations
diff --git a/flang/test/Driver/mlir-pass-pipeline.f90 b/flang/test/Driver/mlir-pass-pipeline.f90
index dadf3ce28c66f..837d7154c7105 100644
--- a/flang/test/Driver/mlir-pass-pipeline.f90
+++ b/flang/test/Driver/mlir-pass-pipeline.f90
@@ -136,6 +136,7 @@
 ! ALL-NEXT:   (S) 0 num-dce'd - Number of operations DCE'd
 
 ! ALL-NEXT: PolymorphicOpConversion
+! ALL-NEXT: SelectOpsConversion
 ! ALL-NEXT: AssumedRankOpConversion
 ! O2-NEXT:  'func.func' Pipeline
 ! O2-NEXT:    OptimizeArrayRepacking
diff --git a/flang/test/Fir/SelectOpsConversion/select.fir b/flang/test/Fir/SelectOpsConversion/select.fir
new file mode 100644
index 0000000000000..b85c69742fc4c
--- /dev/null
+++ b/flang/test/Fir/SelectOpsConversion/select.fir
@@ -0,0 +1,113 @@
+// RUN: fir-opt %s --fir-select-ops-conversion | FileCheck %s
+
+// Exercises fir.select with an `index` selector and a mix of successors
+// that forward one, two, or three block arguments, plus a unit default.
+
+func.func @select(%arg : index, %arg2 : i32) -> i32 {
+  %c1 = arith.constant 1 : i32
+  %c2 = arith.constant 2 : i32
+  %c3 = arith.constant 3 : i32
+  %c4 = arith.constant 4 : i32
+  fir.select %arg:index [ 1, ^bb1(%c1:i32),
+                          2, ^bb2(%c3,%arg,%arg2:i32,index,i32),
+                          3, ^bb3(%arg2,%c3:i32,i32),
+                          4, ^bb4(%c2:i32),
+                          unit, ^bb5 ]
+  ^bb1(%a : i32) :
+    return %a : i32
+  ^bb2(%b : i32, %b2 : index, %b3:i32) :
+    %castidx = arith.index_cast %b2 : index to i32
+    %4 = arith.addi %b, %castidx : i32
+    %5 = arith.addi %4, %b3 : i32
+    return %5 : i32
+  ^bb3(%c:i32, %c2b:i32) :
+    %6 = arith.addi %c, %c2b : i32
+    return %6 : i32
+  ^bb4(%d : i32) :
+    return %d : i32
+  ^bb5 :
+    %zero = arith.constant 0 : i32
+    return %zero : i32
+}
+
+// CHECK-LABEL: func.func @select(
+// CHECK-SAME:               %[[SELECTVALUE:.*]]: index,
+// CHECK-SAME:               %[[ARG1:.*]]: i32)
+// CHECK-DAG:     %[[C1:.*]] = arith.constant 1 : i32
+// CHECK-DAG:     %[[C2:.*]] = arith.constant 2 : i32
+// CHECK-DAG:     %[[C3:.*]] = arith.constant 3 : i32
+// CHECK:         %[[SEL:.*]] = arith.index_cast %[[SELECTVALUE]] : index to i64
+// CHECK:         cf.switch %[[SEL]] : i64, [
+// CHECK:           default: ^[[BB5:.*]],
+// CHECK:           1: ^[[BB1:.*]](%[[C1]] : i32),
+// CHECK:           2: ^[[BB2:.*]](%[[C3]], %[[SELECTVALUE]], %[[ARG1]] : i32, index, i32),
+// CHECK:           3: ^[[BB3:.*]](%[[ARG1]], %[[C3]] : i32, i32),
+// CHECK:           4: ^[[BB4:.*]](%[[C2]] : i32)
+// CHECK:         ]
+// CHECK:       ^[[BB1]](%{{.*}}: i32):
+// CHECK:         return
+// CHECK:       ^[[BB2]](%{{.*}}: i32, %{{.*}}: index, %{{.*}}: i32):
+// CHECK:         return
+// CHECK:       ^[[BB3]](%{{.*}}: i32, %{{.*}}: i32):
+// CHECK:         return
+// CHECK:       ^[[BB4]](%{{.*}}: i32):
+// CHECK:         return
+// CHECK:       ^[[BB5]]:
+// CHECK:         %[[CST0:.*]] = arith.constant 0 : i32
+// CHECK:         return %[[CST0]] : i32
+
+// -----
+
+// Exercises the selector-widening code path across the four permitted
+// selector types (i8, i16, i64, index) plus a case value that doesn't fit
+// in i32. i8/i16 selectors are extended with `arith.extsi`, i64 is used
+// as-is, and an `index` selector is bridged via `arith.index_cast`.
+
+func.func @select_with_cast(%arg1 : i8, %arg2 : i16, %arg3: i64, %arg4: index) -> () {
+  fir.select %arg1 : i8 [ 1, ^bb1, unit, ^bb1 ]
+  ^bb1:
+  fir.select %arg2 : i16 [ 1, ^bb2, unit, ^bb2 ]
+  ^bb2:
+  fir.select %arg3 : i64 [ 1, ^bb3, unit, ^bb3 ]
+  ^bb3:
+  fir.select %arg4 : index [ 1, ^bb4, unit, ^bb4 ]
+  ^bb4:
+  fir.select %arg3 : i64 [ 4294967296, ^bb5, unit, ^bb5 ]
+  ^bb5:
+  return
+}
+
+// CHECK-LABEL:   func.func @select_with_cast(
+// CHECK-SAME:      %[[ARG0:.*]]: i8,
+// CHECK-SAME:      %[[ARG1:.*]]: i16,
+// CHECK-SAME:      %[[ARG2:.*]]: i64,
+// CHECK-SAME:      %[[ARG3:.*]]: index)
+// CHECK:           %[[V0:.*]] = arith.extsi %[[ARG0]] : i8 to i64
+// CHECK:           cf.switch %[[V0]] : i64, [
+// CHECK:             default: ^bb1,
+// CHECK:             1: ^bb1
+// CHECK:           ]
+// CHECK:         ^bb1:
+// CHECK:           %[[V1:.*]] = arith.extsi %[[ARG1]] : i16 to i64
+// CHECK:           cf.switch %[[V1]] : i64, [
+// CHECK:             default: ^bb2,
+// CHECK:             1: ^bb2
+// CHECK:           ]
+// CHECK:         ^bb2:
+// CHECK:           cf.switch %[[ARG2]] : i64, [
+// CHECK:             default: ^bb3,
+// CHECK:             1: ^bb3
+// CHECK:           ]
+// CHECK:         ^bb3:
+// CHECK:           %[[V3:.*]] = arith.index_cast %[[ARG3]] : index to i64
+// CHECK:           cf.switch %[[V3]] : i64, [
+// CHECK:             default: ^bb4,
+// CHECK:             1: ^bb4
+// CHECK:           ]
+// CHECK:         ^bb4:
+// CHECK:           cf.switch %[[ARG2]] : i64, [
+// CHECK:             default: ^bb5,
+// CHECK:             4294967296: ^bb5
+// CHECK:           ]
+// CHECK:         ^bb5:
+// CHECK:           return
diff --git a/flang/test/Fir/SelectOpsConversion/select_case.fir b/flang/test/Fir/SelectOpsConversion/select_case.fir
new file mode 100644
index 0000000000000..b1068801edfb5
--- /dev/null
+++ b/flang/test/Fir/SelectOpsConversion/select_case.fir
@@ -0,0 +1,192 @@
+// RUN: fir-opt %s --fir-select-ops-conversion | FileCheck %s
+
+// Exercises all five case-attribute kinds for fir.select_case with an i32
+// selector: #fir.upper, #fir.point, #fir.interval, #fir.lower, and unit.
+
+func.func @select_case_integer(%arg0: !fir.ref<i32>) -> i32 {
+  %2 = fir.load %arg0 : !fir.ref<i32>
+  %c1_i32 = arith.constant 1 : i32
+  %c2_i32 = arith.constant 2 : i32
+  %c4_i32 = arith.constant 4 : i32
+  %c5_i32 = arith.constant 5 : i32
+  %c7_i32 = arith.constant 7 : i32
+  %c8_i32 = arith.constant 8 : i32
+  %c15_i32 = arith.constant 15 : i32
+  %c21_i32 = arith.constant 21 : i32
+  fir.select_case %2 : i32 [#fir.upper, %c1_i32, ^bb1,
+                            #fir.point, %c2_i32, ^bb2,
+                            #fir.interval, %c4_i32, %c5_i32, ^bb4,
+                            #fir.point, %c7_i32, ^bb5,
+                            #fir.interval, %c8_i32, %c15_i32, ^bb5,
+                            #fir.lower, %c21_i32, ^bb5,
+                            unit, ^bb3]
+^bb1:  // pred: ^bb0
+  %c1_i32_0 = arith.constant 1 : i32
+  fir.store %c1_i32_0 to %arg0 : !fir.ref<i32>
+  cf.br ^bb6
+^bb2:  // pred: ^bb0
+  %c2_i32_1 = arith.constant 2 : i32
+  fir.store %c2_i32_1 to %arg0 : !fir.ref<i32>
+  cf.br ^bb6
+^bb3:  // pred: ^bb0
+  %c0_i32 = arith.constant 0 : i32
+  fir.store %c0_i32 to %arg0 : !fir.ref<i32>
+  cf.br ^bb6
+^bb4:  // pred: ^bb0
+  %c4_i32_2 = arith.constant 4 : i32
+  fir.store %c4_i32_2 to %arg0 : !fir.ref<i32>
+  cf.br ^bb6
+^bb5:  // 3 preds: ^bb0, ^bb0, ^bb0
+  %c7_i32_3 = arith.constant 7 : i32
+  fir.store %c7_i32_3 to %arg0 : !fir.ref<i32>
+  cf.br ^bb6
+^bb6:  // 5 preds: ^bb1, ^bb2, ^bb3, ^bb4, ^bb5
+  %3 = fir.load %arg0 : !fir.ref<i32>
+  return %3 : i32
+}
+
+// CHECK-LABEL: func.func @select_case_integer(
+// CHECK-SAME:                          %[[ARG0:.*]]: !fir.ref<i32>) -> i32 {
+// CHECK:         %[[SELECT_VALUE:.*]] = fir.load %[[ARG0]] : !fir.ref<i32>
+// CHECK:         %[[CST1:.*]] = arith.constant 1 : i32
+// CHECK:         %[[CST2:.*]] = arith.constant 2 : i32
+// CHECK:         %[[CST4:.*]] = arith.constant 4 : i32
+// CHECK:         %[[CST5:.*]] = arith.constant 5 : i32
+// CHECK:         %[[CST7:.*]] = arith.constant 7 : i32
+// CHECK:         %[[CST8:.*]] = arith.constant 8 : i32
+// CHECK:         %[[CST15:.*]] = arith.constant 15 : i32
+// CHECK:         %[[CST21:.*]] = arith.constant 21 : i32
+// Check for upper bound `case (:1)`
+// CHECK:         %[[CMP_SLE:.*]] = arith.cmpi sle, %[[SELECT_VALUE]], %[[CST1]] : i32
+// CHECK:         cf.cond_br %[[CMP_SLE]], ^[[BB_UPPER:bb[0-9]+]], ^bb1
+// CHECK-LABEL: ^bb1:
+// Check for point value `case (2)`
+// CHECK:         %[[CMP_EQ:.*]] = arith.cmpi eq, %[[SELECT_VALUE]], %[[CST2]] : i32
+// CHECK:         cf.cond_br %[[CMP_EQ]], ^[[BB_POINT2:bb[0-9]+]], ^bb2
+// CHECK-LABEL: ^bb2:
+// Check for the lower bound for the interval `case (4:5)`
+// CHECK:        %[[CMP_SLE:.*]] = arith.cmpi sle, %[[CST4]], %[[SELECT_VALUE]] : i32
+// CHECK:        cf.cond_br %[[CMP_SLE]], ^[[BB_UPPERBOUND5:bb[0-9]+]], ^bb4
+// CHECK:       ^[[BB_UPPERBOUND5]]:
+// Check for the upper bound for the interval `case (4:5)`
+// CHECK:        %[[CMP_SLE:.*]] = arith.cmpi sle, %[[SELECT_VALUE]], %[[CST5]] : i32
+// CHECK:        cf.cond_br %[[CMP_SLE]], ^[[BB_INTERVAL45:bb[0-9]+]], ^bb4
+// CHECK-LABEL: ^bb4:
+// Check for the point value 7 in `case (7,8:15,21:)`
+// CHECK:        %[[CMP_EQ:.*]] = arith.cmpi eq, %[[SELECT_VALUE]], %[[CST7]] : i32
+// CHECK:        cf.cond_br %[[CMP_EQ]], ^[[BB_7_8_15_21:bb[0-9]+]], ^bb5
+// CHECK-LABEL: ^bb5:
+// Check for lower bound 8 in `case (7,8:15,21:)`
+// CHECK:        %[[CMP_SLE:.*]] = arith.cmpi sle, %[[CST8]], %[[SELECT_VALUE]] : i32
+// CHECK:        cf.cond_br %[[CMP_SLE]], ^[[BB_INTERVAL8_15:bb[0-9]+]], ^bb7
+// CHECK:       ^[[BB_INTERVAL8_15]]:
+// Check for upper bound 15 in `case (7,8:15,21:)`
+// CHECK:        %[[CMP_SLE:.*]] = arith.cmpi sle, %[[SELECT_VALUE]], %[[CST15]] : i32
+// CHECK:        cf.cond_br %[[CMP_SLE]], ^[[BB_7_8_15_21]], ^bb7
+// CHECK-LABEL: ^bb7:
+// Check for lower bound 21 in `case (7,8:15,21:)`
+// CHECK:        %[[CMP_SLE:.*]]  = arith.cmpi sle, %[[CST21]], %[[SELECT_VALUE]] : i32
+// CHECK:        cf.cond_br %[[CMP_SLE]], ^[[BB_7_8_15_21]], ^bb8
+// CHECK-LABEL: ^bb8:
+// Default branch (unit case) falls through to the CLASS DEFAULT successor.
+// CHECK:         cf.br ^[[BB_DEFAULT:bb[0-9]+]]
+// Each original case successor is preserved in source order after the
+// ladder; each stores its case's constant then falls into the shared
+// join block. The block names captured above are reused so the CFG
+// topology is checked, not just the shape.
+// CHECK:       ^[[BB_UPPER]]:
+// CHECK:         %[[STORE_UPPER:.*]] = arith.constant 1 : i32
+// CHECK:         fir.store %[[STORE_UPPER]] to %[[ARG0]] : !fir.ref<i32>
+// CHECK:         cf.br ^[[BB_JOIN:bb[0-9]+]]
+// CHECK:       ^[[BB_POINT2]]:
+// CHECK:         %[[STORE_POINT2:.*]] = arith.constant 2 : i32
+// CHECK:         fir.store %[[STORE_POINT2]] to %[[ARG0]] : !fir.ref<i32>
+// CHECK:         cf.br ^[[BB_JOIN]]
+// CHECK:       ^[[BB_DEFAULT]]:
+// CHECK:         %[[STORE_DEFAULT:.*]] = arith.constant 0 : i32
+// CHECK:         fir.store %[[STORE_DEFAULT]] to %[[ARG0]] : !fir.ref<i32>
+// CHECK:         cf.br ^[[BB_JOIN]]
+// CHECK:       ^[[BB_INTERVAL45]]:
+// CHECK:         %[[STORE_INTERVAL45:.*]] = arith.constant 4 : i32
+// CHECK:         fir.store %[[STORE_INTERVAL45]] to %[[ARG0]] : !fir.ref<i32>
+// CHECK:         cf.br ^[[BB_JOIN]]
+// CHECK:       ^[[BB_7_8_15_21]]:
+// CHECK:         %[[STORE_7_8_15_21:.*]] = arith.constant 7 : i32
+// CHECK:         fir.store %[[STORE_7_8_15_21]] to %[[ARG0]] : !fir.ref<i32>
+// CHECK:         cf.br ^[[BB_JOIN]]
+// Join block reads the ref and returns it.
+// CHECK:       ^[[BB_JOIN]]:
+// CHECK:         %[[RET:.*]] = fir.load %[[ARG0]] : !fir.ref<i32>
+// CHECK:         return %[[RET]] : i32
+
+// -----
+
+// Exercises fir.select_case with an i1 selector (converted from a Fortran
+// LOGICAL) and two #fir.point cases plus a unit default.
+
+func.func @select_case_logical(%arg0: !fir.ref<!fir.logical<4>>) {
+  %1 = fir.load %arg0 : !fir.ref<!fir.logical<4>>
+  %2 = fir.convert %1 : (!fir.logical<4>) -> i1
+  %false = arith.constant false
+  %true = arith.constant true
+  fir.select_case %2 : i1 [#fir.point, %false, ^bb1,
+                           #fir.point, %true, ^bb2,
+                            unit, ^bb3]
+^bb1:
+  %c1_i32 = arith.constant 1 : i32
+  cf.br ^bb3
+^bb2:
+  %c2_i32 = arith.constant 2 : i32
+  cf.br ^bb3
+^bb3:
+  return
+}
+
+// CHECK-LABEL: func.func @select_case_logical(
+// CHECK-SAME:                          %[[ARG0:.*]]: !fir.ref<!fir.logical<4>>)
+// CHECK:         %[[LD:.*]] = fir.load %[[ARG0]] : !fir.ref<!fir.logical<4>>
+// CHECK:         %[[SEL:.*]] = fir.convert %[[LD]] : (!fir.logical<4>) -> i1
+// CHECK-DAG:     %[[FALSE:.*]] = arith.constant false
+// CHECK-DAG:     %[[TRUE:.*]] = arith.constant true
+// CHECK:         %[[C0:.*]] = arith.cmpi eq, %[[SEL]], %[[FALSE]] : i1
+// CHECK:         cf.cond_br %[[C0]], ^[[BB_FALSE:bb[0-9]+]], ^bb1
+// CHECK-LABEL: ^bb1:
+// CHECK:         %[[C1:.*]] = arith.cmpi eq, %[[SEL]], %[[TRUE]] : i1
+// CHECK:         cf.cond_br %[[C1]], ^[[BB_TRUE:bb[0-9]+]], ^bb2
+// CHECK-LABEL: ^bb2:
+// Unit-default fall-through.
+// CHECK:         cf.br ^[[BB_JOIN:bb[0-9]+]]
+// Two #fir.point successor bodies (each declares its constant then
+// falls into the shared join block), and the join block returns.
+// CHECK:       ^[[BB_FALSE]]:
+// CHECK:         arith.constant 1 : i32
+// CHECK:         cf.br ^[[BB_JOIN]]
+// CHECK:       ^[[BB_TRUE]]:
+// CHECK:         arith.constant 2 : i32
+// CHECK:         cf.br ^[[BB_JOIN]]
+// CHECK:       ^[[BB_JOIN]]:
+// CHECK:         return
+
+// -----
+
+// Exercises fir.select_case with per-successor block operands: two
+// #fir.point cases pass different `!fir.ref<!fir.array<2xi32>>` values to
+// the same target block. The pass propagates the operand forwarding
+// verbatim; block-argument types are preserved (no type conversion runs).
+
+func.func @select_case_block_args(%arg0: !fir.ref<i32>, %arg1: !fir.ref<!fir.array<2xi32>>, %arg2: !fir.ref<!fir.array<2xi32>>) {
+  %0 = fir.load %arg0 : !fir.ref<i32>
+  %c1 = arith.constant 1 : i32
+  %c2 = arith.constant 2 : i32
+  fir.select_case %0 : i32 [#fir.point, %c1, ^bb1(%arg1 : !fir.ref<!fir.array<2xi32>>),
+                            #fir.point, %c2, ^bb1(%arg2 : !fir.ref<!fir.array<2xi32>>),
+                            unit, ^bb2]
+^bb1(%1: !fir.ref<!fir.array<2xi32>>):
+  cf.br ^bb2
+^bb2:
+  return
+}
+
+// CHECK-LABEL: func.func @select_case_block_args
+// CHECK:         cf.cond_br %{{.*}}, ^{{.*}}(%{{.*}} : !fir.ref<!fir.array<2xi32>>), ^{{.*}}
+// CHECK:       ^{{.*}}(%{{.*}}: !fir.ref<!fir.array<2xi32>>):
diff --git a/flang/test/Fir/SelectOpsConversion/select_rank.fir b/flang/test/Fir/SelectOpsConversion/select_rank.fir
new file mode 100644
index 0000000000000..84db7b7efea37
--- /dev/null
+++ b/flang/test/Fir/SelectOpsConversion/select_rank.fir
@@ -0,0 +1,46 @@
+// RUN: fir-opt %s --fir-select-ops-conversion | FileCheck %s
+
+// Exercises fir.select_rank with an i32 selector and successors that
+// forward one, two, or three block arguments, plus a unit default. The
+// rank selector is widened to i64 with `arith.extsi` before `cf.switch`.
+
+func.func @select_rank(%arg : i32, %arg2 : i32) -> i32 {
+  %c1 = arith.constant 1 : i32
+  %c2 = arith.constant 2 : i32
+  %c3 = arith.constant 3 : i32
+  %c4 = arith.constant 4 : i32
+  fir.select_rank %arg:i32 [ 1, ^bb1(%c1:i32),
+                             2, ^bb2(%c3,%arg,%arg2:i32,i32,i32),
+                             3, ^bb3(%arg2,%c3:i32,i32),
+                             4, ^bb4(%c2:i32),
+                             unit, ^bb5 ]
+  ^bb1(%a : i32) :
+    return %a : i32
+  ^bb2(%b : i32, %b2 : i32, %b3:i32) :
+    %4 = arith.addi %b, %b2 : i32
+    %5 = arith.addi %4, %b3 : i32
+    return %5 : i32
+  ^bb3(%c:i32, %c2b:i32) :
+    %6 = arith.addi %c, %c2b : i32
+    return %6 : i32
+  ^bb4(%d : i32) :
+    return %d : i32
+  ^bb5 :
+    %zero = arith.constant 0 : i32
+    return %zero : i32
+}
+
+// CHECK-LABEL: func.func @select_rank(
+// CHECK-SAME:                    %[[SELECTVALUE:.*]]: i32,
+// CHECK-SAME:                    %[[ARG1:.*]]: i32)
+// CHECK-DAG:     %[[C1:.*]] = arith.constant 1 : i32
+// CHECK-DAG:     %[[C2:.*]] = arith.constant 2 : i32
+// CHECK-DAG:     %[[C3:.*]] = arith.constant 3 : i32
+// CHECK:         %[[SELECTOR:.*]] = arith.extsi %[[SELECTVALUE]] : i32 to i64
+// CHECK:         cf.switch %[[SELECTOR]] : i64, [
+// CHECK:           default: ^{{.*}},
+// CHECK:           1: ^{{.*}}(%[[C1]] : i32),
+// CHECK:           2: ^{{.*}}(%[[C3]], %[[SELECTVALUE]], %[[ARG1]] : i32, i32, i32),
+// CHECK:           3: ^{{.*}}(%[[ARG1]], %[[C3]] : i32, i32),
+// CHECK:           4: ^{{.*}}(%[[C2]] : i32)
+// CHECK:         ]
diff --git a/flang/test/Fir/basic-program.fir b/flang/test/Fir/basic-program.fir
index d1e7cdb44d0c7..344379f624f3e 100644
--- a/flang/test/Fir/basic-program.fir
+++ b/flang/test/Fir/basic-program.fir
@@ -119,6 +119,7 @@ func.func @_QQmain() {
 // PASSES-NEXT:   (S) 0 num-dce'd - Number of operations DCE'd
 
 // PASSES-NEXT: PolymorphicOpConversion
+// PASSES-NEXT: SelectOpsConversion
 // PASSES-NEXT: AssumedRankOpConversion
 // PASSES-NEXT: 'func.func' Pipeline
 // PASSES-NEXT:   OptimizeArrayRepacking
diff --git a/flang/test/Fir/select.fir b/flang/test/Fir/select.fir
index b342b7cff1c6a..cde09a78bc788 100644
--- a/flang/test/Fir/select.fir
+++ b/flang/test/Fir/select.fir
@@ -47,10 +47,13 @@ func.func @h(%a : i32) -> i32 {
    %b2 = arith.constant 14 : i32
    %b3 = arith.constant 82 : i32
    %b4 = arith.constant 96 : i32
+   // `sle const, sel` and `sge sel, const` are semantically equivalent; the
+   // full flang pipeline canonicalizes to the latter while tco keeps the
+   // former, so accept both forms for the lower-bound / interval-lo checks.
    // CHECK-DAG: icmp eq i32 %{{.*}}, 1
-   // CHECK-DAG: icmp sle i32 4, %{{.*}}
+   // CHECK-DAG: icmp {{sle i32 4, %.*|sge i32 %.*, 4}}
    // CHECK-DAG: icmp sle i32 %{{.*}}, 14
-   // CHECK-DAG: icmp sle i32 82, %{{.*}}
+   // CHECK-DAG: icmp {{sle i32 82, %.*|sge i32 %.*, 82}}
    // CHECK-DAG: icmp sle i32 %{{.*}}, 96
    fir.select_case %a : i32 [#fir.point, %1, ^bb2(%1:i32), #fir.lower, %b1, ^bb4, #fir.upper, %b2, ^bb6, #fir.interval, %b3, %b4, ^bb5, unit, ^bb3(%2:i32)]
 ^bb2(%3 : i32) :
@@ -76,8 +79,11 @@ func.func @empty_cases(%arg0 : i32) {
 }
 func.func private @crash()
 
+// The empty-cases fir.select has only a `unit` default: no switch is emitted.
+// The `tco` path preserves the intermediate `br label %B; B:` block, whereas
+// the full flang pipeline canonicalizes the trivial cf.br away and drops
+// straight into `call @crash; unreachable`. Either shape is acceptable.
 //CHECK-LABEL: @empty_cases(i32 %0) {
-//CHECK:  br label %[[BLOCK:.*]]
-//CHECK: [[BLOCK]]:
-//CHECK:  call void @crash()
-//CHECK:  unreachable
+//CHECK-NOT:    switch
+//CHECK:        call void @crash()
+//CHECK-NEXT:   unreachable
diff --git a/flang/test/Lower/volatile3.f90 b/flang/test/Lower/volatile3.f90
index 06bcbfeee42fa..8afb8a70af3a9 100644
--- a/flang/test/Lower/volatile3.f90
+++ b/flang/test/Lower/volatile3.f90
@@ -247,22 +247,28 @@ subroutine sub_select_rank(arr)
 ! CHECK:           %[[VOLATILE_CAST_1:.*]] = fir.volatile_cast %[[DECLARE_0]]#0 : (!fir.box<!fir.array<*:i32>, volatile>) -> !fir.box<!fir.array<*:i32>>
 ! CHECK:           %[[CONVERT_0:.*]] = fir.convert %[[VOLATILE_CAST_1]] : (!fir.box<!fir.array<*:i32>>) -> !fir.box<none>
 ! CHECK:           %[[CALL_0:.*]] = fir.call @_FortranAIsAssumedSize(%[[CONVERT_0]]) : (!fir.box<none>) -> i1
-! CHECK:           cf.cond_br %[[CALL_0]], ^bb4, ^bb1
+! CHECK:           cf.cond_br %[[CALL_0]], ^bb5, ^bb1
 ! CHECK:         ^bb1:
 ! CHECK:           %[[BOX_RANK_0:.*]] = fir.box_rank %[[DECLARE_0]]#0 : (!fir.box<!fir.array<*:i32>, volatile>) -> i8
-! CHECK:           fir.select_case %[[BOX_RANK_0]] : i8 [#fir.point, %[[CONSTANT_3]], ^bb2, #fir.point, %[[CONSTANT_2]], ^bb3, unit, ^bb4]
+! `fir.select_case` for rank dispatch is now expanded into an arith.cmpi +
+! cf.cond_br ladder by --fir-select-ops-conversion during the pipeline.
+! CHECK:           %[[CMP_R1:.*]] = arith.cmpi eq, %[[BOX_RANK_0]], %[[CONSTANT_3]] : i8
+! CHECK:           cf.cond_br %[[CMP_R1]], ^bb3, ^bb2
 ! CHECK:         ^bb2:
+! CHECK:           %[[CMP_R4:.*]] = arith.cmpi eq, %[[BOX_RANK_0]], %[[CONSTANT_2]] : i8
+! CHECK:           cf.cond_br %[[CMP_R4]], ^bb4, ^bb5
+! CHECK:         ^bb3:
 ! CHECK:           %[[CONVERT_1:.*]] = fir.convert %[[DECLARE_0]]#0 : (!fir.box<!fir.array<*:i32>, volatile>) -> !fir.box<!fir.array<?xi32>, volatile>
 ! CHECK:           %[[DECLARE_1:.*]]:2 = hlfir.declare %[[CONVERT_1]] {fortran_attrs = #fir.var_attrs<volatile>, uniq_name = "_QFFsub_select_rankEarr"} : (!fir.box<!fir.array<?xi32>, volatile>) -> (!fir.box<!fir.array<?xi32>, volatile>, !fir.box<!fir.array<?xi32>, volatile>)
 ! CHECK:           %[[DESIGNATE_0:.*]] = hlfir.designate %[[DECLARE_1]]#0 (%[[CONSTANT_0]])  : (!fir.box<!fir.array<?xi32>, volatile>, index) -> !fir.ref<i32, volatile>
 ! CHECK:           hlfir.assign %[[CONSTANT_1]] to %[[DESIGNATE_0]] : i32, !fir.ref<i32, volatile>
-! CHECK:           cf.br ^bb4
-! CHECK:         ^bb3:
+! CHECK:           cf.br ^bb5
+! CHECK:         ^bb4:
 ! CHECK:           %[[CONVERT_2:.*]] = fir.convert %[[DECLARE_0]]#0 : (!fir.box<!fir.array<*:i32>, volatile>) -> !fir.box<!fir.array<?x?x?x?xi32>, volatile>
 ! CHECK:           %[[DECLARE_2:.*]]:2 = hlfir.declare %[[CONVERT_2]] {fortran_attrs = #fir.var_attrs<volatile>, uniq_name = "_QFFsub_select_rankEarr"} : (!fir.box<!fir.array<?x?x?x?xi32>, volatile>) -> (!fir.box<!fir.array<?x?x?x?xi32>, volatile>, !fir.box<!fir.array<?x?x?x?xi32>, volatile>)
 ! CHECK:           %[[DESIGNATE_1:.*]] = hlfir.designate %[[DECLARE_2]]#0 (%[[CONSTANT_0]], %[[CONSTANT_0]], %[[CONSTANT_0]], %[[CONSTANT_0]])  : (!fir.box<!fir.array<?x?x?x?xi32>, volatile>, index, index, index, index) -> !fir.ref<i32, volatile>
 ! CHECK:           hlfir.assign %[[CONSTANT_1]] to %[[DESIGNATE_1]] : i32, !fir.ref<i32, volatile>
-! CHECK:           cf.br ^bb4
-! CHECK:         ^bb4:
+! CHECK:           cf.br ^bb5
+! CHECK:         ^bb5:
 ! CHECK:           return
 ! CHECK:         }



More information about the flang-commits mailing list