Skip to content

Commit 6b5c38d

Browse files
committed
Revert "[flang][OpenMP] Reassociate ATOMIC update expressions (#153098)"
This reverts commit 4f6ae2a. This PR causes build breaks with older versions of GCC.
1 parent ed2fa6f commit 6b5c38d

File tree

5 files changed

+46
-329
lines changed

5 files changed

+46
-329
lines changed

flang/lib/Semantics/check-omp-atomic.cpp

Lines changed: 41 additions & 241 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@
1313
#include "check-omp-structure.h"
1414

1515
#include "flang/Common/indirection.h"
16-
#include "flang/Common/template.h"
1716
#include "flang/Evaluate/expression.h"
18-
#include "flang/Evaluate/match.h"
1917
#include "flang/Evaluate/rewrite.h"
2018
#include "flang/Evaluate/tools.h"
2119
#include "flang/Parser/char-block.h"
@@ -52,127 +50,6 @@ static bool operator!=(const evaluate::Expr<T> &e, const evaluate::Expr<U> &f) {
5250
return !(e == f);
5351
}
5452

55-
namespace {
56-
template <typename...> struct IsIntegral {
57-
static constexpr bool value{false};
58-
};
59-
60-
template <common::TypeCategory C, int K>
61-
struct IsIntegral<evaluate::Type<C, K>> {
62-
static constexpr bool value{//
63-
C == common::TypeCategory::Integer ||
64-
C == common::TypeCategory::Unsigned ||
65-
C == common::TypeCategory::Logical};
66-
};
67-
68-
template <typename T> constexpr bool is_integral_v{IsIntegral<T>::value};
69-
70-
template <typename T, typename Op0, typename Op1>
71-
using ReassocOpBase = evaluate::match::AnyOfPattern< //
72-
evaluate::match::Add<T, Op0, Op1>, //
73-
evaluate::match::Mul<T, Op0, Op1>>;
74-
75-
template <typename T, typename Op0, typename Op1>
76-
struct ReassocOp : public ReassocOpBase<T, Op0, Op1> {
77-
using Base = ReassocOpBase<T, Op0, Op1>;
78-
using Base::Base;
79-
};
80-
81-
template <typename T, typename Op0, typename Op1>
82-
ReassocOp<T, Op0, Op1> reassocOp(const Op0 &op0, const Op1 &op1) {
83-
return ReassocOp<T, Op0, Op1>(op0, op1);
84-
}
85-
} // namespace
86-
87-
struct ReassocRewriter : public evaluate::rewrite::Identity {
88-
using Id = evaluate::rewrite::Identity;
89-
using Id::operator();
90-
struct NonIntegralTag {};
91-
92-
ReassocRewriter(const SomeExpr &atom) : atom_(atom) {}
93-
94-
// Try to find cases where the input expression is of the form
95-
// (1) (a . b) . c, or
96-
// (2) a . (b . c),
97-
// where . denotes an associative operation (currently + or *), and a, b, c
98-
// are some subexpresions.
99-
// If one of the operands in the nested operation is the atomic variable
100-
// (with some possible type conversions applied to it), bring it to the
101-
// top-level operation, and move the top-level operand into the nested
102-
// operation.
103-
// For example, assuming x is the atomic variable:
104-
// (a + x) + b -> (a + b) + x, i.e. (conceptually) swap x and b.
105-
template <typename T, typename U,
106-
typename = std::enable_if_t<is_integral_v<T>>>
107-
evaluate::Expr<T> operator()(evaluate::Expr<T> &&x, const U &u) {
108-
// As per the above comment, there are 3 subexpressions involved in this
109-
// transformation. A match::Expr<T> will match evaluate::Expr<U> when T is
110-
// same as U, plus it will store a pointer (ref) to the matched expression.
111-
// When the match is successful, the sub[i].ref will point to a, b, x (in
112-
// some order) from the example above.
113-
evaluate::match::Expr<T> sub[3];
114-
auto inner{reassocOp<T>(sub[0], sub[1])};
115-
auto outer1{reassocOp<T>(inner, sub[2])}; // inner + something
116-
auto outer2{reassocOp<T>(sub[2], inner)}; // something + inner
117-
// There is no way to ensure that the outer operation is the same as
118-
// the inner one. They are matched independently, so we need to compare
119-
// the index in the member variant that represents the matched type.
120-
if ((match(outer1, x) && outer1.ref.index() == inner.ref.index()) ||
121-
(match(outer2, x) && outer2.ref.index() == inner.ref.index())) {
122-
size_t atomIdx{[&]() { // sub[atomIdx] will be the atom.
123-
size_t idx;
124-
for (idx = 0; idx != 3; ++idx) {
125-
if (IsAtom(*sub[idx].ref)) {
126-
break;
127-
}
128-
}
129-
return idx;
130-
}()};
131-
132-
if (atomIdx > 2) {
133-
return Id::operator()(std::move(x), u);
134-
}
135-
return common::visit(
136-
[&](auto &&s) {
137-
using Expr = evaluate::Expr<T>;
138-
using TypeS = llvm::remove_cvref_t<decltype(s)>;
139-
// This visitor has to be semantically correct for all possible
140-
// types of s even though at runtime s will only be one of the
141-
// matched types.
142-
// Limit the construction to the operation types that we tried
143-
// to match (otherwise TypeS(op1, op2) would fail for non-binary
144-
// operations).
145-
if constexpr (common::HasMember<TypeS,
146-
typename decltype(outer1)::MatchTypes>) {
147-
Expr atom{*sub[atomIdx].ref};
148-
Expr op1{*sub[(atomIdx + 1) % 3].ref};
149-
Expr op2{*sub[(atomIdx + 2) % 3].ref};
150-
return Expr(
151-
TypeS(atom, Expr(TypeS(std::move(op1), std::move(op2)))));
152-
} else {
153-
return Expr(TypeS(s));
154-
}
155-
},
156-
evaluate::match::deparen(x).u);
157-
}
158-
return Id::operator()(std::move(x), u);
159-
}
160-
161-
template <typename T, typename U,
162-
typename = std::enable_if_t<!is_integral_v<T>>>
163-
evaluate::Expr<T> operator()(
164-
evaluate::Expr<T> &&x, const U &u, NonIntegralTag = {}) {
165-
return Id::operator()(std::move(x), u);
166-
}
167-
168-
private:
169-
template <typename T> bool IsAtom(const evaluate::Expr<T> &x) const {
170-
return IsSameOrConvertOf(evaluate::AsGenericExpr(AsRvalue(x)), atom_);
171-
}
172-
173-
const SomeExpr &atom_;
174-
};
175-
17653
struct AnalyzedCondStmt {
17754
SomeExpr cond{evaluate::NullPointer{}}; // Default ctor is deleted
17855
parser::CharBlock source;
@@ -322,26 +199,6 @@ static std::pair<parser::CharBlock, parser::CharBlock> SplitAssignmentSource(
322199
llvm_unreachable("Could not find assignment operator");
323200
}
324201

325-
static std::vector<SomeExpr> GetNonAtomExpressions(
326-
const SomeExpr &atom, const std::vector<SomeExpr> &exprs) {
327-
std::vector<SomeExpr> nonAtom;
328-
for (const SomeExpr &e : exprs) {
329-
if (!IsSameOrConvertOf(e, atom)) {
330-
nonAtom.push_back(e);
331-
}
332-
}
333-
return nonAtom;
334-
}
335-
336-
static std::vector<SomeExpr> GetNonAtomArguments(
337-
const SomeExpr &atom, const SomeExpr &expr) {
338-
if (auto &&maybe{GetConvertInput(expr)}) {
339-
return GetNonAtomExpressions(
340-
atom, GetTopLevelOperationIgnoreResizing(*maybe).second);
341-
}
342-
return {};
343-
}
344-
345202
static bool IsCheckForAssociated(const SomeExpr &cond) {
346203
return GetTopLevelOperationIgnoreResizing(cond).first ==
347204
operation::Operator::Associated;
@@ -768,8 +625,7 @@ void OmpStructureChecker::CheckAtomicWriteAssignment(
768625
}
769626
}
770627

771-
std::optional<evaluate::Assignment>
772-
OmpStructureChecker::CheckAtomicUpdateAssignment(
628+
void OmpStructureChecker::CheckAtomicUpdateAssignment(
773629
const evaluate::Assignment &update, parser::CharBlock source) {
774630
// [6.0:191:1-7]
775631
// An update structured block is update-statement, an update statement
@@ -785,46 +641,14 @@ OmpStructureChecker::CheckAtomicUpdateAssignment(
785641
if (!IsVarOrFunctionRef(atom)) {
786642
ErrorShouldBeVariable(atom, rsrc);
787643
// Skip other checks.
788-
return std::nullopt;
644+
return;
789645
}
790646

791647
CheckAtomicVariable(atom, lsrc);
792648

793-
auto [hasErrors, tryReassoc]{CheckAtomicUpdateAssignmentRhs(
794-
atom, update.rhs, source, /*suppressDiagnostics=*/true)};
795-
796-
if (!hasErrors) {
797-
CheckStorageOverlap(atom, GetNonAtomArguments(atom, update.rhs), source);
798-
return std::nullopt;
799-
} else if (tryReassoc) {
800-
ReassocRewriter ra(atom);
801-
SomeExpr raRhs{evaluate::rewrite::Mutator(ra)(update.rhs)};
802-
803-
std::tie(hasErrors, tryReassoc) = CheckAtomicUpdateAssignmentRhs(
804-
atom, raRhs, source, /*suppressDiagnostics=*/true);
805-
if (!hasErrors) {
806-
CheckStorageOverlap(atom, GetNonAtomArguments(atom, raRhs), source);
807-
808-
evaluate::Assignment raAssign(update);
809-
raAssign.rhs = raRhs;
810-
return raAssign;
811-
}
812-
}
813-
814-
// This is guaranteed to report errors.
815-
CheckAtomicUpdateAssignmentRhs(
816-
atom, update.rhs, source, /*suppressDiagnostics=*/false);
817-
return std::nullopt;
818-
}
819-
820-
std::pair<bool, bool> OmpStructureChecker::CheckAtomicUpdateAssignmentRhs(
821-
const SomeExpr &atom, const SomeExpr &rhs, parser::CharBlock source,
822-
bool suppressDiagnostics) {
823-
auto [lsrc, rsrc]{SplitAssignmentSource(source)};
824-
825649
std::pair<operation::Operator, std::vector<SomeExpr>> top{
826650
operation::Operator::Unknown, {}};
827-
if (auto &&maybeInput{GetConvertInput(rhs)}) {
651+
if (auto &&maybeInput{GetConvertInput(update.rhs)}) {
828652
top = GetTopLevelOperationIgnoreResizing(*maybeInput);
829653
}
830654
switch (top.first) {
@@ -841,39 +665,29 @@ std::pair<bool, bool> OmpStructureChecker::CheckAtomicUpdateAssignmentRhs(
841665
case operation::Operator::Identity:
842666
break;
843667
case operation::Operator::Call:
844-
if (!suppressDiagnostics) {
845-
context_.Say(source,
846-
"A call to this function is not a valid ATOMIC UPDATE operation"_err_en_US);
847-
}
848-
return std::make_pair(true, false);
668+
context_.Say(source,
669+
"A call to this function is not a valid ATOMIC UPDATE operation"_err_en_US);
670+
return;
849671
case operation::Operator::Convert:
850-
if (!suppressDiagnostics) {
851-
context_.Say(source,
852-
"An implicit or explicit type conversion is not a valid ATOMIC UPDATE operation"_err_en_US);
853-
}
854-
return std::make_pair(true, false);
672+
context_.Say(source,
673+
"An implicit or explicit type conversion is not a valid ATOMIC UPDATE operation"_err_en_US);
674+
return;
855675
case operation::Operator::Intrinsic:
856-
if (!suppressDiagnostics) {
857-
context_.Say(source,
858-
"This intrinsic function is not a valid ATOMIC UPDATE operation"_err_en_US);
859-
}
860-
return std::make_pair(true, false);
676+
context_.Say(source,
677+
"This intrinsic function is not a valid ATOMIC UPDATE operation"_err_en_US);
678+
return;
861679
case operation::Operator::Constant:
862680
case operation::Operator::Unknown:
863-
if (!suppressDiagnostics) {
864-
context_.Say(
865-
source, "This is not a valid ATOMIC UPDATE operation"_err_en_US);
866-
}
867-
return std::make_pair(true, false);
681+
context_.Say(
682+
source, "This is not a valid ATOMIC UPDATE operation"_err_en_US);
683+
return;
868684
default:
869685
assert(
870686
top.first != operation::Operator::Identity && "Handle this separately");
871-
if (!suppressDiagnostics) {
872-
context_.Say(source,
873-
"The %s operator is not a valid ATOMIC UPDATE operation"_err_en_US,
874-
operation::ToString(top.first));
875-
}
876-
return std::make_pair(true, false);
687+
context_.Say(source,
688+
"The %s operator is not a valid ATOMIC UPDATE operation"_err_en_US,
689+
operation::ToString(top.first));
690+
return;
877691
}
878692
// Check how many times `atom` occurs as an argument, if it's a subexpression
879693
// of an argument, and collect the non-atom arguments.
@@ -894,48 +708,39 @@ std::pair<bool, bool> OmpStructureChecker::CheckAtomicUpdateAssignmentRhs(
894708
return count;
895709
}()};
896710

897-
bool hasError{false}, tryReassoc{false};
711+
bool hasError{false};
898712
if (subExpr) {
899-
if (!suppressDiagnostics) {
900-
context_.Say(rsrc,
901-
"The atomic variable %s cannot be a proper subexpression of an argument (here: %s) in the update operation"_err_en_US,
902-
atom.AsFortran(), subExpr->AsFortran());
903-
}
713+
context_.Say(rsrc,
714+
"The atomic variable %s cannot be a proper subexpression of an argument (here: %s) in the update operation"_err_en_US,
715+
atom.AsFortran(), subExpr->AsFortran());
904716
hasError = true;
905717
}
906718
if (top.first == operation::Operator::Identity) {
907719
// This is "x = y".
908720
assert((atomCount == 0 || atomCount == 1) && "Unexpected count");
909721
if (atomCount == 0) {
910-
if (!suppressDiagnostics) {
911-
context_.Say(rsrc,
912-
"The atomic variable %s should appear as an argument in the update operation"_err_en_US,
913-
atom.AsFortran());
914-
}
722+
context_.Say(rsrc,
723+
"The atomic variable %s should appear as an argument in the update operation"_err_en_US,
724+
atom.AsFortran());
915725
hasError = true;
916726
}
917727
} else {
918728
if (atomCount == 0) {
919-
if (!suppressDiagnostics) {
920-
context_.Say(rsrc,
921-
"The atomic variable %s should appear as an argument of the top-level %s operator"_err_en_US,
922-
atom.AsFortran(), operation::ToString(top.first));
923-
}
924-
// If `atom` is a proper subexpression, and it not present as an
925-
// argument on its own, reassociation may be able to help.
926-
tryReassoc = subExpr.has_value();
729+
context_.Say(rsrc,
730+
"The atomic variable %s should appear as an argument of the top-level %s operator"_err_en_US,
731+
atom.AsFortran(), operation::ToString(top.first));
927732
hasError = true;
928733
} else if (atomCount > 1) {
929-
if (!suppressDiagnostics) {
930-
context_.Say(rsrc,
931-
"The atomic variable %s should be exactly one of the arguments of the top-level %s operator"_err_en_US,
932-
atom.AsFortran(), operation::ToString(top.first));
933-
}
734+
context_.Say(rsrc,
735+
"The atomic variable %s should be exactly one of the arguments of the top-level %s operator"_err_en_US,
736+
atom.AsFortran(), operation::ToString(top.first));
934737
hasError = true;
935738
}
936739
}
937740

938-
return std::make_pair(hasError, tryReassoc);
741+
if (!hasError) {
742+
CheckStorageOverlap(atom, nonAtom, source);
743+
}
939744
}
940745

941746
void OmpStructureChecker::CheckAtomicConditionalUpdateAssignment(
@@ -1038,13 +843,11 @@ void OmpStructureChecker::CheckAtomicUpdateOnly(
1038843
SourcedActionStmt action{GetActionStmt(&body.front())};
1039844
if (auto maybeUpdate{GetEvaluateAssignment(action.stmt)}) {
1040845
const SomeExpr &atom{maybeUpdate->lhs};
1041-
auto maybeAssign{
1042-
CheckAtomicUpdateAssignment(*maybeUpdate, action.source)};
1043-
auto &updateAssign{maybeAssign.has_value() ? maybeAssign : maybeUpdate};
846+
CheckAtomicUpdateAssignment(*maybeUpdate, action.source);
1044847

1045848
using Analysis = parser::OpenMPAtomicConstruct::Analysis;
1046849
x.analysis = AtomicAnalysis(atom)
1047-
.addOp0(Analysis::Update, updateAssign)
850+
.addOp0(Analysis::Update, maybeUpdate)
1048851
.addOp1(Analysis::None);
1049852
} else if (!IsAssignment(action.stmt)) {
1050853
context_.Say(
@@ -1160,32 +963,29 @@ void OmpStructureChecker::CheckAtomicUpdateCapture(
1160963
using Analysis = parser::OpenMPAtomicConstruct::Analysis;
1161964
int action;
1162965

1163-
std::optional<evaluate::Assignment> updateAssign{update};
1164966
if (IsMaybeAtomicWrite(update)) {
1165967
action = Analysis::Write;
1166968
CheckAtomicWriteAssignment(update, uact.source);
1167969
} else {
1168970
action = Analysis::Update;
1169-
if (auto &&maybe{CheckAtomicUpdateAssignment(update, uact.source)}) {
1170-
updateAssign = maybe;
1171-
}
971+
CheckAtomicUpdateAssignment(update, uact.source);
1172972
}
1173973
CheckAtomicCaptureAssignment(capture, atom, cact.source);
1174974

1175-
if (IsPointerAssignment(*updateAssign) != IsPointerAssignment(capture)) {
975+
if (IsPointerAssignment(update) != IsPointerAssignment(capture)) {
1176976
context_.Say(cact.source,
1177977
"The update and capture assignments should both be pointer-assignments or both be non-pointer-assignments"_err_en_US);
1178978
return;
1179979
}
1180980

1181981
if (GetActionStmt(&body.front()).stmt == uact.stmt) {
1182982
x.analysis = AtomicAnalysis(atom)
1183-
.addOp0(action, updateAssign)
983+
.addOp0(action, update)
1184984
.addOp1(Analysis::Read, capture);
1185985
} else {
1186986
x.analysis = AtomicAnalysis(atom)
1187987
.addOp0(Analysis::Read, capture)
1188-
.addOp1(action, updateAssign);
988+
.addOp1(action, update);
1189989
}
1190990
}
1191991

0 commit comments

Comments
 (0)