Skip to content

Commit 062b22e

Browse files
authored
[flang][openacc] Add semantic checks for atomic constructs (#149579)
An error report of the following code generating non-atomic code led us to realize there are missing checks in the OpenACC atomics code. Add some of those checks for atomic and sketch how the rest of the code should proceed in checking the rest of the properties. The following cases are all reported as errors. ```fortran ! Originally reported error! !$acc atomic capture a = b c = b !$acc end atomic capture ! Other ambiguous, but related errors! !$acc atomic capture x = i i = x !$acc end atomic capture !$acc atomic capture a = b b = b !$acc end atomic capture !$acc atomic capture a = b a = c !$acc end atomic capture ```
1 parent 8a50337 commit 062b22e

File tree

10 files changed

+372
-58
lines changed

10 files changed

+372
-58
lines changed

flang/include/flang/Evaluate/tools.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#define FORTRAN_EVALUATE_TOOLS_H_
1111

1212
#include "traverse.h"
13+
#include "flang/Common/enum-set.h"
1314
#include "flang/Common/idioms.h"
1415
#include "flang/Common/template.h"
1516
#include "flang/Common/unwrap.h"
@@ -1397,6 +1398,8 @@ enum class Operator {
13971398
True,
13981399
};
13991400

1401+
using OperatorSet = common::EnumSet<Operator, 32>;
1402+
14001403
std::string ToString(Operator op);
14011404

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

1515+
// Return information about the top-level operation (ignoring parentheses, and
1516+
// resizing converts)
1517+
std::pair<operation::Operator, std::vector<Expr<SomeType>>>
1518+
GetTopLevelOperationIgnoreResizing(const Expr<SomeType> &expr);
1519+
15121520
// Check if expr is same as x, or a sequence of Convert operations on x.
15131521
bool IsSameOrConvertOf(const Expr<SomeType> &expr, const Expr<SomeType> &x);
15141522

1523+
// Check if the Variable appears as a subexpression of the expression.
1524+
bool IsVarSubexpressionOf(
1525+
const Expr<SomeType> &var, const Expr<SomeType> &super);
1526+
15151527
// Strip away any top-level Convert operations (if any exist) and return
15161528
// the input value. A ComplexConstructor(x, 0) is also considered as a
15171529
// convert operation.

flang/lib/Evaluate/tools.cpp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1809,10 +1809,15 @@ operation::Operator operation::OperationCode(const ProcedureDesignator &proc) {
18091809
}
18101810

18111811
std::pair<operation::Operator, std::vector<Expr<SomeType>>>
1812-
GetTopLevelOperation(const Expr<SomeType> &expr) {
1812+
GetTopLevelOperationIgnoreResizing(const Expr<SomeType> &expr) {
18131813
return operation::ArgumentExtractor<true>{}(expr);
18141814
}
18151815

1816+
std::pair<operation::Operator, std::vector<Expr<SomeType>>>
1817+
GetTopLevelOperation(const Expr<SomeType> &expr) {
1818+
return operation::ArgumentExtractor<false>{}(expr);
1819+
}
1820+
18161821
namespace operation {
18171822
struct ConvertCollector
18181823
: public Traverse<ConvertCollector,
@@ -1936,6 +1941,33 @@ bool IsSameOrConvertOf(const Expr<SomeType> &expr, const Expr<SomeType> &x) {
19361941
return false;
19371942
}
19381943
}
1944+
1945+
struct VariableFinder : public evaluate::AnyTraverse<VariableFinder> {
1946+
using Base = evaluate::AnyTraverse<VariableFinder>;
1947+
using SomeExpr = Expr<SomeType>;
1948+
VariableFinder(const SomeExpr &v) : Base(*this), var(v) {}
1949+
1950+
using Base::operator();
1951+
1952+
template <typename T>
1953+
bool operator()(const evaluate::Designator<T> &x) const {
1954+
return evaluate::AsGenericExpr(common::Clone(x)) == var;
1955+
}
1956+
1957+
template <typename T>
1958+
bool operator()(const evaluate::FunctionRef<T> &x) const {
1959+
return evaluate::AsGenericExpr(common::Clone(x)) == var;
1960+
}
1961+
1962+
private:
1963+
const SomeExpr &var;
1964+
};
1965+
1966+
bool IsVarSubexpressionOf(
1967+
const Expr<SomeType> &sub, const Expr<SomeType> &super) {
1968+
return VariableFinder{sub}(super);
1969+
}
1970+
19391971
} // namespace Fortran::evaluate
19401972

19411973
namespace Fortran::semantics {

flang/lib/Lower/OpenMP/Atomic.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,7 @@ genAtomicUpdate(lower::AbstractConverter &converter,
607607
// This must exist by now.
608608
semantics::SomeExpr rhs = assign.rhs;
609609
semantics::SomeExpr input = *evaluate::GetConvertInput(rhs);
610-
auto [opcode, args] = evaluate::GetTopLevelOperation(input);
610+
auto [opcode, args] = evaluate::GetTopLevelOperationIgnoreResizing(input);
611611
assert(!args.empty() && "Update operation without arguments");
612612

613613
// Pass args as an argument to avoid capturing a structured binding.
@@ -625,7 +625,8 @@ genAtomicUpdate(lower::AbstractConverter &converter,
625625
// operations with exactly two (non-optional) arguments.
626626
rhs = genReducedMinMax(rhs, atomArg, args);
627627
input = *evaluate::GetConvertInput(rhs);
628-
std::tie(opcode, args) = evaluate::GetTopLevelOperation(input);
628+
std::tie(opcode, args) =
629+
evaluate::GetTopLevelOperationIgnoreResizing(input);
629630
atomArg = nullptr; // No longer valid.
630631
}
631632
for (auto &arg : args) {

flang/lib/Semantics/check-acc-structure.cpp

Lines changed: 216 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,15 @@
77
//===----------------------------------------------------------------------===//
88
#include "check-acc-structure.h"
99
#include "flang/Common/enum-set.h"
10+
#include "flang/Evaluate/tools.h"
1011
#include "flang/Parser/parse-tree.h"
12+
#include "flang/Semantics/symbol.h"
1113
#include "flang/Semantics/tools.h"
14+
#include "flang/Semantics/type.h"
15+
#include "flang/Support/Fortran.h"
16+
#include "llvm/Support/AtomicOrdering.h"
17+
18+
#include <optional>
1219

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

345-
void AccStructureChecker::Enter(const parser::AccAtomicUpdate &x) {
346-
const parser::AssignmentStmt &assignment{
347-
std::get<parser::Statement<parser::AssignmentStmt>>(x.t).statement};
348-
const auto &var{std::get<parser::Variable>(assignment.t)};
349-
const auto &expr{std::get<parser::Expr>(assignment.t)};
352+
void AccStructureChecker::CheckAtomicStmt(
353+
const parser::AssignmentStmt &assign, const std::string &construct) {
354+
const auto &var{std::get<parser::Variable>(assign.t)};
355+
const auto &expr{std::get<parser::Expr>(assign.t)};
350356
const auto *rhs{GetExpr(context_, expr)};
351357
const auto *lhs{GetExpr(context_, var)};
352-
if (lhs && rhs) {
353-
if (lhs->Rank() != 0)
358+
359+
if (lhs) {
360+
if (lhs->Rank() != 0) {
354361
context_.Say(expr.source,
355-
"LHS of atomic update statement must be scalar"_err_en_US);
356-
if (rhs->Rank() != 0)
362+
"LHS of atomic %s statement must be scalar"_err_en_US, construct);
363+
}
364+
// TODO: Check if lhs is intrinsic type.
365+
}
366+
if (rhs) {
367+
if (rhs->Rank() != 0) {
357368
context_.Say(var.GetSource(),
358-
"RHS of atomic update statement must be scalar"_err_en_US);
369+
"RHS of atomic %s statement must be scalar"_err_en_US, construct);
370+
}
371+
// TODO: Check if rhs is intrinsic type.
372+
}
373+
}
374+
375+
static constexpr evaluate::operation::OperatorSet validAccAtomicUpdateOperators{
376+
evaluate::operation::Operator::Add, evaluate::operation::Operator::Mul,
377+
evaluate::operation::Operator::Sub, evaluate::operation::Operator::Div,
378+
evaluate::operation::Operator::And, evaluate::operation::Operator::Or,
379+
evaluate::operation::Operator::Eqv, evaluate::operation::Operator::Neqv,
380+
evaluate::operation::Operator::Max, evaluate::operation::Operator::Min};
381+
382+
static bool IsValidAtomicUpdateOperation(
383+
const evaluate::operation::Operator &op) {
384+
return validAccAtomicUpdateOperators.test(op);
385+
}
386+
387+
// Couldn't reproduce this behavior with evaluate::UnwrapConvertedExpr which
388+
// is similar but only works within a single type category.
389+
static SomeExpr GetExprModuloConversion(const SomeExpr &expr) {
390+
const auto [op, args]{evaluate::GetTopLevelOperation(expr)};
391+
// Check: if it is a conversion then it must have at least one argument.
392+
CHECK(((op != evaluate::operation::Operator::Convert &&
393+
op != evaluate::operation::Operator::Resize) ||
394+
args.size() >= 1) &&
395+
"Invalid conversion operation");
396+
if ((op == evaluate::operation::Operator::Convert ||
397+
op == evaluate::operation::Operator::Resize) &&
398+
args.size() >= 1) {
399+
return args[0];
400+
}
401+
return expr;
402+
}
403+
404+
void AccStructureChecker::CheckAtomicUpdateStmt(
405+
const parser::AssignmentStmt &assign, const SomeExpr &updateVar,
406+
const SomeExpr *captureVar) {
407+
CheckAtomicStmt(assign, "update");
408+
const auto &expr{std::get<parser::Expr>(assign.t)};
409+
const auto *rhs{GetExpr(context_, expr)};
410+
if (rhs) {
411+
const auto [op, args]{
412+
evaluate::GetTopLevelOperation(GetExprModuloConversion(*rhs))};
413+
if (!IsValidAtomicUpdateOperation(op)) {
414+
context_.Say(expr.source,
415+
"Invalid atomic update operation, can only use: *, +, -, *, /, and, or, eqv, neqv, max, min, iand, ior, ieor"_err_en_US);
416+
} else {
417+
bool foundUpdateVar{false};
418+
for (const auto &arg : args) {
419+
if (updateVar == GetExprModuloConversion(arg)) {
420+
if (foundUpdateVar) {
421+
context_.Say(expr.source,
422+
"The updated variable, %s, cannot appear more than once in the atomic update operation"_err_en_US,
423+
updateVar.AsFortran());
424+
} else {
425+
foundUpdateVar = true;
426+
}
427+
} else if (evaluate::IsVarSubexpressionOf(updateVar, arg)) {
428+
// TODO: Get the source location of arg and point to the individual
429+
// argument.
430+
context_.Say(expr.source,
431+
"Arguments to the atomic update operation cannot reference the updated variable, %s, as a subexpression"_err_en_US,
432+
updateVar.AsFortran());
433+
}
434+
}
435+
if (!foundUpdateVar) {
436+
context_.Say(expr.source,
437+
"The RHS of this atomic update statement must reference the updated variable: %s"_err_en_US,
438+
updateVar.AsFortran());
439+
}
440+
}
441+
}
442+
}
443+
444+
void AccStructureChecker::CheckAtomicWriteStmt(
445+
const parser::AssignmentStmt &assign, const SomeExpr &updateVar,
446+
const SomeExpr *captureVar) {
447+
CheckAtomicStmt(assign, "write");
448+
const auto &expr{std::get<parser::Expr>(assign.t)};
449+
const auto *rhs{GetExpr(context_, expr)};
450+
if (rhs) {
451+
if (evaluate::IsVarSubexpressionOf(updateVar, *rhs)) {
452+
context_.Say(expr.source,
453+
"The RHS of this atomic write statement cannot reference the atomic variable: %s"_err_en_US,
454+
updateVar.AsFortran());
455+
}
456+
}
457+
}
458+
459+
void AccStructureChecker::CheckAtomicCaptureStmt(
460+
const parser::AssignmentStmt &assign, const SomeExpr *updateVar,
461+
const SomeExpr &captureVar) {
462+
CheckAtomicStmt(assign, "capture");
463+
}
464+
465+
void AccStructureChecker::Enter(const parser::AccAtomicCapture &capture) {
466+
const Fortran::parser::AssignmentStmt &stmt1{
467+
std::get<Fortran::parser::AccAtomicCapture::Stmt1>(capture.t)
468+
.v.statement};
469+
const Fortran::parser::AssignmentStmt &stmt2{
470+
std::get<Fortran::parser::AccAtomicCapture::Stmt2>(capture.t)
471+
.v.statement};
472+
const auto &var1{std::get<parser::Variable>(stmt1.t)};
473+
const auto &var2{std::get<parser::Variable>(stmt2.t)};
474+
const auto *lhs1{GetExpr(context_, var1)};
475+
const auto *lhs2{GetExpr(context_, var2)};
476+
if (!lhs1 || !lhs2) {
477+
// Not enough information to check.
478+
return;
479+
}
480+
if (*lhs1 == *lhs2) {
481+
context_.Say(std::get<parser::Verbatim>(capture.t).source,
482+
"The variables assigned in this atomic capture construct must be distinct"_err_en_US);
483+
return;
484+
}
485+
const auto &expr1{std::get<parser::Expr>(stmt1.t)};
486+
const auto &expr2{std::get<parser::Expr>(stmt2.t)};
487+
const auto *rhs1{GetExpr(context_, expr1)};
488+
const auto *rhs2{GetExpr(context_, expr2)};
489+
if (!rhs1 || !rhs2) {
490+
return;
491+
}
492+
bool stmt1CapturesLhs2{*lhs2 == GetExprModuloConversion(*rhs1)};
493+
bool stmt2CapturesLhs1{*lhs1 == GetExprModuloConversion(*rhs2)};
494+
if (stmt1CapturesLhs2 && !stmt2CapturesLhs1) {
495+
if (*lhs2 == GetExprModuloConversion(*rhs2)) {
496+
// a = b; b = b: Doesn't fit the spec.
497+
context_.Say(std::get<parser::Verbatim>(capture.t).source,
498+
"The assignments in this atomic capture construct do not update a variable and capture either its initial or final value"_err_en_US);
499+
// TODO: Add attatchment that a = b seems to be a capture,
500+
// but b = b is not a valid update or write.
501+
} else if (evaluate::IsVarSubexpressionOf(*lhs2, *rhs2)) {
502+
// Take v = x; x = <expr w/ x> as capture; update
503+
const auto &updateVar{*lhs2};
504+
const auto &captureVar{*lhs1};
505+
CheckAtomicCaptureStmt(stmt1, &updateVar, captureVar);
506+
CheckAtomicUpdateStmt(stmt2, updateVar, &captureVar);
507+
} else {
508+
// Take v = x; x = <expr w/o x> as capture; write
509+
const auto &updateVar{*lhs2};
510+
const auto &captureVar{*lhs1};
511+
CheckAtomicCaptureStmt(stmt1, &updateVar, captureVar);
512+
CheckAtomicWriteStmt(stmt2, updateVar, &captureVar);
513+
}
514+
} else if (stmt2CapturesLhs1 && !stmt1CapturesLhs2) {
515+
if (*lhs1 == GetExprModuloConversion(*rhs1)) {
516+
// Error a = a; b = a;
517+
context_.Say(var1.GetSource(),
518+
"The first assignment in this atomic capture construct doesn't perform a valid update"_err_en_US);
519+
// Add attatchment that a = a is not considered an update,
520+
// but b = a seems to be a capture.
521+
} else {
522+
// Take x = <expr>; v = x: as update; capture
523+
const auto &updateVar{*lhs1};
524+
const auto &captureVar{*lhs2};
525+
CheckAtomicUpdateStmt(stmt1, updateVar, &captureVar);
526+
CheckAtomicCaptureStmt(stmt2, &updateVar, captureVar);
527+
}
528+
} else if (stmt1CapturesLhs2 && stmt2CapturesLhs1) {
529+
// x1 = x2; x2 = x1; Doesn't fit the spec.
530+
context_.Say(std::get<parser::Verbatim>(capture.t).source,
531+
"The assignments in this atomic capture construct do not update a variable and capture either its initial or final value"_err_en_US);
532+
// TODO: Add attatchment that both assignments seem to be captures.
533+
} else { // !stmt1CapturesLhs2 && !stmt2CapturesLhs1
534+
// a = <expr != b>; b = <expr != a>; Doesn't fit the spec
535+
context_.Say(std::get<parser::Verbatim>(capture.t).source,
536+
"The assignments in this atomic capture construct do not update a variable and capture either its initial or final value"_err_en_US);
537+
// TODO: Add attatchment that neither assignment seems to be a capture.
538+
}
539+
}
540+
541+
void AccStructureChecker::Enter(const parser::AccAtomicUpdate &x) {
542+
const auto &assign{
543+
std::get<parser::Statement<parser::AssignmentStmt>>(x.t).statement};
544+
const auto &var{std::get<parser::Variable>(assign.t)};
545+
if (const auto *updateVar{GetExpr(context_, var)}) {
546+
CheckAtomicUpdateStmt(assign, *updateVar, /*captureVar=*/nullptr);
547+
}
548+
}
549+
550+
void AccStructureChecker::Enter(const parser::AccAtomicWrite &x) {
551+
const auto &assign{
552+
std::get<parser::Statement<parser::AssignmentStmt>>(x.t).statement};
553+
const auto &var{std::get<parser::Variable>(assign.t)};
554+
if (const auto *updateVar{GetExpr(context_, var)}) {
555+
CheckAtomicWriteStmt(assign, *updateVar, /*captureVar=*/nullptr);
556+
}
557+
}
558+
559+
void AccStructureChecker::Enter(const parser::AccAtomicRead &x) {
560+
const auto &assign{
561+
std::get<parser::Statement<parser::AssignmentStmt>>(x.t).statement};
562+
const auto &var{std::get<parser::Variable>(assign.t)};
563+
if (const auto *captureVar{GetExpr(context_, var)}) {
564+
CheckAtomicCaptureStmt(assign, /*updateVar=*/nullptr, *captureVar);
359565
}
360566
}
361567

flang/lib/Semantics/check-acc-structure.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ class AccStructureChecker
6363
void Enter(const parser::OpenACCCacheConstruct &);
6464
void Leave(const parser::OpenACCCacheConstruct &);
6565
void Enter(const parser::AccAtomicUpdate &);
66+
void Enter(const parser::AccAtomicCapture &);
67+
void Enter(const parser::AccAtomicWrite &);
68+
void Enter(const parser::AccAtomicRead &);
6669
void Enter(const parser::OpenACCEndConstruct &);
6770

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

8285
private:
86+
void CheckAtomicStmt(
87+
const parser::AssignmentStmt &assign, const std::string &construct);
88+
void CheckAtomicUpdateStmt(const parser::AssignmentStmt &assign,
89+
const SomeExpr &updateVar, const SomeExpr *captureVar);
90+
void CheckAtomicCaptureStmt(const parser::AssignmentStmt &assign,
91+
const SomeExpr *updateVar, const SomeExpr &captureVar);
92+
void CheckAtomicWriteStmt(const parser::AssignmentStmt &assign,
93+
const SomeExpr &updateVar, const SomeExpr *captureVar);
94+
void CheckAtomicUpdateVariable(
95+
const parser::Variable &updateVar, const parser::Variable &captureVar);
96+
void CheckAtomicCaptureVariable(
97+
const parser::Variable &captureVar, const parser::Variable &updateVar);
98+
8399
bool CheckAllowedModifier(llvm::acc::Clause clause);
84100
bool IsComputeConstruct(llvm::acc::Directive directive) const;
85101
bool IsInsideComputeConstruct() const;

0 commit comments

Comments
 (0)