Skip to content

[flang][openacc] Add semantic checks for atomic constructs #149579

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jul 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions flang/include/flang/Evaluate/tools.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#define FORTRAN_EVALUATE_TOOLS_H_

#include "traverse.h"
#include "flang/Common/enum-set.h"
#include "flang/Common/idioms.h"
#include "flang/Common/template.h"
#include "flang/Common/unwrap.h"
Expand Down Expand Up @@ -1397,6 +1398,8 @@ enum class Operator {
True,
};

using OperatorSet = common::EnumSet<Operator, 32>;

std::string ToString(Operator op);

template <typename... Ts, int Kind>
Expand Down Expand Up @@ -1509,9 +1512,18 @@ Operator OperationCode(const evaluate::ProcedureDesignator &proc);
std::pair<operation::Operator, std::vector<Expr<SomeType>>>
GetTopLevelOperation(const Expr<SomeType> &expr);

// Return information about the top-level operation (ignoring parentheses, and
// resizing converts)
std::pair<operation::Operator, std::vector<Expr<SomeType>>>
GetTopLevelOperationIgnoreResizing(const Expr<SomeType> &expr);

// Check if expr is same as x, or a sequence of Convert operations on x.
bool IsSameOrConvertOf(const Expr<SomeType> &expr, const Expr<SomeType> &x);

// Check if the Variable appears as a subexpression of the expression.
bool IsVarSubexpressionOf(
const Expr<SomeType> &var, const Expr<SomeType> &super);

// Strip away any top-level Convert operations (if any exist) and return
// the input value. A ComplexConstructor(x, 0) is also considered as a
// convert operation.
Expand Down
34 changes: 33 additions & 1 deletion flang/lib/Evaluate/tools.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1809,10 +1809,15 @@ operation::Operator operation::OperationCode(const ProcedureDesignator &proc) {
}

std::pair<operation::Operator, std::vector<Expr<SomeType>>>
GetTopLevelOperation(const Expr<SomeType> &expr) {
GetTopLevelOperationIgnoreResizing(const Expr<SomeType> &expr) {
return operation::ArgumentExtractor<true>{}(expr);
}

std::pair<operation::Operator, std::vector<Expr<SomeType>>>
GetTopLevelOperation(const Expr<SomeType> &expr) {
return operation::ArgumentExtractor<false>{}(expr);
}

namespace operation {
struct ConvertCollector
: public Traverse<ConvertCollector,
Expand Down Expand Up @@ -1936,6 +1941,33 @@ bool IsSameOrConvertOf(const Expr<SomeType> &expr, const Expr<SomeType> &x) {
return false;
}
}

struct VariableFinder : public evaluate::AnyTraverse<VariableFinder> {
using Base = evaluate::AnyTraverse<VariableFinder>;
using SomeExpr = Expr<SomeType>;
VariableFinder(const SomeExpr &v) : Base(*this), var(v) {}

using Base::operator();

template <typename T>
bool operator()(const evaluate::Designator<T> &x) const {
return evaluate::AsGenericExpr(common::Clone(x)) == var;
}

template <typename T>
bool operator()(const evaluate::FunctionRef<T> &x) const {
return evaluate::AsGenericExpr(common::Clone(x)) == var;
}

private:
const SomeExpr &var;
};

bool IsVarSubexpressionOf(
const Expr<SomeType> &sub, const Expr<SomeType> &super) {
return VariableFinder{sub}(super);
}

} // namespace Fortran::evaluate

namespace Fortran::semantics {
Expand Down
5 changes: 3 additions & 2 deletions flang/lib/Lower/OpenMP/Atomic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,7 @@ genAtomicUpdate(lower::AbstractConverter &converter,
// This must exist by now.
semantics::SomeExpr rhs = assign.rhs;
semantics::SomeExpr input = *evaluate::GetConvertInput(rhs);
auto [opcode, args] = evaluate::GetTopLevelOperation(input);
auto [opcode, args] = evaluate::GetTopLevelOperationIgnoreResizing(input);
assert(!args.empty() && "Update operation without arguments");

// Pass args as an argument to avoid capturing a structured binding.
Expand All @@ -625,7 +625,8 @@ genAtomicUpdate(lower::AbstractConverter &converter,
// operations with exactly two (non-optional) arguments.
rhs = genReducedMinMax(rhs, atomArg, args);
input = *evaluate::GetConvertInput(rhs);
std::tie(opcode, args) = evaluate::GetTopLevelOperation(input);
std::tie(opcode, args) =
evaluate::GetTopLevelOperationIgnoreResizing(input);
atomArg = nullptr; // No longer valid.
}
for (auto &arg : args) {
Expand Down
226 changes: 216 additions & 10 deletions flang/lib/Semantics/check-acc-structure.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@
//===----------------------------------------------------------------------===//
#include "check-acc-structure.h"
#include "flang/Common/enum-set.h"
#include "flang/Evaluate/tools.h"
#include "flang/Parser/parse-tree.h"
#include "flang/Semantics/symbol.h"
#include "flang/Semantics/tools.h"
#include "flang/Semantics/type.h"
#include "flang/Support/Fortran.h"
#include "llvm/Support/AtomicOrdering.h"

#include <optional>

#define CHECK_SIMPLE_CLAUSE(X, Y) \
void AccStructureChecker::Enter(const parser::AccClause::X &) { \
Expand Down Expand Up @@ -342,20 +349,219 @@ void AccStructureChecker::Leave(const parser::OpenACCAtomicConstruct &x) {
dirContext_.pop_back();
}

void AccStructureChecker::Enter(const parser::AccAtomicUpdate &x) {
const parser::AssignmentStmt &assignment{
std::get<parser::Statement<parser::AssignmentStmt>>(x.t).statement};
const auto &var{std::get<parser::Variable>(assignment.t)};
const auto &expr{std::get<parser::Expr>(assignment.t)};
void AccStructureChecker::CheckAtomicStmt(
const parser::AssignmentStmt &assign, const std::string &construct) {
const auto &var{std::get<parser::Variable>(assign.t)};
const auto &expr{std::get<parser::Expr>(assign.t)};
const auto *rhs{GetExpr(context_, expr)};
const auto *lhs{GetExpr(context_, var)};
if (lhs && rhs) {
if (lhs->Rank() != 0)

if (lhs) {
if (lhs->Rank() != 0) {
context_.Say(expr.source,
"LHS of atomic update statement must be scalar"_err_en_US);
if (rhs->Rank() != 0)
"LHS of atomic %s statement must be scalar"_err_en_US, construct);
}
// TODO: Check if lhs is intrinsic type.
}
if (rhs) {
if (rhs->Rank() != 0) {
context_.Say(var.GetSource(),
"RHS of atomic update statement must be scalar"_err_en_US);
"RHS of atomic %s statement must be scalar"_err_en_US, construct);
}
// TODO: Check if rhs is intrinsic type.
}
}

static constexpr evaluate::operation::OperatorSet validAccAtomicUpdateOperators{
evaluate::operation::Operator::Add, evaluate::operation::Operator::Mul,
evaluate::operation::Operator::Sub, evaluate::operation::Operator::Div,
evaluate::operation::Operator::And, evaluate::operation::Operator::Or,
evaluate::operation::Operator::Eqv, evaluate::operation::Operator::Neqv,
evaluate::operation::Operator::Max, evaluate::operation::Operator::Min};

static bool IsValidAtomicUpdateOperation(
const evaluate::operation::Operator &op) {
return validAccAtomicUpdateOperators.test(op);
}

// Couldn't reproduce this behavior with evaluate::UnwrapConvertedExpr which
// is similar but only works within a single type category.
static SomeExpr GetExprModuloConversion(const SomeExpr &expr) {
const auto [op, args]{evaluate::GetTopLevelOperation(expr)};
// Check: if it is a conversion then it must have at least one argument.
CHECK(((op != evaluate::operation::Operator::Convert &&
op != evaluate::operation::Operator::Resize) ||
args.size() >= 1) &&
"Invalid conversion operation");
if ((op == evaluate::operation::Operator::Convert ||
op == evaluate::operation::Operator::Resize) &&
args.size() >= 1) {
return args[0];
}
return expr;
}

void AccStructureChecker::CheckAtomicUpdateStmt(
const parser::AssignmentStmt &assign, const SomeExpr &updateVar,
const SomeExpr *captureVar) {
CheckAtomicStmt(assign, "update");
const auto &expr{std::get<parser::Expr>(assign.t)};
const auto *rhs{GetExpr(context_, expr)};
if (rhs) {
const auto [op, args]{
evaluate::GetTopLevelOperation(GetExprModuloConversion(*rhs))};
if (!IsValidAtomicUpdateOperation(op)) {
context_.Say(expr.source,
"Invalid atomic update operation, can only use: *, +, -, *, /, and, or, eqv, neqv, max, min, iand, ior, ieor"_err_en_US);
} else {
bool foundUpdateVar{false};
for (const auto &arg : args) {
if (updateVar == GetExprModuloConversion(arg)) {
if (foundUpdateVar) {
context_.Say(expr.source,
"The updated variable, %s, cannot appear more than once in the atomic update operation"_err_en_US,
updateVar.AsFortran());
} else {
foundUpdateVar = true;
}
} else if (evaluate::IsVarSubexpressionOf(updateVar, arg)) {
// TODO: Get the source location of arg and point to the individual
// argument.
context_.Say(expr.source,
"Arguments to the atomic update operation cannot reference the updated variable, %s, as a subexpression"_err_en_US,
updateVar.AsFortran());
}
}
if (!foundUpdateVar) {
context_.Say(expr.source,
"The RHS of this atomic update statement must reference the updated variable: %s"_err_en_US,
updateVar.AsFortran());
}
}
}
}

void AccStructureChecker::CheckAtomicWriteStmt(
const parser::AssignmentStmt &assign, const SomeExpr &updateVar,
const SomeExpr *captureVar) {
CheckAtomicStmt(assign, "write");
const auto &expr{std::get<parser::Expr>(assign.t)};
const auto *rhs{GetExpr(context_, expr)};
if (rhs) {
if (evaluate::IsVarSubexpressionOf(updateVar, *rhs)) {
context_.Say(expr.source,
"The RHS of this atomic write statement cannot reference the atomic variable: %s"_err_en_US,
updateVar.AsFortran());
}
}
}

void AccStructureChecker::CheckAtomicCaptureStmt(
const parser::AssignmentStmt &assign, const SomeExpr *updateVar,
const SomeExpr &captureVar) {
CheckAtomicStmt(assign, "capture");
}

void AccStructureChecker::Enter(const parser::AccAtomicCapture &capture) {
const Fortran::parser::AssignmentStmt &stmt1{
std::get<Fortran::parser::AccAtomicCapture::Stmt1>(capture.t)
.v.statement};
const Fortran::parser::AssignmentStmt &stmt2{
std::get<Fortran::parser::AccAtomicCapture::Stmt2>(capture.t)
.v.statement};
const auto &var1{std::get<parser::Variable>(stmt1.t)};
const auto &var2{std::get<parser::Variable>(stmt2.t)};
const auto *lhs1{GetExpr(context_, var1)};
const auto *lhs2{GetExpr(context_, var2)};
if (!lhs1 || !lhs2) {
// Not enough information to check.
return;
}
if (*lhs1 == *lhs2) {
context_.Say(std::get<parser::Verbatim>(capture.t).source,
"The variables assigned in this atomic capture construct must be distinct"_err_en_US);
return;
}
const auto &expr1{std::get<parser::Expr>(stmt1.t)};
const auto &expr2{std::get<parser::Expr>(stmt2.t)};
const auto *rhs1{GetExpr(context_, expr1)};
const auto *rhs2{GetExpr(context_, expr2)};
if (!rhs1 || !rhs2) {
return;
}
bool stmt1CapturesLhs2{*lhs2 == GetExprModuloConversion(*rhs1)};
bool stmt2CapturesLhs1{*lhs1 == GetExprModuloConversion(*rhs2)};
if (stmt1CapturesLhs2 && !stmt2CapturesLhs1) {
if (*lhs2 == GetExprModuloConversion(*rhs2)) {
// a = b; b = b: Doesn't fit the spec.
context_.Say(std::get<parser::Verbatim>(capture.t).source,
"The assignments in this atomic capture construct do not update a variable and capture either its initial or final value"_err_en_US);
// TODO: Add attatchment that a = b seems to be a capture,
// but b = b is not a valid update or write.
} else if (evaluate::IsVarSubexpressionOf(*lhs2, *rhs2)) {
// Take v = x; x = <expr w/ x> as capture; update
const auto &updateVar{*lhs2};
const auto &captureVar{*lhs1};
CheckAtomicCaptureStmt(stmt1, &updateVar, captureVar);
CheckAtomicUpdateStmt(stmt2, updateVar, &captureVar);
} else {
// Take v = x; x = <expr w/o x> as capture; write
const auto &updateVar{*lhs2};
const auto &captureVar{*lhs1};
CheckAtomicCaptureStmt(stmt1, &updateVar, captureVar);
CheckAtomicWriteStmt(stmt2, updateVar, &captureVar);
}
} else if (stmt2CapturesLhs1 && !stmt1CapturesLhs2) {
if (*lhs1 == GetExprModuloConversion(*rhs1)) {
// Error a = a; b = a;
context_.Say(var1.GetSource(),
"The first assignment in this atomic capture construct doesn't perform a valid update"_err_en_US);
// Add attatchment that a = a is not considered an update,
// but b = a seems to be a capture.
} else {
// Take x = <expr>; v = x: as update; capture
const auto &updateVar{*lhs1};
const auto &captureVar{*lhs2};
CheckAtomicUpdateStmt(stmt1, updateVar, &captureVar);
CheckAtomicCaptureStmt(stmt2, &updateVar, captureVar);
}
} else if (stmt1CapturesLhs2 && stmt2CapturesLhs1) {
// x1 = x2; x2 = x1; Doesn't fit the spec.
context_.Say(std::get<parser::Verbatim>(capture.t).source,
"The assignments in this atomic capture construct do not update a variable and capture either its initial or final value"_err_en_US);
// TODO: Add attatchment that both assignments seem to be captures.
} else { // !stmt1CapturesLhs2 && !stmt2CapturesLhs1
// a = <expr != b>; b = <expr != a>; Doesn't fit the spec
context_.Say(std::get<parser::Verbatim>(capture.t).source,
"The assignments in this atomic capture construct do not update a variable and capture either its initial or final value"_err_en_US);
// TODO: Add attatchment that neither assignment seems to be a capture.
}
}

void AccStructureChecker::Enter(const parser::AccAtomicUpdate &x) {
const auto &assign{
std::get<parser::Statement<parser::AssignmentStmt>>(x.t).statement};
const auto &var{std::get<parser::Variable>(assign.t)};
if (const auto *updateVar{GetExpr(context_, var)}) {
CheckAtomicUpdateStmt(assign, *updateVar, /*captureVar=*/nullptr);
}
}

void AccStructureChecker::Enter(const parser::AccAtomicWrite &x) {
const auto &assign{
std::get<parser::Statement<parser::AssignmentStmt>>(x.t).statement};
const auto &var{std::get<parser::Variable>(assign.t)};
if (const auto *updateVar{GetExpr(context_, var)}) {
CheckAtomicWriteStmt(assign, *updateVar, /*captureVar=*/nullptr);
}
}

void AccStructureChecker::Enter(const parser::AccAtomicRead &x) {
const auto &assign{
std::get<parser::Statement<parser::AssignmentStmt>>(x.t).statement};
const auto &var{std::get<parser::Variable>(assign.t)};
if (const auto *captureVar{GetExpr(context_, var)}) {
CheckAtomicCaptureStmt(assign, /*updateVar=*/nullptr, *captureVar);
}
}

Expand Down
16 changes: 16 additions & 0 deletions flang/lib/Semantics/check-acc-structure.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ class AccStructureChecker
void Enter(const parser::OpenACCCacheConstruct &);
void Leave(const parser::OpenACCCacheConstruct &);
void Enter(const parser::AccAtomicUpdate &);
void Enter(const parser::AccAtomicCapture &);
void Enter(const parser::AccAtomicWrite &);
void Enter(const parser::AccAtomicRead &);
void Enter(const parser::OpenACCEndConstruct &);

// Clauses
Expand All @@ -80,6 +83,19 @@ class AccStructureChecker
#include "llvm/Frontend/OpenACC/ACC.inc"

private:
void CheckAtomicStmt(
const parser::AssignmentStmt &assign, const std::string &construct);
void CheckAtomicUpdateStmt(const parser::AssignmentStmt &assign,
const SomeExpr &updateVar, const SomeExpr *captureVar);
void CheckAtomicCaptureStmt(const parser::AssignmentStmt &assign,
const SomeExpr *updateVar, const SomeExpr &captureVar);
void CheckAtomicWriteStmt(const parser::AssignmentStmt &assign,
const SomeExpr &updateVar, const SomeExpr *captureVar);
void CheckAtomicUpdateVariable(
const parser::Variable &updateVar, const parser::Variable &captureVar);
void CheckAtomicCaptureVariable(
const parser::Variable &captureVar, const parser::Variable &updateVar);

bool CheckAllowedModifier(llvm::acc::Clause clause);
bool IsComputeConstruct(llvm::acc::Directive directive) const;
bool IsInsideComputeConstruct() const;
Expand Down
Loading