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