diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp index 82666df0b0615..ffaec4df07051 100644 --- a/flang/lib/Lower/Bridge.cpp +++ b/flang/lib/Lower/Bridge.cpp @@ -22,6 +22,7 @@ #include "flang/Lower/SymbolMap.h" #include "flang/Lower/Todo.h" #include "flang/Optimizer/Support/FIRContext.h" +#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" #include "mlir/IR/PatternMatch.h" #include "mlir/Transforms/RegionUtils.h" #include "llvm/Support/CommandLine.h" @@ -132,6 +133,10 @@ class FirConverter : public Fortran::lower::AbstractConverter { void setCurrentEval(Fortran::lower::pft::Evaluation &eval) { evalPtr = &eval; } + Fortran::lower::pft::Evaluation &getEval() { + assert(evalPtr && "current evaluation not set"); + return *evalPtr; + } mlir::Location getCurrentLocation() override final { return toLocation(); } @@ -181,6 +186,29 @@ class FirConverter : public Fortran::lower::AbstractConverter { !currentBlock->back().hasTrait(); } + /// Unconditionally switch code insertion to a new block. + void startBlock(mlir::Block *newBlock) { + assert(newBlock && "missing block"); + // Default termination for the current block is a fallthrough branch to + // the new block. + if (blockIsUnterminated()) + genFIRBranch(newBlock); + // Some blocks may be re/started more than once, and might not be empty. + // If the new block already has (only) a terminator, set the insertion + // point to the start of the block. Otherwise set it to the end. + // Note that setting the insertion point causes the subsequent function + // call to check the existence of terminator in the newBlock. + builder->setInsertionPointToStart(newBlock); + if (blockIsUnterminated()) + builder->setInsertionPointToEnd(newBlock); + } + + /// Conditionally switch code insertion to a new block. + void maybeStartBlock(mlir::Block *newBlock) { + if (newBlock) + startBlock(newBlock); + } + /// Emit return and cleanup after the function has been translated. void endNewFunction(Fortran::lower::pft::FunctionLikeUnit &funit) { setCurrentPosition(Fortran::lower::pft::stmtSourceLoc(funit.endStmt)); @@ -191,6 +219,10 @@ class FirConverter : public Fortran::lower::AbstractConverter { funit.finalBlock = nullptr; LLVM_DEBUG(llvm::dbgs() << "*** Lowering result:\n\n" << *builder->getFunction() << '\n'); + // FIXME: Simplification should happen in a normal pass, not here. + mlir::IRRewriter rewriter(*builder); + (void)mlir::simplifyRegions(rewriter, + {builder->getRegion()}); // remove dead code delete builder; builder = nullptr; localSymbols.clear(); @@ -218,6 +250,38 @@ class FirConverter : public Fortran::lower::AbstractConverter { if (!sym.IsFuncResult() || !funit.primaryResult) instantiateVar(var); } + + // Create most function blocks in advance. + createEmptyGlobalBlocks(funit.evaluationList); + + // Reinstate entry block as the current insertion point. + builder->setInsertionPointToEnd(&func.front()); + } + + /// Create global blocks for the current function. This eliminates the + /// distinction between forward and backward targets when generating + /// branches. A block is "global" if it can be the target of a GOTO or + /// other source code branch. A block that can only be targeted by a + /// compiler generated branch is "local". For example, a DO loop preheader + /// block containing loop initialization code is global. A loop header + /// block, which is the target of the loop back edge, is local. Blocks + /// belong to a region. Any block within a nested region must be replaced + /// with a block belonging to that region. Branches may not cross region + /// boundaries. + void createEmptyGlobalBlocks( + std::list &evaluationList) { + mlir::Region *region = &builder->getRegion(); + for (Fortran::lower::pft::Evaluation &eval : evaluationList) { + if (eval.isNewBlock) + eval.block = builder->createBlock(region); + if (eval.isConstruct() || eval.isDirective()) { + if (eval.lowerAsUnstructured()) { + createEmptyGlobalBlocks(eval.getNestedEvaluations()); + } else if (eval.hasNestedEvaluations()) { + TODO(toLocation(), "Constructs with nested evaluations"); + } + } + } } /// Lower a procedure (nest). @@ -253,6 +317,11 @@ class FirConverter : public Fortran::lower::AbstractConverter { return {}; } + void genFIRBranch(mlir::Block *targetBlock) { + assert(targetBlock && "missing unconditional target block"); + builder->create(toLocation(), targetBlock); + } + //===--------------------------------------------------------------------===// // Termination of symbolically referenced execution units //===--------------------------------------------------------------------===// @@ -595,7 +664,7 @@ class FirConverter : public Fortran::lower::AbstractConverter { } void genFIR(const Fortran::parser::GotoStmt &) { - TODO(toLocation(), "GotoStmt lowering"); + genFIRBranch(getEval().controlSuccessor->block); } void genFIR(const Fortran::parser::AssociateStmt &) { @@ -671,6 +740,14 @@ class FirConverter : public Fortran::lower::AbstractConverter { void genFIR(Fortran::lower::pft::Evaluation &eval, bool unstructuredContext = true) { + if (unstructuredContext) { + // When transitioning from unstructured to structured code, + // the structured code could be a target that starts a new block. + maybeStartBlock(eval.isConstruct() && eval.lowerAsStructured() + ? eval.getFirstNestedEvaluation().block + : eval.block); + } + setCurrentEval(eval); setCurrentPosition(eval.position); eval.visit([&](const auto &stmt) { genFIR(stmt); }); diff --git a/flang/test/Lower/goto-statement.f90 b/flang/test/Lower/goto-statement.f90 new file mode 100644 index 0000000000000..b5359d50e8a30 --- /dev/null +++ b/flang/test/Lower/goto-statement.f90 @@ -0,0 +1,66 @@ +! RUN: bbc %s -emit-fir -o - | FileCheck %s + +! Test trivial goto statement +subroutine sub1() +goto 1 +1 stop +end subroutine +! CHECK-LABEL: sub1 +! CHECK: cf.br ^[[BB1:.*]] +! CHECK: ^[[BB1]]: +! CHECK: {{.*}} fir.call @_FortranAStopStatement({{.*}}, {{.*}}, {{.*}}) : (i32, i1, i1) -> none +! CHECK: } + +! Test multiple goto statements +subroutine sub2() +goto 1 +1 goto 2 +2 goto 3 +3 stop +end subroutine +! CHECK-LABEL: sub2 +! CHECK: cf.br ^[[BB1:.*]] +! CHECK: ^[[BB1]]: +! CHECK: cf.br ^[[BB2:.*]] +! CHECK: ^[[BB2]]: +! CHECK: cf.br ^[[BB3:.*]] +! CHECK: ^[[BB3]]: +! CHECK: {{.*}} fir.call @_FortranAStopStatement({{.*}}, {{.*}}, {{.*}}) : (i32, i1, i1) -> none +! CHECK: } + +! Test goto which branches to a previous label +subroutine sub3() +pause +1 goto 3 +2 stop +3 goto 2 +end subroutine +! CHECK: sub3 +! CHECK: {{.*}} fir.call @_FortranAPauseStatement() : () -> none +! CHECK: cf.br ^[[BB2:.*]] +! CHECK: ^[[BB1:.*]]: // +! CHECK: {{.*}} fir.call @_FortranAStopStatement({{.*}}, {{.*}}, {{.*}}) : (i32, i1, i1) -> none +! CHECK: ^[[BB2]]: +! CHECK: cf.br ^[[BB1]] +! CHECK: } + +! Test removal of blocks (pauses) which are not reachable +subroutine sub4() +pause +1 goto 2 +pause +2 goto 3 +pause +3 goto 1 +pause +end subroutine +! CHECK-LABEL: sub4 +! CHECK: {{.*}} fir.call @_FortranAPauseStatement() : () -> none +! CHECK-NEXT: cf.br ^[[BB1:.*]] +! CHECK-NEXT: ^[[BB1]]: +! CHECK-NEXT: cf.br ^[[BB2:.*]] +! CHECK-NEXT: ^[[BB2]]: +! CHECK-NEXT: cf.br ^[[BB3:.*]] +! CHECK-NEXT: ^[[BB3]]: +! CHECK-NEXT: cf.br ^[[BB1]] +! CHECK-NEXT: }