[flang-commits] [flang] [flang][Semantics] reject COMMON/EQUIVALENCE/derived types that do not fit in int64 (PR #219976)
via flang-commits
flang-commits at lists.llvm.org
Thu Sep 3 01:03:58 PDT 2026
https://github.com/jeanPerier updated https://github.com/llvm/llvm-project/pull/219976
>From ce9e5bc7ebdeeab3d6ceb481e5e80d6d970c3d81 Mon Sep 17 00:00:00 2001
From: Jean Perier <jperier at nvidia.com>
Date: Mon, 31 Aug 2026 06:46:30 -0700
Subject: [PATCH 1/4] [flang][Semantics] reject COMMON/EQUIVALENCE/derived
types that do not fit in int64
---
flang/lib/Semantics/compute-offsets.cpp | 135 +++++++++++++++++----
flang/test/Semantics/common-block-size.f90 | 29 +++++
flang/test/Semantics/storage-size.f90 | 107 ++++++++++++++++
3 files changed, 250 insertions(+), 21 deletions(-)
create mode 100644 flang/test/Semantics/common-block-size.f90
create mode 100644 flang/test/Semantics/storage-size.f90
diff --git a/flang/lib/Semantics/compute-offsets.cpp b/flang/lib/Semantics/compute-offsets.cpp
index f72bc844e89b6..6fe04617f4aff 100644
--- a/flang/lib/Semantics/compute-offsets.cpp
+++ b/flang/lib/Semantics/compute-offsets.cpp
@@ -9,6 +9,7 @@
#include "compute-offsets.h"
#include "flang/Evaluate/fold-designator.h"
#include "flang/Evaluate/fold.h"
+#include "flang/Evaluate/shape.h"
#include "flang/Evaluate/type.h"
#include "flang/Runtime/descriptor-consts.h"
#include "flang/Semantics/scope.h"
@@ -19,10 +20,26 @@
#include "llvm/TargetParser/Host.h"
#include "llvm/TargetParser/Triple.h"
#include <algorithm>
+#include <cstdint>
+#include <limits>
#include <vector>
namespace Fortran::semantics {
+// Generated IR represents storage sizes and offsets as signed 64-bit integers.
+static constexpr std::size_t maxStorageSizeInBytes{
+ static_cast<std::size_t>(std::numeric_limits<std::int64_t>::max())};
+
+static bool IsTooBig(std::size_t bytes) {
+ return bytes > maxStorageSizeInBytes;
+}
+
+// Add sizes while tracking whether the signed 64-bit limit was exceeded.
+static std::size_t AddSizes(std::size_t x, std::size_t y, bool &tooBig) {
+ tooBig |= IsTooBig(x) || IsTooBig(y) || x > maxStorageSizeInBytes - y;
+ return x + y;
+}
+
class ComputeOffsetsHelper {
public:
ComputeOffsetsHelper(SemanticsContext &context) : context_{context} {}
@@ -34,8 +51,11 @@ class ComputeOffsetsHelper {
SizeAndAlignment(std::size_t bytes) : size{bytes}, alignment{bytes} {}
SizeAndAlignment(std::size_t bytes, std::size_t align)
: size{bytes}, alignment{align} {}
+ SizeAndAlignment(std::size_t bytes, std::size_t align, bool tooBig)
+ : size{bytes}, alignment{align}, overflow{tooBig} {}
std::size_t size{0};
std::size_t alignment{0};
+ bool overflow{false};
};
struct SymbolAndOffset {
SymbolAndOffset(Symbol &s, std::size_t off, const EquivalenceObject &obj)
@@ -44,6 +64,7 @@ class ComputeOffsetsHelper {
MutableSymbolRef symbol;
std::size_t offset;
const EquivalenceObject *object;
+ bool offsetOverflow{false};
};
void DoCommonBlock(Symbol &);
@@ -62,6 +83,7 @@ class ComputeOffsetsHelper {
SemanticsContext &context_;
std::size_t offset_{0};
std::size_t alignment_{1};
+ bool sizeOverflow_{false};
// symbol -> symbol+offset that determines its location, from EQUIVALENCE
std::map<MutableSymbolRef, SymbolAndOffset, SymbolAddressCompare> dependents_;
// base symbol -> SizeAndAlignment for each distinct EQUIVALENCE block
@@ -156,21 +178,35 @@ void ComputeOffsetsHelper::Compute(Scope &scope) {
symbol->set_size(symInfo.size);
Symbol &base{*dep.symbol};
auto iter{equivalenceBlock_.find(base)};
- std::size_t minBlockSize{dep.offset + symInfo.size};
+ bool blockOverflow{dep.offsetOverflow || symInfo.overflow};
+ std::size_t minBlockSize{AddSizes(dep.offset, symInfo.size, blockOverflow)};
if (iter == equivalenceBlock_.end()) {
- equivalenceBlock_.emplace(
- base, SizeAndAlignment{minBlockSize, symInfo.alignment});
+ equivalenceBlock_.emplace(base,
+ SizeAndAlignment{minBlockSize, symInfo.alignment, blockOverflow});
} else {
SizeAndAlignment &blockInfo{iter->second};
blockInfo.size = std::max(blockInfo.size, minBlockSize);
blockInfo.alignment = std::max(blockInfo.alignment, symInfo.alignment);
+ blockInfo.overflow |= blockOverflow;
}
}
- // Assign offsets for non-COMMON EQUIVALENCE blocks
+ // Complete each EQUIVALENCE block with its base object, and assign offsets
+ // for non-COMMON blocks.
for (auto &[symbol, blockInfo] : equivalenceBlock_) {
+ // The base does not appear in dependents_.
+ SizeAndAlignment baseInfo{GetSizeAndAlignment(*symbol, true)};
+ blockInfo.size = std::max(blockInfo.size, baseInfo.size);
+ blockInfo.alignment = std::max(blockInfo.alignment, baseInfo.alignment);
+ blockInfo.overflow |= baseInfo.overflow;
if (!FindCommonBlockContaining(*symbol)) {
DoSymbol(*symbol);
DoEquivalenceBlockBase(*symbol, blockInfo);
+ // Each EQUIVALENCE block is lowered as one aggregate.
+ if (blockInfo.overflow || IsTooBig(blockInfo.size)) {
+ context_.Say(symbol->name(),
+ "The size of the storage sequence created by EQUIVALENCE with '%s' exceeds the maximum supported size of %zd bytes"_err_en_US,
+ symbol->name(), maxStorageSizeInBytes);
+ }
offset_ = std::max(offset_, symbol->offset() + blockInfo.size);
}
}
@@ -200,6 +236,13 @@ void ComputeOffsetsHelper::Compute(Scope &scope) {
}
// Ensure that the size is a multiple of the alignment
offset_ = Align(offset_, alignment_);
+ sizeOverflow_ |= IsTooBig(offset_);
+ // Only derived-type scope sizes are materialized in generated IR.
+ if (sizeOverflow_ && scope.IsDerivedType() && scope.symbol()) {
+ context_.Say(scope.symbol()->name(),
+ "The size of derived type '%s' exceeds the maximum supported size of %zd bytes"_err_en_US,
+ scope.symbol()->name(), maxStorageSizeInBytes);
+ }
scope.set_size(offset_);
scope.SetAlignment(alignment_);
// Assign offsets in COMMON blocks, unless this scope is a BLOCK construct,
@@ -226,7 +269,8 @@ auto ComputeOffsetsHelper::Resolve(const SymbolAndOffset &dep)
return dep;
} else {
SymbolAndOffset result{Resolve(it->second)};
- result.offset += dep.offset;
+ // Preserve overflow while resolving EQUIVALENCE chains.
+ result.offset = AddSizes(result.offset, dep.offset, result.offsetOverflow);
result.object = dep.object;
return result;
}
@@ -236,6 +280,7 @@ void ComputeOffsetsHelper::DoCommonBlock(Symbol &commonBlock) {
auto &details{commonBlock.get<CommonBlockDetails>()};
offset_ = 0;
alignment_ = 0;
+ sizeOverflow_ = false;
std::size_t minSize{0};
std::size_t minAlignment{0};
UnorderedSymbolSet previous;
@@ -290,12 +335,20 @@ void ComputeOffsetsHelper::DoCommonBlock(Symbol &commonBlock) {
// 8.10.2.2 point 1 (2))
if (eqIter != equivalenceBlock_.end()) {
SizeAndAlignment &blockInfo{eqIter->second};
- minSize = std::max(
- minSize, std::max(offset_, eqIter->first->offset() + blockInfo.size));
+ sizeOverflow_ |= blockInfo.overflow;
+ std::size_t blockEnd{
+ AddSizes(eqIter->first->offset(), blockInfo.size, sizeOverflow_)};
+ minSize = std::max(minSize, std::max(offset_, blockEnd));
minAlignment = std::max(minAlignment, blockInfo.alignment);
}
}
- commonBlock.set_size(std::max(minSize, offset_));
+ std::size_t size{std::max(minSize, offset_)};
+ if (sizeOverflow_) {
+ context_.Say(details.sourceLocation(),
+ "The size of COMMON block /%s/ exceeds the maximum supported size of %zd bytes"_err_en_US,
+ commonBlock.name(), maxStorageSizeInBytes);
+ }
+ commonBlock.set_size(size);
details.set_alignment(std::max(minAlignment, alignment_));
context_.MapCommonBlockAndCheckConflicts(commonBlock);
}
@@ -325,7 +378,7 @@ void ComputeOffsetsHelper::DoEquivalenceSet(const EquivalenceSet &set) {
}
CHECK(representative);
const SymbolAndOffset &base{symbolOffsets[*representative]};
- for (const auto &[symbol, offset, object] : symbolOffsets) {
+ for (const auto &[symbol, offset, object, offsetOverflow] : symbolOffsets) {
if (symbol == base.symbol) {
if (offset != base.offset) {
auto x{evaluate::OffsetToDesignator(
@@ -350,8 +403,9 @@ void ComputeOffsetsHelper::DoEquivalenceSet(const EquivalenceSet &set) {
}
}
} else {
- dependents_.emplace(*symbol,
- SymbolAndOffset{*base.symbol, base.offset - offset, *object});
+ SymbolAndOffset dependent{*base.symbol, base.offset - offset, *object};
+ dependent.offsetOverflow = base.offsetOverflow || offsetOverflow;
+ dependents_.emplace(*symbol, dependent);
}
}
}
@@ -398,6 +452,8 @@ std::size_t ComputeOffsetsHelper::DoSymbol(
return 0;
}
SizeAndAlignment s{GetSizeAndAlignment(symbol, true)};
+ // Oversized standalone objects are left to object emission.
+ sizeOverflow_ |= s.overflow;
if (s.size == 0) {
// Zero-size symbols (e.g. CHARACTER*0) still occupy their sequential
// position in a COMMON block or derived-type sequence. Record the current
@@ -410,10 +466,11 @@ std::size_t ComputeOffsetsHelper::DoSymbol(
std::size_t previousOffset{offset_};
size_t alignVal{newAlign.value_or(s.alignment)};
offset_ = Align(offset_, alignVal);
+ sizeOverflow_ |= IsTooBig(offset_);
std::size_t padding{offset_ - previousOffset};
symbol.set_size(s.size);
symbol.set_offset(offset_);
- offset_ += s.size;
+ offset_ = AddSizes(offset_, s.size, sizeOverflow_);
alignment_ = std::max(alignment_, alignVal);
return padding;
}
@@ -444,17 +501,53 @@ auto ComputeOffsetsHelper::GetSizeAndAlignment(
auto &foldingContext{context_.foldingContext()};
if (auto chars{evaluate::characteristics::TypeAndShape::Characterize(
symbol, foldingContext)}) {
- if (entire) {
- if (auto size{ToInt64(chars->MeasureSizeInBytes(foldingContext))}) {
- return {static_cast<std::size_t>(*size),
- chars->type().GetAlignment(targetCharacteristics)};
+ std::size_t alignment{chars->type().GetAlignment(targetCharacteristics)};
+ // Avoid folded products, which can wrap in signed 64-bit arithmetic.
+ bool aligned{!entire || chars->Rank() > 0};
+ std::size_t size;
+ if (chars->type().category() == TypeCategory::Character && chars->LEN()) {
+ auto length{ToInt64(*chars->LEN())};
+ if (!length) {
+ return {};
+ }
+ auto bytesPerCharacter{
+ static_cast<std::size_t>(targetCharacteristics.GetByteSize(
+ TypeCategory::Character, chars->type().kind()))};
+ if (*length < 0 ||
+ static_cast<std::size_t>(*length) >
+ maxStorageSizeInBytes / bytesPerCharacter) {
+ return {maxStorageSizeInBytes, alignment, /*tooBig=*/true};
}
- } else { // element size only
- if (auto size{ToInt64(chars->MeasureElementSizeInBytes(
- foldingContext, true /*aligned*/))}) {
- return {static_cast<std::size_t>(*size),
- chars->type().GetAlignment(targetCharacteristics)};
+ size = static_cast<std::size_t>(*length) * bytesPerCharacter;
+ } else {
+ auto elementSize{
+ ToInt64(chars->MeasureElementSizeInBytes(foldingContext, aligned))};
+ if (!elementSize) {
+ return {};
+ }
+ if (*elementSize < 0) {
+ return {maxStorageSizeInBytes, alignment, /*tooBig=*/true};
+ }
+ size = static_cast<std::size_t>(*elementSize);
+ }
+ if (!entire) { // element size only
+ return {size, alignment};
+ }
+ if (auto extents{
+ evaluate::AsConstantExtents(foldingContext, chars->shape())}) {
+ for (ConstantSubscript extent : *extents) {
+ if (extent <= 0) { // a zero-sized array occupies no storage
+ return {0, alignment};
+ }
+ }
+ for (ConstantSubscript extent : *extents) {
+ auto n{static_cast<std::size_t>(extent)};
+ if (size > maxStorageSizeInBytes / n) {
+ return {maxStorageSizeInBytes, alignment, /*tooBig=*/true};
+ }
+ size *= n;
}
+ return {size, alignment};
}
}
return {};
diff --git a/flang/test/Semantics/common-block-size.f90 b/flang/test/Semantics/common-block-size.f90
new file mode 100644
index 0000000000000..650cfcbf9d985
--- /dev/null
+++ b/flang/test/Semantics/common-block-size.f90
@@ -0,0 +1,29 @@
+! RUN: %python %S/test_errors.py %s %flang_fc1
+
+! COMMON block byte sizes and offsets must fit in signed 64-bit integers.
+
+subroutine biggest
+ ! 2305843009213693951 * 4 bytes == huge(0_8) - 3, the largest REAL(4)
+ ! array that still fits.
+ real :: a(2305843009213693951_8)
+ common /fits/ a
+end subroutine
+
+subroutine one_object
+ real :: a(3000000000000000000_8)
+ !ERROR: The size of COMMON block /one/ exceeds the maximum supported size of 9223372036854775807 bytes
+ common /one/ a
+end subroutine
+
+subroutine several_objects
+ real :: a(1999999999999999999_8), b(1999999999999999999_8), &
+ c(1999999999999999999_8)
+ !ERROR: The size of COMMON block /several/ exceeds the maximum supported size of 9223372036854775807 bytes
+ common /several/ a, b, c
+end subroutine
+
+subroutine blank_common
+ real :: a(1999999999999999999_8), b(1999999999999999999_8)
+ !ERROR: The size of COMMON block // exceeds the maximum supported size of 9223372036854775807 bytes
+ common a, b
+end subroutine
diff --git a/flang/test/Semantics/storage-size.f90 b/flang/test/Semantics/storage-size.f90
new file mode 100644
index 0000000000000..5761a5ccc7cab
--- /dev/null
+++ b/flang/test/Semantics/storage-size.f90
@@ -0,0 +1,107 @@
+! RUN: %python %S/test_errors.py %s %flang_fc1
+
+! Compiler-generated storage sequences must fit in a signed 64-bit byte size.
+! Oversized standalone objects are left to the assembler and linker.
+
+subroutine biggest_object
+ ! huge(0_8) bytes exactly, the largest object that can be laid out
+ character(len=9223372036854775807_8) :: c
+end subroutine
+
+subroutine biggest_array
+ ! 2305843009213693951 * 4 == huge(0_8) - 3 bytes
+ real :: a(2305843009213693951_8)
+end subroutine
+
+subroutine zero_sized_array
+ ! a zero-sized array is not a wrapped size
+ real :: z(2305843009213693952_8, 0)
+end subroutine
+
+subroutine unassociated_objects
+ ! A procedure scope is not laid out as one storage sequence.
+ integer(8) :: a(576460752303423488_8), b(576460752303423488_8), &
+ c(576460752303423488_8)
+ ! Standalone object sizes are also accepted here.
+ integer(8) :: d(1152921504606846976_8)
+ integer(8) :: e(2305843009213693952_8)
+ character(kind=4, len=2305843009213693952_8) :: f
+end subroutine
+
+subroutine object_size_in_common
+ ! 2305843009213693952 * 8 bytes is a multiple of 2**64, so the size folds to
+ ! exactly zero; without a diagnostic 'b' would silently overlap 'a'
+ integer(8) :: a(2305843009213693952_8)
+ integer(8) :: b(4)
+ !ERROR: The size of COMMON block /blk/ exceeds the maximum supported size of 9223372036854775807 bytes
+ common /blk/ a, b
+end subroutine
+
+subroutine element_size_in_common
+ ! the size of a single element does not fit: 4 * 2305843009213693952 bytes
+ character(kind=4, len=2305843009213693952_8) :: c
+ !ERROR: The size of COMMON block /blk/ exceeds the maximum supported size of 9223372036854775807 bytes
+ common /blk/ c
+end subroutine
+
+subroutine element_size_wraps_positive
+ ! 4 * (2**62 + 1) wraps around to 4 when folded as a signed 64-bit
+ ! integer, but the true element size does not fit
+ character(kind=4, len=4611686018427387905_8) :: c
+ !ERROR: The size of COMMON block /blk/ exceeds the maximum supported size of 9223372036854775807 bytes
+ common /blk/ c
+end subroutine
+
+module derived_type_size
+ !ERROR: The size of derived type 't' exceeds the maximum supported size of 9223372036854775807 bytes
+ type t
+ sequence
+ integer(8) :: a(576460752303423488_8), b(576460752303423488_8), &
+ c(576460752303423488_8)
+ end type
+ !ERROR: The size of derived type 'padded' exceeds the maximum supported size of 9223372036854775807 bytes
+ type padded
+ ! the components occupy exactly huge(0_8) bytes, but rounding that up to a
+ ! multiple of the alignment of the type no longer fits
+ sequence
+ integer(8) :: n
+ character(len=9223372036854775799_8) :: c
+ end type
+end module
+
+subroutine equivalence_block_size
+ ! the EQUIVALENCE chain extends the storage sequence to 3 * 2**62 bytes; the
+ ! accumulated offsets are what can wrap around into a plausible small value
+ !ERROR: The size of the storage sequence created by EQUIVALENCE with 'a' exceeds the maximum supported size of 9223372036854775807 bytes
+ integer(8) :: a(576460752303423488_8), b(576460752303423488_8), &
+ c(576460752303423488_8)
+ equivalence (a(576460752303423488_8), b(1))
+ equivalence (b(576460752303423488_8), c(1))
+end subroutine
+
+subroutine equivalence_base_size
+ ! List the small object first so that the oversized array is selected as the
+ ! base of the EQUIVALENCE storage sequence.
+ !ERROR: The size of the storage sequence created by EQUIVALENCE with 'a' exceeds the maximum supported size of 9223372036854775807 bytes
+ integer(8) :: a(2305843009213693952_8)
+ integer(8) :: b
+ equivalence (b, a(1))
+end subroutine
+
+subroutine equivalence_block_placement
+ ! EQUIVALENCE block extents must fit independently of their scope offsets.
+ character(len=9223372036854775800_8) :: c1
+ character(len=8) :: c2
+ equivalence (c1(9223372036854775793_8:), c2)
+ integer :: a(2), b(2)
+ equivalence (a(1), b(1))
+end subroutine
+
+subroutine equivalence_block_size_in_common
+ integer(8) :: a(576460752303423488_8), b(576460752303423488_8), &
+ c(576460752303423488_8)
+ equivalence (a(576460752303423488_8), b(1))
+ equivalence (b(576460752303423488_8), c(1))
+ !ERROR: The size of COMMON block /blk/ exceeds the maximum supported size of 9223372036854775807 bytes
+ common /blk/ a
+end subroutine
>From 4f9871f9a43c3f90cf5cd68ac2ebb0abdeee1ef1 Mon Sep 17 00:00:00 2001
From: Jean Perier <jperier at nvidia.com>
Date: Tue, 1 Sep 2026 06:50:57 -0700
Subject: [PATCH 2/4] Address review comments
- Use %zu instead of %zd for the std::size_t arguments of the new
messages.
- Assert that std::size_t is at least 64 bits wide. The extent checks
divide by a value narrowed from a ConstantSubscript, which could be a
division by zero if that narrowing ever lost bits.
- Do not mistake a dimension whose extent wrapped around for an empty
one. A folded extent is (ub-lb+1) computed with signed 64-bit
arithmetic, so a(0:huge(0_8)) yields a negative extent and the array
was silently laid out as if it were empty, letting the objects behind
it in a COMMON block overlap it. Consult the declared bounds to tell
the two cases apart.
Test explicit lower bounds (zero, negative, and negative-to-positive),
empty dimensions spelled with explicit bounds, and multidimensional
arrays whose element count wraps around.
---
flang/lib/Semantics/compute-offsets.cpp | 39 +++++++++++++++---
flang/test/Semantics/storage-size.f90 | 54 +++++++++++++++++++++++++
2 files changed, 87 insertions(+), 6 deletions(-)
diff --git a/flang/lib/Semantics/compute-offsets.cpp b/flang/lib/Semantics/compute-offsets.cpp
index 6fe04617f4aff..1c8684c3d32b5 100644
--- a/flang/lib/Semantics/compute-offsets.cpp
+++ b/flang/lib/Semantics/compute-offsets.cpp
@@ -27,6 +27,9 @@
namespace Fortran::semantics {
// Generated IR represents storage sizes and offsets as signed 64-bit integers.
+static_assert(sizeof(std::size_t) >= sizeof(std::int64_t),
+ "byte sizes and offsets are accumulated in std::size_t and must not be "
+ "narrowed");
static constexpr std::size_t maxStorageSizeInBytes{
static_cast<std::size_t>(std::numeric_limits<std::int64_t>::max())};
@@ -40,6 +43,23 @@ static std::size_t AddSizes(std::size_t x, std::size_t y, bool &tooBig) {
return x + y;
}
+// A folded extent is (ub-lb+1) evaluated with signed 64-bit arithmetic, so it
+// comes out nonpositive both for an empty dimension and for one that wrapped
+// around, as in a(0:huge(0_8)). Tell those apart with the declared bounds.
+static bool IsEmptyDimension(const Symbol &symbol, int dimension) {
+ if (const auto *object{symbol.detailsIf<ObjectEntityDetails>()}) {
+ const ArraySpec &shape{object->shape()};
+ if (dimension < shape.Rank()) {
+ auto lb{evaluate::ToInt64(shape[dimension].lbound().GetExplicit())};
+ auto ub{evaluate::ToInt64(shape[dimension].ubound().GetExplicit())};
+ if (lb && ub) {
+ return *ub < *lb;
+ }
+ }
+ }
+ return true; // no constant bounds to contradict the folded extent
+}
+
class ComputeOffsetsHelper {
public:
ComputeOffsetsHelper(SemanticsContext &context) : context_{context} {}
@@ -204,7 +224,7 @@ void ComputeOffsetsHelper::Compute(Scope &scope) {
// Each EQUIVALENCE block is lowered as one aggregate.
if (blockInfo.overflow || IsTooBig(blockInfo.size)) {
context_.Say(symbol->name(),
- "The size of the storage sequence created by EQUIVALENCE with '%s' exceeds the maximum supported size of %zd bytes"_err_en_US,
+ "The size of the storage sequence created by EQUIVALENCE with '%s' exceeds the maximum supported size of %zu bytes"_err_en_US,
symbol->name(), maxStorageSizeInBytes);
}
offset_ = std::max(offset_, symbol->offset() + blockInfo.size);
@@ -240,7 +260,7 @@ void ComputeOffsetsHelper::Compute(Scope &scope) {
// Only derived-type scope sizes are materialized in generated IR.
if (sizeOverflow_ && scope.IsDerivedType() && scope.symbol()) {
context_.Say(scope.symbol()->name(),
- "The size of derived type '%s' exceeds the maximum supported size of %zd bytes"_err_en_US,
+ "The size of derived type '%s' exceeds the maximum supported size of %zu bytes"_err_en_US,
scope.symbol()->name(), maxStorageSizeInBytes);
}
scope.set_size(offset_);
@@ -345,7 +365,7 @@ void ComputeOffsetsHelper::DoCommonBlock(Symbol &commonBlock) {
std::size_t size{std::max(minSize, offset_)};
if (sizeOverflow_) {
context_.Say(details.sourceLocation(),
- "The size of COMMON block /%s/ exceeds the maximum supported size of %zd bytes"_err_en_US,
+ "The size of COMMON block /%s/ exceeds the maximum supported size of %zu bytes"_err_en_US,
commonBlock.name(), maxStorageSizeInBytes);
}
commonBlock.set_size(size);
@@ -535,9 +555,16 @@ auto ComputeOffsetsHelper::GetSizeAndAlignment(
}
if (auto extents{
evaluate::AsConstantExtents(foldingContext, chars->shape())}) {
- for (ConstantSubscript extent : *extents) {
- if (extent <= 0) { // a zero-sized array occupies no storage
- return {0, alignment};
+ if (size == 0) { // zero-sized elements occupy no storage
+ return {0, alignment};
+ }
+ for (int dimension{0}; dimension < static_cast<int>(extents->size());
+ ++dimension) {
+ if ((*extents)[dimension] <= 0) {
+ if (!IsEmptyDimension(symbol, dimension)) {
+ return {maxStorageSizeInBytes, alignment, /*tooBig=*/true};
+ }
+ return {0, alignment}; // a zero-sized array occupies no storage
}
}
for (ConstantSubscript extent : *extents) {
diff --git a/flang/test/Semantics/storage-size.f90 b/flang/test/Semantics/storage-size.f90
index 5761a5ccc7cab..b1f3b8ac5c1e0 100644
--- a/flang/test/Semantics/storage-size.f90
+++ b/flang/test/Semantics/storage-size.f90
@@ -18,6 +18,22 @@ subroutine zero_sized_array
real :: z(2305843009213693952_8, 0)
end subroutine
+subroutine explicit_bounds_that_fit
+ ! extent = huge(0_8) in both cases, one byte per element
+ integer(1) :: a(0_8:9223372036854775806_8)
+ integer(1) :: b(-9223372036854775807_8:-1_8)
+ common /fits1/ a
+ common /fits2/ b
+end subroutine
+
+subroutine empty_explicit_bounds
+ ! an upper bound below the lower bound makes the whole array empty
+ integer(8) :: a(1_8:0_8)
+ integer(8) :: b(-5_8:-10_8)
+ integer(8) :: c(2305843009213693952_8, 1_8:0_8)
+ common /empty/ a, b, c
+end subroutine
+
subroutine unassociated_objects
! A procedure scope is not laid out as one storage sequence.
integer(8) :: a(576460752303423488_8), b(576460752303423488_8), &
@@ -52,6 +68,44 @@ subroutine element_size_wraps_positive
common /blk/ c
end subroutine
+subroutine zero_lower_bound_in_common
+ ! extent = huge(0_8) + 1 wraps around to a negative value, which must not be
+ ! taken for an empty dimension; without a diagnostic 'b' would overlap 'a'
+ integer(1) :: a(0_8:9223372036854775807_8)
+ integer(1) :: b(4)
+ !ERROR: The size of COMMON block /blk/ exceeds the maximum supported size of 9223372036854775807 bytes
+ common /blk/ a, b
+end subroutine
+
+subroutine negative_bounds_in_common
+ ! extent = huge(0_8) fits, but there are two bytes per element
+ integer(2) :: a(-9223372036854775807_8:-1_8)
+ !ERROR: The size of COMMON block /blk/ exceeds the maximum supported size of 9223372036854775807 bytes
+ common /blk/ a
+end subroutine
+
+subroutine negative_to_positive_bounds_in_common
+ ! extent = 2**63 wraps around to a negative value
+ integer(1) :: a(-4611686018427387904_8:4611686018427387903_8)
+ !ERROR: The size of COMMON block /blk/ exceeds the maximum supported size of 9223372036854775807 bytes
+ common /blk/ a
+end subroutine
+
+subroutine multidimensional_in_common
+ ! 2**32 * 2**32 == 2**64 elements, so the size folds to exactly zero
+ integer(1) :: a(4294967296_8, 4294967296_8)
+ integer(1) :: b(4)
+ !ERROR: The size of COMMON block /blk/ exceeds the maximum supported size of 9223372036854775807 bytes
+ common /blk/ a, b
+end subroutine
+
+subroutine multidimensional_bounds_in_common
+ ! same element count, spelled with explicit zero lower bounds
+ integer(1) :: a(0_8:4294967295_8, 0_8:4294967295_8)
+ !ERROR: The size of COMMON block /blk/ exceeds the maximum supported size of 9223372036854775807 bytes
+ common /blk/ a
+end subroutine
+
module derived_type_size
!ERROR: The size of derived type 't' exceeds the maximum supported size of 9223372036854775807 bytes
type t
>From 326fb55ca66f773112782e5e268ff8b53e17d0f3 Mon Sep 17 00:00:00 2001
From: Jean Perier <jperier at nvidia.com>
Date: Thu, 3 Sep 2026 00:26:51 -0700
Subject: [PATCH 3/4] Address second round of review comments
- Propagate the overflow already recorded on the offset being resolved
in Resolve(), which was preserved when the chain ended but dropped
when it recursed.
- Blame the EQUIVALENCE size diagnostic on a member that does not fit
instead of the base of the storage sequence. Which object becomes the
base is a layout decision made by DoEquivalenceSet (the object at the
largest offset within its own symbol, so that every other member sits
at a non-negative offset from it), it is invisible to the user, and it
routinely lands on the smallest object in the sequence.
Rather than track that member alongside the overflow flag, replace the
flag with it: every place that flagged an overflow had the offending
symbol in hand, so a null symbol now means the size fits.
---
flang/lib/Semantics/compute-offsets.cpp | 55 ++++++++++++++++---------
flang/test/Semantics/storage-size.f90 | 16 +++++--
2 files changed, 49 insertions(+), 22 deletions(-)
diff --git a/flang/lib/Semantics/compute-offsets.cpp b/flang/lib/Semantics/compute-offsets.cpp
index 1c8684c3d32b5..06d9c50ecc6eb 100644
--- a/flang/lib/Semantics/compute-offsets.cpp
+++ b/flang/lib/Semantics/compute-offsets.cpp
@@ -71,11 +71,14 @@ class ComputeOffsetsHelper {
SizeAndAlignment(std::size_t bytes) : size{bytes}, alignment{bytes} {}
SizeAndAlignment(std::size_t bytes, std::size_t align)
: size{bytes}, alignment{align} {}
- SizeAndAlignment(std::size_t bytes, std::size_t align, bool tooBig)
- : size{bytes}, alignment{align}, overflow{tooBig} {}
+ SizeAndAlignment(std::size_t bytes, std::size_t align, const Symbol *tooBig)
+ : size{bytes}, alignment{align}, oversized{tooBig} {}
std::size_t size{0};
std::size_t alignment{0};
- bool overflow{false};
+ // Null unless the size exceeds maxStorageSizeInBytes, in which case the
+ // size is clamped to it and this is the symbol to blame in a diagnostic:
+ // for an EQUIVALENCE storage sequence, the first member found not to fit.
+ const Symbol *oversized{nullptr};
};
struct SymbolAndOffset {
SymbolAndOffset(Symbol &s, std::size_t off, const EquivalenceObject &obj)
@@ -198,16 +201,20 @@ void ComputeOffsetsHelper::Compute(Scope &scope) {
symbol->set_size(symInfo.size);
Symbol &base{*dep.symbol};
auto iter{equivalenceBlock_.find(base)};
- bool blockOverflow{dep.offsetOverflow || symInfo.overflow};
+ bool blockOverflow{dep.offsetOverflow || symInfo.oversized};
std::size_t minBlockSize{AddSizes(dep.offset, symInfo.size, blockOverflow)};
+ const Symbol *oversized{
+ blockOverflow || IsTooBig(minBlockSize) ? &*symbol : nullptr};
if (iter == equivalenceBlock_.end()) {
- equivalenceBlock_.emplace(base,
- SizeAndAlignment{minBlockSize, symInfo.alignment, blockOverflow});
+ equivalenceBlock_.emplace(
+ base, SizeAndAlignment{minBlockSize, symInfo.alignment, oversized});
} else {
SizeAndAlignment &blockInfo{iter->second};
blockInfo.size = std::max(blockInfo.size, minBlockSize);
blockInfo.alignment = std::max(blockInfo.alignment, symInfo.alignment);
- blockInfo.overflow |= blockOverflow;
+ if (!blockInfo.oversized) {
+ blockInfo.oversized = oversized;
+ }
}
}
// Complete each EQUIVALENCE block with its base object, and assign offsets
@@ -217,15 +224,22 @@ void ComputeOffsetsHelper::Compute(Scope &scope) {
SizeAndAlignment baseInfo{GetSizeAndAlignment(*symbol, true)};
blockInfo.size = std::max(blockInfo.size, baseInfo.size);
blockInfo.alignment = std::max(blockInfo.alignment, baseInfo.alignment);
- blockInfo.overflow |= baseInfo.overflow;
+ if (!blockInfo.oversized &&
+ (baseInfo.oversized || IsTooBig(baseInfo.size))) {
+ blockInfo.oversized = &*symbol;
+ }
if (!FindCommonBlockContaining(*symbol)) {
DoSymbol(*symbol);
DoEquivalenceBlockBase(*symbol, blockInfo);
- // Each EQUIVALENCE block is lowered as one aggregate.
- if (blockInfo.overflow || IsTooBig(blockInfo.size)) {
- context_.Say(symbol->name(),
+ // Each EQUIVALENCE block is lowered as one aggregate. Blame a member
+ // that does not fit rather than the base object, whose selection in
+ // DoEquivalenceSet is a layout decision that is invisible to the user.
+ if (blockInfo.oversized || IsTooBig(blockInfo.size)) {
+ const Symbol &blamed{
+ blockInfo.oversized ? *blockInfo.oversized : *symbol};
+ context_.Say(blamed.name(),
"The size of the storage sequence created by EQUIVALENCE with '%s' exceeds the maximum supported size of %zu bytes"_err_en_US,
- symbol->name(), maxStorageSizeInBytes);
+ blamed.name(), maxStorageSizeInBytes);
}
offset_ = std::max(offset_, symbol->offset() + blockInfo.size);
}
@@ -289,7 +303,10 @@ auto ComputeOffsetsHelper::Resolve(const SymbolAndOffset &dep)
return dep;
} else {
SymbolAndOffset result{Resolve(it->second)};
- // Preserve overflow while resolving EQUIVALENCE chains.
+ // Preserve overflow while resolving EQUIVALENCE chains, both the overflow
+ // already recorded for the offset being resolved and any that appears when
+ // accumulating it.
+ result.offsetOverflow |= dep.offsetOverflow;
result.offset = AddSizes(result.offset, dep.offset, result.offsetOverflow);
result.object = dep.object;
return result;
@@ -355,7 +372,7 @@ void ComputeOffsetsHelper::DoCommonBlock(Symbol &commonBlock) {
// 8.10.2.2 point 1 (2))
if (eqIter != equivalenceBlock_.end()) {
SizeAndAlignment &blockInfo{eqIter->second};
- sizeOverflow_ |= blockInfo.overflow;
+ sizeOverflow_ |= blockInfo.oversized != nullptr;
std::size_t blockEnd{
AddSizes(eqIter->first->offset(), blockInfo.size, sizeOverflow_)};
minSize = std::max(minSize, std::max(offset_, blockEnd));
@@ -473,7 +490,7 @@ std::size_t ComputeOffsetsHelper::DoSymbol(
}
SizeAndAlignment s{GetSizeAndAlignment(symbol, true)};
// Oversized standalone objects are left to object emission.
- sizeOverflow_ |= s.overflow;
+ sizeOverflow_ |= s.oversized != nullptr;
if (s.size == 0) {
// Zero-size symbols (e.g. CHARACTER*0) still occupy their sequential
// position in a COMMON block or derived-type sequence. Record the current
@@ -536,7 +553,7 @@ auto ComputeOffsetsHelper::GetSizeAndAlignment(
if (*length < 0 ||
static_cast<std::size_t>(*length) >
maxStorageSizeInBytes / bytesPerCharacter) {
- return {maxStorageSizeInBytes, alignment, /*tooBig=*/true};
+ return {maxStorageSizeInBytes, alignment, &symbol};
}
size = static_cast<std::size_t>(*length) * bytesPerCharacter;
} else {
@@ -546,7 +563,7 @@ auto ComputeOffsetsHelper::GetSizeAndAlignment(
return {};
}
if (*elementSize < 0) {
- return {maxStorageSizeInBytes, alignment, /*tooBig=*/true};
+ return {maxStorageSizeInBytes, alignment, &symbol};
}
size = static_cast<std::size_t>(*elementSize);
}
@@ -562,7 +579,7 @@ auto ComputeOffsetsHelper::GetSizeAndAlignment(
++dimension) {
if ((*extents)[dimension] <= 0) {
if (!IsEmptyDimension(symbol, dimension)) {
- return {maxStorageSizeInBytes, alignment, /*tooBig=*/true};
+ return {maxStorageSizeInBytes, alignment, &symbol};
}
return {0, alignment}; // a zero-sized array occupies no storage
}
@@ -570,7 +587,7 @@ auto ComputeOffsetsHelper::GetSizeAndAlignment(
for (ConstantSubscript extent : *extents) {
auto n{static_cast<std::size_t>(extent)};
if (size > maxStorageSizeInBytes / n) {
- return {maxStorageSizeInBytes, alignment, /*tooBig=*/true};
+ return {maxStorageSizeInBytes, alignment, &symbol};
}
size *= n;
}
diff --git a/flang/test/Semantics/storage-size.f90 b/flang/test/Semantics/storage-size.f90
index b1f3b8ac5c1e0..471de885e4df0 100644
--- a/flang/test/Semantics/storage-size.f90
+++ b/flang/test/Semantics/storage-size.f90
@@ -126,13 +126,23 @@ module derived_type_size
subroutine equivalence_block_size
! the EQUIVALENCE chain extends the storage sequence to 3 * 2**62 bytes; the
! accumulated offsets are what can wrap around into a plausible small value
- !ERROR: The size of the storage sequence created by EQUIVALENCE with 'a' exceeds the maximum supported size of 9223372036854775807 bytes
- integer(8) :: a(576460752303423488_8), b(576460752303423488_8), &
- c(576460752303423488_8)
+ integer(8) :: a(576460752303423488_8), b(576460752303423488_8)
+ !ERROR: The size of the storage sequence created by EQUIVALENCE with 'c' exceeds the maximum supported size of 9223372036854775807 bytes
+ integer(8) :: c(576460752303423488_8)
equivalence (a(576460752303423488_8), b(1))
equivalence (b(576460752303423488_8), c(1))
end subroutine
+subroutine equivalence_oversized_member
+ ! The base of a storage sequence is picked for layout reasons and is often
+ ! its smallest member, so name the member that does not fit instead: here
+ ! 'marker' is the base, but 'oversized' is what the user has to shrink.
+ !ERROR: The size of the storage sequence created by EQUIVALENCE with 'oversized' exceeds the maximum supported size of 9223372036854775807 bytes
+ integer(1) :: oversized(0_8:9223372036854775807_8)
+ integer(8) :: marker
+ equivalence (oversized, marker)
+end subroutine
+
subroutine equivalence_base_size
! List the small object first so that the oversized array is selected as the
! base of the EQUIVALENCE storage sequence.
>From 70ad17362ae265932984cf9e593887bac843eb80 Mon Sep 17 00:00:00 2001
From: Jean Perier <jperier at nvidia.com>
Date: Thu, 3 Sep 2026 01:01:31 -0700
Subject: [PATCH 4/4] [NFC] Rename storage-size.f90 to
oversized-storage-sequence.f90
The test has nothing to do with the STORAGE_SIZE intrinsic, and it sat
next to numeric_storage_size.f90, which does test a storage size
inquiry. Name it after what it checks: storage sequences that are too
big to lay out.
---
.../{storage-size.f90 => oversized-storage-sequence.f90} | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
rename flang/test/Semantics/{storage-size.f90 => oversized-storage-sequence.f90} (98%)
diff --git a/flang/test/Semantics/storage-size.f90 b/flang/test/Semantics/oversized-storage-sequence.f90
similarity index 98%
rename from flang/test/Semantics/storage-size.f90
rename to flang/test/Semantics/oversized-storage-sequence.f90
index 471de885e4df0..afe2835b7f754 100644
--- a/flang/test/Semantics/storage-size.f90
+++ b/flang/test/Semantics/oversized-storage-sequence.f90
@@ -1,6 +1,7 @@
! RUN: %python %S/test_errors.py %s %flang_fc1
-! Compiler-generated storage sequences must fit in a signed 64-bit byte size.
+! The storage sequences laid out for COMMON blocks, EQUIVALENCE sets, and
+! derived types must fit in a signed 64-bit byte size.
! Oversized standalone objects are left to the assembler and linker.
subroutine biggest_object
More information about the flang-commits
mailing list