[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
Mon Aug 31 06:57:29 PDT 2026
https://github.com/jeanPerier created https://github.com/llvm/llvm-project/pull/219976
Currently, semantics does not enforce the overall byte sizes when of common block and equivalence storage when building the related symbols and computing offsets for members. The size and offset silently overflow, leading to computing inconsistent offsets in lowering and aborting compilation with internal errors like "error: 'hlfir.declare' op storage offset exceeds the storage size".
This patch limits the storage of such objects to what can fit a signed 64 bit integer (like gfortran/ifx/classic flang).
Assisted-by: AI
>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] [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
More information about the flang-commits
mailing list