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