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 "flang/Evaluate/fold-designator.h"
11 #include "flang/Evaluate/fold.h"
12 #include "flang/Evaluate/shape.h"
13 #include "flang/Evaluate/type.h"
14 #include "flang/Runtime/descriptor.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:
ComputeOffsetsHelper(SemanticsContext & context)27   ComputeOffsetsHelper(SemanticsContext &context) : context_{context} {}
28   void Compute(Scope &);
29 
30 private:
31   struct SizeAndAlignment {
SizeAndAlignmentFortran::semantics::ComputeOffsetsHelper::SizeAndAlignment32     SizeAndAlignment() {}
SizeAndAlignmentFortran::semantics::ComputeOffsetsHelper::SizeAndAlignment33     SizeAndAlignment(std::size_t bytes) : size{bytes}, alignment{bytes} {}
SizeAndAlignmentFortran::semantics::ComputeOffsetsHelper::SizeAndAlignment34     SizeAndAlignment(std::size_t bytes, std::size_t align)
35         : size{bytes}, alignment{align} {}
36     std::size_t size{0};
37     std::size_t alignment{0};
38   };
39   struct SymbolAndOffset {
SymbolAndOffsetFortran::semantics::ComputeOffsetsHelper::SymbolAndOffset40     SymbolAndOffset(Symbol &s, std::size_t off, const EquivalenceObject &obj)
41         : symbol{s}, offset{off}, object{&obj} {}
42     SymbolAndOffset(const SymbolAndOffset &) = default;
43     MutableSymbolRef symbol;
44     std::size_t offset;
45     const EquivalenceObject *object;
46   };
47 
48   void DoCommonBlock(Symbol &);
49   void DoEquivalenceBlockBase(Symbol &, SizeAndAlignment &);
50   void DoEquivalenceSet(const EquivalenceSet &);
51   SymbolAndOffset Resolve(const SymbolAndOffset &);
52   std::size_t ComputeOffset(const EquivalenceObject &);
53   // Returns amount of padding that was needed for alignment
54   std::size_t DoSymbol(Symbol &);
55   SizeAndAlignment GetSizeAndAlignment(const Symbol &, bool entire);
56   std::size_t Align(std::size_t, std::size_t);
57 
58   SemanticsContext &context_;
59   std::size_t offset_{0};
60   std::size_t alignment_{1};
61   // symbol -> symbol+offset that determines its location, from EQUIVALENCE
62   std::map<MutableSymbolRef, SymbolAndOffset, SymbolAddressCompare> dependents_;
63   // base symbol -> SizeAndAlignment for each distinct EQUIVALENCE block
64   std::map<MutableSymbolRef, SizeAndAlignment, SymbolAddressCompare>
65       equivalenceBlock_;
66 };
67 
Compute(Scope & scope)68 void ComputeOffsetsHelper::Compute(Scope &scope) {
69   for (Scope &child : scope.children()) {
70     ComputeOffsets(context_, child);
71   }
72   if (scope.symbol() && scope.IsDerivedTypeWithKindParameter()) {
73     return; // only process instantiations of kind parameterized derived types
74   }
75   if (scope.alignment().has_value()) {
76     return; // prevent infinite recursion in error cases
77   }
78   scope.SetAlignment(0);
79   // Build dependents_ from equivalences: symbol -> symbol+offset
80   for (const EquivalenceSet &set : scope.equivalenceSets()) {
81     DoEquivalenceSet(set);
82   }
83   // Compute a base symbol and overall block size for each
84   // disjoint EQUIVALENCE storage sequence.
85   for (auto &[symbol, dep] : dependents_) {
86     dep = Resolve(dep);
87     CHECK(symbol->size() == 0);
88     auto symInfo{GetSizeAndAlignment(*symbol, true)};
89     symbol->set_size(symInfo.size);
90     Symbol &base{*dep.symbol};
91     auto iter{equivalenceBlock_.find(base)};
92     std::size_t minBlockSize{dep.offset + symInfo.size};
93     if (iter == equivalenceBlock_.end()) {
94       equivalenceBlock_.emplace(
95           base, SizeAndAlignment{minBlockSize, symInfo.alignment});
96     } else {
97       SizeAndAlignment &blockInfo{iter->second};
98       blockInfo.size = std::max(blockInfo.size, minBlockSize);
99       blockInfo.alignment = std::max(blockInfo.alignment, symInfo.alignment);
100     }
101   }
102   // Assign offsets for non-COMMON EQUIVALENCE blocks
103   for (auto &[symbol, blockInfo] : equivalenceBlock_) {
104     if (!InCommonBlock(*symbol)) {
105       DoSymbol(*symbol);
106       DoEquivalenceBlockBase(*symbol, blockInfo);
107       offset_ = std::max(offset_, symbol->offset() + blockInfo.size);
108     }
109   }
110   // Process remaining non-COMMON symbols; this is all of them if there
111   // was no use of EQUIVALENCE in the scope.
112   for (auto &symbol : scope.GetSymbols()) {
113     if (!InCommonBlock(*symbol) &&
114         dependents_.find(symbol) == dependents_.end() &&
115         equivalenceBlock_.find(symbol) == equivalenceBlock_.end()) {
116       DoSymbol(*symbol);
117     }
118   }
119   scope.set_size(offset_);
120   scope.SetAlignment(alignment_);
121   // Assign offsets in COMMON blocks.
122   for (auto &pair : scope.commonBlocks()) {
123     DoCommonBlock(*pair.second);
124   }
125   for (auto &[symbol, dep] : dependents_) {
126     symbol->set_offset(dep.symbol->offset() + dep.offset);
127     if (const auto *block{FindCommonBlockContaining(*dep.symbol)}) {
128       symbol->get<ObjectEntityDetails>().set_commonBlock(*block);
129     }
130   }
131 }
132 
Resolve(const SymbolAndOffset & dep)133 auto ComputeOffsetsHelper::Resolve(const SymbolAndOffset &dep)
134     -> SymbolAndOffset {
135   auto it{dependents_.find(*dep.symbol)};
136   if (it == dependents_.end()) {
137     return dep;
138   } else {
139     SymbolAndOffset result{Resolve(it->second)};
140     result.offset += dep.offset;
141     result.object = dep.object;
142     return result;
143   }
144 }
145 
DoCommonBlock(Symbol & commonBlock)146 void ComputeOffsetsHelper::DoCommonBlock(Symbol &commonBlock) {
147   auto &details{commonBlock.get<CommonBlockDetails>()};
148   offset_ = 0;
149   alignment_ = 0;
150   std::size_t minSize{0};
151   std::size_t minAlignment{0};
152   for (auto &object : details.objects()) {
153     Symbol &symbol{*object};
154     auto errorSite{
155         commonBlock.name().empty() ? symbol.name() : commonBlock.name()};
156     if (std::size_t padding{DoSymbol(symbol)}) {
157       context_.Say(errorSite,
158           "COMMON block /%s/ requires %zd bytes of padding before '%s' for alignment"_port_en_US,
159           commonBlock.name(), padding, symbol.name());
160     }
161     auto eqIter{equivalenceBlock_.end()};
162     auto iter{dependents_.find(symbol)};
163     if (iter == dependents_.end()) {
164       eqIter = equivalenceBlock_.find(symbol);
165       if (eqIter != equivalenceBlock_.end()) {
166         DoEquivalenceBlockBase(symbol, eqIter->second);
167       }
168     } else {
169       SymbolAndOffset &dep{iter->second};
170       Symbol &base{*dep.symbol};
171       if (const auto *baseBlock{FindCommonBlockContaining(base)}) {
172         if (baseBlock == &commonBlock) {
173           context_.Say(errorSite,
174               "'%s' is storage associated with '%s' by EQUIVALENCE elsewhere in COMMON block /%s/"_err_en_US,
175               symbol.name(), base.name(), commonBlock.name());
176         } else { // 8.10.3(1)
177           context_.Say(errorSite,
178               "'%s' in COMMON block /%s/ must not be storage associated with '%s' in COMMON block /%s/ by EQUIVALENCE"_err_en_US,
179               symbol.name(), commonBlock.name(), base.name(),
180               baseBlock->name());
181         }
182       } else if (dep.offset > symbol.offset()) { // 8.10.3(3)
183         context_.Say(errorSite,
184             "'%s' cannot backward-extend COMMON block /%s/ via EQUIVALENCE with '%s'"_err_en_US,
185             symbol.name(), commonBlock.name(), base.name());
186       } else {
187         eqIter = equivalenceBlock_.find(base);
188         base.get<ObjectEntityDetails>().set_commonBlock(commonBlock);
189         base.set_offset(symbol.offset() - dep.offset);
190       }
191     }
192     // Get full extent of any EQUIVALENCE block into size of COMMON ( see
193     // 8.10.2.2 point 1 (2))
194     if (eqIter != equivalenceBlock_.end()) {
195       SizeAndAlignment &blockInfo{eqIter->second};
196       minSize = std::max(
197           minSize, std::max(offset_, eqIter->first->offset() + blockInfo.size));
198       minAlignment = std::max(minAlignment, blockInfo.alignment);
199     }
200   }
201   commonBlock.set_size(std::max(minSize, offset_));
202   details.set_alignment(std::max(minAlignment, alignment_));
203   context_.MapCommonBlockAndCheckConflicts(commonBlock);
204 }
205 
DoEquivalenceBlockBase(Symbol & symbol,SizeAndAlignment & blockInfo)206 void ComputeOffsetsHelper::DoEquivalenceBlockBase(
207     Symbol &symbol, SizeAndAlignment &blockInfo) {
208   if (symbol.size() > blockInfo.size) {
209     blockInfo.size = symbol.size();
210   }
211 }
212 
DoEquivalenceSet(const EquivalenceSet & set)213 void ComputeOffsetsHelper::DoEquivalenceSet(const EquivalenceSet &set) {
214   std::vector<SymbolAndOffset> symbolOffsets;
215   std::optional<std::size_t> representative;
216   for (const EquivalenceObject &object : set) {
217     std::size_t offset{ComputeOffset(object)};
218     SymbolAndOffset resolved{
219         Resolve(SymbolAndOffset{object.symbol, offset, object})};
220     symbolOffsets.push_back(resolved);
221     if (!representative ||
222         resolved.offset >= symbolOffsets[*representative].offset) {
223       // The equivalenced object with the largest offset from its resolved
224       // symbol will be the representative of this set, since the offsets
225       // of the other objects will be positive relative to it.
226       representative = symbolOffsets.size() - 1;
227     }
228   }
229   CHECK(representative);
230   const SymbolAndOffset &base{symbolOffsets[*representative]};
231   for (const auto &[symbol, offset, object] : symbolOffsets) {
232     if (symbol == base.symbol) {
233       if (offset != base.offset) {
234         auto x{evaluate::OffsetToDesignator(
235             context_.foldingContext(), *symbol, base.offset, 1)};
236         auto y{evaluate::OffsetToDesignator(
237             context_.foldingContext(), *symbol, offset, 1)};
238         if (x && y) {
239           context_
240               .Say(base.object->source,
241                   "'%s' and '%s' cannot have the same first storage unit"_err_en_US,
242                   x->AsFortran(), y->AsFortran())
243               .Attach(object->source, "Incompatible reference to '%s'"_en_US,
244                   y->AsFortran());
245         } else { // error recovery
246           context_
247               .Say(base.object->source,
248                   "'%s' (offset %zd bytes and %zd bytes) cannot have the same first storage unit"_err_en_US,
249                   symbol->name(), base.offset, offset)
250               .Attach(object->source,
251                   "Incompatible reference to '%s' offset %zd bytes"_en_US,
252                   symbol->name(), offset);
253         }
254       }
255     } else {
256       dependents_.emplace(*symbol,
257           SymbolAndOffset{*base.symbol, base.offset - offset, *object});
258     }
259   }
260 }
261 
262 // Offset of this equivalence object from the start of its variable.
ComputeOffset(const EquivalenceObject & object)263 std::size_t ComputeOffsetsHelper::ComputeOffset(
264     const EquivalenceObject &object) {
265   std::size_t offset{0};
266   if (!object.subscripts.empty()) {
267     const ArraySpec &shape{object.symbol.get<ObjectEntityDetails>().shape()};
268     auto lbound{[&](std::size_t i) {
269       return *ToInt64(shape[i].lbound().GetExplicit());
270     }};
271     auto ubound{[&](std::size_t i) {
272       return *ToInt64(shape[i].ubound().GetExplicit());
273     }};
274     for (std::size_t i{object.subscripts.size() - 1};;) {
275       offset += object.subscripts[i] - lbound(i);
276       if (i == 0) {
277         break;
278       }
279       --i;
280       offset *= ubound(i) - lbound(i) + 1;
281     }
282   }
283   auto result{offset * GetSizeAndAlignment(object.symbol, false).size};
284   if (object.substringStart) {
285     int kind{context_.defaultKinds().GetDefaultKind(TypeCategory::Character)};
286     if (const DeclTypeSpec * type{object.symbol.GetType()}) {
287       if (const IntrinsicTypeSpec * intrinsic{type->AsIntrinsic()}) {
288         kind = ToInt64(intrinsic->kind()).value_or(kind);
289       }
290     }
291     result += kind * (*object.substringStart - 1);
292   }
293   return result;
294 }
295 
DoSymbol(Symbol & symbol)296 std::size_t ComputeOffsetsHelper::DoSymbol(Symbol &symbol) {
297   if (!symbol.has<ObjectEntityDetails>() && !symbol.has<ProcEntityDetails>()) {
298     return 0;
299   }
300   SizeAndAlignment s{GetSizeAndAlignment(symbol, true)};
301   if (s.size == 0) {
302     return 0;
303   }
304   std::size_t previousOffset{offset_};
305   offset_ = Align(offset_, s.alignment);
306   std::size_t padding{offset_ - previousOffset};
307   symbol.set_size(s.size);
308   symbol.set_offset(offset_);
309   offset_ += s.size;
310   alignment_ = std::max(alignment_, s.alignment);
311   return padding;
312 }
313 
GetSizeAndAlignment(const Symbol & symbol,bool entire)314 auto ComputeOffsetsHelper::GetSizeAndAlignment(
315     const Symbol &symbol, bool entire) -> SizeAndAlignment {
316   auto &targetCharacteristics{context_.targetCharacteristics()};
317   if (IsDescriptor(symbol)) {
318     const auto *derived{
319         evaluate::GetDerivedTypeSpec(evaluate::DynamicType::From(symbol))};
320     int lenParams{derived ? CountLenParameters(*derived) : 0};
321     std::size_t size{runtime::Descriptor::SizeInBytes(
322         symbol.Rank(), derived != nullptr, lenParams)};
323     return {size, targetCharacteristics.descriptorAlignment()};
324   }
325   if (IsProcedurePointer(symbol)) {
326     return {targetCharacteristics.procedurePointerByteSize(),
327         targetCharacteristics.procedurePointerAlignment()};
328   }
329   if (IsProcedure(symbol)) {
330     return {};
331   }
332   auto &foldingContext{context_.foldingContext()};
333   if (auto chars{evaluate::characteristics::TypeAndShape::Characterize(
334           symbol, foldingContext)}) {
335     if (entire) {
336       if (auto size{ToInt64(chars->MeasureSizeInBytes(foldingContext))}) {
337         return {static_cast<std::size_t>(*size),
338             chars->type().GetAlignment(targetCharacteristics)};
339       }
340     } else { // element size only
341       if (auto size{ToInt64(chars->MeasureElementSizeInBytes(
342               foldingContext, true /*aligned*/))}) {
343         return {static_cast<std::size_t>(*size),
344             chars->type().GetAlignment(targetCharacteristics)};
345       }
346     }
347   }
348   return {};
349 }
350 
351 // Align a size to its natural alignment, up to maxAlignment.
Align(std::size_t x,std::size_t alignment)352 std::size_t ComputeOffsetsHelper::Align(std::size_t x, std::size_t alignment) {
353   alignment =
354       std::min(alignment, context_.targetCharacteristics().maxAlignment());
355   return (x + alignment - 1) & -alignment;
356 }
357 
ComputeOffsets(SemanticsContext & context,Scope & scope)358 void ComputeOffsets(SemanticsContext &context, Scope &scope) {
359   ComputeOffsetsHelper{context}.Compute(scope);
360 }
361 
362 } // namespace Fortran::semantics
363