1 //===-- lib/Semantics/compute-offsets.cpp -----------------------*- 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 #include "compute-offsets.h"
10 #include "../../runtime/descriptor.h"
11 #include "flang/Evaluate/fold-designator.h"
12 #include "flang/Evaluate/fold.h"
13 #include "flang/Evaluate/shape.h"
14 #include "flang/Evaluate/type.h"
15 #include "flang/Semantics/scope.h"
16 #include "flang/Semantics/semantics.h"
17 #include "flang/Semantics/symbol.h"
18 #include "flang/Semantics/tools.h"
19 #include "flang/Semantics/type.h"
20 #include <algorithm>
21 #include <vector>
22 
23 namespace Fortran::semantics {
24 
25 class ComputeOffsetsHelper {
26 public:
27   // TODO: configure based on target
28   static constexpr std::size_t maxAlignment{8};
29 
30   ComputeOffsetsHelper(SemanticsContext &context) : context_{context} {}
31   void Compute() { Compute(context_.globalScope()); }
32 
33 private:
34   struct SizeAndAlignment {
35     SizeAndAlignment() {}
36     SizeAndAlignment(std::size_t bytes) : size{bytes}, alignment{bytes} {}
37     SizeAndAlignment(std::size_t bytes, std::size_t align)
38         : size{bytes}, alignment{align} {}
39     std::size_t size{0};
40     std::size_t alignment{0};
41   };
42   struct SymbolAndOffset {
43     SymbolAndOffset(Symbol &s, std::size_t off, const EquivalenceObject &obj)
44         : symbol{&s}, offset{off}, object{&obj} {}
45     SymbolAndOffset(const SymbolAndOffset &) = default;
46     Symbol *symbol;
47     std::size_t offset;
48     const EquivalenceObject *object;
49   };
50 
51   void Compute(Scope &);
52   void DoScope(Scope &);
53   void DoCommonBlock(Symbol &);
54   void DoEquivalenceBlockBase(Symbol &, SizeAndAlignment &);
55   void DoEquivalenceSet(const EquivalenceSet &);
56   SymbolAndOffset Resolve(const SymbolAndOffset &);
57   std::size_t ComputeOffset(const EquivalenceObject &);
58   void DoSymbol(Symbol &);
59   SizeAndAlignment GetSizeAndAlignment(const Symbol &);
60   SizeAndAlignment GetElementSize(const Symbol &);
61   std::size_t CountElements(const Symbol &);
62   static std::size_t Align(std::size_t, std::size_t);
63   static SizeAndAlignment GetIntrinsicSizeAndAlignment(TypeCategory, int);
64 
65   SemanticsContext &context_;
66   evaluate::FoldingContext &foldingContext_{context_.foldingContext()};
67   std::size_t offset_{0};
68   std::size_t alignment_{0};
69   // symbol -> symbol+offset that determines its location, from EQUIVALENCE
70   std::map<MutableSymbolRef, SymbolAndOffset> dependents_;
71   // base symbol -> SizeAndAlignment for each distinct EQUIVALENCE block
72   std::map<MutableSymbolRef, SizeAndAlignment> equivalenceBlock_;
73 };
74 
75 void ComputeOffsetsHelper::Compute(Scope &scope) {
76   for (Scope &child : scope.children()) {
77     Compute(child);
78   }
79   DoScope(scope);
80   dependents_.clear();
81   equivalenceBlock_.clear();
82 }
83 
84 void ComputeOffsetsHelper::DoScope(Scope &scope) {
85   if (scope.symbol() && scope.IsParameterizedDerivedType()) {
86     return; // only process instantiations of parameterized derived types
87   }
88   if (scope.alignment().has_value()) {
89     return; // prevent infinite recursion in error cases
90   }
91   scope.SetAlignment(0);
92   // Build dependents_ from equivalences: symbol -> symbol+offset
93   for (const EquivalenceSet &set : scope.equivalenceSets()) {
94     DoEquivalenceSet(set);
95   }
96   offset_ = 0;
97   alignment_ = 1;
98   // Compute a base symbol and overall block size for each
99   // disjoint EQUIVALENCE storage sequence.
100   for (auto &[symbol, dep] : dependents_) {
101     dep = Resolve(dep);
102     CHECK(symbol->size() == 0);
103     auto symInfo{GetSizeAndAlignment(*symbol)};
104     symbol->set_size(symInfo.size);
105     Symbol &base{*dep.symbol};
106     auto iter{equivalenceBlock_.find(base)};
107     std::size_t minBlockSize{dep.offset + symInfo.size};
108     if (iter == equivalenceBlock_.end()) {
109       equivalenceBlock_.emplace(
110           base, SizeAndAlignment{minBlockSize, symInfo.alignment});
111     } else {
112       SizeAndAlignment &blockInfo{iter->second};
113       blockInfo.size = std::max(blockInfo.size, minBlockSize);
114       blockInfo.alignment = std::max(blockInfo.alignment, symInfo.alignment);
115     }
116   }
117   // Assign offsets for non-COMMON EQUIVALENCE blocks
118   for (auto &[symbol, blockInfo] : equivalenceBlock_) {
119     if (!InCommonBlock(*symbol)) {
120       DoSymbol(*symbol);
121       DoEquivalenceBlockBase(*symbol, blockInfo);
122       offset_ = std::max(offset_, symbol->offset() + blockInfo.size);
123     }
124   }
125   // Process remaining non-COMMON symbols; this is all of them if there
126   // was no use of EQUIVALENCE in the scope.
127   for (auto &symbol : scope.GetSymbols()) {
128     if (!InCommonBlock(*symbol) &&
129         dependents_.find(symbol) == dependents_.end() &&
130         equivalenceBlock_.find(symbol) == equivalenceBlock_.end()) {
131       DoSymbol(*symbol);
132     }
133   }
134   scope.set_size(offset_);
135   scope.SetAlignment(alignment_);
136   // Assign offsets in COMMON blocks.
137   for (auto &pair : scope.commonBlocks()) {
138     DoCommonBlock(*pair.second);
139   }
140   for (auto &[symbol, dep] : dependents_) {
141     symbol->set_offset(dep.symbol->offset() + dep.offset);
142     if (const auto *block{FindCommonBlockContaining(*dep.symbol)}) {
143       symbol->get<ObjectEntityDetails>().set_commonBlock(*block);
144     }
145   }
146 }
147 
148 auto ComputeOffsetsHelper::Resolve(const SymbolAndOffset &dep)
149     -> SymbolAndOffset {
150   auto it{dependents_.find(*dep.symbol)};
151   if (it == dependents_.end()) {
152     return dep;
153   } else {
154     SymbolAndOffset result{Resolve(it->second)};
155     result.offset += dep.offset;
156     result.object = dep.object;
157     return result;
158   }
159 }
160 
161 void ComputeOffsetsHelper::DoCommonBlock(Symbol &commonBlock) {
162   auto &details{commonBlock.get<CommonBlockDetails>()};
163   offset_ = 0;
164   alignment_ = 0;
165   std::size_t minSize{0};
166   std::size_t minAlignment{0};
167   for (auto &object : details.objects()) {
168     Symbol &symbol{*object};
169     DoSymbol(symbol);
170     auto iter{dependents_.find(symbol)};
171     if (iter == dependents_.end()) {
172       // Get full extent of any EQUIVALENCE block into size of COMMON
173       auto eqIter{equivalenceBlock_.find(symbol)};
174       if (eqIter != equivalenceBlock_.end()) {
175         SizeAndAlignment &blockInfo{eqIter->second};
176         DoEquivalenceBlockBase(symbol, blockInfo);
177         minSize = std::max(
178             minSize, std::max(offset_, symbol.offset() + blockInfo.size));
179         minAlignment = std::max(minAlignment, blockInfo.alignment);
180       }
181     } else {
182       SymbolAndOffset &dep{iter->second};
183       Symbol &base{*dep.symbol};
184       auto errorSite{
185           commonBlock.name().empty() ? symbol.name() : commonBlock.name()};
186       if (const auto *baseBlock{FindCommonBlockContaining(base)}) {
187         if (baseBlock == &commonBlock) {
188           context_.Say(errorSite,
189               "'%s' is storage associated with '%s' by EQUIVALENCE elsewhere in COMMON block /%s/"_err_en_US,
190               symbol.name(), base.name(), commonBlock.name());
191         } else { // 8.10.3(1)
192           context_.Say(errorSite,
193               "'%s' in COMMON block /%s/ must not be storage associated with '%s' in COMMON block /%s/ by EQUIVALENCE"_err_en_US,
194               symbol.name(), commonBlock.name(), base.name(),
195               baseBlock->name());
196         }
197       } else if (dep.offset > symbol.offset()) { // 8.10.3(3)
198         context_.Say(errorSite,
199             "'%s' cannot backward-extend COMMON block /%s/ via EQUIVALENCE with '%s'"_err_en_US,
200             symbol.name(), commonBlock.name(), base.name());
201       } else {
202         base.get<ObjectEntityDetails>().set_commonBlock(commonBlock);
203         base.set_offset(symbol.offset() - dep.offset);
204       }
205     }
206   }
207   commonBlock.set_size(std::max(minSize, offset_));
208   details.set_alignment(std::max(minAlignment, alignment_));
209 }
210 
211 void ComputeOffsetsHelper::DoEquivalenceBlockBase(
212     Symbol &symbol, SizeAndAlignment &blockInfo) {
213   if (symbol.size() > blockInfo.size) {
214     blockInfo.size = symbol.size();
215   }
216 }
217 
218 void ComputeOffsetsHelper::DoEquivalenceSet(const EquivalenceSet &set) {
219   std::vector<SymbolAndOffset> symbolOffsets;
220   std::optional<std::size_t> representative;
221   for (const EquivalenceObject &object : set) {
222     std::size_t offset{ComputeOffset(object)};
223     SymbolAndOffset resolved{
224         Resolve(SymbolAndOffset{object.symbol, offset, object})};
225     symbolOffsets.push_back(resolved);
226     if (!representative ||
227         resolved.offset >= symbolOffsets[*representative].offset) {
228       // The equivalenced object with the largest offset from its resolved
229       // symbol will be the representative of this set, since the offsets
230       // of the other objects will be positive relative to it.
231       representative = symbolOffsets.size() - 1;
232     }
233   }
234   CHECK(representative);
235   const SymbolAndOffset &base{symbolOffsets[*representative]};
236   for (const auto &[symbol, offset, object] : symbolOffsets) {
237     if (symbol == base.symbol) {
238       if (offset != base.offset) {
239         auto x{evaluate::OffsetToDesignator(
240             context_.foldingContext(), *symbol, base.offset, 1)};
241         auto y{evaluate::OffsetToDesignator(
242             context_.foldingContext(), *symbol, offset, 1)};
243         if (x && y) {
244           context_
245               .Say(base.object->source,
246                   "'%s' and '%s' cannot have the same first storage unit"_err_en_US,
247                   x->AsFortran(), y->AsFortran())
248               .Attach(object->source, "Incompatible reference to '%s'"_en_US,
249                   y->AsFortran());
250         } else { // error recovery
251           context_
252               .Say(base.object->source,
253                   "'%s' (offset %zd bytes and %zd bytes) cannot have the same first storage unit"_err_en_US,
254                   symbol->name(), base.offset, offset)
255               .Attach(object->source,
256                   "Incompatible reference to '%s' offset %zd bytes"_en_US,
257                   symbol->name(), offset);
258         }
259       }
260     } else {
261       dependents_.emplace(*symbol,
262           SymbolAndOffset{*base.symbol, base.offset - offset, *object});
263     }
264   }
265 }
266 
267 // Offset of this equivalence object from the start of its variable.
268 std::size_t ComputeOffsetsHelper::ComputeOffset(
269     const EquivalenceObject &object) {
270   std::size_t offset{0};
271   if (!object.subscripts.empty()) {
272     const ArraySpec &shape{object.symbol.get<ObjectEntityDetails>().shape()};
273     auto lbound{[&](std::size_t i) {
274       return *ToInt64(shape[i].lbound().GetExplicit());
275     }};
276     auto ubound{[&](std::size_t i) {
277       return *ToInt64(shape[i].ubound().GetExplicit());
278     }};
279     for (std::size_t i{object.subscripts.size() - 1};;) {
280       offset += object.subscripts[i] - lbound(i);
281       if (i == 0) {
282         break;
283       }
284       --i;
285       offset *= ubound(i) - lbound(i) + 1;
286     }
287   }
288   auto result{offset * GetElementSize(object.symbol).size};
289   if (object.substringStart) {
290     int kind{context_.defaultKinds().GetDefaultKind(TypeCategory::Character)};
291     if (const DeclTypeSpec * type{object.symbol.GetType()}) {
292       if (const IntrinsicTypeSpec * intrinsic{type->AsIntrinsic()}) {
293         kind = ToInt64(intrinsic->kind()).value_or(kind);
294       }
295     }
296     result += kind * (*object.substringStart - 1);
297   }
298   return result;
299 }
300 
301 void ComputeOffsetsHelper::DoSymbol(Symbol &symbol) {
302   if (!symbol.has<ObjectEntityDetails>() && !symbol.has<ProcEntityDetails>()) {
303     return;
304   }
305   SizeAndAlignment s{GetSizeAndAlignment(symbol)};
306   if (s.size == 0) {
307     return;
308   }
309   offset_ = Align(offset_, s.alignment);
310   symbol.set_size(s.size);
311   symbol.set_offset(offset_);
312   offset_ += s.size;
313   alignment_ = std::max(alignment_, s.alignment);
314 }
315 
316 auto ComputeOffsetsHelper::GetSizeAndAlignment(const Symbol &symbol)
317     -> SizeAndAlignment {
318   SizeAndAlignment result{GetElementSize(symbol)};
319   std::size_t elements{CountElements(symbol)};
320   if (elements > 1) {
321     result.size = Align(result.size, result.alignment);
322   }
323   result.size *= elements;
324   return result;
325 }
326 
327 auto ComputeOffsetsHelper::GetElementSize(const Symbol &symbol)
328     -> SizeAndAlignment {
329   const DeclTypeSpec *type{symbol.GetType()};
330   if (!evaluate::DynamicType::From(type).has_value()) {
331     return {};
332   }
333   // TODO: The size of procedure pointers is not yet known
334   // and is independent of rank (and probably also the number
335   // of length type parameters).
336   if (IsDescriptor(symbol) || IsProcedurePointer(symbol)) {
337     int lenParams{0};
338     if (const DerivedTypeSpec * derived{type->AsDerived()}) {
339       lenParams = CountLenParameters(*derived);
340     }
341     std::size_t size{
342         runtime::Descriptor::SizeInBytes(symbol.Rank(), false, lenParams)};
343     return {size, maxAlignment};
344   }
345   if (IsProcedure(symbol)) {
346     return {};
347   }
348   SizeAndAlignment result;
349   if (const IntrinsicTypeSpec * intrinsic{type->AsIntrinsic()}) {
350     if (auto kind{ToInt64(intrinsic->kind())}) {
351       result = GetIntrinsicSizeAndAlignment(intrinsic->category(), *kind);
352     }
353     if (type->category() == DeclTypeSpec::Character) {
354       ParamValue length{type->characterTypeSpec().length()};
355       CHECK(length.isExplicit()); // else should be descriptor
356       if (MaybeIntExpr lengthExpr{length.GetExplicit()}) {
357         if (auto lengthInt{ToInt64(*lengthExpr)}) {
358           result.size *= *lengthInt;
359         }
360       }
361     }
362   } else if (const DerivedTypeSpec * derived{type->AsDerived()}) {
363     if (derived->scope()) {
364       DoScope(*const_cast<Scope *>(derived->scope()));
365       result.size = derived->scope()->size();
366       result.alignment = derived->scope()->alignment().value_or(0);
367     }
368   } else {
369     DIE("not intrinsic or derived");
370   }
371   return result;
372 }
373 
374 std::size_t ComputeOffsetsHelper::CountElements(const Symbol &symbol) {
375   if (auto shape{GetShape(foldingContext_, symbol)}) {
376     if (auto sizeExpr{evaluate::GetSize(std::move(*shape))}) {
377       if (auto size{ToInt64(Fold(foldingContext_, std::move(*sizeExpr)))}) {
378         return *size;
379       }
380     }
381   }
382   return 1;
383 }
384 
385 // Align a size to its natural alignment, up to maxAlignment.
386 std::size_t ComputeOffsetsHelper::Align(std::size_t x, std::size_t alignment) {
387   if (alignment > maxAlignment) {
388     alignment = maxAlignment;
389   }
390   return (x + alignment - 1) & -alignment;
391 }
392 
393 auto ComputeOffsetsHelper::GetIntrinsicSizeAndAlignment(
394     TypeCategory category, int kind) -> SizeAndAlignment {
395   if (category == TypeCategory::Character) {
396     return {static_cast<std::size_t>(kind)};
397   }
398   auto bytes{evaluate::ToInt64(
399       evaluate::DynamicType{category, kind}.MeasureSizeInBytes())};
400   CHECK(bytes && *bytes > 0);
401   std::size_t size{static_cast<std::size_t>(*bytes)};
402   if (category == TypeCategory::Complex) {
403     return {size, size >> 1};
404   } else {
405     return {size};
406   }
407 }
408 
409 void ComputeOffsets(SemanticsContext &context) {
410   ComputeOffsetsHelper{context}.Compute();
411 }
412 
413 } // namespace Fortran::semantics
414