FLANG
shape.h
1//===-- include/flang/Evaluate/shape.h --------------------------*- C++ -*-===//
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// GetShape() analyzes an expression and determines its shape, if possible,
10// representing the result as a vector of scalar integer expressions.
11
12#ifndef FORTRAN_EVALUATE_SHAPE_H_
13#define FORTRAN_EVALUATE_SHAPE_H_
14
15#include "expression.h"
16#include "traverse.h"
17#include "variable.h"
18#include "flang/Evaluate/type.h"
19#include <optional>
20
21namespace Fortran::parser {
23}
24
25namespace Fortran::evaluate {
26
27class FoldingContext;
28
29using ExtentType = SubscriptInteger;
30using ExtentExpr = Expr<ExtentType>;
31using MaybeExtentExpr = std::optional<ExtentExpr>;
32using Shape = std::vector<MaybeExtentExpr>;
33
34inline constexpr int ExtentIntKind = Fortran::evaluate::SubscriptIntegerKind;
35
36inline Constant<ExtentType> MakeExtentConstant(int64_t v) {
38 ExtentIntKind, Scalar<ExtentType>{ExtentIntKind, v}};
39}
40
41inline Constant<ExtentType> MakeExtentConstant(const value::IntegerValue &v) {
43 ExtentIntKind, Scalar<ExtentType>{ExtentIntKind, v}};
44}
45
46inline ExtentExpr MakeExtentExpr(int64_t v) {
47 return ExtentExpr{MakeExtentConstant(v)};
48}
49
50inline ExtentExpr MakeExtentExpr(const value::IntegerValue &v) {
51 return ExtentExpr{MakeExtentConstant(v)};
52}
53
54bool IsImpliedShape(const Symbol &);
55bool IsExplicitShape(const Symbol &);
56
57// Conversions between various representations of shapes.
58std::optional<ExtentExpr> AsExtentArrayExpr(const Shape &);
59
60std::optional<Constant<ExtentType>> AsConstantShape(
61 FoldingContext &, const Shape &);
62Constant<ExtentType> AsConstantShape(const ConstantSubscripts &);
63
64// AsConstantExtents returns a constant shape. It may contain
65// invalid negative extents; use HasNegativeExtent() to check.
66ConstantSubscripts AsConstantExtents(const Constant<ExtentType> &);
67std::optional<ConstantSubscripts> AsConstantExtents(
68 FoldingContext &, const Shape &);
69inline std::optional<ConstantSubscripts> AsConstantExtents(
70 FoldingContext &foldingContext, const std::optional<Shape> &maybeShape) {
71 if (maybeShape) {
72 return AsConstantExtents(foldingContext, *maybeShape);
73 }
74 return std::nullopt;
75}
76
77Shape AsShape(const ConstantSubscripts &);
78std::optional<Shape> AsShape(const std::optional<ConstantSubscripts> &);
79
80inline int GetRank(const Shape &s) { return static_cast<int>(s.size()); }
81
82Shape Fold(FoldingContext &, Shape &&);
83std::optional<Shape> Fold(FoldingContext &, std::optional<Shape> &&);
84
85// Computes shapes in terms of expressions that are scope-invariant, by
86// default, which is nearly always what one wants outside of procedure
87// characterization.
88template <typename A>
89std::optional<Shape> GetShape(
90 FoldingContext &, const A &, bool invariantOnly = true);
91template <typename A>
92std::optional<Shape> GetShape(
93 FoldingContext *, const A &, bool invariantOnly = true);
94template <typename A>
95std::optional<Shape> GetShape(const A &, bool invariantOnly = true);
96
97// The dimension argument to these inquiries is zero-based,
98// unlike the DIM= arguments to many intrinsics.
99//
100// GetRawLowerBound() returns a lower bound expression, which may
101// not be suitable for all purposes; specifically, it might not be invariant
102// in its scope, and it will not have been forced to 1 on an empty dimension.
103// GetLBOUND()'s result is safer, but it is optional because it does fail
104// in those circumstances.
105// Similarly, GetUBOUND result will be forced to 0 on an empty dimension,
106// but will fail if the extent is not a compile time constant.
107ExtentExpr GetRawLowerBound(
108 const NamedEntity &, int dimension, bool invariantOnly = true);
109ExtentExpr GetRawLowerBound(FoldingContext &, const NamedEntity &,
110 int dimension, bool invariantOnly = true);
111MaybeExtentExpr GetLBOUND(
112 const NamedEntity &, int dimension, bool invariantOnly = true);
113MaybeExtentExpr GetLBOUND(FoldingContext &, const NamedEntity &, int dimension,
114 bool invariantOnly = true);
115MaybeExtentExpr GetRawUpperBound(
116 const NamedEntity &, int dimension, bool invariantOnly = true);
117MaybeExtentExpr GetRawUpperBound(FoldingContext &, const NamedEntity &,
118 int dimension, bool invariantOnly = true);
119MaybeExtentExpr GetUBOUND(
120 const NamedEntity &, int dimension, bool invariantOnly = true);
121MaybeExtentExpr GetUBOUND(FoldingContext &, const NamedEntity &, int dimension,
122 bool invariantOnly = true);
123MaybeExtentExpr ComputeUpperBound(ExtentExpr &&lower, MaybeExtentExpr &&extent);
124MaybeExtentExpr ComputeUpperBound(
125 FoldingContext &, ExtentExpr &&lower, MaybeExtentExpr &&extent);
126Shape GetRawLowerBounds(const NamedEntity &, bool invariantOnly = true);
127Shape GetRawLowerBounds(
128 FoldingContext &, const NamedEntity &, bool invariantOnly = true);
129Shape GetLBOUNDs(const NamedEntity &, bool invariantOnly = true);
130Shape GetLBOUNDs(
131 FoldingContext &, const NamedEntity &, bool invariantOnly = true);
132Shape GetUBOUNDs(const NamedEntity &, bool invariantOnly = true);
133Shape GetUBOUNDs(
134 FoldingContext &, const NamedEntity &, bool invariantOnly = true);
135MaybeExtentExpr GetExtent(
136 const NamedEntity &, int dimension, bool invariantOnly = true);
137MaybeExtentExpr GetExtent(FoldingContext &, const NamedEntity &, int dimension,
138 bool invariantOnly = true);
139MaybeExtentExpr GetExtent(const Subscript &, const NamedEntity &, int dimension,
140 bool invariantOnly = true);
141MaybeExtentExpr GetExtent(FoldingContext &, const Subscript &,
142 const NamedEntity &, int dimension, bool invariantOnly = true);
143
144// Similar analyses for coarrays
145MaybeExtentExpr GetLCOBOUND(
146 const Symbol &, int dimension, bool invariantOnly = true);
147MaybeExtentExpr GetUCOBOUND(
148 const Symbol &, int dimension, bool invariantOnly = true);
149Shape GetLCOBOUNDs(const Symbol &, bool invariantOnly = true);
150Shape GetUCOBOUNDs(const Symbol &, bool invariantOnly = true);
151
152// Compute an element count for a triplet or trip count for a DO.
153ExtentExpr CountTrips(
154 ExtentExpr &&lower, ExtentExpr &&upper, ExtentExpr &&stride);
155ExtentExpr CountTrips(
156 const ExtentExpr &lower, const ExtentExpr &upper, const ExtentExpr &stride);
157MaybeExtentExpr CountTrips(
158 MaybeExtentExpr &&lower, MaybeExtentExpr &&upper, MaybeExtentExpr &&stride);
159
160// Computes SIZE() == PRODUCT(shape)
161MaybeExtentExpr GetSize(Shape &&);
162ConstantSubscript GetSize(const ConstantSubscripts &);
163inline MaybeExtentExpr GetSize(const std::optional<Shape> &maybeShape) {
164 if (maybeShape) {
165 return GetSize(Shape(*maybeShape));
166 }
167 return std::nullopt;
168}
169
170// Utility predicate: does an expression reference any implied DO index?
171bool ContainsAnyImpliedDoIndex(const ExtentExpr &);
172
173// Utility predicate: does an expression embed a RankOneBoundElement that has
174// no ubound()/lbound() provenance? Such an element has no Fortran spelling
175// (its debug rendering is the synthetic %ROBE marker) and must never reach a
176// mod file; an inquiry whose folded result would contain one must stay intact.
177template <typename T> bool ContainsUntaggedRankOneBoundElement(const Expr<T> &);
178extern template bool ContainsUntaggedRankOneBoundElement(
179 const Expr<SomeType> &);
180extern template bool ContainsUntaggedRankOneBoundElement(
181 const Expr<SomeInteger> &);
182extern template bool ContainsUntaggedRankOneBoundElement(
183 const Expr<SubscriptInteger> &);
184
185// GetShape()
186
187class GetShapeHelper
188 : public AnyTraverse<GetShapeHelper, std::optional<Shape>> {
189public:
190 using Result = std::optional<Shape>;
191 using Base = AnyTraverse<GetShapeHelper, Result>;
192 using Base::operator();
193 GetShapeHelper(FoldingContext *context, bool invariantOnly)
194 : Base{*this}, context_{context}, invariantOnly_{invariantOnly} {}
195
196 Result operator()(const ImpliedDoIndex &) const { return ScalarShape(); }
197 Result operator()(const DescriptorInquiry &) const { return ScalarShape(); }
198 Result operator()(const RankOneBoundElement &) const { return ScalarShape(); }
199 Result operator()(const TypeParamInquiry &) const { return ScalarShape(); }
200 Result operator()(const BOZLiteralConstant &) const { return ScalarShape(); }
201 Result operator()(const StaticDataObject::Pointer &) const {
202 return ScalarShape();
203 }
204 Result operator()(const StructureConstructor &) const {
205 return ScalarShape();
206 }
207
208 template <typename T> Result operator()(const Constant<T> &c) const {
209 return ConstantShape(c.SHAPE());
210 }
211
212 Result operator()(const Symbol &) const;
213 Result operator()(const Component &) const;
214 Result operator()(const ArrayRef &) const;
215 Result operator()(const CoarrayRef &) const;
216 Result operator()(const Substring &) const;
217 Result operator()(const ProcedureRef &) const;
218 Result operator()(const ActualArgument &) const;
219
220 template <typename T>
221 Result operator()(const ArrayConstructor<T> &aconst) const {
222 return Shape{GetArrayConstructorExtent(aconst)};
223 }
224 template <typename T>
225 Result operator()(const ConditionalExpr<T> &conditional) const {
226 // Per F2023 10.1.4(7), the shape is that of the selected branch.
227 // When all branches have identical static extents, return the common shape.
228 int rank{conditional.thenValue().Rank()};
229 Result thenShape{(*this)(conditional.thenValue())};
230 if (!thenShape) {
231 return Shape(rank, std::nullopt);
232 }
233 Result elseShape{(*this)(conditional.elseValue())};
234 if (thenShape != elseShape) {
235 return Shape(rank, std::nullopt);
236 }
237 return thenShape;
238 }
239 template <typename D, typename R, typename LO, typename RO>
240 Result operator()(const Operation<D, R, LO, RO> &operation) const {
241 if (int rr{operation.right().Rank()}; rr > 0) {
242 if (int lr{operation.left().Rank()}; lr == 0 || lr == rr) {
243 return (*this)(operation.right());
244 } else {
245 return std::nullopt;
246 }
247 } else {
248 return (*this)(operation.left());
249 }
250 }
251
252private:
253 static Result ScalarShape() { return Shape{}; }
254 static Shape ConstantShape(const Constant<ExtentType> &);
255 Result AsShapeResult(ExtentExpr &&) const;
256 Shape CreateShape(int rank, NamedEntity &) const;
257
258 template <typename T>
259 MaybeExtentExpr GetArrayConstructorValueExtent(
260 const ArrayConstructorValue<T> &value) const {
261 return common::visit(
263 [&](const Expr<T> &x) -> MaybeExtentExpr {
264 if (auto xShape{(*this)(x)}) {
265 // Array values in array constructors get linearized.
266 return GetSize(std::move(*xShape));
267 } else {
268 return std::nullopt;
269 }
270 },
271 [&](const ImpliedDo<T> &ido) -> MaybeExtentExpr {
272 // Don't be heroic and try to figure out triangular implied DO
273 // nests.
274 if (!ContainsAnyImpliedDoIndex(ido.lower()) &&
275 !ContainsAnyImpliedDoIndex(ido.upper()) &&
276 !ContainsAnyImpliedDoIndex(ido.stride())) {
277 if (auto nValues{GetArrayConstructorExtent(ido.values())}) {
278 if (!ContainsAnyImpliedDoIndex(*nValues)) {
279 return std::move(*nValues) *
280 CountTrips(ido.lower(), ido.upper(), ido.stride());
281 }
282 }
283 }
284 return std::nullopt;
285 },
286 },
287 value.u);
288 }
289
290 template <typename T>
291 MaybeExtentExpr GetArrayConstructorExtent(
292 const ArrayConstructorValues<T> &values) const {
293 ExtentExpr result{MakeExtentConstant(0)};
294 for (const auto &value : values) {
295 if (MaybeExtentExpr n{GetArrayConstructorValueExtent(value)}) {
296 AccumulateExtent(result, std::move(*n));
297 } else {
298 return std::nullopt;
299 }
300 }
301 return result;
302 }
303
304 // Add an extent to another, with folding
305 void AccumulateExtent(ExtentExpr &, ExtentExpr &&) const;
306
307 FoldingContext *context_{nullptr};
308 mutable bool useResultSymbolShape_{true};
309 // When invariantOnly=false, the returned shape need not be invariant
310 // in its scope; in particular, it may contain references to dummy arguments.
311 bool invariantOnly_{true};
312};
313
314template <typename A>
315std::optional<Shape> GetShape(
316 FoldingContext *context, const A &x, bool invariantOnly) {
317 if (auto shape{GetShapeHelper{context, invariantOnly}(x)}) {
318 if (context) {
319 return Fold(*context, std::move(shape));
320 } else {
321 return shape;
322 }
323 } else {
324 return std::nullopt;
325 }
326}
327
328template <typename A>
329std::optional<Shape> GetShape(
330 FoldingContext &context, const A &x, bool invariantOnly) {
331 return GetShape(&context, x, invariantOnly);
332}
333
334template <typename A>
335std::optional<Shape> GetShape(const A &x, bool invariantOnly) {
336 return GetShape(/*context=*/nullptr, x, invariantOnly);
337}
338
339template <typename A>
340std::optional<Constant<ExtentType>> GetConstantShape(
341 FoldingContext &context, const A &x) {
342 if (auto shape{GetShape(context, x, /*invariantonly=*/true)}) {
343 return AsConstantShape(context, *shape);
344 } else {
345 return std::nullopt;
346 }
347}
348
349// Combines GetShape and AsConstantExtents; only returns valid shapes.
350template <typename A>
351std::optional<ConstantSubscripts> GetConstantExtents(
352 FoldingContext &context, const A &x) {
353 if (auto shape{GetShape(context, x, /*invariantOnly=*/true)}) {
354 if (auto extents{AsConstantExtents(context, *shape)}) {
355 if (!HasNegativeExtent(*extents)) {
356 return extents;
357 }
358 }
359 }
360 return std::nullopt;
361}
362
363// Get shape that does not depends on callee scope symbols if the expression
364// contains calls. Return std::nullopt if it is not possible to build such shape
365// (e.g. for calls to array-valued functions whose result shape depends on the
366// arguments).
367template <typename A>
368std::optional<Shape> GetContextFreeShape(FoldingContext &context, const A &x) {
369 return GetShapeHelper{&context, /*invariantOnly=*/true}(x);
370}
371
372// Compilation-time shape conformance checking, when corresponding extents
373// are or should be known. The result is an optional Boolean:
374// - nullopt: no error found or reported, but conformance cannot
375// be guaranteed during compilation; this result is possible only
376// when one or both arrays are allowed to have deferred shape
377// - true: no error found or reported, arrays conform
378// - false: errors found and reported
379// Use "CheckConformance(...).value_or()" to specify a default result
380// when you don't care whether messages have been emitted.
382 enum Flags {
383 None = 0,
384 LeftScalarExpandable = 1,
385 RightScalarExpandable = 2,
386 LeftIsDeferredShape = 4,
387 RightIsDeferredShape = 8,
388 EitherScalarExpandable = LeftScalarExpandable | RightScalarExpandable,
389 BothDeferredShape = LeftIsDeferredShape | RightIsDeferredShape,
390 RightIsExpandableDeferred = RightScalarExpandable | RightIsDeferredShape,
391 };
392};
393std::optional<bool> CheckConformance(parser::ContextualMessages &,
394 const Shape &left, const Shape &right,
395 CheckConformanceFlags::Flags flags = CheckConformanceFlags::None,
396 const char *leftIs = "left operand", const char *rightIs = "right operand");
397
398// Increments one-based subscripts in element order (first varies fastest)
399// and returns true when they remain in range; resets them all to one and
400// return false otherwise (including the case where one or more of the
401// extents are zero).
402bool IncrementSubscripts(
403 ConstantSubscripts &, const ConstantSubscripts &extents);
404
405} // namespace Fortran::evaluate
406#endif // FORTRAN_EVALUATE_SHAPE_H_
Definition expression.h:537
Definition variable.h:210
Definition variable.h:248
Definition variable.h:73
Definition expression.h:412
Definition constant.h:191
Definition variable.h:450
Definition common.h:222
Definition common.h:224
Definition shape.h:188
Definition expression.h:475
Definition variable.h:101
Definition expression.h:114
Definition call.h:348
Definition expression.h:849
Definition variable.h:309
Definition variable.h:136
Definition integer-value.h:35
Definition message.h:399
Definition symbol.h:918
Definition call.h:34
Definition check-expression.h:19
Definition idioms.h:60
Definition expression.h:503
Definition expression.h:461
Definition variable.h:196