| //===- 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 |