1 //===-- lib/Semantics/compute-offsets.cpp -----------------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
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"
23 namespace Fortran::semantics
{
25 class ComputeOffsetsHelper
{
27 ComputeOffsetsHelper(SemanticsContext
&context
) : context_
{context
} {}
28 void Compute(Scope
&);
31 struct SizeAndAlignment
{
33 SizeAndAlignment(std::size_t bytes
) : size
{bytes
}, alignment
{bytes
} {}
34 SizeAndAlignment(std::size_t bytes
, std::size_t align
)
35 : size
{bytes
}, alignment
{align
} {}
37 std::size_t alignment
{0};
39 struct SymbolAndOffset
{
40 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
;
45 const EquivalenceObject
*object
;
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);
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
>
68 void ComputeOffsetsHelper::Compute(Scope
&scope
) {
69 for (Scope
&child
: scope
.children()) {
70 ComputeOffsets(context_
, child
);
72 if (scope
.symbol() && scope
.IsDerivedTypeWithKindParameter()) {
73 return; // only process instantiations of kind parameterized derived types
75 if (scope
.alignment().has_value()) {
76 return; // prevent infinite recursion in error cases
78 scope
.SetAlignment(0);
79 // Build dependents_ from equivalences: symbol -> symbol+offset
80 for (const EquivalenceSet
&set
: scope
.equivalenceSets()) {
81 DoEquivalenceSet(set
);
83 // Compute a base symbol and overall block size for each
84 // disjoint EQUIVALENCE storage sequence.
85 for (auto &[symbol
, dep
] : dependents_
) {
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
});
97 SizeAndAlignment
&blockInfo
{iter
->second
};
98 blockInfo
.size
= std::max(blockInfo
.size
, minBlockSize
);
99 blockInfo
.alignment
= std::max(blockInfo
.alignment
, symInfo
.alignment
);
102 // Assign offsets for non-COMMON EQUIVALENCE blocks
103 for (auto &[symbol
, blockInfo
] : equivalenceBlock_
) {
104 if (!FindCommonBlockContaining(*symbol
)) {
106 DoEquivalenceBlockBase(*symbol
, blockInfo
);
107 offset_
= std::max(offset_
, symbol
->offset() + blockInfo
.size
);
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 (!FindCommonBlockContaining(*symbol
) &&
114 dependents_
.find(symbol
) == dependents_
.end() &&
115 equivalenceBlock_
.find(symbol
) == equivalenceBlock_
.end()) {
119 scope
.set_size(offset_
);
120 scope
.SetAlignment(alignment_
);
121 // Assign offsets in COMMON blocks, unless this scope is a BLOCK construct,
122 // where COMMON blocks are illegal (C1107 and C1108).
123 if (scope
.kind() != Scope::Kind::BlockConstruct
) {
124 for (auto &pair
: scope
.commonBlocks()) {
125 DoCommonBlock(*pair
.second
);
128 for (auto &[symbol
, dep
] : dependents_
) {
129 symbol
->set_offset(dep
.symbol
->offset() + dep
.offset
);
130 if (const auto *block
{FindCommonBlockContaining(*dep
.symbol
)}) {
131 symbol
->get
<ObjectEntityDetails
>().set_commonBlock(*block
);
136 auto ComputeOffsetsHelper::Resolve(const SymbolAndOffset
&dep
)
138 auto it
{dependents_
.find(*dep
.symbol
)};
139 if (it
== dependents_
.end()) {
142 SymbolAndOffset result
{Resolve(it
->second
)};
143 result
.offset
+= dep
.offset
;
144 result
.object
= dep
.object
;
149 void ComputeOffsetsHelper::DoCommonBlock(Symbol
&commonBlock
) {
150 auto &details
{commonBlock
.get
<CommonBlockDetails
>()};
153 std::size_t minSize
{0};
154 std::size_t minAlignment
{0};
155 for (auto &object
: details
.objects()) {
156 Symbol
&symbol
{*object
};
158 commonBlock
.name().empty() ? symbol
.name() : commonBlock
.name()};
159 if (std::size_t padding
{DoSymbol(symbol
.GetUltimate())}) {
160 context_
.Say(errorSite
,
161 "COMMON block /%s/ requires %zd bytes of padding before '%s' for alignment"_port_en_US
,
162 commonBlock
.name(), padding
, symbol
.name());
164 auto eqIter
{equivalenceBlock_
.end()};
165 auto iter
{dependents_
.find(symbol
)};
166 if (iter
== dependents_
.end()) {
167 eqIter
= equivalenceBlock_
.find(symbol
);
168 if (eqIter
!= equivalenceBlock_
.end()) {
169 DoEquivalenceBlockBase(symbol
, eqIter
->second
);
172 SymbolAndOffset
&dep
{iter
->second
};
173 Symbol
&base
{*dep
.symbol
};
174 if (const auto *baseBlock
{FindCommonBlockContaining(base
)}) {
175 if (baseBlock
== &commonBlock
) {
176 if (base
.offset() != symbol
.offset() - dep
.offset
||
177 llvm::is_contained(details
.objects(), base
)) {
178 context_
.Say(errorSite
,
179 "'%s' is storage associated with '%s' by EQUIVALENCE elsewhere in COMMON block /%s/"_err_en_US
,
180 symbol
.name(), base
.name(), commonBlock
.name());
182 } else { // 8.10.3(1)
183 context_
.Say(errorSite
,
184 "'%s' in COMMON block /%s/ must not be storage associated with '%s' in COMMON block /%s/ by EQUIVALENCE"_err_en_US
,
185 symbol
.name(), commonBlock
.name(), base
.name(),
188 } else if (dep
.offset
> symbol
.offset()) { // 8.10.3(3)
189 context_
.Say(errorSite
,
190 "'%s' cannot backward-extend COMMON block /%s/ via EQUIVALENCE with '%s'"_err_en_US
,
191 symbol
.name(), commonBlock
.name(), base
.name());
193 eqIter
= equivalenceBlock_
.find(base
);
194 base
.get
<ObjectEntityDetails
>().set_commonBlock(commonBlock
);
195 base
.set_offset(symbol
.offset() - dep
.offset
);
198 // Get full extent of any EQUIVALENCE block into size of COMMON ( see
199 // 8.10.2.2 point 1 (2))
200 if (eqIter
!= equivalenceBlock_
.end()) {
201 SizeAndAlignment
&blockInfo
{eqIter
->second
};
203 minSize
, std::max(offset_
, eqIter
->first
->offset() + blockInfo
.size
));
204 minAlignment
= std::max(minAlignment
, blockInfo
.alignment
);
207 commonBlock
.set_size(std::max(minSize
, offset_
));
208 details
.set_alignment(std::max(minAlignment
, alignment_
));
209 context_
.MapCommonBlockAndCheckConflicts(commonBlock
);
212 void ComputeOffsetsHelper::DoEquivalenceBlockBase(
213 Symbol
&symbol
, SizeAndAlignment
&blockInfo
) {
214 if (symbol
.size() > blockInfo
.size
) {
215 blockInfo
.size
= symbol
.size();
219 void ComputeOffsetsHelper::DoEquivalenceSet(const EquivalenceSet
&set
) {
220 std::vector
<SymbolAndOffset
> symbolOffsets
;
221 std::optional
<std::size_t> representative
;
222 for (const EquivalenceObject
&object
: set
) {
223 std::size_t offset
{ComputeOffset(object
)};
224 SymbolAndOffset resolved
{
225 Resolve(SymbolAndOffset
{object
.symbol
, offset
, object
})};
226 symbolOffsets
.push_back(resolved
);
227 if (!representative
||
228 resolved
.offset
>= symbolOffsets
[*representative
].offset
) {
229 // The equivalenced object with the largest offset from its resolved
230 // symbol will be the representative of this set, since the offsets
231 // of the other objects will be positive relative to it.
232 representative
= symbolOffsets
.size() - 1;
235 CHECK(representative
);
236 const SymbolAndOffset
&base
{symbolOffsets
[*representative
]};
237 for (const auto &[symbol
, offset
, object
] : symbolOffsets
) {
238 if (symbol
== base
.symbol
) {
239 if (offset
!= base
.offset
) {
240 auto x
{evaluate::OffsetToDesignator(
241 context_
.foldingContext(), *symbol
, base
.offset
, 1)};
242 auto y
{evaluate::OffsetToDesignator(
243 context_
.foldingContext(), *symbol
, offset
, 1)};
246 .Say(base
.object
->source
,
247 "'%s' and '%s' cannot have the same first storage unit"_err_en_US
,
248 x
->AsFortran(), y
->AsFortran())
249 .Attach(object
->source
, "Incompatible reference to '%s'"_en_US
,
251 } else { // error recovery
253 .Say(base
.object
->source
,
254 "'%s' (offset %zd bytes and %zd bytes) cannot have the same first storage unit"_err_en_US
,
255 symbol
->name(), base
.offset
, offset
)
256 .Attach(object
->source
,
257 "Incompatible reference to '%s' offset %zd bytes"_en_US
,
258 symbol
->name(), offset
);
262 dependents_
.emplace(*symbol
,
263 SymbolAndOffset
{*base
.symbol
, base
.offset
- offset
, *object
});
268 // Offset of this equivalence object from the start of its variable.
269 std::size_t ComputeOffsetsHelper::ComputeOffset(
270 const EquivalenceObject
&object
) {
271 std::size_t offset
{0};
272 if (!object
.subscripts
.empty()) {
273 const ArraySpec
&shape
{object
.symbol
.get
<ObjectEntityDetails
>().shape()};
274 auto lbound
{[&](std::size_t i
) {
275 return *ToInt64(shape
[i
].lbound().GetExplicit());
277 auto ubound
{[&](std::size_t i
) {
278 return *ToInt64(shape
[i
].ubound().GetExplicit());
280 for (std::size_t i
{object
.subscripts
.size() - 1};;) {
281 offset
+= object
.subscripts
[i
] - lbound(i
);
286 offset
*= ubound(i
) - lbound(i
) + 1;
289 auto result
{offset
* GetSizeAndAlignment(object
.symbol
, false).size
};
290 if (object
.substringStart
) {
291 int kind
{context_
.defaultKinds().GetDefaultKind(TypeCategory::Character
)};
292 if (const DeclTypeSpec
* type
{object
.symbol
.GetType()}) {
293 if (const IntrinsicTypeSpec
* intrinsic
{type
->AsIntrinsic()}) {
294 kind
= ToInt64(intrinsic
->kind()).value_or(kind
);
297 result
+= kind
* (*object
.substringStart
- 1);
302 std::size_t ComputeOffsetsHelper::DoSymbol(Symbol
&symbol
) {
303 if (!symbol
.has
<ObjectEntityDetails
>() && !symbol
.has
<ProcEntityDetails
>()) {
306 SizeAndAlignment s
{GetSizeAndAlignment(symbol
, true)};
310 std::size_t previousOffset
{offset_
};
311 offset_
= Align(offset_
, s
.alignment
);
312 std::size_t padding
{offset_
- previousOffset
};
313 symbol
.set_size(s
.size
);
314 symbol
.set_offset(offset_
);
316 alignment_
= std::max(alignment_
, s
.alignment
);
320 auto ComputeOffsetsHelper::GetSizeAndAlignment(
321 const Symbol
&symbol
, bool entire
) -> SizeAndAlignment
{
322 auto &targetCharacteristics
{context_
.targetCharacteristics()};
323 if (IsDescriptor(symbol
)) {
324 auto dyType
{evaluate::DynamicType::From(symbol
)};
325 const auto *derived
{evaluate::GetDerivedTypeSpec(dyType
)};
326 int lenParams
{derived
? CountLenParameters(*derived
) : 0};
327 bool needAddendum
{derived
|| (dyType
&& dyType
->IsUnlimitedPolymorphic())};
328 std::size_t size
{runtime::Descriptor::SizeInBytes(
329 symbol
.Rank(), needAddendum
, lenParams
)};
330 return {size
, targetCharacteristics
.descriptorAlignment()};
332 if (IsProcedurePointer(symbol
)) {
333 return {targetCharacteristics
.procedurePointerByteSize(),
334 targetCharacteristics
.procedurePointerAlignment()};
336 if (IsProcedure(symbol
)) {
339 auto &foldingContext
{context_
.foldingContext()};
340 if (auto chars
{evaluate::characteristics::TypeAndShape::Characterize(
341 symbol
, foldingContext
)}) {
343 if (auto size
{ToInt64(chars
->MeasureSizeInBytes(foldingContext
))}) {
344 return {static_cast<std::size_t>(*size
),
345 chars
->type().GetAlignment(targetCharacteristics
)};
347 } else { // element size only
348 if (auto size
{ToInt64(chars
->MeasureElementSizeInBytes(
349 foldingContext
, true /*aligned*/))}) {
350 return {static_cast<std::size_t>(*size
),
351 chars
->type().GetAlignment(targetCharacteristics
)};
358 // Align a size to its natural alignment, up to maxAlignment.
359 std::size_t ComputeOffsetsHelper::Align(std::size_t x
, std::size_t alignment
) {
361 std::min(alignment
, context_
.targetCharacteristics().maxAlignment());
362 return (x
+ alignment
- 1) & -alignment
;
365 void ComputeOffsets(SemanticsContext
&context
, Scope
&scope
) {
366 ComputeOffsetsHelper
{context
}.Compute(scope
);
369 } // namespace Fortran::semantics