[flang] [llvm] [flang][OpenMP] Overhaul implementation of ATOMIC construct (PR #137852)
Tom Eccles via llvm-commits
llvm-commits at lists.llvm.org
Wed May 28 10:19:08 PDT 2025
================
@@ -2656,527 +2665,1857 @@ void OmpStructureChecker::Leave(const parser::OmpEndBlockDirective &x) {
}
}
-inline void OmpStructureChecker::ErrIfAllocatableVariable(
- const parser::Variable &var) {
- // Err out if the given symbol has
- // ALLOCATABLE attribute
- if (const auto *e{GetExpr(context_, var)})
- for (const Symbol &symbol : evaluate::CollectSymbols(*e))
- if (IsAllocatable(symbol)) {
- const auto &designator =
- std::get<common::Indirection<parser::Designator>>(var.u);
- const auto *dataRef =
- std::get_if<parser::DataRef>(&designator.value().u);
- const parser::Name *name =
- dataRef ? std::get_if<parser::Name>(&dataRef->u) : nullptr;
- if (name)
- context_.Say(name->source,
- "%s must not have ALLOCATABLE "
- "attribute"_err_en_US,
- name->ToString());
+/// parser::Block is a list of executable constructs, parser::BlockConstruct
+/// is Fortran's BLOCK/ENDBLOCK construct.
+/// Strip the outermost BlockConstructs, return the reference to the Block
+/// in the executable part of the innermost of the stripped constructs.
+/// Specifically, if the given `block` has a single entry (it's a list), and
+/// the entry is a BlockConstruct, get the Block contained within. Repeat
+/// this step as many times as possible.
+static const parser::Block &GetInnermostExecPart(const parser::Block &block) {
+ const parser::Block *iter{&block};
+ while (iter->size() == 1) {
+ const parser::ExecutionPartConstruct &ep{iter->front()};
+ if (auto *exec{std::get_if<parser::ExecutableConstruct>(&ep.u)}) {
+ using BlockConstruct = common::Indirection<parser::BlockConstruct>;
+ if (auto *bc{std::get_if<BlockConstruct>(&exec->u)}) {
+ iter = &std::get<parser::Block>(bc->value().t);
+ continue;
}
+ }
+ break;
+ }
+ return *iter;
}
-inline void OmpStructureChecker::ErrIfLHSAndRHSSymbolsMatch(
- const parser::Variable &var, const parser::Expr &expr) {
- // Err out if the symbol on the LHS is also used on the RHS of the assignment
- // statement
- const auto *e{GetExpr(context_, expr)};
- const auto *v{GetExpr(context_, var)};
- if (e && v) {
- auto vSyms{evaluate::GetSymbolVector(*v)};
- const Symbol &varSymbol = vSyms.front();
- for (const Symbol &symbol : evaluate::GetSymbolVector(*e)) {
- if (varSymbol == symbol) {
- const common::Indirection<parser::Designator> *designator =
- std::get_if<common::Indirection<parser::Designator>>(&expr.u);
- if (designator) {
- auto *z{var.typedExpr.get()};
- auto *c{expr.typedExpr.get()};
- if (z->v == c->v) {
- context_.Say(expr.source,
- "RHS expression on atomic assignment statement cannot access '%s'"_err_en_US,
- var.GetSource());
- }
- } else {
- context_.Say(expr.source,
- "RHS expression on atomic assignment statement cannot access '%s'"_err_en_US,
- var.GetSource());
- }
- }
+// There is no consistent way to get the source of a given ActionStmt, so
+// extract the source information from Statement<ActionStmt> when we can,
+// and keep it around for error reporting in further analyses.
+struct SourcedActionStmt {
+ const parser::ActionStmt *stmt{nullptr};
+ parser::CharBlock source;
+
+ operator bool() const { return stmt != nullptr; }
+};
+
+struct AnalyzedCondStmt {
+ SomeExpr cond{evaluate::NullPointer{}}; // Default ctor is deleted
+ parser::CharBlock source;
+ SourcedActionStmt ift, iff;
+};
+
+static SourcedActionStmt GetActionStmt(
+ const parser::ExecutionPartConstruct *x) {
+ if (x == nullptr) {
+ return SourcedActionStmt{};
+ }
+ if (auto *exec{std::get_if<parser::ExecutableConstruct>(&x->u)}) {
+ using ActionStmt = parser::Statement<parser::ActionStmt>;
+ if (auto *stmt{std::get_if<ActionStmt>(&exec->u)}) {
+ return SourcedActionStmt{&stmt->statement, stmt->source};
}
}
+ return SourcedActionStmt{};
}
-inline void OmpStructureChecker::ErrIfNonScalarAssignmentStmt(
- const parser::Variable &var, const parser::Expr &expr) {
- // Err out if either the variable on the LHS or the expression on the RHS of
- // the assignment statement are non-scalar (i.e. have rank > 0 or is of
- // CHARACTER type)
- const auto *e{GetExpr(context_, expr)};
- const auto *v{GetExpr(context_, var)};
- if (e && v) {
- if (e->Rank() != 0 ||
- (e->GetType().has_value() &&
- e->GetType().value().category() == common::TypeCategory::Character))
- context_.Say(expr.source,
- "Expected scalar expression "
- "on the RHS of atomic assignment "
- "statement"_err_en_US);
- if (v->Rank() != 0 ||
- (v->GetType().has_value() &&
- v->GetType()->category() == common::TypeCategory::Character))
- context_.Say(var.GetSource(),
- "Expected scalar variable "
- "on the LHS of atomic assignment "
- "statement"_err_en_US);
- }
-}
-
-template <typename T, typename D>
-bool OmpStructureChecker::IsOperatorValid(const T &node, const D &variable) {
- using AllowedBinaryOperators =
- std::variant<parser::Expr::Add, parser::Expr::Multiply,
- parser::Expr::Subtract, parser::Expr::Divide, parser::Expr::AND,
- parser::Expr::OR, parser::Expr::EQV, parser::Expr::NEQV>;
- using BinaryOperators = std::variant<parser::Expr::Add,
- parser::Expr::Multiply, parser::Expr::Subtract, parser::Expr::Divide,
- parser::Expr::AND, parser::Expr::OR, parser::Expr::EQV,
- parser::Expr::NEQV, parser::Expr::Power, parser::Expr::Concat,
- parser::Expr::LT, parser::Expr::LE, parser::Expr::EQ, parser::Expr::NE,
- parser::Expr::GE, parser::Expr::GT>;
-
- if constexpr (common::HasMember<T, BinaryOperators>) {
- const auto &variableName{variable.GetSource().ToString()};
- const auto &exprLeft{std::get<0>(node.t)};
- const auto &exprRight{std::get<1>(node.t)};
- if ((exprLeft.value().source.ToString() != variableName) &&
- (exprRight.value().source.ToString() != variableName)) {
- context_.Say(variable.GetSource(),
- "Atomic update statement should be of form "
- "`%s = %s operator expr` OR `%s = expr operator %s`"_err_en_US,
- variableName, variableName, variableName, variableName);
- }
- return common::HasMember<T, AllowedBinaryOperators>;
+static SourcedActionStmt GetActionStmt(const parser::Block &block) {
+ if (block.size() == 1) {
+ return GetActionStmt(&block.front());
}
- return false;
+ return SourcedActionStmt{};
}
-void OmpStructureChecker::CheckAtomicCaptureStmt(
- const parser::AssignmentStmt &assignmentStmt) {
- const auto &var{std::get<parser::Variable>(assignmentStmt.t)};
- const auto &expr{std::get<parser::Expr>(assignmentStmt.t)};
- common::visit(
- common::visitors{
- [&](const common::Indirection<parser::Designator> &designator) {
- const auto *dataRef =
- std::get_if<parser::DataRef>(&designator.value().u);
- const auto *name =
- dataRef ? std::get_if<parser::Name>(&dataRef->u) : nullptr;
- if (name && IsAllocatable(*name->symbol))
- context_.Say(name->source,
- "%s must not have ALLOCATABLE "
- "attribute"_err_en_US,
- name->ToString());
- },
- [&](const auto &) {
- // Anything other than a `parser::Designator` is not allowed
- context_.Say(expr.source,
- "Expected scalar variable "
- "of intrinsic type on RHS of atomic "
- "assignment statement"_err_en_US);
- }},
- expr.u);
- ErrIfLHSAndRHSSymbolsMatch(var, expr);
- ErrIfNonScalarAssignmentStmt(var, expr);
-}
-
-void OmpStructureChecker::CheckAtomicWriteStmt(
- const parser::AssignmentStmt &assignmentStmt) {
- const auto &var{std::get<parser::Variable>(assignmentStmt.t)};
- const auto &expr{std::get<parser::Expr>(assignmentStmt.t)};
- ErrIfAllocatableVariable(var);
- ErrIfLHSAndRHSSymbolsMatch(var, expr);
- ErrIfNonScalarAssignmentStmt(var, expr);
-}
-
-void OmpStructureChecker::CheckAtomicUpdateStmt(
- const parser::AssignmentStmt &assignment) {
- const auto &expr{std::get<parser::Expr>(assignment.t)};
- const auto &var{std::get<parser::Variable>(assignment.t)};
- bool isIntrinsicProcedure{false};
- bool isValidOperator{false};
- common::visit(
- common::visitors{
- [&](const common::Indirection<parser::FunctionReference> &x) {
- isIntrinsicProcedure = true;
- const auto &procedureDesignator{
- std::get<parser::ProcedureDesignator>(x.value().v.t)};
- const parser::Name *name{
- std::get_if<parser::Name>(&procedureDesignator.u)};
- if (name &&
- !(name->source == "max" || name->source == "min" ||
- name->source == "iand" || name->source == "ior" ||
- name->source == "ieor")) {
- context_.Say(expr.source,
- "Invalid intrinsic procedure name in "
- "OpenMP ATOMIC (UPDATE) statement"_err_en_US);
- }
- },
- [&](const auto &x) {
- if (!IsOperatorValid(x, var)) {
- context_.Say(expr.source,
- "Invalid or missing operator in atomic update "
- "statement"_err_en_US);
- } else
- isValidOperator = true;
- },
- },
- expr.u);
- if (const auto *e{GetExpr(context_, expr)}) {
- const auto *v{GetExpr(context_, var)};
- if (e->Rank() != 0 ||
- (e->GetType().has_value() &&
- e->GetType().value().category() == common::TypeCategory::Character))
- context_.Say(expr.source,
- "Expected scalar expression "
- "on the RHS of atomic update assignment "
- "statement"_err_en_US);
- if (v->Rank() != 0 ||
- (v->GetType().has_value() &&
- v->GetType()->category() == common::TypeCategory::Character))
- context_.Say(var.GetSource(),
- "Expected scalar variable "
- "on the LHS of atomic update assignment "
- "statement"_err_en_US);
- auto vSyms{evaluate::GetSymbolVector(*v)};
- const Symbol &varSymbol = vSyms.front();
- int numOfSymbolMatches{0};
- SymbolVector exprSymbols{evaluate::GetSymbolVector(*e)};
- for (const Symbol &symbol : exprSymbols) {
- if (varSymbol == symbol) {
- numOfSymbolMatches++;
- }
- }
- if (isIntrinsicProcedure) {
- std::string varName = var.GetSource().ToString();
- if (numOfSymbolMatches != 1)
- context_.Say(expr.source,
- "Intrinsic procedure"
- " arguments in atomic update statement"
- " must have exactly one occurence of '%s'"_err_en_US,
- varName);
- else if (varSymbol != exprSymbols.front() &&
- varSymbol != exprSymbols.back())
- context_.Say(expr.source,
- "Atomic update statement "
- "should be of the form `%s = intrinsic_procedure(%s, expr_list)` "
- "OR `%s = intrinsic_procedure(expr_list, %s)`"_err_en_US,
- varName, varName, varName, varName);
- } else if (isValidOperator) {
- if (numOfSymbolMatches != 1)
- context_.Say(expr.source,
- "Exactly one occurence of '%s' "
- "expected on the RHS of atomic update assignment statement"_err_en_US,
- var.GetSource().ToString());
- }
+// Compute the `evaluate::Assignment` from parser::ActionStmt. The assumption
+// is that the ActionStmt will be either an assignment or a pointer-assignment,
+// otherwise return std::nullopt.
+static std::optional<evaluate::Assignment> GetEvaluateAssignment(
+ const parser::ActionStmt *x) {
+ if (x == nullptr) {
+ return std::nullopt;
}
- ErrIfAllocatableVariable(var);
+ using AssignmentStmt = common::Indirection<parser::AssignmentStmt>;
+ using PointerAssignmentStmt =
+ common::Indirection<parser::PointerAssignmentStmt>;
+ using TypedAssignment = parser::AssignmentStmt::TypedAssignment;
+
+ return common::visit(
+ [](auto &&s) -> std::optional<evaluate::Assignment> {
+ using BareS = llvm::remove_cvref_t<decltype(s)>;
+ if constexpr (std::is_same_v<BareS, AssignmentStmt> ||
+ std::is_same_v<BareS, PointerAssignmentStmt>) {
+ const TypedAssignment &typed{s.value().typedAssignment};
+ // ForwardOwningPointer typedAssignment
+ // `- GenericAssignmentWrapper ^.get()
+ // `- std::optional<Assignment> ^->v
+ return typed.get()->v;
+ } else {
+ return std::nullopt;
+ }
+ },
+ x->u);
}
-void OmpStructureChecker::CheckAtomicCompareConstruct(
- const parser::OmpAtomicCompare &atomicCompareConstruct) {
+static std::optional<AnalyzedCondStmt> AnalyzeConditionalStmt(
+ const parser::ExecutionPartConstruct *x) {
+ if (x == nullptr) {
+ return std::nullopt;
+ }
- // TODO: Check that the if-stmt is `if (var == expr) var = new`
- // [with or without then/end-do]
+ // Extract the evaluate::Expr from ScalarLogicalExpr.
+ auto getFromLogical{[](const parser::ScalarLogicalExpr &logical) {
+ // ScalarLogicalExpr is Scalar<Logical<common::Indirection<Expr>>>
+ const parser::Expr &expr{logical.thing.thing.value()};
+ return GetEvaluateExpr(expr);
+ }};
- unsigned version{context_.langOptions().OpenMPVersion};
- if (version < 51) {
- context_.Say(atomicCompareConstruct.source,
- "%s construct not allowed in %s, %s"_err_en_US,
- atomicCompareConstruct.source, ThisVersion(version), TryVersion(51));
- }
-
- // TODO: More work needed here. Some of the Update restrictions need to
- // be added, but Update isn't the same either.
-}
-
-// TODO: Allow cond-update-stmt once compare clause is supported.
-void OmpStructureChecker::CheckAtomicCaptureConstruct(
- const parser::OmpAtomicCapture &atomicCaptureConstruct) {
- const parser::AssignmentStmt &stmt1 =
- std::get<parser::OmpAtomicCapture::Stmt1>(atomicCaptureConstruct.t)
- .v.statement;
- const auto &stmt1Var{std::get<parser::Variable>(stmt1.t)};
- const auto &stmt1Expr{std::get<parser::Expr>(stmt1.t)};
- const auto *v1 = GetExpr(context_, stmt1Var);
- const auto *e1 = GetExpr(context_, stmt1Expr);
-
- const parser::AssignmentStmt &stmt2 =
- std::get<parser::OmpAtomicCapture::Stmt2>(atomicCaptureConstruct.t)
- .v.statement;
- const auto &stmt2Var{std::get<parser::Variable>(stmt2.t)};
- const auto &stmt2Expr{std::get<parser::Expr>(stmt2.t)};
- const auto *v2 = GetExpr(context_, stmt2Var);
- const auto *e2 = GetExpr(context_, stmt2Expr);
-
- if (e1 && v1 && e2 && v2) {
- if (semantics::checkForSingleVariableOnRHS(stmt1)) {
- CheckAtomicCaptureStmt(stmt1);
- if (semantics::checkForSymbolMatch(v2, e2)) {
- // ATOMIC CAPTURE construct is of the form [capture-stmt, update-stmt]
- CheckAtomicUpdateStmt(stmt2);
- } else {
- // ATOMIC CAPTURE construct is of the form [capture-stmt, write-stmt]
- CheckAtomicWriteStmt(stmt2);
+ // Recognize either
+ // ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> IfStmt, or
+ // ExecutionPartConstruct -> ExecutableConstruct -> IfConstruct.
+
+ if (auto &&action{GetActionStmt(x)}) {
+ if (auto *ifs{std::get_if<common::Indirection<parser::IfStmt>>(
+ &action.stmt->u)}) {
+ const parser::IfStmt &s{ifs->value()};
+ auto &&maybeCond{
+ getFromLogical(std::get<parser::ScalarLogicalExpr>(s.t))};
+ auto &thenStmt{
+ std::get<parser::UnlabeledStatement<parser::ActionStmt>>(s.t)};
+ if (maybeCond) {
+ return AnalyzedCondStmt{std::move(*maybeCond), action.source,
+ SourcedActionStmt{&thenStmt.statement, thenStmt.source},
+ SourcedActionStmt{}};
}
- if (!(*e1 == *v2)) {
- context_.Say(stmt1Expr.source,
- "Captured variable/array element/derived-type component %s expected to be assigned in the second statement of ATOMIC CAPTURE construct"_err_en_US,
- stmt1Expr.source);
- }
- } else if (semantics::checkForSymbolMatch(v1, e1) &&
- semantics::checkForSingleVariableOnRHS(stmt2)) {
- // ATOMIC CAPTURE construct is of the form [update-stmt, capture-stmt]
- CheckAtomicUpdateStmt(stmt1);
- CheckAtomicCaptureStmt(stmt2);
- // Variable updated in stmt1 should be captured in stmt2
- if (!(*v1 == *e2)) {
- context_.Say(stmt1Var.GetSource(),
- "Updated variable/array element/derived-type component %s expected to be captured in the second statement of ATOMIC CAPTURE construct"_err_en_US,
- stmt1Var.GetSource());
- }
- } else {
- context_.Say(stmt1Expr.source,
- "Invalid ATOMIC CAPTURE construct statements. Expected one of [update-stmt, capture-stmt], [capture-stmt, update-stmt], or [capture-stmt, write-stmt]"_err_en_US);
}
+ return std::nullopt;
}
-}
-void OmpStructureChecker::CheckAtomicMemoryOrderClause(
- const parser::OmpAtomicClauseList *leftHandClauseList,
- const parser::OmpAtomicClauseList *rightHandClauseList) {
- int numMemoryOrderClause{0};
- int numFailClause{0};
- auto checkForValidMemoryOrderClause = [&](const parser::OmpAtomicClauseList
- *clauseList) {
- for (const auto &clause : clauseList->v) {
- if (std::get_if<parser::OmpFailClause>(&clause.u)) {
- numFailClause++;
- if (numFailClause > 1) {
- context_.Say(clause.source,
- "More than one FAIL clause not allowed on OpenMP ATOMIC construct"_err_en_US);
- return;
+ if (auto *exec{std::get_if<parser::ExecutableConstruct>(&x->u)}) {
+ if (auto *ifc{
+ std::get_if<common::Indirection<parser::IfConstruct>>(&exec->u)}) {
+ using ElseBlock = parser::IfConstruct::ElseBlock;
+ using ElseIfBlock = parser::IfConstruct::ElseIfBlock;
+ const parser::IfConstruct &s{ifc->value()};
+
+ if (!std::get<std::list<ElseIfBlock>>(s.t).empty()) {
+ // Not expecting any else-if statements.
+ return std::nullopt;
+ }
+ auto &stmt{std::get<parser::Statement<parser::IfThenStmt>>(s.t)};
+ auto &&maybeCond{getFromLogical(
+ std::get<parser::ScalarLogicalExpr>(stmt.statement.t))};
+ if (!maybeCond) {
+ return std::nullopt;
+ }
+
+ if (auto &maybeElse{std::get<std::optional<ElseBlock>>(s.t)}) {
+ AnalyzedCondStmt result{std::move(*maybeCond), stmt.source,
+ GetActionStmt(std::get<parser::Block>(s.t)),
+ GetActionStmt(std::get<parser::Block>(maybeElse->t))};
+ if (result.ift.stmt && result.iff.stmt) {
+ return result;
}
} else {
- if (std::get_if<parser::OmpMemoryOrderClause>(&clause.u)) {
- numMemoryOrderClause++;
- if (numMemoryOrderClause > 1) {
- context_.Say(clause.source,
- "More than one memory order clause not allowed on OpenMP ATOMIC construct"_err_en_US);
- return;
- }
+ AnalyzedCondStmt result{std::move(*maybeCond), stmt.source,
+ GetActionStmt(std::get<parser::Block>(s.t))};
+ if (result.ift.stmt) {
+ return result;
}
}
}
- };
- if (leftHandClauseList) {
- checkForValidMemoryOrderClause(leftHandClauseList);
- }
- if (rightHandClauseList) {
- checkForValidMemoryOrderClause(rightHandClauseList);
+ return std::nullopt;
}
-}
-void OmpStructureChecker::Enter(const parser::OpenMPAtomicConstruct &x) {
- common::visit(
- common::visitors{
- [&](const parser::OmpAtomic &atomicConstruct) {
- const auto &dir{std::get<parser::Verbatim>(atomicConstruct.t)};
- PushContextAndClauseSets(
- dir.source, llvm::omp::Directive::OMPD_atomic);
- CheckAtomicUpdateStmt(
- std::get<parser::Statement<parser::AssignmentStmt>>(
- atomicConstruct.t)
- .statement);
- CheckAtomicMemoryOrderClause(
- &std::get<parser::OmpAtomicClauseList>(atomicConstruct.t),
- nullptr);
- CheckHintClause<const parser::OmpAtomicClauseList>(
- &std::get<parser::OmpAtomicClauseList>(atomicConstruct.t),
- nullptr, "ATOMIC");
- },
- [&](const parser::OmpAtomicUpdate &atomicUpdate) {
- const auto &dir{std::get<parser::Verbatim>(atomicUpdate.t)};
- PushContextAndClauseSets(
- dir.source, llvm::omp::Directive::OMPD_atomic);
- CheckAtomicUpdateStmt(
- std::get<parser::Statement<parser::AssignmentStmt>>(
- atomicUpdate.t)
- .statement);
- CheckAtomicMemoryOrderClause(
- &std::get<0>(atomicUpdate.t), &std::get<2>(atomicUpdate.t));
- CheckHintClause<const parser::OmpAtomicClauseList>(
- &std::get<0>(atomicUpdate.t), &std::get<2>(atomicUpdate.t),
- "UPDATE");
- },
- [&](const parser::OmpAtomicRead &atomicRead) {
- const auto &dir{std::get<parser::Verbatim>(atomicRead.t)};
- PushContextAndClauseSets(
- dir.source, llvm::omp::Directive::OMPD_atomic);
- CheckAtomicMemoryOrderClause(
- &std::get<0>(atomicRead.t), &std::get<2>(atomicRead.t));
- CheckHintClause<const parser::OmpAtomicClauseList>(
- &std::get<0>(atomicRead.t), &std::get<2>(atomicRead.t), "READ");
- CheckAtomicCaptureStmt(
- std::get<parser::Statement<parser::AssignmentStmt>>(
- atomicRead.t)
- .statement);
- },
- [&](const parser::OmpAtomicWrite &atomicWrite) {
- const auto &dir{std::get<parser::Verbatim>(atomicWrite.t)};
- PushContextAndClauseSets(
- dir.source, llvm::omp::Directive::OMPD_atomic);
- CheckAtomicMemoryOrderClause(
- &std::get<0>(atomicWrite.t), &std::get<2>(atomicWrite.t));
- CheckHintClause<const parser::OmpAtomicClauseList>(
- &std::get<0>(atomicWrite.t), &std::get<2>(atomicWrite.t),
- "WRITE");
- CheckAtomicWriteStmt(
- std::get<parser::Statement<parser::AssignmentStmt>>(
- atomicWrite.t)
- .statement);
- },
- [&](const parser::OmpAtomicCapture &atomicCapture) {
- const auto &dir{std::get<parser::Verbatim>(atomicCapture.t)};
- PushContextAndClauseSets(
- dir.source, llvm::omp::Directive::OMPD_atomic);
- CheckAtomicMemoryOrderClause(
- &std::get<0>(atomicCapture.t), &std::get<2>(atomicCapture.t));
- CheckHintClause<const parser::OmpAtomicClauseList>(
- &std::get<0>(atomicCapture.t), &std::get<2>(atomicCapture.t),
- "CAPTURE");
- CheckAtomicCaptureConstruct(atomicCapture);
- },
- [&](const parser::OmpAtomicCompare &atomicCompare) {
- const auto &dir{std::get<parser::Verbatim>(atomicCompare.t)};
- PushContextAndClauseSets(
- dir.source, llvm::omp::Directive::OMPD_atomic);
- CheckAtomicMemoryOrderClause(
- &std::get<0>(atomicCompare.t), &std::get<2>(atomicCompare.t));
- CheckHintClause<const parser::OmpAtomicClauseList>(
- &std::get<0>(atomicCompare.t), &std::get<2>(atomicCompare.t),
- "CAPTURE");
- CheckAtomicCompareConstruct(atomicCompare);
- },
- },
- x.u);
+ return std::nullopt;
}
-void OmpStructureChecker::Leave(const parser::OpenMPAtomicConstruct &) {
- dirContext_.pop_back();
+static std::pair<parser::CharBlock, parser::CharBlock> SplitAssignmentSource(
+ parser::CharBlock source) {
+ // Find => in the range, if not found, find = that is not a part of
+ // <=, >=, ==, or /=.
+ auto trim{[](std::string_view v) {
+ const char *begin{v.data()};
+ const char *end{begin + v.size()};
+ while (*begin == ' ' && begin != end) {
+ ++begin;
+ }
+ while (begin != end && end[-1] == ' ') {
+ --end;
+ }
+ assert(begin != end && "Source should not be empty");
+ return parser::CharBlock(begin, end - begin);
+ }};
+
+ std::string_view sv(source.begin(), source.size());
+
+ if (auto where{sv.find("=>")}; where != sv.npos) {
+ std::string_view lhs(sv.data(), where);
+ std::string_view rhs(sv.data() + where + 2, sv.size() - where - 2);
+ return std::make_pair(trim(lhs), trim(rhs));
+ }
+
+ // Go backwards, since all the exclusions above end with a '='.
+ for (size_t next{source.size()}; next > 1; --next) {
+ if (sv[next - 1] == '=' && !llvm::is_contained("<>=/", sv[next - 2])) {
+ std::string_view lhs(sv.data(), next - 1);
+ std::string_view rhs(sv.data() + next, sv.size() - next);
+ return std::make_pair(trim(lhs), trim(rhs));
+ }
+ }
+ llvm_unreachable("Could not find assignment operator");
+}
+
+namespace atomic {
+
+template <typename V> static void MoveAppend(V &accum, V &&other) {
+ for (auto &&s : other) {
+ accum.push_back(std::move(s));
+ }
+}
+
+enum class Operator {
+ Unk,
+ // Operators that are officially allowed in the update operation
+ Add,
+ And,
+ Associated,
+ Div,
+ Eq,
+ Eqv,
+ Ge, // extension
+ Gt,
+ Identity, // extension: x = x is allowed (*), but we should never print
+ // "identity" as the name of the operator
+ Le, // extension
+ Lt,
+ Max,
+ Min,
+ Mul,
+ Ne, // extension
+ Neqv,
+ Or,
+ Sub,
+ // Operators that we recognize for technical reasons
+ True,
+ False,
+ Not,
+ Convert,
+ Resize,
+ Intrinsic,
+ Call,
+ Pow,
+
+ // (*): "x = x + 0" is a valid update statement, but it will be folded
+ // to "x = x" by the time we look at it. Since the source statements
+ // "x = x" and "x = x + 0" will end up looking the same, accept the
+ // former as an extension.
+};
+
+std::string ToString(Operator op) {
+ switch (op) {
+ case Operator::Add:
+ return "+";
+ case Operator::And:
+ return "AND";
+ case Operator::Associated:
+ return "ASSOCIATED";
+ case Operator::Div:
+ return "/";
+ case Operator::Eq:
+ return "==";
+ case Operator::Eqv:
+ return "EQV";
+ case Operator::Ge:
+ return ">=";
+ case Operator::Gt:
+ return ">";
+ case Operator::Identity:
+ return "identity";
----------------
tblah wrote:
>From above:
```
// extension: x = x is allowed (*), but we should never print
// "identity" as the name of the operator
```
maybe this should be an llvm::unreachable, plus some logic at each call?
https://github.com/llvm/llvm-project/pull/137852
More information about the llvm-commits
mailing list