llvm-project/flang/lib/Lower/PFTBuilder.cpp

698 lines
25 KiB
C++

//===-- lib/Lower/PFTBuilder.cc -------------------------------------------===//
//
// 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/Lower/PFTBuilder.h"
#include "flang/Parser/dump-parse-tree.h"
#include "flang/Parser/parse-tree-visitor.h"
#include "llvm/ADT/DenseMap.h"
#include <algorithm>
#include <cassert>
#include <utility>
namespace Fortran::lower {
namespace {
/// Helpers to unveil parser node inside parser::Statement<>,
/// parser::UnlabeledStatement, and common::Indirection<>
template <typename A>
struct RemoveIndirectionHelper {
using Type = A;
static constexpr const Type &unwrap(const A &a) { return a; }
};
template <typename A>
struct RemoveIndirectionHelper<common::Indirection<A>> {
using Type = A;
static constexpr const Type &unwrap(const common::Indirection<A> &a) {
return a.value();
}
};
template <typename A>
const auto &removeIndirection(const A &a) {
return RemoveIndirectionHelper<A>::unwrap(a);
}
template <typename A>
struct UnwrapStmt {
static constexpr bool isStmt{false};
};
template <typename A>
struct UnwrapStmt<parser::Statement<A>> {
static constexpr bool isStmt{true};
using Type = typename RemoveIndirectionHelper<A>::Type;
constexpr UnwrapStmt(const parser::Statement<A> &a)
: unwrapped{removeIndirection(a.statement)}, pos{a.source}, lab{a.label} {
}
const Type &unwrapped;
parser::CharBlock pos;
std::optional<parser::Label> lab;
};
template <typename A>
struct UnwrapStmt<parser::UnlabeledStatement<A>> {
static constexpr bool isStmt{true};
using Type = typename RemoveIndirectionHelper<A>::Type;
constexpr UnwrapStmt(const parser::UnlabeledStatement<A> &a)
: unwrapped{removeIndirection(a.statement)}, pos{a.source} {}
const Type &unwrapped;
parser::CharBlock pos;
std::optional<parser::Label> lab;
};
/// The instantiation of a parse tree visitor (Pre and Post) is extremely
/// expensive in terms of compile and link time, so one goal here is to limit
/// the bridge to one such instantiation.
class PFTBuilder {
public:
PFTBuilder() : pgm{new pft::Program}, parents{*pgm.get()} {}
/// Get the result
std::unique_ptr<pft::Program> result() { return std::move(pgm); }
template <typename A>
constexpr bool Pre(const A &a) {
bool visit{true};
if constexpr (pft::isFunctionLike<A>) {
return enterFunc(a);
} else if constexpr (pft::isConstruct<A>) {
return enterConstruct(a);
} else if constexpr (UnwrapStmt<A>::isStmt) {
using T = typename UnwrapStmt<A>::Type;
// Node "a" being visited has one of the following types:
// Statement<T>, Statement<Indirection<T>, UnlabeledStatement<T>,
// or UnlabeledStatement<Indirection<T>>
auto stmt{UnwrapStmt<A>(a)};
if constexpr (pft::isConstructStmt<T> || pft::isOtherStmt<T>) {
addEval(pft::Evaluation{stmt.unwrapped, parents.back(), stmt.pos,
stmt.lab});
visit = false;
} else if constexpr (std::is_same_v<T, parser::ActionStmt>) {
addEval(makeEvalAction(stmt.unwrapped, stmt.pos, stmt.lab));
visit = false;
}
}
return visit;
}
template <typename A>
constexpr void Post(const A &) {
if constexpr (pft::isFunctionLike<A>) {
exitFunc();
} else if constexpr (pft::isConstruct<A>) {
exitConstruct();
}
}
// Module like
bool Pre(const parser::Module &node) { return enterModule(node); }
bool Pre(const parser::Submodule &node) { return enterModule(node); }
void Post(const parser::Module &) { exitModule(); }
void Post(const parser::Submodule &) { exitModule(); }
// Block data
bool Pre(const parser::BlockData &node) {
addUnit(pft::BlockDataUnit{node, parents.back()});
return false;
}
// Get rid of production wrapper
bool Pre(const parser::UnlabeledStatement<parser::ForallAssignmentStmt>
&statement) {
addEval(std::visit(
[&](const auto &x) {
return pft::Evaluation{x, parents.back(), statement.source, {}};
},
statement.statement.u));
return false;
}
bool Pre(const parser::Statement<parser::ForallAssignmentStmt> &statement) {
addEval(std::visit(
[&](const auto &x) {
return pft::Evaluation{x, parents.back(), statement.source,
statement.label};
},
statement.statement.u));
return false;
}
bool Pre(const parser::WhereBodyConstruct &whereBody) {
return std::visit(
common::visitors{
[&](const parser::Statement<parser::AssignmentStmt> &stmt) {
// Not caught as other AssignmentStmt because it is not
// wrapped in a parser::ActionStmt.
addEval(pft::Evaluation{stmt.statement, parents.back(),
stmt.source, stmt.label});
return false;
},
[&](const auto &) { return true; },
},
whereBody.u);
}
private:
// ActionStmt has a couple of non-conforming cases, which get handled
// explicitly here. The other cases use an Indirection, which we discard in
// the PFT.
pft::Evaluation makeEvalAction(const parser::ActionStmt &statement,
parser::CharBlock pos,
std::optional<parser::Label> lab) {
return std::visit(
common::visitors{
[&](const auto &x) {
return pft::Evaluation{removeIndirection(x), parents.back(), pos,
lab};
},
},
statement.u);
}
// When we enter a function-like structure, we want to build a new unit and
// set the builder's cursors to point to it.
template <typename A>
bool enterFunc(const A &func) {
auto &unit = addFunc(pft::FunctionLikeUnit{func, parents.back()});
funclist = &unit.funcs;
pushEval(&unit.evals);
parents.emplace_back(unit);
return true;
}
/// Make funclist to point to current parent function list if it exists.
void setFunctListToParentFuncs() {
if (!parents.empty()) {
std::visit(common::visitors{
[&](pft::FunctionLikeUnit *p) { funclist = &p->funcs; },
[&](pft::ModuleLikeUnit *p) { funclist = &p->funcs; },
[&](auto *) { funclist = nullptr; },
},
parents.back().p);
}
}
void exitFunc() {
popEval();
parents.pop_back();
setFunctListToParentFuncs();
}
// When we enter a construct structure, we want to build a new construct and
// set the builder's evaluation cursor to point to it.
template <typename A>
bool enterConstruct(const A &construct) {
auto &con = addEval(pft::Evaluation{construct, parents.back()});
con.subs.reset(new pft::EvaluationCollection);
pushEval(con.subs.get());
parents.emplace_back(con);
return true;
}
void exitConstruct() {
popEval();
parents.pop_back();
}
// When we enter a module structure, we want to build a new module and
// set the builder's function cursor to point to it.
template <typename A>
bool enterModule(const A &func) {
auto &unit = addUnit(pft::ModuleLikeUnit{func, parents.back()});
funclist = &unit.funcs;
parents.emplace_back(unit);
return true;
}
void exitModule() {
parents.pop_back();
setFunctListToParentFuncs();
}
template <typename A>
A &addUnit(A &&unit) {
pgm->getUnits().emplace_back(std::move(unit));
return std::get<A>(pgm->getUnits().back());
}
template <typename A>
A &addFunc(A &&func) {
if (funclist) {
funclist->emplace_back(std::move(func));
return funclist->back();
}
return addUnit(std::move(func));
}
/// move the Evaluation to the end of the current list
pft::Evaluation &addEval(pft::Evaluation &&eval) {
assert(funclist && "not in a function");
assert(evallist.size() > 0);
evallist.back()->emplace_back(std::move(eval));
return evallist.back()->back();
}
/// push a new list on the stack of Evaluation lists
void pushEval(pft::EvaluationCollection *eval) {
assert(funclist && "not in a function");
assert(eval && eval->empty() && "evaluation list isn't correct");
evallist.emplace_back(eval);
}
/// pop the current list and return to the last Evaluation list
void popEval() {
assert(funclist && "not in a function");
evallist.pop_back();
}
std::unique_ptr<pft::Program> pgm;
/// funclist points to FunctionLikeUnit::funcs list (resp.
/// ModuleLikeUnit::funcs) when building a FunctionLikeUnit (resp.
/// ModuleLikeUnit) to store internal procedures (resp. module procedures).
/// Otherwise (e.g. when building the top level Program), it is null.
std::list<pft::FunctionLikeUnit> *funclist{nullptr};
/// evallist is a stack of pointer to FunctionLikeUnit::evals (or
/// Evaluation::subs) that are being build.
std::vector<pft::EvaluationCollection *> evallist;
std::vector<pft::ParentType> parents;
};
template <typename Label, typename A>
constexpr bool hasLabel(const A &stmt) {
auto isLabel{
[](const auto &v) { return std::holds_alternative<Label>(v.u); }};
if constexpr (std::is_same_v<A, parser::ReadStmt> ||
std::is_same_v<A, parser::WriteStmt>) {
return std::any_of(std::begin(stmt.controls), std::end(stmt.controls),
isLabel);
}
if constexpr (std::is_same_v<A, parser::WaitStmt>) {
return std::any_of(std::begin(stmt.v), std::end(stmt.v), isLabel);
}
if constexpr (std::is_same_v<Label, parser::ErrLabel>) {
if constexpr (common::HasMember<
A, std::tuple<parser::OpenStmt, parser::CloseStmt,
parser::BackspaceStmt, parser::EndfileStmt,
parser::RewindStmt, parser::FlushStmt>>)
return std::any_of(std::begin(stmt.v), std::end(stmt.v), isLabel);
if constexpr (std::is_same_v<A, parser::InquireStmt>) {
const auto &specifiers{std::get<std::list<parser::InquireSpec>>(stmt.u)};
return std::any_of(std::begin(specifiers), std::end(specifiers), isLabel);
}
}
return false;
}
bool hasAltReturns(const parser::CallStmt &callStmt) {
const auto &args{std::get<std::list<parser::ActualArgSpec>>(callStmt.v.t)};
for (const auto &arg : args) {
const auto &actual{std::get<parser::ActualArg>(arg.t)};
if (std::holds_alternative<parser::AltReturnSpec>(actual.u))
return true;
}
return false;
}
/// Determine if `callStmt` has alternate returns and if so set `e` to be the
/// origin of a switch-like control flow
///
/// \param cstr points to the current construct. It may be null at the top-level
/// of a FunctionLikeUnit.
void altRet(pft::Evaluation &evaluation, const parser::CallStmt &callStmt,
pft::Evaluation *cstr) {
if (hasAltReturns(callStmt))
evaluation.setCFG(pft::CFGAnnotation::Switch, cstr);
}
/// \param cstr points to the current construct. It may be null at the top-level
/// of a FunctionLikeUnit.
void annotateEvalListCFG(pft::EvaluationCollection &evaluationCollection,
pft::Evaluation *cstr) {
bool nextIsTarget = false;
for (auto &eval : evaluationCollection) {
eval.isTarget = nextIsTarget;
nextIsTarget = false;
if (auto *subs{eval.getConstructEvals()}) {
annotateEvalListCFG(*subs, &eval);
// assume that the entry and exit are both possible branch targets
nextIsTarget = true;
}
if (eval.isActionOrGenerated() && eval.lab.has_value())
eval.isTarget = true;
eval.visit(common::visitors{
[&](const parser::CallStmt &statement) {
altRet(eval, statement, cstr);
},
[&](const parser::CycleStmt &) {
eval.setCFG(pft::CFGAnnotation::Goto, cstr);
},
[&](const parser::ExitStmt &) {
eval.setCFG(pft::CFGAnnotation::Goto, cstr);
},
[&](const parser::FailImageStmt &) {
eval.setCFG(pft::CFGAnnotation::Terminate, cstr);
},
[&](const parser::GotoStmt &) {
eval.setCFG(pft::CFGAnnotation::Goto, cstr);
},
[&](const parser::IfStmt &) {
eval.setCFG(pft::CFGAnnotation::CondGoto, cstr);
},
[&](const parser::ReturnStmt &) {
eval.setCFG(pft::CFGAnnotation::Return, cstr);
},
[&](const parser::StopStmt &) {
eval.setCFG(pft::CFGAnnotation::Terminate, cstr);
},
[&](const parser::ArithmeticIfStmt &) {
eval.setCFG(pft::CFGAnnotation::Switch, cstr);
},
[&](const parser::AssignedGotoStmt &) {
eval.setCFG(pft::CFGAnnotation::IndGoto, cstr);
},
[&](const parser::ComputedGotoStmt &) {
eval.setCFG(pft::CFGAnnotation::Switch, cstr);
},
[&](const parser::WhereStmt &) {
// fir.loop + fir.where around the next stmt
eval.isTarget = true;
eval.setCFG(pft::CFGAnnotation::Iterative, cstr);
},
[&](const parser::ForallStmt &) {
// fir.loop around the next stmt
eval.isTarget = true;
eval.setCFG(pft::CFGAnnotation::Iterative, cstr);
},
[&](pft::CGJump &) { eval.setCFG(pft::CFGAnnotation::Goto, cstr); },
[&](const parser::SelectCaseStmt &) {
eval.setCFG(pft::CFGAnnotation::Switch, cstr);
},
[&](const parser::NonLabelDoStmt &) {
eval.isTarget = true;
eval.setCFG(pft::CFGAnnotation::Iterative, cstr);
},
[&](const parser::EndDoStmt &) {
eval.isTarget = true;
eval.setCFG(pft::CFGAnnotation::Goto, cstr);
},
[&](const parser::IfThenStmt &) {
eval.setCFG(pft::CFGAnnotation::CondGoto, cstr);
},
[&](const parser::ElseIfStmt &) {
eval.setCFG(pft::CFGAnnotation::CondGoto, cstr);
},
[&](const parser::SelectRankStmt &) {
eval.setCFG(pft::CFGAnnotation::Switch, cstr);
},
[&](const parser::SelectTypeStmt &) {
eval.setCFG(pft::CFGAnnotation::Switch, cstr);
},
[&](const parser::WhereConstruct &) {
// mark the WHERE as if it were a DO loop
eval.isTarget = true;
eval.setCFG(pft::CFGAnnotation::Iterative, cstr);
},
[&](const parser::WhereConstructStmt &) {
eval.setCFG(pft::CFGAnnotation::CondGoto, cstr);
},
[&](const parser::MaskedElsewhereStmt &) {
eval.isTarget = true;
eval.setCFG(pft::CFGAnnotation::CondGoto, cstr);
},
[&](const parser::ForallConstructStmt &) {
eval.isTarget = true;
eval.setCFG(pft::CFGAnnotation::Iterative, cstr);
},
[&](const auto &stmt) {
// Handle statements with similar impact on control flow
using IoStmts = std::tuple<parser::BackspaceStmt, parser::CloseStmt,
parser::EndfileStmt, parser::FlushStmt,
parser::InquireStmt, parser::OpenStmt,
parser::ReadStmt, parser::RewindStmt,
parser::WaitStmt, parser::WriteStmt>;
using TargetStmts =
std::tuple<parser::EndAssociateStmt, parser::EndBlockStmt,
parser::CaseStmt, parser::EndSelectStmt,
parser::EndChangeTeamStmt, parser::EndCriticalStmt,
parser::ElseStmt, parser::EndIfStmt,
parser::SelectRankCaseStmt, parser::TypeGuardStmt,
parser::ElsewhereStmt, parser::EndWhereStmt,
parser::EndForallStmt>;
using DoNothingConstructStmts =
std::tuple<parser::BlockStmt, parser::AssociateStmt,
parser::CriticalStmt, parser::ChangeTeamStmt>;
using A = std::decay_t<decltype(stmt)>;
if constexpr (common::HasMember<A, IoStmts>) {
if (hasLabel<parser::ErrLabel>(stmt) ||
hasLabel<parser::EorLabel>(stmt) ||
hasLabel<parser::EndLabel>(stmt))
eval.setCFG(pft::CFGAnnotation::IoSwitch, cstr);
} else if constexpr (common::HasMember<A, TargetStmts>) {
eval.isTarget = true;
} else if constexpr (common::HasMember<A, DoNothingConstructStmts>) {
// Explicitly do nothing for these construct statements
} else {
static_assert(!pft::isConstructStmt<A>,
"All ConstructStmts impact on the control flow "
"should be explicitly handled");
}
/* else do nothing */
},
});
}
}
/// Annotate the PFT with CFG source decorations (see CFGAnnotation) and mark
/// potential branch targets
inline void annotateFuncCFG(pft::FunctionLikeUnit &functionLikeUnit) {
annotateEvalListCFG(functionLikeUnit.evals, nullptr);
for (auto &internalFunc : functionLikeUnit.funcs)
annotateFuncCFG(internalFunc);
}
class PFTDumper {
public:
void dumpPFT(llvm::raw_ostream &outputStream, pft::Program &pft) {
for (auto &unit : pft.getUnits()) {
std::visit(common::visitors{
[&](pft::BlockDataUnit &unit) {
outputStream << getNodeIndex(unit) << " ";
outputStream << "BlockData: ";
outputStream << "\nEndBlockData\n\n";
},
[&](pft::FunctionLikeUnit &func) {
dumpFunctionLikeUnit(outputStream, func);
},
[&](pft::ModuleLikeUnit &unit) {
dumpModuleLikeUnit(outputStream, unit);
},
},
unit);
}
resetIndexes();
}
llvm::StringRef evalName(pft::Evaluation &eval) {
return eval.visit(common::visitors{
[](const pft::CGJump) { return "CGJump"; },
[](const auto &parseTreeNode) {
return parser::ParseTreeDumper::GetNodeName(parseTreeNode);
},
});
}
void dumpEvalList(llvm::raw_ostream &outputStream,
pft::EvaluationCollection &evaluationCollection,
int indent = 1) {
static const std::string white{" ++"};
std::string indentString{white.substr(0, indent * 2)};
for (pft::Evaluation &eval : evaluationCollection) {
outputStream << indentString << getNodeIndex(eval) << " ";
llvm::StringRef name{evalName(eval)};
if (auto *subs{eval.getConstructEvals()}) {
outputStream << "<<" << name << ">>";
outputStream << "\n";
dumpEvalList(outputStream, *subs, indent + 1);
outputStream << indentString << "<<End" << name << ">>\n";
} else {
outputStream << name;
outputStream << ": " << eval.pos.ToString() + "\n";
}
}
}
void dumpFunctionLikeUnit(llvm::raw_ostream &outputStream,
pft::FunctionLikeUnit &functionLikeUnit) {
outputStream << getNodeIndex(functionLikeUnit) << " ";
llvm::StringRef unitKind{};
std::string name{};
std::string header{};
if (functionLikeUnit.beginStmt) {
std::visit(
common::visitors{
[&](const parser::Statement<parser::ProgramStmt> *statement) {
unitKind = "Program";
name = statement->statement.v.ToString();
},
[&](const parser::Statement<parser::FunctionStmt> *statement) {
unitKind = "Function";
name =
std::get<parser::Name>(statement->statement.t).ToString();
header = statement->source.ToString();
},
[&](const parser::Statement<parser::SubroutineStmt> *statement) {
unitKind = "Subroutine";
name =
std::get<parser::Name>(statement->statement.t).ToString();
header = statement->source.ToString();
},
[&](const parser::Statement<parser::MpSubprogramStmt>
*statement) {
unitKind = "MpSubprogram";
name = statement->statement.v.ToString();
header = statement->source.ToString();
},
[&](auto *) {},
},
*functionLikeUnit.beginStmt);
} else {
unitKind = "Program";
name = "<anonymous>";
}
outputStream << unitKind << ' ' << name;
if (header.size())
outputStream << ": " << header;
outputStream << '\n';
dumpEvalList(outputStream, functionLikeUnit.evals);
if (!functionLikeUnit.funcs.empty()) {
outputStream << "\nContains\n";
for (auto &func : functionLikeUnit.funcs)
dumpFunctionLikeUnit(outputStream, func);
outputStream << "EndContains\n";
}
outputStream << "End" << unitKind << ' ' << name << "\n\n";
}
void dumpModuleLikeUnit(llvm::raw_ostream &outputStream,
pft::ModuleLikeUnit &moduleLikeUnit) {
outputStream << getNodeIndex(moduleLikeUnit) << " ";
outputStream << "ModuleLike: ";
outputStream << "\nContains\n";
for (auto &func : moduleLikeUnit.funcs)
dumpFunctionLikeUnit(outputStream, func);
outputStream << "EndContains\nEndModuleLike\n\n";
}
template <typename T>
std::size_t getNodeIndex(const T &node) {
auto addr{static_cast<const void *>(&node)};
auto it{nodeIndexes.find(addr)};
if (it != nodeIndexes.end()) {
return it->second;
}
nodeIndexes.try_emplace(addr, nextIndex);
return nextIndex++;
}
std::size_t getNodeIndex(const pft::Program &) { return 0; }
void resetIndexes() {
nodeIndexes.clear();
nextIndex = 1;
}
private:
llvm::DenseMap<const void *, std::size_t> nodeIndexes;
std::size_t nextIndex{1}; // 0 is the root
};
template <typename A, typename T>
pft::FunctionLikeUnit::FunctionStatement getFunctionStmt(const T &func) {
return pft::FunctionLikeUnit::FunctionStatement{
&std::get<parser::Statement<A>>(func.t)};
}
template <typename A, typename T>
pft::ModuleLikeUnit::ModuleStatement getModuleStmt(const T &mod) {
return pft::ModuleLikeUnit::ModuleStatement{
&std::get<parser::Statement<A>>(mod.t)};
}
} // namespace
pft::FunctionLikeUnit::FunctionLikeUnit(const parser::MainProgram &func,
const pft::ParentType &parent)
: ProgramUnit{func, parent} {
auto &ps{
std::get<std::optional<parser::Statement<parser::ProgramStmt>>>(func.t)};
if (ps.has_value()) {
const parser::Statement<parser::ProgramStmt> &statement{ps.value()};
beginStmt = &statement;
}
endStmt = getFunctionStmt<parser::EndProgramStmt>(func);
}
pft::FunctionLikeUnit::FunctionLikeUnit(const parser::FunctionSubprogram &func,
const pft::ParentType &parent)
: ProgramUnit{func, parent},
beginStmt{getFunctionStmt<parser::FunctionStmt>(func)},
endStmt{getFunctionStmt<parser::EndFunctionStmt>(func)} {}
pft::FunctionLikeUnit::FunctionLikeUnit(
const parser::SubroutineSubprogram &func, const pft::ParentType &parent)
: ProgramUnit{func, parent},
beginStmt{getFunctionStmt<parser::SubroutineStmt>(func)},
endStmt{getFunctionStmt<parser::EndSubroutineStmt>(func)} {}
pft::FunctionLikeUnit::FunctionLikeUnit(
const parser::SeparateModuleSubprogram &func, const pft::ParentType &parent)
: ProgramUnit{func, parent},
beginStmt{getFunctionStmt<parser::MpSubprogramStmt>(func)},
endStmt{getFunctionStmt<parser::EndMpSubprogramStmt>(func)} {}
pft::ModuleLikeUnit::ModuleLikeUnit(const parser::Module &m,
const pft::ParentType &parent)
: ProgramUnit{m, parent}, beginStmt{getModuleStmt<parser::ModuleStmt>(m)},
endStmt{getModuleStmt<parser::EndModuleStmt>(m)} {}
pft::ModuleLikeUnit::ModuleLikeUnit(const parser::Submodule &m,
const pft::ParentType &parent)
: ProgramUnit{m, parent}, beginStmt{getModuleStmt<parser::SubmoduleStmt>(
m)},
endStmt{getModuleStmt<parser::EndSubmoduleStmt>(m)} {}
pft::BlockDataUnit::BlockDataUnit(const parser::BlockData &bd,
const pft::ParentType &parent)
: ProgramUnit{bd, parent} {}
std::unique_ptr<pft::Program> createPFT(const parser::Program &root) {
PFTBuilder walker;
Walk(root, walker);
return walker.result();
}
void annotateControl(pft::Program &pft) {
for (auto &unit : pft.getUnits()) {
std::visit(common::visitors{
[](pft::BlockDataUnit &) {},
[](pft::FunctionLikeUnit &func) { annotateFuncCFG(func); },
[](pft::ModuleLikeUnit &unit) {
for (auto &func : unit.funcs)
annotateFuncCFG(func);
},
},
unit);
}
}
/// Dump a PFT.
void dumpPFT(llvm::raw_ostream &outputStream, pft::Program &pft) {
PFTDumper{}.dumpPFT(outputStream, pft);
}
} // namespace Fortran::lower