//===-- lib/Semantics/compute-offsets.cpp -----------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "compute-offsets.h" #include "../../runtime/descriptor.h" #include "flang/Evaluate/fold-designator.h" #include "flang/Evaluate/fold.h" #include "flang/Evaluate/shape.h" #include "flang/Evaluate/type.h" #include "flang/Semantics/scope.h" #include "flang/Semantics/semantics.h" #include "flang/Semantics/symbol.h" #include "flang/Semantics/tools.h" #include "flang/Semantics/type.h" #include #include namespace Fortran::semantics { class ComputeOffsetsHelper { public: // TODO: configure based on target static constexpr std::size_t maxAlignment{8}; ComputeOffsetsHelper(SemanticsContext &context) : context_{context} {} void Compute() { Compute(context_.globalScope()); } private: struct SizeAndAlignment { SizeAndAlignment() {} SizeAndAlignment(std::size_t bytes) : size{bytes}, alignment{bytes} {} SizeAndAlignment(std::size_t bytes, std::size_t align) : size{bytes}, alignment{align} {} std::size_t size{0}; std::size_t alignment{0}; }; struct SymbolAndOffset { SymbolAndOffset(Symbol &s, std::size_t off, const EquivalenceObject &obj) : symbol{&s}, offset{off}, object{&obj} {} SymbolAndOffset(const SymbolAndOffset &) = default; Symbol *symbol; std::size_t offset; const EquivalenceObject *object; }; void Compute(Scope &); void DoScope(Scope &); void DoCommonBlock(Symbol &); void DoEquivalenceSet(const EquivalenceSet &); SymbolAndOffset Resolve(const SymbolAndOffset &); std::size_t ComputeOffset(const EquivalenceObject &); void DoSymbol(Symbol &); SizeAndAlignment GetSizeAndAlignment(const Symbol &); SizeAndAlignment GetElementSize(const Symbol &); std::size_t CountElements(const Symbol &); static std::size_t Align(std::size_t, std::size_t); static SizeAndAlignment GetIntrinsicSizeAndAlignment(TypeCategory, int); SemanticsContext &context_; evaluate::FoldingContext &foldingContext_{context_.foldingContext()}; std::size_t offset_{0}; std::size_t alignment_{0}; // symbol -> symbol+offset that determines its location, from EQUIVALENCE std::map dependents_; }; void ComputeOffsetsHelper::Compute(Scope &scope) { for (Scope &child : scope.children()) { Compute(child); } DoScope(scope); } static bool InCommonBlock(const Symbol &symbol) { const auto *details{symbol.detailsIf()}; return details && details->commonBlock(); } void ComputeOffsetsHelper::DoScope(Scope &scope) { if (scope.symbol() && scope.IsParameterizedDerivedType()) { return; // only process instantiations of parameterized derived types } // Symbols in common block get offsets from the beginning of the block for (auto &pair : scope.commonBlocks()) { DoCommonBlock(*pair.second); } // Build dependents_ from equivalences: symbol -> symbol+offset for (const EquivalenceSet &set : scope.equivalenceSets()) { DoEquivalenceSet(set); } offset_ = 0; alignment_ = 0; for (auto &symbol : scope.GetSymbols()) { if (!InCommonBlock(*symbol) && dependents_.find(symbol) == dependents_.end()) { DoSymbol(*symbol); } } for (auto &[symbol, dep] : dependents_) { if (symbol->size() == 0) { SizeAndAlignment s{GetSizeAndAlignment(*symbol)}; symbol->set_size(s.size); SymbolAndOffset resolved{Resolve(dep)}; symbol->set_offset(dep.symbol->offset() + resolved.offset); offset_ = std::max(offset_, symbol->offset() + symbol->size()); } } scope.set_size(offset_); scope.set_alignment(alignment_); } auto ComputeOffsetsHelper::Resolve(const SymbolAndOffset &dep) -> SymbolAndOffset { auto it{dependents_.find(*dep.symbol)}; if (it == dependents_.end()) { return dep; } else { SymbolAndOffset result{Resolve(it->second)}; result.offset += dep.offset; result.object = dep.object; return result; } } void ComputeOffsetsHelper::DoCommonBlock(Symbol &commonBlock) { auto &details{commonBlock.get()}; offset_ = 0; alignment_ = 0; for (auto &object : details.objects()) { DoSymbol(*object); } commonBlock.set_size(offset_); details.set_alignment(alignment_); } void ComputeOffsetsHelper::DoEquivalenceSet(const EquivalenceSet &set) { std::vector symbolOffsets; std::optional representative; for (const EquivalenceObject &object : set) { std::size_t offset{ComputeOffset(object)}; SymbolAndOffset resolved{ Resolve(SymbolAndOffset{object.symbol, offset, object})}; symbolOffsets.push_back(resolved); if (!representative || resolved.offset >= symbolOffsets[*representative].offset) { // The equivalenced object with the largest offset from its resolved // symbol will be the representative of this set, since the offsets // of the other objects will be positive relative to it. representative = symbolOffsets.size() - 1; } } CHECK(representative); const SymbolAndOffset &base{symbolOffsets[*representative]}; for (const auto &[symbol, offset, object] : symbolOffsets) { if (symbol == base.symbol) { if (offset != base.offset) { auto x{evaluate::OffsetToDesignator( context_.foldingContext(), *symbol, base.offset, 1)}; auto y{evaluate::OffsetToDesignator( context_.foldingContext(), *symbol, offset, 1)}; if (x && y) { context_ .Say(base.object->source, "'%s' and '%s' cannot have the same first storage unit"_err_en_US, x->AsFortran(), y->AsFortran()) .Attach(object->source, "Incompatible reference to '%s'"_en_US, y->AsFortran()); } else { // error recovery context_ .Say(base.object->source, "'%s' (offset %zd bytes and %zd bytes) cannot have the same first storage unit"_err_en_US, symbol->name(), base.offset, offset) .Attach(object->source, "Incompatible reference to '%s' offset %zd bytes"_en_US, symbol->name(), offset); } } } else { dependents_.emplace(*symbol, SymbolAndOffset{*base.symbol, base.offset - offset, *object}); } } } // Offset of this equivalence object from the start of its variable. std::size_t ComputeOffsetsHelper::ComputeOffset( const EquivalenceObject &object) { std::size_t offset{0}; if (!object.subscripts.empty()) { const ArraySpec &shape{object.symbol.get().shape()}; auto lbound{[&](std::size_t i) { return *ToInt64(shape[i].lbound().GetExplicit()); }}; auto ubound{[&](std::size_t i) { return *ToInt64(shape[i].ubound().GetExplicit()); }}; for (std::size_t i{object.subscripts.size() - 1};;) { offset += object.subscripts[i] - lbound(i); if (i == 0) { break; } --i; offset *= ubound(i) - lbound(i) + 1; } } auto result{offset * GetElementSize(object.symbol).size}; if (object.substringStart) { int kind{context_.defaultKinds().GetDefaultKind(TypeCategory::Character)}; if (const DeclTypeSpec * type{object.symbol.GetType()}) { if (const IntrinsicTypeSpec * intrinsic{type->AsIntrinsic()}) { kind = ToInt64(intrinsic->kind()).value_or(kind); } } result += kind * (*object.substringStart - 1); } return result; } void ComputeOffsetsHelper::DoSymbol(Symbol &symbol) { if (symbol.has() || symbol.has() || symbol.has() || symbol.has()) { return; // these have type but no size } SizeAndAlignment s{GetSizeAndAlignment(symbol)}; if (s.size == 0) { return; } offset_ = Align(offset_, s.alignment); symbol.set_size(s.size); symbol.set_offset(offset_); offset_ += s.size; alignment_ = std::max(alignment_, s.alignment); } auto ComputeOffsetsHelper::GetSizeAndAlignment(const Symbol &symbol) -> SizeAndAlignment { SizeAndAlignment result{GetElementSize(symbol)}; std::size_t elements{CountElements(symbol)}; if (elements > 1) { result.size = Align(result.size, result.alignment); } result.size *= elements; return result; } auto ComputeOffsetsHelper::GetElementSize(const Symbol &symbol) -> SizeAndAlignment { const DeclTypeSpec *type{symbol.GetType()}; if (!type) { return {}; } // TODO: The size of procedure pointers is not yet known // and is independent of rank (and probably also the number // of length type parameters). if (IsDescriptor(symbol) || IsProcedurePointer(symbol)) { int lenParams{0}; if (const DerivedTypeSpec * derived{type->AsDerived()}) { lenParams = CountLenParameters(*derived); } std::size_t size{ runtime::Descriptor::SizeInBytes(symbol.Rank(), false, lenParams)}; return {size, maxAlignment}; } if (IsProcedure(symbol)) { return {}; } SizeAndAlignment result; if (const IntrinsicTypeSpec * intrinsic{type->AsIntrinsic()}) { if (auto kind{ToInt64(intrinsic->kind())}) { result = GetIntrinsicSizeAndAlignment(intrinsic->category(), *kind); } if (type->category() == DeclTypeSpec::Character) { ParamValue length{type->characterTypeSpec().length()}; CHECK(length.isExplicit()); // else should be descriptor if (MaybeIntExpr lengthExpr{length.GetExplicit()}) { if (auto lengthInt{ToInt64(*lengthExpr)}) { result.size *= *lengthInt; } } } } else if (const DerivedTypeSpec * derived{type->AsDerived()}) { if (derived->scope()) { result.size = derived->scope()->size(); result.alignment = derived->scope()->alignment(); } } else { DIE("not intrinsic or derived"); } return result; } std::size_t ComputeOffsetsHelper::CountElements(const Symbol &symbol) { if (auto shape{GetShape(foldingContext_, symbol)}) { if (auto sizeExpr{evaluate::GetSize(std::move(*shape))}) { if (auto size{ToInt64(Fold(foldingContext_, std::move(*sizeExpr)))}) { return *size; } } } return 1; } // Align a size to its natural alignment, up to maxAlignment. std::size_t ComputeOffsetsHelper::Align(std::size_t x, std::size_t alignment) { if (alignment > maxAlignment) { alignment = maxAlignment; } return (x + alignment - 1) & -alignment; } auto ComputeOffsetsHelper::GetIntrinsicSizeAndAlignment( TypeCategory category, int kind) -> SizeAndAlignment { if (category == TypeCategory::Character) { return {static_cast(kind)}; } std::optional size{ evaluate::DynamicType{category, kind}.MeasureSizeInBytes()}; CHECK(size.has_value()); if (category == TypeCategory::Complex) { return {*size, *size >> 1}; } else { return {*size}; } } void ComputeOffsets(SemanticsContext &context) { ComputeOffsetsHelper{context}.Compute(); } } // namespace Fortran::semantics