FLANG
fold-reduction.h
1//===-- lib/Evaluate/fold-reduction.h -------------------------------------===//
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#ifndef FORTRAN_EVALUATE_FOLD_REDUCTION_H_
10#define FORTRAN_EVALUATE_FOLD_REDUCTION_H_
11
12#include "fold-implementation.h"
13
14namespace Fortran::evaluate {
15
16// DOT_PRODUCT
17template <typename T>
18Expr<T> FoldDotProduct(FoldingContext &context, FunctionRef<T> &&funcRef) {
19 using Element = typename Constant<T>::Element;
20 const int kind{funcRef.kind()};
21 auto args{funcRef.arguments()};
22 CHECK(args.size() == 2);
23 Folder<T> folder{kind, context};
24 Constant<T> *va{folder.Folding(args[0])};
25 Constant<T> *vb{folder.Folding(args[1])};
26 if (va && vb) {
27 CHECK(va->Rank() == 1 && vb->Rank() == 1);
28 if (va->size() != vb->size()) {
29 context.messages().Say(
30 "Vector arguments to DOT_PRODUCT have distinct extents %zd and %zd"_err_en_US,
31 va->size(), vb->size());
32 return MakeInvalidIntrinsic(std::move(funcRef));
33 }
34 Element sum{Element::Zero(kind)};
35 bool overflow{false};
36 if constexpr (T::category == TypeCategory::Complex) {
37 std::vector<Element> conjugates;
38 for (const Element &x : va->values()) {
39 conjugates.emplace_back(x.CONJG());
40 }
41 Constant<T> conjgA{
42 kind, std::move(conjugates), ConstantSubscripts{va->shape()}};
43 Expr<T> products{Fold(
44 context, Expr<T>{std::move(conjgA)} * Expr<T>{Constant<T>{*vb}})};
45 Constant<T> &cProducts{DEREF(UnwrapConstantValue<T>(kind, products))};
46 [[maybe_unused]] Element correction{Element::Zero(kind)};
47 const auto &rounding{context.targetCharacteristics().roundingMode()};
48 for (const Element &x : cProducts.values()) {
49 if constexpr (useKahanSummation) {
50 auto added{sum.KahanSummation(x, correction, rounding)};
51 overflow |= added.flags.test(RealFlag::Overflow);
52 sum = added.value;
53 } else {
54 auto added{sum.Add(x, rounding)};
55 overflow |= added.flags.test(RealFlag::Overflow);
56 sum = added.value;
57 }
58 }
59 } else if constexpr (T::category == TypeCategory::Logical) {
60 Expr<T> conjunctions{Fold(context,
61 Expr<T>{LogicalOperation{LogicalOperator::And,
62 Expr<T>{Constant<T>{*va}}, Expr<T>{Constant<T>{*vb}}}})};
63 Constant<T> &cConjunctions{
64 DEREF(UnwrapConstantValue<T>(kind, conjunctions))};
65 for (const Element &x : cConjunctions.values()) {
66 if (x.IsTrue()) {
67 sum = Element{kind, true};
68 break;
69 }
70 }
71 } else if constexpr (T::category == TypeCategory::Integer) {
72 Expr<T> products{
73 Fold(context, Expr<T>{Constant<T>{*va}} * Expr<T>{Constant<T>{*vb}})};
74 Constant<T> &cProducts{DEREF(UnwrapConstantValue<T>(kind, products))};
75 for (const Element &x : cProducts.values()) {
76 auto next{sum.AddSigned(x)};
77 overflow |= next.overflow;
78 sum = std::move(next.value);
79 }
80 } else if constexpr (T::category == TypeCategory::Unsigned) {
81 Expr<T> products{
82 Fold(context, Expr<T>{Constant<T>{*va}} * Expr<T>{Constant<T>{*vb}})};
83 Constant<T> &cProducts{DEREF(UnwrapConstantValue<T>(kind, products))};
84 for (const Element &x : cProducts.values()) {
85 sum = sum.AddUnsigned(x).value;
86 }
87 } else {
88 static_assert(T::category == TypeCategory::Real);
89 Expr<T> products{
90 Fold(context, Expr<T>{Constant<T>{*va}} * Expr<T>{Constant<T>{*vb}})};
91 Constant<T> &cProducts{DEREF(UnwrapConstantValue<T>(kind, products))};
92 [[maybe_unused]] Element correction{Element::Zero(kind)};
93 const auto &rounding{context.targetCharacteristics().roundingMode()};
94 for (const Element &x : cProducts.values()) {
95 if constexpr (useKahanSummation) {
96 auto added{sum.KahanSummation(x, correction, rounding)};
97 overflow |= added.flags.test(RealFlag::Overflow);
98 sum = added.value;
99 } else {
100 auto added{sum.Add(x, rounding)};
101 overflow |= added.flags.test(RealFlag::Overflow);
102 sum = added.value;
103 }
104 }
105 }
106 if (overflow) {
107 context.Warn(common::UsageWarning::FoldingException,
108 "DOT_PRODUCT of %s data overflowed during computation"_warn_en_US,
109 T{kind}.AsFortran());
110 }
111 return MakeConstantExpr<T>(kind, std::move(sum));
112 }
113 return Expr<T>{std::move(funcRef)};
114}
115
116// Fold and validate a DIM= argument. Returns false on error.
117bool CheckReductionDIM(std::optional<int> &dim, FoldingContext &,
118 ActualArguments &, std::optional<int> dimIndex, int rank);
119
120// Fold and validate a MASK= argument. Return null on error, absent MASK=, or
121// non-constant MASK=.
122Constant<LogicalResult> *GetReductionMASK(
123 std::optional<ActualArgument> &maskArg, const ConstantSubscripts &shape,
125
126// Common preprocessing for reduction transformational intrinsic function
127// folding. If the intrinsic can have DIM= &/or MASK= arguments, extract
128// and check them. If a MASK= is present, apply it to the array data and
129// substitute replacement values for elements corresponding to .FALSE. in
130// the mask. If the result is present, the intrinsic call can be folded.
131template <typename T> struct ArrayAndMask {
132 Constant<T> array;
134};
135template <typename T>
136std::optional<ArrayAndMask<T>> ProcessReductionArgs(int kind,
137 FoldingContext &context, ActualArguments &arg, std::optional<int> &dim,
138 int arrayIndex, std::optional<int> dimIndex = std::nullopt,
139 std::optional<int> maskIndex = std::nullopt) {
140 if (arg.empty()) {
141 return std::nullopt;
142 }
143 Constant<T> *folded{Folder<T>{kind, context}.Folding(arg[arrayIndex])};
144 if (!folded || folded->Rank() < 1) {
145 return std::nullopt;
146 }
147 if (!CheckReductionDIM(dim, context, arg, dimIndex, folded->Rank())) {
148 return std::nullopt;
149 }
150 std::size_t n{folded->size()};
151 std::vector<Scalar<LogicalResult>> maskElement;
152 if (maskIndex && static_cast<std::size_t>(*maskIndex) < arg.size() &&
153 arg[*maskIndex]) {
154 if (const Constant<LogicalResult> *origMask{
155 GetReductionMASK(arg[*maskIndex], folded->shape(), context)}) {
156 if (auto scalarMask{origMask->GetScalarValue()}) {
157 maskElement = std::vector<Scalar<LogicalResult>>(
158 n, Scalar<LogicalResult>{LogicalResultKind, scalarMask->IsTrue()});
159 } else {
160 maskElement = origMask->values();
161 }
162 } else {
163 return std::nullopt;
164 }
165 } else {
166 maskElement = std::vector<Scalar<LogicalResult>>(
167 n, Scalar<LogicalResult>{LogicalResultKind, true});
168 }
169 return ArrayAndMask<T>{Constant<T>(*folded),
170 Constant<LogicalResult>{LogicalResultKind, std::move(maskElement),
171 ConstantSubscripts{folded->shape()}}};
172}
173
174// Generalized reduction to an array of one dimension fewer (w/ DIM=)
175// or to a scalar (w/o DIM=). The ACCUMULATOR type must define
176// operator()(Scalar<T> &, const ConstantSubscripts &, bool first)
177// and Done(Scalar<T> &).
178template <typename T, typename ACCUMULATOR, typename ARRAY>
179Constant<T> DoReduction(int kind, const Constant<ARRAY> &array,
180 const Constant<LogicalResult> &mask, std::optional<int> &dim,
181 const Scalar<T> &identity, ACCUMULATOR &accumulator) {
182 ConstantSubscripts at{array.lbounds()};
183 ConstantSubscripts maskAt{mask.lbounds()};
184 std::vector<typename Constant<T>::Element> elements;
185 ConstantSubscripts resultShape; // empty -> scalar
186 if (dim) { // DIM= is present, so result is an array
187 resultShape = array.shape();
188 resultShape.erase(resultShape.begin() + (*dim - 1));
189 ConstantSubscript dimExtent{array.shape().at(*dim - 1)};
190 CHECK(dimExtent == mask.shape().at(*dim - 1));
191 ConstantSubscript &dimAt{at[*dim - 1]};
192 ConstantSubscript dimLbound{dimAt};
193 ConstantSubscript &maskDimAt{maskAt[*dim - 1]};
194 ConstantSubscript maskDimLbound{maskDimAt};
195 for (auto n{GetSize(resultShape)}; n-- > 0;
196 array.IncrementSubscripts(at), mask.IncrementSubscripts(maskAt)) {
197 elements.push_back(identity);
198 if (dimExtent > 0) {
199 dimAt = dimLbound;
200 maskDimAt = maskDimLbound;
201 bool firstUnmasked{true};
202 for (ConstantSubscript j{0}; j < dimExtent; ++j, ++dimAt, ++maskDimAt) {
203 if (mask.At(maskAt).IsTrue()) {
204 accumulator(elements.back(), at, firstUnmasked);
205 firstUnmasked = false;
206 }
207 }
208 --dimAt, --maskDimAt;
209 }
210 accumulator.Done(elements.back());
211 }
212 } else { // no DIM=, result is scalar
213 elements.push_back(identity);
214 bool firstUnmasked{true};
215 for (auto n{array.size()}; n-- > 0;
216 array.IncrementSubscripts(at), mask.IncrementSubscripts(maskAt)) {
217 if (mask.At(maskAt).IsTrue()) {
218 accumulator(elements.back(), at, firstUnmasked);
219 firstUnmasked = false;
220 }
221 }
222 accumulator.Done(elements.back());
223 }
224 if constexpr (T::category == TypeCategory::Character) {
225 return {kind, static_cast<ConstantSubscript>(identity.size()),
226 std::move(elements), std::move(resultShape)};
227 } else {
228 return {kind, std::move(elements), std::move(resultShape)};
229 }
230}
231
232// MAXVAL & MINVAL
233template <typename T, bool ABS = false> class MaxvalMinvalAccumulator {
234public:
235 constexpr int kind() const { return kind_; }
236
237 MaxvalMinvalAccumulator(int kind, RelationalOperator opr,
238 FoldingContext &context, const Constant<T> &array)
239 : kind_{kind}, opr_{opr}, context_{context}, array_{array} {};
240 void operator()(Scalar<T> &element, const ConstantSubscripts &at,
241 [[maybe_unused]] bool firstUnmasked) const {
242 auto aAt{array_.At(at)};
243 if constexpr (ABS) {
244 aAt = aAt.ABS();
245 }
246 if constexpr (T::category == TypeCategory::Real) {
247 if (firstUnmasked || element.IsNotANumber()) {
248 // Return NaN if and only if all unmasked elements are NaNs and
249 // at least one unmasked element is visible.
250 element = aAt;
251 return;
252 }
253 }
255 PackageRelation(opr_, MakeConstantExpr<T>(kind(), aAt),
256 MakeConstantExpr<T>(kind(), element))};
257 auto folded{GetScalarConstantValue<LogicalResult>(
258 LogicalResultKind, test.Rewrite(context_, std::move(test)))};
259 CHECK(folded.has_value());
260 if (folded->IsTrue()) {
261 element = aAt;
262 }
263 }
264 void Done(Scalar<T> &) const {}
265
266private:
267 int kind_;
268 RelationalOperator opr_;
269 FoldingContext &context_;
270 const Constant<T> &array_;
271};
272
273template <typename T>
274Expr<T> FoldMaxvalMinval(FoldingContext &context, FunctionRef<T> &&ref,
275 RelationalOperator opr, const Scalar<T> &identity) {
276 static_assert(T::category == TypeCategory::Integer ||
277 T::category == TypeCategory::Unsigned ||
278 T::category == TypeCategory::Real ||
279 T::category == TypeCategory::Character);
280 const int kind{ref.kind()};
281 std::optional<int> dim;
282 if (std::optional<ArrayAndMask<T>> arrayAndMask{ProcessReductionArgs<T>(kind,
283 context, ref.arguments(), dim, /*ARRAY=*/0, /*DIM=*/1,
284 /*MASK=*/2)}) {
285 MaxvalMinvalAccumulator<T> accumulator{
286 kind, opr, context, arrayAndMask->array};
287 return Expr<T>{DoReduction<T>(kind, arrayAndMask->array, arrayAndMask->mask,
288 dim, identity, accumulator)};
289 }
290 return Expr<T>{std::move(ref)};
291}
292
293// PRODUCT
294template <typename T> class ProductAccumulator {
295public:
296 ProductAccumulator(const Constant<T> &array) : array_{array} {}
297 void operator()(
298 Scalar<T> &element, const ConstantSubscripts &at, bool /*first*/) {
299 if constexpr (T::category == TypeCategory::Integer) {
300 auto prod{element.MultiplySigned(array_.At(at))};
301 overflow_ |= prod.SignedMultiplicationOverflowed();
302 element = prod.lower;
303 } else if constexpr (T::category == TypeCategory::Unsigned) {
304 element = element.MultiplyUnsigned(array_.At(at)).lower;
305 } else { // Real & Complex
306 auto prod{element.Multiply(array_.At(at))};
307 overflow_ |= prod.flags.test(RealFlag::Overflow);
308 element = prod.value;
309 }
310 }
311 bool overflow() const { return overflow_; }
312 void Done(Scalar<T> &) const {}
313
314private:
315 const Constant<T> &array_;
316 bool overflow_{false};
317};
318
319template <typename T>
320Expr<T> FoldProduct(
321 FoldingContext &context, FunctionRef<T> &&ref, Scalar<T> identity) {
322 static_assert(T::category == TypeCategory::Integer ||
323 T::category == TypeCategory::Unsigned ||
324 T::category == TypeCategory::Real ||
325 T::category == TypeCategory::Complex);
326 const int kind{ref.kind()};
327 std::optional<int> dim;
328 if (std::optional<ArrayAndMask<T>> arrayAndMask{
329 ProcessReductionArgs<T>(kind, context, ref.arguments(), dim,
330 /*ARRAY=*/0, /*DIM=*/1, /*MASK=*/2)}) {
331 ProductAccumulator accumulator{arrayAndMask->array};
332 auto result{Expr<T>{DoReduction<T>(kind, arrayAndMask->array,
333 arrayAndMask->mask, dim, identity, accumulator)}};
334 if (accumulator.overflow()) {
335 context.Warn(common::UsageWarning::FoldingException,
336 "PRODUCT() of %s data overflowed"_warn_en_US, T{kind}.AsFortran());
337 }
338 return result;
339 }
340 return Expr<T>{std::move(ref)};
341}
342
343// SUM
344template <typename T> class SumAccumulator {
345 using Element = typename Constant<T>::Element;
346
347public:
348 SumAccumulator(const Constant<T> &array, Rounding rounding)
349 : kind_{array.kind()}, array_{array}, rounding_{rounding},
350 correction_{Element::Zero(array.kind())} {}
351 void operator()(
352 Element &element, const ConstantSubscripts &at, bool /*first*/) {
353 if constexpr (T::category == TypeCategory::Integer) {
354 auto sum{element.AddSigned(array_.At(at))};
355 overflow_ |= sum.overflow;
356 element = sum.value;
357 } else if constexpr (T::category == TypeCategory::Unsigned) {
358 element = element.AddUnsigned(array_.At(at)).value;
359 } else { // Real & Complex: use Kahan summation
360 auto sum{element.KahanSummation(array_.At(at), correction_, rounding_)};
361 overflow_ |= sum.flags.test(RealFlag::Overflow);
362 element = sum.value;
363 }
364 }
365 bool overflow() const { return overflow_; }
366 void Done([[maybe_unused]] Element &element) {
367 if constexpr (T::category != TypeCategory::Integer &&
368 T::category != TypeCategory::Unsigned) {
369 auto corrected{element.Add(correction_, rounding_)};
370 overflow_ |= corrected.flags.test(RealFlag::Overflow);
371 correction_ = Element::Zero(kind_);
372 element = corrected.value;
373 }
374 }
375
376private:
377 int kind_;
378 const Constant<T> &array_;
379 Rounding rounding_;
380 bool overflow_{false};
381 Element correction_;
382};
383
384template <typename T>
385Expr<T> FoldSum(FoldingContext &context, FunctionRef<T> &&ref) {
386 static_assert(T::category == TypeCategory::Integer ||
387 T::category == TypeCategory::Unsigned ||
388 T::category == TypeCategory::Real ||
389 T::category == TypeCategory::Complex);
390 using Element = typename Constant<T>::Element;
391 const int kind{ref.kind()};
392 std::optional<int> dim;
393 Element identity{Element::Zero(kind)};
394 if (std::optional<ArrayAndMask<T>> arrayAndMask{
395 ProcessReductionArgs<T>(kind, context, ref.arguments(), dim,
396 /*ARRAY=*/0, /*DIM=*/1, /*MASK=*/2)}) {
397 SumAccumulator accumulator{
398 arrayAndMask->array, context.targetCharacteristics().roundingMode()};
399 auto result{Expr<T>{DoReduction<T>(kind, arrayAndMask->array,
400 arrayAndMask->mask, dim, identity, accumulator)}};
401 if (accumulator.overflow()) {
402 context.Warn(common::UsageWarning::FoldingException,
403 "SUM() of %s data overflowed"_warn_en_US, T{kind}.AsFortran());
404 }
405 return result;
406 }
407 return Expr<T>{std::move(ref)};
408}
409
410// Utility for IALL, IANY, IPARITY, ALL, ANY, & PARITY
411template <typename T> class OperationAccumulator {
412public:
413 OperationAccumulator(const Constant<T> &array,
414 Scalar<T> (Scalar<T>::*operation)(const Scalar<T> &) const)
415 : array_{array}, operation_{operation} {}
416 void operator()(
417 Scalar<T> &element, const ConstantSubscripts &at, bool /*first*/) {
418 element = (element.*operation_)(array_.At(at));
419 }
420 void Done(Scalar<T> &) const {}
421
422private:
423 const Constant<T> &array_;
424 Scalar<T> (Scalar<T>::*operation_)(const Scalar<T> &) const;
425};
426
427} // namespace Fortran::evaluate
428#endif // FORTRAN_EVALUATE_FOLD_REDUCTION_H_
Definition constant.h:191
Definition common.h:222
Definition fold-implementation.h:54
Definition common.h:224
Definition call.h:413
Definition fold-reduction.h:344
Definition call.h:34
Definition target-rounding.h:18
Definition fold-reduction.h:131
Definition expression.h:398