1//===-- lib/Semantics/check-omp-atomic.cpp --------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// Semantic checks related to the ATOMIC construct.
10//
11//===----------------------------------------------------------------------===//
12
13#include "check-omp-structure.h"
14#include "openmp-utils.h"
15
16#include "flang/Common/indirection.h"
17#include "flang/Evaluate/expression.h"
18#include "flang/Evaluate/tools.h"
19#include "flang/Parser/char-block.h"
20#include "flang/Parser/parse-tree.h"
21#include "flang/Semantics/symbol.h"
22#include "flang/Semantics/tools.h"
23#include "flang/Semantics/type.h"
24
25#include "llvm/ADT/ArrayRef.h"
26#include "llvm/ADT/STLExtras.h"
27#include "llvm/Frontend/OpenMP/OMP.h"
28#include "llvm/Support/ErrorHandling.h"
29
30#include <cassert>
31#include <list>
32#include <optional>
33#include <string_view>
34#include <tuple>
35#include <utility>
36#include <variant>
37#include <vector>
38
39namespace Fortran::semantics {
40
41using namespace Fortran::semantics::omp;
42
43namespace operation = Fortran::evaluate::operation;
44
45template <typename T, typename U>
46static bool operator!=(const evaluate::Expr<T> &e, const evaluate::Expr<U> &f) {
47 return !(e == f);
48}
49
50struct AnalyzedCondStmt {
51 SomeExpr cond{evaluate::NullPointer{}}; // Default ctor is deleted
52 parser::CharBlock source;
53 SourcedActionStmt ift, iff;
54};
55
56// Compute the `evaluate::Assignment` from parser::ActionStmt. The assumption
57// is that the ActionStmt will be either an assignment or a pointer-assignment,
58// otherwise return std::nullopt.
59// Note: This function can return std::nullopt on [Pointer]AssignmentStmt where
60// the "typedAssignment" is unset. This can happen if there are semantic errors
61// in the purported assignment.
62static std::optional<evaluate::Assignment> GetEvaluateAssignment(
63 const parser::ActionStmt *x) {
64 if (x == nullptr) {
65 return std::nullopt;
66 }
67
68 using AssignmentStmt = common::Indirection<parser::AssignmentStmt>;
69 using PointerAssignmentStmt =
70 common::Indirection<parser::PointerAssignmentStmt>;
71 using TypedAssignment = parser::AssignmentStmt::TypedAssignment;
72
73 return common::visit(
74 [](auto &&s) -> std::optional<evaluate::Assignment> {
75 using BareS = llvm::remove_cvref_t<decltype(s)>;
76 if constexpr (std::is_same_v<BareS, AssignmentStmt> ||
77 std::is_same_v<BareS, PointerAssignmentStmt>) {
78 const TypedAssignment &typed{s.value().typedAssignment};
79 // ForwardOwningPointer typedAssignment
80 // `- GenericAssignmentWrapper ^.get()
81 // `- std::optional<Assignment> ^->v
82 return typed.get()->v;
83 } else {
84 return std::nullopt;
85 }
86 },
87 x->u);
88}
89
90static std::optional<AnalyzedCondStmt> AnalyzeConditionalStmt(
91 const parser::ExecutionPartConstruct *x) {
92 if (x == nullptr) {
93 return std::nullopt;
94 }
95
96 // Extract the evaluate::Expr from ScalarLogicalExpr.
97 auto getFromLogical{[](const parser::ScalarLogicalExpr &logical) {
98 // ScalarLogicalExpr is Scalar<Logical<common::Indirection<Expr>>>
99 const parser::Expr &expr{logical.thing.thing.value()};
100 return GetEvaluateExpr(expr);
101 }};
102
103 // Recognize either
104 // ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> IfStmt, or
105 // ExecutionPartConstruct -> ExecutableConstruct -> IfConstruct.
106
107 if (auto &&action{GetActionStmt(x)}) {
108 if (auto *ifs{std::get_if<common::Indirection<parser::IfStmt>>(
109 &action.stmt->u)}) {
110 const parser::IfStmt &s{ifs->value()};
111 auto &&maybeCond{
112 getFromLogical(std::get<parser::ScalarLogicalExpr>(s.t))};
113 auto &thenStmt{
114 std::get<parser::UnlabeledStatement<parser::ActionStmt>>(s.t)};
115 if (maybeCond) {
116 return AnalyzedCondStmt{std::move(*maybeCond), action.source,
117 SourcedActionStmt{&thenStmt.statement, thenStmt.source},
118 SourcedActionStmt{}};
119 }
120 }
121 return std::nullopt;
122 }
123
124 if (auto *exec{std::get_if<parser::ExecutableConstruct>(&x->u)}) {
125 if (auto *ifc{
126 std::get_if<common::Indirection<parser::IfConstruct>>(&exec->u)}) {
127 using ElseBlock = parser::IfConstruct::ElseBlock;
128 using ElseIfBlock = parser::IfConstruct::ElseIfBlock;
129 const parser::IfConstruct &s{ifc->value()};
130
131 if (!std::get<std::list<ElseIfBlock>>(s.t).empty()) {
132 // Not expecting any else-if statements.
133 return std::nullopt;
134 }
135 auto &stmt{std::get<parser::Statement<parser::IfThenStmt>>(s.t)};
136 auto &&maybeCond{getFromLogical(
137 std::get<parser::ScalarLogicalExpr>(stmt.statement.t))};
138 if (!maybeCond) {
139 return std::nullopt;
140 }
141
142 if (auto &maybeElse{std::get<std::optional<ElseBlock>>(s.t)}) {
143 AnalyzedCondStmt result{std::move(*maybeCond), stmt.source,
144 GetActionStmt(std::get<parser::Block>(s.t)),
145 GetActionStmt(std::get<parser::Block>(maybeElse->t))};
146 if (result.ift.stmt && result.iff.stmt) {
147 return result;
148 }
149 } else {
150 AnalyzedCondStmt result{std::move(*maybeCond), stmt.source,
151 GetActionStmt(std::get<parser::Block>(s.t)), SourcedActionStmt{}};
152 if (result.ift.stmt) {
153 return result;
154 }
155 }
156 }
157 return std::nullopt;
158 }
159
160 return std::nullopt;
161}
162
163static std::pair<parser::CharBlock, parser::CharBlock> SplitAssignmentSource(
164 parser::CharBlock source) {
165 // Find => in the range, if not found, find = that is not a part of
166 // <=, >=, ==, or /=.
167 auto trim{[](std::string_view v) {
168 const char *begin{v.data()};
169 const char *end{begin + v.size()};
170 while (*begin == ' ' && begin != end) {
171 ++begin;
172 }
173 while (begin != end && end[-1] == ' ') {
174 --end;
175 }
176 assert(begin != end && "Source should not be empty");
177 return parser::CharBlock(begin, end - begin);
178 }};
179
180 std::string_view sv(source.begin(), source.size());
181
182 if (auto where{sv.find(str: "=>")}; where != sv.npos) {
183 std::string_view lhs(sv.data(), where);
184 std::string_view rhs(sv.data() + where + 2, sv.size() - where - 2);
185 return std::make_pair(trim(lhs), trim(rhs));
186 }
187
188 // Go backwards, since all the exclusions above end with a '='.
189 for (size_t next{source.size()}; next > 1; --next) {
190 if (sv[next - 1] == '=' && !llvm::is_contained(Range: "<>=/", Element: sv[next - 2])) {
191 std::string_view lhs(sv.data(), next - 1);
192 std::string_view rhs(sv.data() + next, sv.size() - next);
193 return std::make_pair(trim(lhs), trim(rhs));
194 }
195 }
196 llvm_unreachable("Could not find assignment operator");
197}
198
199static bool IsCheckForAssociated(const SomeExpr &cond) {
200 return GetTopLevelOperation(cond).first == operation::Operator::Associated;
201}
202
203static bool IsMaybeAtomicWrite(const evaluate::Assignment &assign) {
204 // This ignores function calls, so it will accept "f(x) = f(x) + 1"
205 // for example.
206 return HasStorageOverlap(assign.lhs, assign.rhs) == nullptr;
207}
208
209static void SetExpr(parser::TypedExpr &expr, MaybeExpr value) {
210 if (value) {
211 expr.Reset(new evaluate::GenericExprWrapper(std::move(value)),
212 evaluate::GenericExprWrapper::Deleter);
213 }
214}
215
216static void SetAssignment(parser::AssignmentStmt::TypedAssignment &assign,
217 std::optional<evaluate::Assignment> value) {
218 if (value) {
219 assign.Reset(new evaluate::GenericAssignmentWrapper(std::move(value)),
220 evaluate::GenericAssignmentWrapper::Deleter);
221 }
222}
223
224static parser::OpenMPAtomicConstruct::Analysis::Op MakeAtomicAnalysisOp(
225 int what,
226 const std::optional<evaluate::Assignment> &maybeAssign = std::nullopt) {
227 parser::OpenMPAtomicConstruct::Analysis::Op operation;
228 operation.what = what;
229 SetAssignment(operation.assign, maybeAssign);
230 return operation;
231}
232
233static parser::OpenMPAtomicConstruct::Analysis MakeAtomicAnalysis(
234 const SomeExpr &atom, const MaybeExpr &cond,
235 parser::OpenMPAtomicConstruct::Analysis::Op &&op0,
236 parser::OpenMPAtomicConstruct::Analysis::Op &&op1) {
237 // Defined in flang/include/flang/Parser/parse-tree.h
238 //
239 // struct Analysis {
240 // struct Kind {
241 // static constexpr int None = 0;
242 // static constexpr int Read = 1;
243 // static constexpr int Write = 2;
244 // static constexpr int Update = Read | Write;
245 // static constexpr int Action = 3; // Bits containing N, R, W, U
246 // static constexpr int IfTrue = 4;
247 // static constexpr int IfFalse = 8;
248 // static constexpr int Condition = 12; // Bits containing IfTrue, IfFalse
249 // };
250 // struct Op {
251 // int what;
252 // TypedAssignment assign;
253 // };
254 // TypedExpr atom, cond;
255 // Op op0, op1;
256 // };
257
258 parser::OpenMPAtomicConstruct::Analysis an;
259 SetExpr(an.atom, atom);
260 SetExpr(an.cond, cond);
261 an.op0 = std::move(op0);
262 an.op1 = std::move(op1);
263 return an;
264}
265
266/// Check if `expr` satisfies the following conditions for x and v:
267///
268/// [6.0:189:10-12]
269/// - x and v (as applicable) are either scalar variables or
270/// function references with scalar data pointer result of non-character
271/// intrinsic type or variables that are non-polymorphic scalar pointers
272/// and any length type parameter must be constant.
273void OmpStructureChecker::CheckAtomicType(
274 SymbolRef sym, parser::CharBlock source, std::string_view name) {
275 const DeclTypeSpec *typeSpec{sym->GetType()};
276 if (!typeSpec) {
277 return;
278 }
279
280 if (!IsPointer(sym)) {
281 using Category = DeclTypeSpec::Category;
282 Category cat{typeSpec->category()};
283 if (cat == Category::Character) {
284 context_.Say(source,
285 "Atomic variable %s cannot have CHARACTER type"_err_en_US, name);
286 } else if (cat != Category::Numeric && cat != Category::Logical) {
287 context_.Say(source,
288 "Atomic variable %s should have an intrinsic type"_err_en_US, name);
289 }
290 return;
291 }
292
293 // Variable is a pointer.
294 if (typeSpec->IsPolymorphic()) {
295 context_.Say(source,
296 "Atomic variable %s cannot be a pointer to a polymorphic type"_err_en_US,
297 name);
298 return;
299 }
300
301 // Go over all length parameters, if any, and check if they are
302 // explicit.
303 if (const DerivedTypeSpec *derived{typeSpec->AsDerived()}) {
304 if (llvm::any_of(derived->parameters(), [](auto &&entry) {
305 // "entry" is a map entry
306 return entry.second.isLen() && !entry.second.isExplicit();
307 })) {
308 context_.Say(source,
309 "Atomic variable %s is a pointer to a type with non-constant length parameter"_err_en_US,
310 name);
311 }
312 }
313}
314
315void OmpStructureChecker::CheckAtomicVariable(
316 const SomeExpr &atom, parser::CharBlock source) {
317 if (atom.Rank() != 0) {
318 context_.Say(source, "Atomic variable %s should be a scalar"_err_en_US,
319 atom.AsFortran());
320 }
321
322 std::vector<SomeExpr> dsgs{GetAllDesignators(atom)};
323 assert(dsgs.size() == 1 && "Should have a single top-level designator");
324 evaluate::SymbolVector syms{evaluate::GetSymbolVector(dsgs.front())};
325
326 CheckAtomicType(syms.back(), source, atom.AsFortran());
327
328 if (IsAllocatable(syms.back()) && !IsArrayElement(atom)) {
329 context_.Say(source, "Atomic variable %s cannot be ALLOCATABLE"_err_en_US,
330 atom.AsFortran());
331 }
332}
333
334void OmpStructureChecker::CheckStorageOverlap(const SomeExpr &base,
335 llvm::ArrayRef<evaluate::Expr<evaluate::SomeType>> exprs,
336 parser::CharBlock source) {
337 if (auto *expr{HasStorageOverlap(base, exprs)}) {
338 context_.Say(source,
339 "Within atomic operation %s and %s access the same storage"_warn_en_US,
340 base.AsFortran(), expr->AsFortran());
341 }
342}
343
344void OmpStructureChecker::ErrorShouldBeVariable(
345 const MaybeExpr &expr, parser::CharBlock source) {
346 if (expr) {
347 context_.Say(source, "Atomic expression %s should be a variable"_err_en_US,
348 expr->AsFortran());
349 } else {
350 context_.Say(source, "Atomic expression should be a variable"_err_en_US);
351 }
352}
353
354std::pair<const parser::ExecutionPartConstruct *,
355 const parser::ExecutionPartConstruct *>
356OmpStructureChecker::CheckUpdateCapture(
357 const parser::ExecutionPartConstruct *ec1,
358 const parser::ExecutionPartConstruct *ec2, parser::CharBlock source) {
359 // Decide which statement is the atomic update and which is the capture.
360 //
361 // The two allowed cases are:
362 // x = ... atomic-var = ...
363 // ... = x capture-var = atomic-var (with optional converts)
364 // or
365 // ... = x capture-var = atomic-var (with optional converts)
366 // x = ... atomic-var = ...
367 //
368 // The case of 'a = b; b = a' is ambiguous, so pick the first one as capture
369 // (which makes more sense, as it captures the original value of the atomic
370 // variable).
371 //
372 // If the two statements don't fit these criteria, return a pair of default-
373 // constructed values.
374 using ReturnTy = std::pair<const parser::ExecutionPartConstruct *,
375 const parser::ExecutionPartConstruct *>;
376
377 SourcedActionStmt act1{GetActionStmt(ec1)};
378 SourcedActionStmt act2{GetActionStmt(ec2)};
379 auto maybeAssign1{GetEvaluateAssignment(act1.stmt)};
380 auto maybeAssign2{GetEvaluateAssignment(act2.stmt)};
381 if (!maybeAssign1 || !maybeAssign2) {
382 if (!IsAssignment(act1.stmt) || !IsAssignment(act2.stmt)) {
383 context_.Say(source,
384 "ATOMIC UPDATE operation with CAPTURE should contain two assignments"_err_en_US);
385 }
386 return std::make_pair(x: nullptr, y: nullptr);
387 }
388
389 auto as1{*maybeAssign1}, as2{*maybeAssign2};
390
391 auto isUpdateCapture{
392 [](const evaluate::Assignment &u, const evaluate::Assignment &c) {
393 return IsSameOrConvertOf(c.rhs, u.lhs);
394 }};
395
396 // Do some checks that narrow down the possible choices for the update
397 // and the capture statements. This will help to emit better diagnostics.
398 // 1. An assignment could be an update (cbu) if the left-hand side is a
399 // subexpression of the right-hand side.
400 // 2. An assignment could be a capture (cbc) if the right-hand side is
401 // a variable (or a function ref), with potential type conversions.
402 bool cbu1{IsSubexpressionOf(as1.lhs, as1.rhs)}; // Can as1 be an update?
403 bool cbu2{IsSubexpressionOf(as2.lhs, as2.rhs)}; // Can as2 be an update?
404 bool cbc1{IsVarOrFunctionRef(GetConvertInput(as1.rhs))}; // Can 1 be capture?
405 bool cbc2{IsVarOrFunctionRef(GetConvertInput(as2.rhs))}; // Can 2 be capture?
406
407 // We want to diagnose cases where both assignments cannot be an update,
408 // or both cannot be a capture, as well as cases where either assignment
409 // cannot be any of these two.
410 //
411 // If we organize these boolean values into a matrix
412 // |cbu1 cbu2|
413 // |cbc1 cbc2|
414 // then we want to diagnose cases where the matrix has a zero (i.e. "false")
415 // row or column, including the case where everything is zero. All these
416 // cases correspond to the determinant of the matrix being 0, which suggests
417 // that checking the det may be a convenient diagnostic check. There is only
418 // one additional case where the det is 0, which is when the matrix is all 1
419 // ("true"). The "all true" case represents the situation where both
420 // assignments could be an update as well as a capture. On the other hand,
421 // whenever det != 0, the roles of the update and the capture can be
422 // unambiguously assigned to as1 and as2 [1].
423 //
424 // [1] This can be easily verified by hand: there are 10 2x2 matrices with
425 // det = 0, leaving 6 cases where det != 0:
426 // 0 1 0 1 1 0 1 0 1 1 1 1
427 // 1 0 1 1 0 1 1 1 0 1 1 0
428 // In each case the classification is unambiguous.
429
430 // |cbu1 cbu2|
431 // det |cbc1 cbc2| = cbu1*cbc2 - cbu2*cbc1
432 int det{int(cbu1) * int(cbc2) - int(cbu2) * int(cbc1)};
433
434 auto errorCaptureShouldRead{[&](const parser::CharBlock &source,
435 const std::string &expr) {
436 context_.Say(source,
437 "In ATOMIC UPDATE operation with CAPTURE the right-hand side of the capture assignment should read %s"_err_en_US,
438 expr);
439 }};
440
441 auto errorNeitherWorks{[&]() {
442 context_.Say(source,
443 "In ATOMIC UPDATE operation with CAPTURE neither statement could be the update or the capture"_err_en_US);
444 }};
445
446 auto makeSelectionFromDet{[&](int det) -> ReturnTy {
447 // If det != 0, then the checks unambiguously suggest a specific
448 // categorization.
449 // If det == 0, then this function should be called only if the
450 // checks haven't ruled out any possibility, i.e. when both assigments
451 // could still be either updates or captures.
452 if (det > 0) {
453 // as1 is update, as2 is capture
454 if (isUpdateCapture(as1, as2)) {
455 return std::make_pair(/*Update=*/ec1, /*Capture=*/ec2);
456 } else {
457 errorCaptureShouldRead(act2.source, as1.lhs.AsFortran());
458 return std::make_pair(nullptr, nullptr);
459 }
460 } else if (det < 0) {
461 // as2 is update, as1 is capture
462 if (isUpdateCapture(as2, as1)) {
463 return std::make_pair(/*Update=*/ec2, /*Capture=*/ec1);
464 } else {
465 errorCaptureShouldRead(act1.source, as2.lhs.AsFortran());
466 return std::make_pair(nullptr, nullptr);
467 }
468 } else {
469 bool updateFirst{isUpdateCapture(as1, as2)};
470 bool captureFirst{isUpdateCapture(as2, as1)};
471 if (updateFirst && captureFirst) {
472 // If both assignment could be the update and both could be the
473 // capture, emit a warning about the ambiguity.
474 context_.Say(act1.source,
475 "In ATOMIC UPDATE operation with CAPTURE either statement could be the update and the capture, assuming the first one is the capture statement"_warn_en_US);
476 return std::make_pair(/*Update=*/ec2, /*Capture=*/ec1);
477 }
478 if (updateFirst != captureFirst) {
479 const parser::ExecutionPartConstruct *upd{updateFirst ? ec1 : ec2};
480 const parser::ExecutionPartConstruct *cap{captureFirst ? ec1 : ec2};
481 return std::make_pair(upd, cap);
482 }
483 assert(!updateFirst && !captureFirst);
484 errorNeitherWorks();
485 return std::make_pair(nullptr, nullptr);
486 }
487 }};
488
489 if (det != 0 || (cbu1 && cbu2 && cbc1 && cbc2)) {
490 return makeSelectionFromDet(det);
491 }
492 assert(det == 0 && "Prior checks should have covered det != 0");
493
494 // If neither of the statements is an RMW update, it could still be a
495 // "write" update. Pretty much any assignment can be a write update, so
496 // recompute det with cbu1 = cbu2 = true.
497 if (int writeDet{int(cbc2) - int(cbc1)}; writeDet || (cbc1 && cbc2)) {
498 return makeSelectionFromDet(writeDet);
499 }
500
501 // It's only errors from here on.
502
503 if (!cbu1 && !cbu2 && !cbc1 && !cbc2) {
504 errorNeitherWorks();
505 return std::make_pair(x: nullptr, y: nullptr);
506 }
507
508 // The remaining cases are that
509 // - no candidate for update, or for capture,
510 // - one of the assigments cannot be anything.
511
512 if (!cbu1 && !cbu2) {
513 context_.Say(source,
514 "In ATOMIC UPDATE operation with CAPTURE neither statement could be the update"_err_en_US);
515 return std::make_pair(x: nullptr, y: nullptr);
516 } else if (!cbc1 && !cbc2) {
517 context_.Say(source,
518 "In ATOMIC UPDATE operation with CAPTURE neither statement could be the capture"_err_en_US);
519 return std::make_pair(x: nullptr, y: nullptr);
520 }
521
522 if ((!cbu1 && !cbc1) || (!cbu2 && !cbc2)) {
523 auto &src = (!cbu1 && !cbc1) ? act1.source : act2.source;
524 context_.Say(src,
525 "In ATOMIC UPDATE operation with CAPTURE the statement could be neither the update nor the capture"_err_en_US);
526 return std::make_pair(x: nullptr, y: nullptr);
527 }
528
529 // All cases should have been covered.
530 llvm_unreachable("Unchecked condition");
531}
532
533void OmpStructureChecker::CheckAtomicCaptureAssignment(
534 const evaluate::Assignment &capture, const SomeExpr &atom,
535 parser::CharBlock source) {
536 auto [lsrc, rsrc]{SplitAssignmentSource(source)};
537 const SomeExpr &cap{capture.lhs};
538
539 if (!IsVarOrFunctionRef(atom)) {
540 ErrorShouldBeVariable(atom, rsrc);
541 } else {
542 CheckAtomicVariable(atom, rsrc);
543 // This part should have been checked prior to calling this function.
544 assert(*GetConvertInput(capture.rhs) == atom &&
545 "This cannot be a capture assignment");
546 CheckStorageOverlap(atom, {cap}, source);
547 }
548}
549
550void OmpStructureChecker::CheckAtomicReadAssignment(
551 const evaluate::Assignment &read, parser::CharBlock source) {
552 auto [lsrc, rsrc]{SplitAssignmentSource(source)};
553
554 if (auto maybe{GetConvertInput(read.rhs)}) {
555 const SomeExpr &atom{*maybe};
556
557 if (!IsVarOrFunctionRef(atom)) {
558 ErrorShouldBeVariable(atom, rsrc);
559 } else {
560 CheckAtomicVariable(atom, rsrc);
561 CheckStorageOverlap(atom, {read.lhs}, source);
562 }
563 } else {
564 ErrorShouldBeVariable(read.rhs, rsrc);
565 }
566}
567
568void OmpStructureChecker::CheckAtomicWriteAssignment(
569 const evaluate::Assignment &write, parser::CharBlock source) {
570 // [6.0:190:13-15]
571 // A write structured block is write-statement, a write statement that has
572 // one of the following forms:
573 // x = expr
574 // x => expr
575 auto [lsrc, rsrc]{SplitAssignmentSource(source)};
576 const SomeExpr &atom{write.lhs};
577
578 if (!IsVarOrFunctionRef(atom)) {
579 ErrorShouldBeVariable(atom, rsrc);
580 } else {
581 CheckAtomicVariable(atom, lsrc);
582 CheckStorageOverlap(atom, {write.rhs}, source);
583 }
584}
585
586void OmpStructureChecker::CheckAtomicUpdateAssignment(
587 const evaluate::Assignment &update, parser::CharBlock source) {
588 // [6.0:191:1-7]
589 // An update structured block is update-statement, an update statement
590 // that has one of the following forms:
591 // x = x operator expr
592 // x = expr operator x
593 // x = intrinsic-procedure-name (x)
594 // x = intrinsic-procedure-name (x, expr-list)
595 // x = intrinsic-procedure-name (expr-list, x)
596 auto [lsrc, rsrc]{SplitAssignmentSource(source)};
597 const SomeExpr &atom{update.lhs};
598
599 if (!IsVarOrFunctionRef(atom)) {
600 ErrorShouldBeVariable(atom, rsrc);
601 // Skip other checks.
602 return;
603 }
604
605 CheckAtomicVariable(atom, lsrc);
606
607 std::pair<operation::Operator, std::vector<SomeExpr>> top{
608 operation::Operator::Unknown, {}};
609 if (auto &&maybeInput{GetConvertInput(update.rhs)}) {
610 top = GetTopLevelOperation(*maybeInput);
611 }
612 switch (top.first) {
613 case operation::Operator::Add:
614 case operation::Operator::Sub:
615 case operation::Operator::Mul:
616 case operation::Operator::Div:
617 case operation::Operator::And:
618 case operation::Operator::Or:
619 case operation::Operator::Eqv:
620 case operation::Operator::Neqv:
621 case operation::Operator::Min:
622 case operation::Operator::Max:
623 case operation::Operator::Identity:
624 break;
625 case operation::Operator::Call:
626 context_.Say(source,
627 "A call to this function is not a valid ATOMIC UPDATE operation"_err_en_US);
628 return;
629 case operation::Operator::Convert:
630 context_.Say(source,
631 "An implicit or explicit type conversion is not a valid ATOMIC UPDATE operation"_err_en_US);
632 return;
633 case operation::Operator::Intrinsic:
634 context_.Say(source,
635 "This intrinsic function is not a valid ATOMIC UPDATE operation"_err_en_US);
636 return;
637 case operation::Operator::Constant:
638 case operation::Operator::Unknown:
639 context_.Say(
640 source, "This is not a valid ATOMIC UPDATE operation"_err_en_US);
641 return;
642 default:
643 assert(
644 top.first != operation::Operator::Identity && "Handle this separately");
645 context_.Say(source,
646 "The %s operator is not a valid ATOMIC UPDATE operation"_err_en_US,
647 operation::ToString(top.first));
648 return;
649 }
650 // Check how many times `atom` occurs as an argument, if it's a subexpression
651 // of an argument, and collect the non-atom arguments.
652 std::vector<SomeExpr> nonAtom;
653 MaybeExpr subExpr;
654 auto atomCount{[&]() {
655 int count{0};
656 for (const SomeExpr &arg : top.second) {
657 if (IsSameOrConvertOf(arg, atom)) {
658 ++count;
659 } else {
660 if (!subExpr && IsSubexpressionOf(atom, arg)) {
661 subExpr = arg;
662 }
663 nonAtom.push_back(arg);
664 }
665 }
666 return count;
667 }()};
668
669 bool hasError{false};
670 if (subExpr) {
671 context_.Say(rsrc,
672 "The atomic variable %s cannot be a proper subexpression of an argument (here: %s) in the update operation"_err_en_US,
673 atom.AsFortran(), subExpr->AsFortran());
674 hasError = true;
675 }
676 if (top.first == operation::Operator::Identity) {
677 // This is "x = y".
678 assert((atomCount == 0 || atomCount == 1) && "Unexpected count");
679 if (atomCount == 0) {
680 context_.Say(rsrc,
681 "The atomic variable %s should appear as an argument in the update operation"_err_en_US,
682 atom.AsFortran());
683 hasError = true;
684 }
685 } else {
686 if (atomCount == 0) {
687 context_.Say(rsrc,
688 "The atomic variable %s should appear as an argument of the top-level %s operator"_err_en_US,
689 atom.AsFortran(), operation::ToString(top.first));
690 hasError = true;
691 } else if (atomCount > 1) {
692 context_.Say(rsrc,
693 "The atomic variable %s should be exactly one of the arguments of the top-level %s operator"_err_en_US,
694 atom.AsFortran(), operation::ToString(top.first));
695 hasError = true;
696 }
697 }
698
699 if (!hasError) {
700 CheckStorageOverlap(atom, nonAtom, source);
701 }
702}
703
704void OmpStructureChecker::CheckAtomicConditionalUpdateAssignment(
705 const SomeExpr &cond, parser::CharBlock condSource,
706 const evaluate::Assignment &assign, parser::CharBlock assignSource) {
707 auto [alsrc, arsrc]{SplitAssignmentSource(assignSource)};
708 const SomeExpr &atom{assign.lhs};
709
710 if (!IsVarOrFunctionRef(atom)) {
711 ErrorShouldBeVariable(atom, arsrc);
712 // Skip other checks.
713 return;
714 }
715
716 CheckAtomicVariable(atom, alsrc);
717
718 auto top{GetTopLevelOperation(cond)};
719 // Missing arguments to operations would have been diagnosed by now.
720
721 switch (top.first) {
722 case operation::Operator::Associated:
723 if (atom != top.second.front()) {
724 context_.Say(assignSource,
725 "The pointer argument to ASSOCIATED must be same as the target of the assignment"_err_en_US);
726 }
727 break;
728 // x equalop e | e equalop x (allowing "e equalop x" is an extension)
729 case operation::Operator::Eq:
730 case operation::Operator::Eqv:
731 // x ordop expr | expr ordop x
732 case operation::Operator::Lt:
733 case operation::Operator::Gt: {
734 const SomeExpr &arg0{top.second[0]};
735 const SomeExpr &arg1{top.second[1]};
736 if (IsSameOrConvertOf(arg0, atom)) {
737 CheckStorageOverlap(atom, {arg1}, condSource);
738 } else if (IsSameOrConvertOf(arg1, atom)) {
739 CheckStorageOverlap(atom, {arg0}, condSource);
740 } else {
741 assert(top.first != operation::Operator::Identity &&
742 "Handle this separately");
743 context_.Say(assignSource,
744 "An argument of the %s operator should be the target of the assignment"_err_en_US,
745 operation::ToString(top.first));
746 }
747 break;
748 }
749 case operation::Operator::Identity:
750 case operation::Operator::True:
751 case operation::Operator::False:
752 break;
753 default:
754 assert(
755 top.first != operation::Operator::Identity && "Handle this separately");
756 context_.Say(condSource,
757 "The %s operator is not a valid condition for ATOMIC operation"_err_en_US,
758 operation::ToString(top.first));
759 break;
760 }
761}
762
763void OmpStructureChecker::CheckAtomicConditionalUpdateStmt(
764 const AnalyzedCondStmt &update, parser::CharBlock source) {
765 // The condition/statements must be:
766 // - cond: x equalop e ift: x = d iff: -
767 // - cond: x ordop expr ift: x = expr iff: - (+ commute ordop)
768 // - cond: associated(x) ift: x => expr iff: -
769 // - cond: associated(x, e) ift: x => expr iff: -
770
771 // The if-true statement must be present, and must be an assignment.
772 auto maybeAssign{GetEvaluateAssignment(update.ift.stmt)};
773 if (!maybeAssign) {
774 if (update.ift.stmt && !IsAssignment(update.ift.stmt)) {
775 context_.Say(update.ift.source,
776 "In ATOMIC UPDATE COMPARE the update statement should be an assignment"_err_en_US);
777 } else {
778 context_.Say(
779 source, "Invalid body of ATOMIC UPDATE COMPARE operation"_err_en_US);
780 }
781 return;
782 }
783 const evaluate::Assignment assign{*maybeAssign};
784 const SomeExpr &atom{assign.lhs};
785
786 CheckAtomicConditionalUpdateAssignment(
787 update.cond, update.source, assign, update.ift.source);
788
789 CheckStorageOverlap(atom, {assign.rhs}, update.ift.source);
790
791 if (update.iff) {
792 context_.Say(update.iff.source,
793 "In ATOMIC UPDATE COMPARE the update statement should not have an ELSE branch"_err_en_US);
794 }
795}
796
797void OmpStructureChecker::CheckAtomicUpdateOnly(
798 const parser::OpenMPAtomicConstruct &x, const parser::Block &body,
799 parser::CharBlock source) {
800 if (body.size() == 1) {
801 SourcedActionStmt action{GetActionStmt(&body.front())};
802 if (auto maybeUpdate{GetEvaluateAssignment(action.stmt)}) {
803 const SomeExpr &atom{maybeUpdate->lhs};
804 CheckAtomicUpdateAssignment(*maybeUpdate, action.source);
805
806 using Analysis = parser::OpenMPAtomicConstruct::Analysis;
807 x.analysis = MakeAtomicAnalysis(atom, std::nullopt,
808 MakeAtomicAnalysisOp(Analysis::Update, maybeUpdate),
809 MakeAtomicAnalysisOp(Analysis::None));
810 } else if (!IsAssignment(action.stmt)) {
811 context_.Say(
812 source, "ATOMIC UPDATE operation should be an assignment"_err_en_US);
813 }
814 } else {
815 context_.Say(x.source,
816 "ATOMIC UPDATE operation should have a single statement"_err_en_US);
817 }
818}
819
820void OmpStructureChecker::CheckAtomicConditionalUpdate(
821 const parser::OpenMPAtomicConstruct &x, const parser::Block &body,
822 parser::CharBlock source) {
823 // Allowable forms are (single-statement):
824 // - if ...
825 // - x = (... ? ... : x)
826 // and two-statement:
827 // - r = cond ; if (r) ...
828
829 const parser::ExecutionPartConstruct *ust{nullptr}; // update
830 const parser::ExecutionPartConstruct *cst{nullptr}; // condition
831
832 if (body.size() == 1) {
833 ust = &body.front();
834 } else if (body.size() == 2) {
835 cst = &body.front();
836 ust = &body.back();
837 } else {
838 context_.Say(source,
839 "ATOMIC UPDATE COMPARE operation should contain one or two statements"_err_en_US);
840 return;
841 }
842
843 // Flang doesn't support conditional-expr yet, so all update statements
844 // are if-statements.
845
846 // IfStmt: if (...) ...
847 // IfConstruct: if (...) then ... endif
848 auto maybeUpdate{AnalyzeConditionalStmt(ust)};
849 if (!maybeUpdate) {
850 context_.Say(source,
851 "In ATOMIC UPDATE COMPARE the update statement should be a conditional statement"_err_en_US);
852 return;
853 }
854
855 AnalyzedCondStmt &update{*maybeUpdate};
856
857 if (SourcedActionStmt action{GetActionStmt(cst)}) {
858 // The "condition" statement must be `r = cond`.
859 if (auto maybeCond{GetEvaluateAssignment(action.stmt)}) {
860 if (maybeCond->lhs != update.cond) {
861 context_.Say(update.source,
862 "In ATOMIC UPDATE COMPARE the conditional statement must use %s as the condition"_err_en_US,
863 maybeCond->lhs.AsFortran());
864 } else {
865 // If it's "r = ...; if (r) ..." then put the original condition
866 // in `update`.
867 update.cond = maybeCond->rhs;
868 }
869 } else {
870 context_.Say(action.source,
871 "In ATOMIC UPDATE COMPARE with two statements the first statement should compute the condition"_err_en_US);
872 }
873 }
874
875 evaluate::Assignment assign{*GetEvaluateAssignment(update.ift.stmt)};
876
877 CheckAtomicConditionalUpdateStmt(update, source);
878 if (IsCheckForAssociated(update.cond)) {
879 if (!IsPointerAssignment(assign)) {
880 context_.Say(source,
881 "The assignment should be a pointer-assignment when the condition is ASSOCIATED"_err_en_US);
882 }
883 } else {
884 if (IsPointerAssignment(assign)) {
885 context_.Say(source,
886 "The assignment cannot be a pointer-assignment except when the condition is ASSOCIATED"_err_en_US);
887 }
888 }
889
890 using Analysis = parser::OpenMPAtomicConstruct::Analysis;
891 x.analysis = MakeAtomicAnalysis(assign.lhs, update.cond,
892 MakeAtomicAnalysisOp(Analysis::Update | Analysis::IfTrue, assign),
893 MakeAtomicAnalysisOp(Analysis::None));
894}
895
896void OmpStructureChecker::CheckAtomicUpdateCapture(
897 const parser::OpenMPAtomicConstruct &x, const parser::Block &body,
898 parser::CharBlock source) {
899 if (body.size() != 2) {
900 context_.Say(source,
901 "ATOMIC UPDATE operation with CAPTURE should contain two statements"_err_en_US);
902 return;
903 }
904
905 auto [uec, cec]{CheckUpdateCapture(&body.front(), &body.back(), source)};
906 if (!uec || !cec) {
907 // Diagnostics already emitted.
908 return;
909 }
910 SourcedActionStmt uact{GetActionStmt(uec)};
911 SourcedActionStmt cact{GetActionStmt(cec)};
912 // The "dereferences" of std::optional are guaranteed to be valid after
913 // CheckUpdateCapture.
914 evaluate::Assignment update{*GetEvaluateAssignment(uact.stmt)};
915 evaluate::Assignment capture{*GetEvaluateAssignment(cact.stmt)};
916
917 const SomeExpr &atom{update.lhs};
918
919 using Analysis = parser::OpenMPAtomicConstruct::Analysis;
920 int action;
921
922 if (IsMaybeAtomicWrite(update)) {
923 action = Analysis::Write;
924 CheckAtomicWriteAssignment(update, uact.source);
925 } else {
926 action = Analysis::Update;
927 CheckAtomicUpdateAssignment(update, uact.source);
928 }
929 CheckAtomicCaptureAssignment(capture, atom, cact.source);
930
931 if (IsPointerAssignment(update) != IsPointerAssignment(capture)) {
932 context_.Say(cact.source,
933 "The update and capture assignments should both be pointer-assignments or both be non-pointer-assignments"_err_en_US);
934 return;
935 }
936
937 if (GetActionStmt(&body.front()).stmt == uact.stmt) {
938 x.analysis = MakeAtomicAnalysis(atom, std::nullopt,
939 MakeAtomicAnalysisOp(action, update),
940 MakeAtomicAnalysisOp(Analysis::Read, capture));
941 } else {
942 x.analysis = MakeAtomicAnalysis(atom, std::nullopt,
943 MakeAtomicAnalysisOp(Analysis::Read, capture),
944 MakeAtomicAnalysisOp(action, update));
945 }
946}
947
948void OmpStructureChecker::CheckAtomicConditionalUpdateCapture(
949 const parser::OpenMPAtomicConstruct &x, const parser::Block &body,
950 parser::CharBlock source) {
951 // There are two different variants of this:
952 // (1) conditional-update and capture separately:
953 // This form only allows single-statement updates, i.e. the update
954 // form "r = cond; if (r) ..." is not allowed.
955 // (2) conditional-update combined with capture in a single statement:
956 // This form does allow the condition to be calculated separately,
957 // i.e. "r = cond; if (r) ...".
958 // Regardless of what form it is, the actual update assignment is a
959 // proper write, i.e. "x = d", where d does not depend on x.
960
961 AnalyzedCondStmt update;
962 SourcedActionStmt capture;
963 bool captureAlways{true}, captureFirst{true};
964
965 auto extractCapture{[&]() {
966 capture = update.iff;
967 captureAlways = false;
968 update.iff = SourcedActionStmt{};
969 }};
970
971 auto classifyNonUpdate{[&](const SourcedActionStmt &action) {
972 // The non-update statement is either "r = cond" or the capture.
973 if (auto maybeAssign{GetEvaluateAssignment(action.stmt)}) {
974 if (update.cond == maybeAssign->lhs) {
975 // If this is "r = cond; if (r) ...", then update the condition.
976 update.cond = maybeAssign->rhs;
977 update.source = action.source;
978 // In this form, the update and the capture are combined into
979 // an IF-THEN-ELSE statement.
980 extractCapture();
981 } else {
982 // Assume this is the capture-statement.
983 capture = action;
984 }
985 }
986 }};
987
988 if (body.size() == 2) {
989 // This could be
990 // - capture; conditional-update (in any order), or
991 // - r = cond; if (r) capture-update
992 const parser::ExecutionPartConstruct *st1{&body.front()};
993 const parser::ExecutionPartConstruct *st2{&body.back()};
994 // In either case, the conditional statement can be analyzed by
995 // AnalyzeConditionalStmt, whereas the other statement cannot.
996 if (auto maybeUpdate1{AnalyzeConditionalStmt(st1)}) {
997 update = *maybeUpdate1;
998 classifyNonUpdate(GetActionStmt(st2));
999 captureFirst = false;
1000 } else if (auto maybeUpdate2{AnalyzeConditionalStmt(st2)}) {
1001 update = *maybeUpdate2;
1002 classifyNonUpdate(GetActionStmt(st1));
1003 } else {
1004 // None of the statements are conditional, this rules out the
1005 // "r = cond; if (r) ..." and the "capture + conditional-update"
1006 // variants. This could still be capture + write (which is classified
1007 // as conditional-update-capture in the spec).
1008 auto [uec, cec]{CheckUpdateCapture(st1, st2, source)};
1009 if (!uec || !cec) {
1010 // Diagnostics already emitted.
1011 return;
1012 }
1013 SourcedActionStmt uact{GetActionStmt(uec)};
1014 SourcedActionStmt cact{GetActionStmt(cec)};
1015 update.ift = uact;
1016 capture = cact;
1017 if (uec == st1) {
1018 captureFirst = false;
1019 }
1020 }
1021 } else if (body.size() == 1) {
1022 if (auto maybeUpdate{AnalyzeConditionalStmt(&body.front())}) {
1023 update = *maybeUpdate;
1024 // This is the form with update and capture combined into an IF-THEN-ELSE
1025 // statement. The capture-statement is always the ELSE branch.
1026 extractCapture();
1027 } else {
1028 goto invalid;
1029 }
1030 } else {
1031 context_.Say(source,
1032 "ATOMIC UPDATE COMPARE CAPTURE operation should contain one or two statements"_err_en_US);
1033 return;
1034 invalid:
1035 context_.Say(source,
1036 "Invalid body of ATOMIC UPDATE COMPARE CAPTURE operation"_err_en_US);
1037 return;
1038 }
1039
1040 // The update must have a form `x = d` or `x => d`.
1041 if (auto maybeWrite{GetEvaluateAssignment(update.ift.stmt)}) {
1042 const SomeExpr &atom{maybeWrite->lhs};
1043 CheckAtomicWriteAssignment(*maybeWrite, update.ift.source);
1044 if (auto maybeCapture{GetEvaluateAssignment(capture.stmt)}) {
1045 CheckAtomicCaptureAssignment(*maybeCapture, atom, capture.source);
1046
1047 if (IsPointerAssignment(*maybeWrite) !=
1048 IsPointerAssignment(*maybeCapture)) {
1049 context_.Say(capture.source,
1050 "The update and capture assignments should both be pointer-assignments or both be non-pointer-assignments"_err_en_US);
1051 return;
1052 }
1053 } else {
1054 if (!IsAssignment(capture.stmt)) {
1055 context_.Say(capture.source,
1056 "In ATOMIC UPDATE COMPARE CAPTURE the capture statement should be an assignment"_err_en_US);
1057 }
1058 return;
1059 }
1060 } else {
1061 if (!IsAssignment(update.ift.stmt)) {
1062 context_.Say(update.ift.source,
1063 "In ATOMIC UPDATE COMPARE CAPTURE the update statement should be an assignment"_err_en_US);
1064 }
1065 return;
1066 }
1067
1068 // update.iff should be empty here, the capture statement should be
1069 // stored in "capture".
1070
1071 // Fill out the analysis in the AST node.
1072 using Analysis = parser::OpenMPAtomicConstruct::Analysis;
1073 bool condUnused{std::visit(
1074 [](auto &&s) {
1075 using BareS = llvm::remove_cvref_t<decltype(s)>;
1076 if constexpr (std::is_same_v<BareS, evaluate::NullPointer>) {
1077 return true;
1078 } else {
1079 return false;
1080 }
1081 },
1082 update.cond.u)};
1083
1084 int updateWhen{!condUnused ? Analysis::IfTrue : 0};
1085 int captureWhen{!captureAlways ? Analysis::IfFalse : 0};
1086
1087 evaluate::Assignment updAssign{*GetEvaluateAssignment(update.ift.stmt)};
1088 evaluate::Assignment capAssign{*GetEvaluateAssignment(capture.stmt)};
1089
1090 if (captureFirst) {
1091 x.analysis = MakeAtomicAnalysis(updAssign.lhs, update.cond,
1092 MakeAtomicAnalysisOp(Analysis::Read | captureWhen, capAssign),
1093 MakeAtomicAnalysisOp(Analysis::Write | updateWhen, updAssign));
1094 } else {
1095 x.analysis = MakeAtomicAnalysis(updAssign.lhs, update.cond,
1096 MakeAtomicAnalysisOp(Analysis::Write | updateWhen, updAssign),
1097 MakeAtomicAnalysisOp(Analysis::Read | captureWhen, capAssign));
1098 }
1099}
1100
1101void OmpStructureChecker::CheckAtomicRead(
1102 const parser::OpenMPAtomicConstruct &x) {
1103 // [6.0:190:5-7]
1104 // A read structured block is read-statement, a read statement that has one
1105 // of the following forms:
1106 // v = x
1107 // v => x
1108 auto &dirSpec{std::get<parser::OmpDirectiveSpecification>(x.t)};
1109 auto &block{std::get<parser::Block>(x.t)};
1110
1111 // Read cannot be conditional or have a capture statement.
1112 if (x.IsCompare() || x.IsCapture()) {
1113 context_.Say(dirSpec.source,
1114 "ATOMIC READ cannot have COMPARE or CAPTURE clauses"_err_en_US);
1115 return;
1116 }
1117
1118 const parser::Block &body{GetInnermostExecPart(block)};
1119
1120 if (body.size() == 1) {
1121 SourcedActionStmt action{GetActionStmt(&body.front())};
1122 if (auto maybeRead{GetEvaluateAssignment(action.stmt)}) {
1123 CheckAtomicReadAssignment(*maybeRead, action.source);
1124
1125 if (auto maybe{GetConvertInput(maybeRead->rhs)}) {
1126 const SomeExpr &atom{*maybe};
1127 using Analysis = parser::OpenMPAtomicConstruct::Analysis;
1128 x.analysis = MakeAtomicAnalysis(atom, std::nullopt,
1129 MakeAtomicAnalysisOp(Analysis::Read, maybeRead),
1130 MakeAtomicAnalysisOp(Analysis::None));
1131 }
1132 } else if (!IsAssignment(action.stmt)) {
1133 context_.Say(
1134 x.source, "ATOMIC READ operation should be an assignment"_err_en_US);
1135 }
1136 } else {
1137 context_.Say(x.source,
1138 "ATOMIC READ operation should have a single statement"_err_en_US);
1139 }
1140}
1141
1142void OmpStructureChecker::CheckAtomicWrite(
1143 const parser::OpenMPAtomicConstruct &x) {
1144 auto &dirSpec{std::get<parser::OmpDirectiveSpecification>(x.t)};
1145 auto &block{std::get<parser::Block>(x.t)};
1146
1147 // Write cannot be conditional or have a capture statement.
1148 if (x.IsCompare() || x.IsCapture()) {
1149 context_.Say(dirSpec.source,
1150 "ATOMIC WRITE cannot have COMPARE or CAPTURE clauses"_err_en_US);
1151 return;
1152 }
1153
1154 const parser::Block &body{GetInnermostExecPart(block)};
1155
1156 if (body.size() == 1) {
1157 SourcedActionStmt action{GetActionStmt(&body.front())};
1158 if (auto maybeWrite{GetEvaluateAssignment(action.stmt)}) {
1159 const SomeExpr &atom{maybeWrite->lhs};
1160 CheckAtomicWriteAssignment(*maybeWrite, action.source);
1161
1162 using Analysis = parser::OpenMPAtomicConstruct::Analysis;
1163 x.analysis = MakeAtomicAnalysis(atom, std::nullopt,
1164 MakeAtomicAnalysisOp(Analysis::Write, maybeWrite),
1165 MakeAtomicAnalysisOp(Analysis::None));
1166 } else if (!IsAssignment(action.stmt)) {
1167 context_.Say(
1168 x.source, "ATOMIC WRITE operation should be an assignment"_err_en_US);
1169 }
1170 } else {
1171 context_.Say(x.source,
1172 "ATOMIC WRITE operation should have a single statement"_err_en_US);
1173 }
1174}
1175
1176void OmpStructureChecker::CheckAtomicUpdate(
1177 const parser::OpenMPAtomicConstruct &x) {
1178 auto &block{std::get<parser::Block>(x.t)};
1179
1180 bool isConditional{x.IsCompare()};
1181 bool isCapture{x.IsCapture()};
1182 const parser::Block &body{GetInnermostExecPart(block)};
1183
1184 if (isConditional && isCapture) {
1185 CheckAtomicConditionalUpdateCapture(x, body, x.source);
1186 } else if (isConditional) {
1187 CheckAtomicConditionalUpdate(x, body, x.source);
1188 } else if (isCapture) {
1189 CheckAtomicUpdateCapture(x, body, x.source);
1190 } else { // update-only
1191 CheckAtomicUpdateOnly(x, body, x.source);
1192 }
1193}
1194
1195void OmpStructureChecker::Enter(const parser::OpenMPAtomicConstruct &x) {
1196 if (visitedAtomicSource_.empty())
1197 visitedAtomicSource_ = x.source;
1198
1199 // All of the following groups have the "exclusive" property, i.e. at
1200 // most one clause from each group is allowed.
1201 // The exclusivity-checking code should eventually be unified for all
1202 // clauses, with clause groups defined in OMP.td.
1203 std::array atomic{llvm::omp::Clause::OMPC_read,
1204 llvm::omp::Clause::OMPC_update, llvm::omp::Clause::OMPC_write};
1205 std::array memoryOrder{llvm::omp::Clause::OMPC_acq_rel,
1206 llvm::omp::Clause::OMPC_acquire, llvm::omp::Clause::OMPC_relaxed,
1207 llvm::omp::Clause::OMPC_release, llvm::omp::Clause::OMPC_seq_cst};
1208
1209 auto checkExclusive{[&](llvm::ArrayRef<llvm::omp::Clause> group,
1210 std::string_view name,
1211 const parser::OmpClauseList &clauses) {
1212 const parser::OmpClause *present{nullptr};
1213 for (const parser::OmpClause &clause : clauses.v) {
1214 llvm::omp::Clause id{clause.Id()};
1215 if (!llvm::is_contained(group, id)) {
1216 continue;
1217 }
1218 if (present == nullptr) {
1219 present = &clause;
1220 continue;
1221 } else if (id == present->Id()) {
1222 // Ignore repetitions of the same clause, those will be diagnosed
1223 // separately.
1224 continue;
1225 }
1226 parser::MessageFormattedText txt(
1227 "At most one clause from the '%s' group is allowed on ATOMIC construct"_err_en_US,
1228 name.data());
1229 parser::Message message(clause.source, txt);
1230 message.Attach(present->source,
1231 "Previous clause from this group provided here"_en_US);
1232 context_.Say(std::move(message));
1233 return;
1234 }
1235 }};
1236
1237 auto &dirSpec{std::get<parser::OmpDirectiveSpecification>(x.t)};
1238 auto &dir{std::get<parser::OmpDirectiveName>(dirSpec.t)};
1239 PushContextAndClauseSets(dir.source, llvm::omp::Directive::OMPD_atomic);
1240 llvm::omp::Clause kind{x.GetKind()};
1241
1242 checkExclusive(atomic, "atomic", dirSpec.Clauses());
1243 checkExclusive(memoryOrder, "memory-order", dirSpec.Clauses());
1244
1245 switch (kind) {
1246 case llvm::omp::Clause::OMPC_read:
1247 CheckAtomicRead(x);
1248 break;
1249 case llvm::omp::Clause::OMPC_write:
1250 CheckAtomicWrite(x);
1251 break;
1252 case llvm::omp::Clause::OMPC_update:
1253 CheckAtomicUpdate(x);
1254 break;
1255 default:
1256 break;
1257 }
1258}
1259
1260void OmpStructureChecker::Leave(const parser::OpenMPAtomicConstruct &) {
1261 dirContext_.pop_back();
1262}
1263
1264} // namespace Fortran::semantics
1265

source code of flang/lib/Semantics/check-omp-atomic.cpp