[flang-commits] [flang] [Flang][NFCI] Use abstraction for binary scalar data (PR #212956)

Michael Kruse via flang-commits flang-commits at lists.llvm.org
Mon Aug 3 03:14:10 PDT 2026


https://github.com/Meinersbur updated https://github.com/llvm/llvm-project/pull/212956

>From 189c3f40b4bd5ff64de45faeeac3e90c185e7b7a Mon Sep 17 00:00:00 2001
From: Michael Kruse <llvm-project at meinersbur.de>
Date: Thu, 30 Jul 2026 09:56:36 +0200
Subject: [PATCH 1/6] [Flang] Use abstraction for binary scalar data

---
 .../include/flang/Evaluate/character-value.h  | 71 +++++++++++++++++++
 flang/include/flang/Evaluate/complex.h        | 27 +++++++
 flang/include/flang/Evaluate/initial-image.h  | 46 +++++++-----
 flang/include/flang/Evaluate/integer.h        | 27 +++++++
 flang/include/flang/Evaluate/logical.h        | 17 +++++
 flang/include/flang/Evaluate/real.h           | 17 +++++
 flang/lib/Evaluate/character.h                | 29 ++++++++
 flang/lib/Evaluate/host.h                     | 17 ++---
 flang/lib/Evaluate/initial-image.cpp          | 38 +++++-----
 9 files changed, 238 insertions(+), 51 deletions(-)
 create mode 100644 flang/include/flang/Evaluate/character-value.h

diff --git a/flang/include/flang/Evaluate/character-value.h b/flang/include/flang/Evaluate/character-value.h
new file mode 100644
index 0000000000000..79feef0406064
--- /dev/null
+++ b/flang/include/flang/Evaluate/character-value.h
@@ -0,0 +1,71 @@
+//===-- include/flang/Evaluate/character-tools.h ----------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef FORTRAN_EVALUATE_CHARACTER_TOOLS_H_
+#define FORTRAN_EVALUATE_CHARACTER_TOOLS_H_
+
+#include "flang/Evaluate/type.h"
+#include <string>
+
+namespace Fortran::evaluate {
+
+/// Simple wrapper around a std::string/std:u16string/std::u32string
+template <int KIND> class CharacterValue {
+  using Character = Scalar<Type<TypeCategory::Character, KIND>>;
+  using CharT = typename Character::value_type;
+
+public:
+  CLASS_BOILERPLATE(CharacterValue)
+  CharacterValue(const Character &v) : word_(v) {}
+  CharacterValue(Character &&v) : word_(std::move(v)) {}
+
+  /// Returns the number of characters stored; not the number of bytes
+  auto size() const { return word_.size(); }
+
+  /// Reads a string of characters from \p raw. \p is the number of bytes to
+  /// read; must be a multiple of the size of a single character.
+  static Character FromRawBytes(const void *raw, std::size_t size) {
+    CHECK(size % sizeof(CharT) == 0);
+    Character s;
+    if (size > 0) {
+      s.assign(static_cast<const CharT *>(raw), size / sizeof(CharT));
+    }
+    return s;
+  }
+
+  /// Writes a string of characters to \p dst. \o is the the number of bytes to
+  /// be written; must be a multiple of the size of a single character.  If \p s
+  /// is smaller that \p size, the rest of the memory is set to spaces. If \p s
+  /// is shorter than size, only the first characters are written.
+  /// If \p changes points to bool, it will be set to true if any bytes at \p
+  /// dst have changed.
+  void StoreRawBytes(void *dst, std::size_t size, bool *changed = nullptr) {
+    CHECK(size % sizeof(CharT) == 0);
+    if (size > 0) {
+      std::size_t payloadSize{std::min(size, sizeof(CharT) * word_.size())};
+      std::size_t padSize{size - payloadSize};
+
+      Character strWithPadding{word_};
+      strWithPadding.append(padSize / sizeof(CharT), ' ');
+
+      if (changed) {
+        if (std::memcmp(dst, strWithPadding.data(), size) == 0) {
+          return;
+        }
+        *changed = true;
+      }
+      std::memcpy(dst, strWithPadding.data(), size);
+    }
+  }
+
+private:
+  Character word_;
+};
+
+} // namespace Fortran::evaluate
+#endif // FORTRAN_EVALUATE_CHARACTER_TOOLS_H_
diff --git a/flang/include/flang/Evaluate/complex.h b/flang/include/flang/Evaluate/complex.h
index 9781db9a25a64..f68726e60f4d0 100644
--- a/flang/include/flang/Evaluate/complex.h
+++ b/flang/include/flang/Evaluate/complex.h
@@ -98,6 +98,33 @@ template <typename REAL_TYPE> class Complex {
   std::string DumpHexadecimal() const;
   llvm::raw_ostream &AsFortran(llvm::raw_ostream &, int kind) const;
 
+  /// Number of bytes that FromRawBytes/StoreRawBytes would accesses.
+  /// Note that this can be different from `sizeof(*this)` because of padding
+  /// the compiler may introduce between the real and imageinary part.
+  /// FromRawBytes/StoreRawBytes assume a packed layout, without padding.
+  constexpr static std::size_t bytesStored() { return 2 * Part::bytesStored(); }
+
+  /// De-serializes a complex from \p raw. \p expectedSize must match the the
+  /// number of bytes to be read.
+  static Complex FromRawBytes(const void *raw, std::size_t expectedSize) {
+    CHECK(bytesStored() == expectedSize);
+    const char *data{static_cast<const char *>(raw)};
+    Part realPart{Part ::FromRawBytes(data, Part::bytesStored())};
+    Part imagPart{
+        Part::FromRawBytes(data + Part::bytesStored(), Part::bytesStored())};
+    return {realPart, imagPart};
+  }
+
+  /// Serializes this complex to \p dst. \p expectedSize must match the the
+  /// number of bytes to be written. If \p changed points to a boolean, it will
+  /// be set to true if any bytes at \p dst have changed.
+  void StoreRawBytes(void *dst, size_t expectedSize, bool *changed) const {
+    CHECK(expectedSize == bytesStored());
+    re_.StoreRawBytes(dst, Part::bytesStored(), changed);
+    im_.StoreRawBytes(static_cast<char *>(dst) + Part::bytesStored(),
+        Part::bytesStored(), changed);
+  }
+
   // TODO: unit testing
 
 private:
diff --git a/flang/include/flang/Evaluate/initial-image.h b/flang/include/flang/Evaluate/initial-image.h
index 9a767db95f6c6..2b4c89239887d 100644
--- a/flang/include/flang/Evaluate/initial-image.h
+++ b/flang/include/flang/Evaluate/initial-image.h
@@ -14,12 +14,30 @@
 // initializer for a symbol.
 
 #include "expression.h"
+#include "flang/Evaluate/character-value.h"
 #include <map>
 #include <optional>
 #include <vector>
 
 namespace Fortran::evaluate {
 
+template <typename SCALAR>
+inline void StoreSerialValues(char *dst, llvm::ArrayRef<SCALAR> values,
+    size_t elementSize, bool *changed = nullptr) {
+  for (auto [i, v] : llvm::enumerate(values)) {
+    v.StoreRawBytes(dst + i * elementSize, elementSize, changed);
+  }
+}
+
+template <typename SCALAR>
+inline void LoadSerialValues(
+    const char *src, llvm::MutableArrayRef<SCALAR> values, size_t stride) {
+  for (auto it : llvm::enumerate(values)) {
+    it.value() =
+        SCALAR::FromRawBytes(src + stride * it.index(), SCALAR::bytesStored());
+  }
+}
+
 class InitialImage {
 public:
   enum Result {
@@ -56,14 +74,10 @@ class InitialImage {
         return OkNoChange;
       } else {
         // TODO endianness
-        auto *to{&data_.at(offset)};
-        const auto *from{&x.values().at(0)};
-        if (std::memcmp(to, from, bytes) == 0) {
-          return OkNoChange;
-        } else {
-          std::memcpy(to, from, bytes);
-          return Ok;
-        }
+        bool changed{false};
+        StoreSerialValues<Scalar<T>>(&data_.at(offset),
+            llvm::ArrayRef<Scalar<T>>(x.values()), *elementBytes, &changed);
+        return changed ? Ok : OkNoChange;
       }
     }
   }
@@ -87,23 +101,17 @@ class InitialImage {
       } else {
         Result result{OkNoChange};
         for (auto at{x.lbounds()}; elements-- > 0; x.IncrementSubscripts(at)) {
-          auto scalar{x.At(at)}; // this is a std string; size() in chars
+          CharacterValue<KIND> scalar{x.At(at)};
           auto scalarBytes{scalar.size() * KIND};
           if (scalarBytes != elementBytes) {
             result = LengthMismatch;
           }
-          // Blank padding when short
-          for (; scalarBytes < elementBytes; scalarBytes += KIND) {
-            scalar += ' ';
-          }
           // TODO endianness
           auto *to{&data_.at(offset)};
-          const auto *from{scalar.data()};
-          if (std::memcmp(to, from, elementBytes) != 0) {
-            std::memcpy(to, from, elementBytes);
-            if (result == OkNoChange) {
-              result = Ok;
-            }
+          bool changed{false};
+          scalar.StoreRawBytes(to, elementBytes, &changed);
+          if (changed && result == OkNoChange) {
+            result = Ok;
           }
           offset += elementBytes;
         }
diff --git a/flang/include/flang/Evaluate/integer.h b/flang/include/flang/Evaluate/integer.h
index 5953fc81cb111..2b237d19adcac 100644
--- a/flang/include/flang/Evaluate/integer.h
+++ b/flang/include/flang/Evaluate/integer.h
@@ -1017,6 +1017,33 @@ class Integer {
     return result;
   }
 
+  /// Number of bytes that FromRawBytes/StoreRawBytes would accesses.
+  static constexpr std::size_t bytesStored() { return sizeof(Integer{}); }
+
+  /// De-serializes an integer from \p raw. \p expectedSize must match the the
+  /// number of bytes to be read.
+  static Integer FromRawBytes(const void *raw, std::size_t expectedSize) {
+    CHECK(expectedSize == bytesStored());
+    Integer result;
+    std::memcpy(&result, raw, bytesStored());
+    return result;
+  }
+
+  /// Serializes this integer to \p dst. \p expectedSize must match the the
+  /// number of bytes to be written. If \p changed points to a boolean, it will
+  /// be set to true if any bytes at \p dst have changed.
+  void StoreRawBytes(
+      void *dst, size_t expectedSize, bool *changed = nullptr) const {
+    CHECK(expectedSize == bytesStored());
+    if (changed) {
+      if (std::memcmp(dst, this, bytesStored()) == 0) {
+        return;
+      }
+      *changed = true;
+    }
+    std::memcpy(dst, this, bytesStored());
+  }
+
 private:
   // A private constructor, selected by the use of nullptr,
   // that is used by member functions when it would be a waste
diff --git a/flang/include/flang/Evaluate/logical.h b/flang/include/flang/Evaluate/logical.h
index 5996853215e30..174c2070a1fa8 100644
--- a/flang/include/flang/Evaluate/logical.h
+++ b/flang/include/flang/Evaluate/logical.h
@@ -93,6 +93,23 @@ template <int BITS, bool IS_LIKE_C = true> class Logical {
     return {word_.IEOR(that.word_)};
   }
 
+  /// Number of bytes that FromRawBytes/StoreRawBytes would accesses.
+  static constexpr std::size_t bytesStored() { return Word::bytesStored(); }
+
+  /// De-serializes a logical from \p raw. \p expectedSize must match the the
+  /// number of bytes to be read.
+  static Logical FromRawBytes(const void *raw, std::size_t expectedSize) {
+    return Logical{Word::FromRawBytes(raw, expectedSize)};
+  }
+
+  /// Serializes this logical to \p dst. \p expectedSize must match the the
+  /// number of bytes to be written. If \p changed points to a boolean, it will
+  /// be set to true if any bytes at \p dst have changed.
+  void StoreRawBytes(
+      void *dst, size_t expectedSize, bool *changed = nullptr) const {
+    word_.StoreRawBytes(dst, expectedSize, changed);
+  }
+
 private:
   static constexpr Word canonicalTrue{IsLikeC ? 1 : -std::uint64_t{1}};
   static constexpr Word canonicalFalse{0};
diff --git a/flang/include/flang/Evaluate/real.h b/flang/include/flang/Evaluate/real.h
index 391d4e057f134..aa0311156649a 100644
--- a/flang/include/flang/Evaluate/real.h
+++ b/flang/include/flang/Evaluate/real.h
@@ -452,6 +452,23 @@ template <typename WORD, int PREC> class Real {
       llvm::raw_ostream &, int kind, bool minimal = false) const;
   std::string AsFortran(int kind, bool minimal = false) const;
 
+  /// Number of bytes that FromRawBytes/StoreRawBytes would accesses.
+  static constexpr std::size_t bytesStored() { return Word::bytesStored(); }
+
+  /// De-serializes a real from \p raw. \p expectedSize must match the the
+  /// number of bytes to be read.
+  static Real FromRawBytes(const void *raw, std::size_t expectedSize) {
+    return Real{Word::FromRawBytes(raw, expectedSize)};
+  }
+
+  /// Serializes this real to \p dst. \p expectedSize must match the the number
+  /// of bytes to be written. If \p changed points to a boolean, it will be set
+  /// to true if any bytes at \p dst have changed.
+  void StoreRawBytes(
+      void *dst, size_t expectedSize, bool *changed = nullptr) const {
+    word_.StoreRawBytes(dst, expectedSize, changed);
+  }
+
 private:
   using Significand = Integer<significandBits>; // no implicit bit
 
diff --git a/flang/lib/Evaluate/character.h b/flang/lib/Evaluate/character.h
index 2d6747741161b..d1c9df5afd89e 100644
--- a/flang/lib/Evaluate/character.h
+++ b/flang/lib/Evaluate/character.h
@@ -111,6 +111,35 @@ template <int KIND> class CharacterUtils {
     return str.substr(0, LEN_TRIM(str));
   }
 
+  static Character FromRawBytes(const void *raw, std::size_t size) {
+    CHECK(size % sizeof(CharT) == 0);
+    Character s;
+    if (size > 0) {
+      s.assign(static_cast<const CharT *>(raw), size / sizeof(CharT));
+    }
+    return s;
+  }
+
+  static void StoreRawBytes(void *dst, const Character &s, std::size_t size,
+      bool *changed = nullptr) {
+    CHECK(size % sizeof(CharT) == 0);
+    if (size > 0) {
+      std::size_t payloadSize{std::min(size, sizeof(CharT) * s.size())};
+      std::size_t padSize{size - payloadSize};
+
+      Character strWithPadding{s};
+      strWithPadding.append(padSize / sizeof(CharT), ' ');
+
+      if (changed) {
+        if (std::memcmp(dst, strWithPadding.data(), size) == 0) {
+          return;
+        }
+        *changed = true;
+      }
+      std::memcpy(dst, strWithPadding.data(), size);
+    }
+  }
+
 private:
   // Following helpers assume that character encodings contain ASCII
   static constexpr CharT Space() { return 0x20; }
diff --git a/flang/lib/Evaluate/host.h b/flang/lib/Evaluate/host.h
index 7f6bf76bb5c53..a28e1bbfea61d 100644
--- a/flang/lib/Evaluate/host.h
+++ b/flang/lib/Evaluate/host.h
@@ -73,13 +73,13 @@ template <typename FTN_T>
 inline constexpr Scalar<FTN_T> CastHostToFortran(const HostType<FTN_T> &x) {
   static_assert(HostTypeExists<FTN_T>());
   if constexpr (FTN_T::category == TypeCategory::Complex &&
-      sizeof(Scalar<FTN_T>) != sizeof(HostType<FTN_T>)) {
+      Scalar<FTN_T>::bytesStored() != sizeof(HostType<FTN_T>)) {
     // X87 is usually padded to 12 or 16bytes. Need to cast piecewise for
     // complex
     return Scalar<FTN_T>{CastHostToFortran<typename FTN_T::Part>(std::real(x)),
         CastHostToFortran<typename FTN_T::Part>(std::imag(x))};
   } else {
-    return *reinterpret_cast<const Scalar<FTN_T> *>(&x);
+    return Scalar<FTN_T>::FromRawBytes(&x, sizeof(x));
   }
 }
 
@@ -91,16 +91,11 @@ inline constexpr HostType<FTN_T> CastFortranToHost(const Scalar<FTN_T> &x) {
     using FortranPartType = typename FTN_T::Part;
     return HostType<FTN_T>{CastFortranToHost<FortranPartType>(x.REAL()),
         CastFortranToHost<FortranPartType>(x.AIMAG())};
-  } else if constexpr (std::is_same_v<FTN_T, Type<TypeCategory::Real, 10>>) {
-    // x87 80-bit floating-point occupies 16 bytes as a C "long double";
-    // copy the data to avoid a legitimate (but benign due to little-endianness)
-    // warning from GCC >= 11.2.0.
-    HostType<FTN_T> y;
-    std::memcpy(&y, &x, sizeof x);
-    return y;
   } else {
-    static_assert(sizeof x == sizeof(HostType<FTN_T>));
-    return *reinterpret_cast<const HostType<FTN_T> *>(&x);
+    CHECK(x.bytesStored() == sizeof(HostType<FTN_T>));
+    HostType<FTN_T> result;
+    x.StoreRawBytes(&result, sizeof(result));
+    return result;
   }
 }
 
diff --git a/flang/lib/Evaluate/initial-image.cpp b/flang/lib/Evaluate/initial-image.cpp
index 050c55e399b57..d922ae68fdbe1 100644
--- a/flang/lib/Evaluate/initial-image.cpp
+++ b/flang/lib/Evaluate/initial-image.cpp
@@ -164,6 +164,8 @@ class AsConstantHelper {
         using Char = typename Scalar::value_type;
         auto at{static_cast<std::size_t>(offset_ + j * stride)};
         auto chunk{length};
+        // FIXME: chunk is a number of characters, data_.size() is a number of
+        // bytes
         if (at + chunk > image_.data_.size()) {
           CHECK(padWithZero_);
           if (at >= image_.data_.size()) {
@@ -172,10 +174,8 @@ class AsConstantHelper {
             chunk = image_.data_.size() - at;
           }
         }
-        if (chunk > 0) {
-          const Char *data{reinterpret_cast<const Char *>(&image_.data_[at])};
-          typedValue[j].assign(data, chunk);
-        }
+        typedValue[j] = CharacterValue<T::kind>::FromRawBytes(
+            &image_.data_[at], chunk * T::kind);
         if (chunk < length && padWithZero_) {
           typedValue[j].append(length - chunk, Char{});
         }
@@ -184,23 +184,19 @@ class AsConstantHelper {
           Const{length, std::move(typedValue), std::move(extents_)});
     } else {
       // Lengthless intrinsic type
-      CHECK(sizeof(Scalar) <= stride);
-      for (std::size_t j{0}; j < elements; ++j) {
-        auto at{static_cast<std::size_t>(offset_ + j * stride)};
-        std::size_t chunk{sizeof(Scalar)};
-        if (at + chunk > image_.data_.size()) {
-          CHECK(padWithZero_);
-          if (at >= image_.data_.size()) {
-            chunk = 0;
-          } else {
-            chunk = image_.data_.size() - at;
-          }
-        }
-        // TODO endianness
-        if (chunk > 0) {
-          std::memcpy(&typedValue[j], &image_.data_[at], chunk);
-        }
-      }
+      // There is a test (Evaluate/folding10.f90) where this
+      // wants wants to read 2 elements of kind 8 out of an image_.data_ of
+      // size 12. Fortunately, the second element seems to be unused.
+      size_t scalarSize{evaluate::Scalar<T>::bytesStored()};
+      size_t length{std::min(elements,
+          (image_.data_.size() - offset_ - scalarSize + stride) / stride)};
+      CHECK(length == elements || padWithZero_);
+      // TODO endianness
+      LoadSerialValues(image_.data_.data() + offset_,
+          llvm::MutableArrayRef<evaluate::Scalar<T>>(typedValue)
+              .slice(0, length),
+          stride);
+
       return AsGenericExpr(Const{std::move(typedValue), std::move(extents_)});
     }
   }

>From 421fa81ad6f2900404f1ebf8c3455fbc7efb98ad Mon Sep 17 00:00:00 2001
From: Michael Kruse <llvm-project at meinersbur.de>
Date: Thu, 30 Jul 2026 13:44:56 +0200
Subject: [PATCH 2/6] Use static_assert

---
 flang/include/flang/Evaluate/complex.h | 3 ++-
 flang/lib/Evaluate/host.h              | 6 ++++--
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/flang/include/flang/Evaluate/complex.h b/flang/include/flang/Evaluate/complex.h
index f68726e60f4d0..beb3dddbda5d0 100644
--- a/flang/include/flang/Evaluate/complex.h
+++ b/flang/include/flang/Evaluate/complex.h
@@ -118,7 +118,8 @@ template <typename REAL_TYPE> class Complex {
   /// Serializes this complex to \p dst. \p expectedSize must match the the
   /// number of bytes to be written. If \p changed points to a boolean, it will
   /// be set to true if any bytes at \p dst have changed.
-  void StoreRawBytes(void *dst, size_t expectedSize, bool *changed) const {
+  void StoreRawBytes(
+      void *dst, size_t expectedSize, bool *changed = nullptr) const {
     CHECK(expectedSize == bytesStored());
     re_.StoreRawBytes(dst, Part::bytesStored(), changed);
     im_.StoreRawBytes(static_cast<char *>(dst) + Part::bytesStored(),
diff --git a/flang/lib/Evaluate/host.h b/flang/lib/Evaluate/host.h
index a28e1bbfea61d..e4a81dbd7e0e4 100644
--- a/flang/lib/Evaluate/host.h
+++ b/flang/lib/Evaluate/host.h
@@ -79,6 +79,7 @@ inline constexpr Scalar<FTN_T> CastHostToFortran(const HostType<FTN_T> &x) {
     return Scalar<FTN_T>{CastHostToFortran<typename FTN_T::Part>(std::real(x)),
         CastHostToFortran<typename FTN_T::Part>(std::imag(x))};
   } else {
+    static_assert(Scalar<FTN_T>::bytesStored() == sizeof(HostType<FTN_T>));
     return Scalar<FTN_T>::FromRawBytes(&x, sizeof(x));
   }
 }
@@ -87,12 +88,13 @@ inline constexpr Scalar<FTN_T> CastHostToFortran(const HostType<FTN_T> &x) {
 template <typename FTN_T>
 inline constexpr HostType<FTN_T> CastFortranToHost(const Scalar<FTN_T> &x) {
   static_assert(HostTypeExists<FTN_T>());
-  if constexpr (FTN_T::category == TypeCategory::Complex) {
+  if constexpr (FTN_T::category == TypeCategory::Complex &&
+      Scalar<FTN_T>::bytesStored() != sizeof(HostType<FTN_T>)) {
     using FortranPartType = typename FTN_T::Part;
     return HostType<FTN_T>{CastFortranToHost<FortranPartType>(x.REAL()),
         CastFortranToHost<FortranPartType>(x.AIMAG())};
   } else {
-    CHECK(x.bytesStored() == sizeof(HostType<FTN_T>));
+    static_assert(x.bytesStored() == sizeof(HostType<FTN_T>));
     HostType<FTN_T> result;
     x.StoreRawBytes(&result, sizeof(result));
     return result;

>From c1e4d84b651587472a2977fb5acae854c64b20cd Mon Sep 17 00:00:00 2001
From: Michael Kruse <llvm-project at meinersbur.de>
Date: Thu, 30 Jul 2026 14:28:22 +0200
Subject: [PATCH 3/6] Clearify REAL(10) and COMPLEX(10)

---
 flang/include/flang/Evaluate/complex.h | 4 +---
 flang/include/flang/Evaluate/real.h    | 2 ++
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/flang/include/flang/Evaluate/complex.h b/flang/include/flang/Evaluate/complex.h
index beb3dddbda5d0..9f86c6138ef0d 100644
--- a/flang/include/flang/Evaluate/complex.h
+++ b/flang/include/flang/Evaluate/complex.h
@@ -99,9 +99,7 @@ template <typename REAL_TYPE> class Complex {
   llvm::raw_ostream &AsFortran(llvm::raw_ostream &, int kind) const;
 
   /// Number of bytes that FromRawBytes/StoreRawBytes would accesses.
-  /// Note that this can be different from `sizeof(*this)` because of padding
-  /// the compiler may introduce between the real and imageinary part.
-  /// FromRawBytes/StoreRawBytes assume a packed layout, without padding.
+  /// Note that for COMPLEX(10), this is 32.
   constexpr static std::size_t bytesStored() { return 2 * Part::bytesStored(); }
 
   /// De-serializes a complex from \p raw. \p expectedSize must match the the
diff --git a/flang/include/flang/Evaluate/real.h b/flang/include/flang/Evaluate/real.h
index aa0311156649a..1ae274eed982f 100644
--- a/flang/include/flang/Evaluate/real.h
+++ b/flang/include/flang/Evaluate/real.h
@@ -453,6 +453,8 @@ template <typename WORD, int PREC> class Real {
   std::string AsFortran(int kind, bool minimal = false) const;
 
   /// Number of bytes that FromRawBytes/StoreRawBytes would accesses.
+  /// Note that for REAL(10), this is 16 because X87IntegerContainer specifies
+  /// an alignment of 16 bytes which adds 6 unitialized bytes of limbs.
   static constexpr std::size_t bytesStored() { return Word::bytesStored(); }
 
   /// De-serializes a real from \p raw. \p expectedSize must match the the

>From c1fa61a9a1eb872439a21550d4926efb5f4a387a Mon Sep 17 00:00:00 2001
From: Michael Kruse <llvm-project at meinersbur.de>
Date: Thu, 30 Jul 2026 14:43:21 +0200
Subject: [PATCH 4/6] Satisfy ARM Premerge-CI

---
 flang/lib/Evaluate/host.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/flang/lib/Evaluate/host.h b/flang/lib/Evaluate/host.h
index e4a81dbd7e0e4..e032b28ab8a3d 100644
--- a/flang/lib/Evaluate/host.h
+++ b/flang/lib/Evaluate/host.h
@@ -94,7 +94,7 @@ inline constexpr HostType<FTN_T> CastFortranToHost(const Scalar<FTN_T> &x) {
     return HostType<FTN_T>{CastFortranToHost<FortranPartType>(x.REAL()),
         CastFortranToHost<FortranPartType>(x.AIMAG())};
   } else {
-    static_assert(x.bytesStored() == sizeof(HostType<FTN_T>));
+    static_assert(Scalar<FTN_T>::bytesStored() == sizeof(HostType<FTN_T>));
     HostType<FTN_T> result;
     x.StoreRawBytes(&result, sizeof(result));
     return result;

>From 4d98378adfafba0adf5ebf31fe21a5d721112a1e Mon Sep 17 00:00:00 2001
From: Michael Kruse <llvm-project at meinersbur.de>
Date: Mon, 3 Aug 2026 10:59:08 +0200
Subject: [PATCH 5/6] Fix bounds access, explicit zero-pad

---
 flang/include/flang/Evaluate/complex.h        |  2 +-
 flang/lib/Evaluate/initial-image.cpp          | 61 ++++++++--------
 flang/test/Evaluate/fold-transfer-partial.f90 | 71 +++++++++++++++++++
 3 files changed, 104 insertions(+), 30 deletions(-)
 create mode 100644 flang/test/Evaluate/fold-transfer-partial.f90

diff --git a/flang/include/flang/Evaluate/complex.h b/flang/include/flang/Evaluate/complex.h
index 9f86c6138ef0d..6efc96fbe8372 100644
--- a/flang/include/flang/Evaluate/complex.h
+++ b/flang/include/flang/Evaluate/complex.h
@@ -107,7 +107,7 @@ template <typename REAL_TYPE> class Complex {
   static Complex FromRawBytes(const void *raw, std::size_t expectedSize) {
     CHECK(bytesStored() == expectedSize);
     const char *data{static_cast<const char *>(raw)};
-    Part realPart{Part ::FromRawBytes(data, Part::bytesStored())};
+    Part realPart{Part::FromRawBytes(data, Part::bytesStored())};
     Part imagPart{
         Part::FromRawBytes(data + Part::bytesStored(), Part::bytesStored())};
     return {realPart, imagPart};
diff --git a/flang/lib/Evaluate/initial-image.cpp b/flang/lib/Evaluate/initial-image.cpp
index d922ae68fdbe1..f3334388f2db7 100644
--- a/flang/lib/Evaluate/initial-image.cpp
+++ b/flang/lib/Evaluate/initial-image.cpp
@@ -160,48 +160,51 @@ class AsConstantHelper {
           Const{derived, std::move(typedValue), std::move(extents_)});
     } else if constexpr (T::category == TypeCategory::Character) {
       auto length{static_cast<ConstantSubscript>(stride) / T::kind};
+      llvm::SmallVector<char, 256> buffer;
+      const char *data{GetTailPaddedData(offset_, elements * stride, buffer)};
       for (std::size_t j{0}; j < elements; ++j) {
-        using Char = typename Scalar::value_type;
-        auto at{static_cast<std::size_t>(offset_ + j * stride)};
-        auto chunk{length};
-        // FIXME: chunk is a number of characters, data_.size() is a number of
-        // bytes
-        if (at + chunk > image_.data_.size()) {
-          CHECK(padWithZero_);
-          if (at >= image_.data_.size()) {
-            chunk = 0;
-          } else {
-            chunk = image_.data_.size() - at;
-          }
-        }
         typedValue[j] = CharacterValue<T::kind>::FromRawBytes(
-            &image_.data_[at], chunk * T::kind);
-        if (chunk < length && padWithZero_) {
-          typedValue[j].append(length - chunk, Char{});
-        }
+            data + j * stride, length * T::kind);
       }
       return AsGenericExpr(
           Const{length, std::move(typedValue), std::move(extents_)});
     } else {
       // Lengthless intrinsic type
-      // There is a test (Evaluate/folding10.f90) where this
-      // wants wants to read 2 elements of kind 8 out of an image_.data_ of
-      // size 12. Fortunately, the second element seems to be unused.
-      size_t scalarSize{evaluate::Scalar<T>::bytesStored()};
-      size_t length{std::min(elements,
-          (image_.data_.size() - offset_ - scalarSize + stride) / stride)};
-      CHECK(length == elements || padWithZero_);
+      llvm::SmallVector<char, 256> buffer;
+      const char *data{GetTailPaddedData(offset_,
+          elements == 0
+              ? 0
+              : (elements - 1) * stride + evaluate::Scalar<T>::bytesStored(),
+          buffer)};
       // TODO endianness
-      LoadSerialValues(image_.data_.data() + offset_,
-          llvm::MutableArrayRef<evaluate::Scalar<T>>(typedValue)
-              .slice(0, length),
-          stride);
-
+      LoadSerialValues(
+          data, llvm::MutableArrayRef<evaluate::Scalar<T>>(typedValue), stride);
       return AsGenericExpr(Const{std::move(typedValue), std::move(extents_)});
     }
   }
 
 private:
+  /// Returns the image's bytes, extended with zero bytes when a value is being
+  /// built whose representation reaches past the end of the image.  That
+  /// happens when TRANSFER() is folded with a MOLD= whose representation is
+  /// longer than SOURCE=, and when deserializing a scalar accesses more bytes
+  /// than its element size because its host representation is padded (e.g.,
+  /// REAL(10)).  F2023 16.9.212 leaves the bytes beyond SOURCE= processor
+  /// dependent; flang zero-fills them, as the runtime does.
+  const char *GetTailPaddedData(std::size_t offset, std::size_t bytes,
+      llvm::SmallVectorImpl<char> &buffer) const {
+    if (bytes + offset <= image_.data_.size()) {
+      // If no padding is needed, use original data without copy
+      return image_.data_.data() + offset;
+    }
+    buffer.assign(bytes, 0);
+    if (offset < image_.data_.size()) {
+      std::memcpy(buffer.data(), image_.data_.data() + offset,
+          image_.data_.size() - offset);
+    }
+    return buffer.data();
+  }
+
   FoldingContext &context_;
   const DynamicType &type_;
   std::optional<std::int64_t> charLength_;
diff --git a/flang/test/Evaluate/fold-transfer-partial.f90 b/flang/test/Evaluate/fold-transfer-partial.f90
new file mode 100644
index 0000000000000..4e01635641c71
--- /dev/null
+++ b/flang/test/Evaluate/fold-transfer-partial.f90
@@ -0,0 +1,71 @@
+! RUN: %python %S/test_folding.py %s %flang_fc1
+! Tests folding of TRANSFER(...) when the physical representation of the
+! result is longer than that of SOURCE.  F2023 16.9.212 p.5 requires the
+! leading part of the result's physical representation to be that of
+! SOURCE, and requires TRANSFER(TRANSFER(E, D), E) to have the value of E
+! for scalar D and E (and likewise TRANSFER(TRANSFER(E, D), E, SIZE(E))
+! when D is an array and E has rank one); the Examples paragraph's
+! Case (ii) shows a trailing array element only partially covered by
+! SOURCE.  The remainder of the result beyond SOURCE's representation is
+! processor dependent; flang zero-fills it (as already pinned for
+! CHARACTER by fold-transfer.f90's test_i2c_s).
+! Same-size and mold-shorter values are covered by fold-transfer.f90;
+! this file pins the mold-longer cases.  All checks are byte-order
+! independent: the round trips prove the leading-part byte placement,
+! and the two-endian .or. checks (idiom precedent: fold-transfer.f90's
+! test_c2i_s) are portable value/zero-fill pins, not placement proofs.
+
+module m
+  ! Scalar MOLD longer than SOURCE: round trips (16.9.212 p.5), ...
+  logical, parameter :: test_rt_scalar = transfer(transfer(1_4, 0_8), 0_4) == 1_4
+  logical, parameter :: test_rt_neg = transfer(transfer(-1_4, 0_8), 0_4) == -1_4
+  logical, parameter :: test_rt_real = transfer(transfer(1.5, 0._8), 0.0) == 1.5
+  ! ... and a portable leading-part + zero-fill value pin (either
+  ! byte order's correct value; placement is proven by the round trips)
+  integer(8), parameter :: w1 = transfer(1_4, 0_8)
+  logical, parameter :: test_lead_zfill = w1 == 1_8 .or. w1 == 4294967296_8
+
+  ! Rank-one results whose trailing element is only partially covered
+  ! by SOURCE, with and without SIZE=
+  integer(8), parameter :: via8(2) = transfer([1_4, 2_4, 3_4], 0_8, 2)
+  logical, parameter :: test_rt_array = all(transfer(via8, 0_4, 3) == [1_4, 2_4, 3_4])
+  logical, parameter :: test_elem2_zfill = via8(2) == 3_8 .or. via8(2) == 12884901888_8
+  integer(8), parameter :: via8b(*) = transfer([1_4, 2_4, 3_4], [0_8])
+  logical, parameter :: test_rt_array2 = all(transfer(via8b, 0_4, 3) == [1_4, 2_4, 3_4])
+  real(8), parameter :: rvia8(2) = transfer([1.5, 2.5, 3.5], 0._8, 2)
+  logical, parameter :: test_rt_real_arr = all(transfer(rvia8, 0.0, 3) == [1.5, 2.5, 3.5])
+
+  ! The standard's own Case (ii) example (16.9.212 p.6): the second
+  ! element's real part has the value 3.3; its imaginary part is
+  ! processor dependent
+  complex, parameter :: cx(2) = transfer([1.1, 2.2, 3.3], [(0.0, 0.0)])
+  logical, parameter :: test_case_ii = cx(1) == (1.1, 2.2) .and. real(cx(2)) == 3.3
+
+  ! Derived-type MOLD longer than SOURCE: the leading part is preserved
+  ! (observed portably via round trips); components at or beyond the end
+  ! of SOURCE's representation are zero-filled
+  type t1
+    integer(8) :: a, b
+  end type
+  type(t1), parameter :: x1 = transfer([1_4, 2_4, 3_4], t1(0, 0)) ! b partially covered
+  logical, parameter :: test_derived_rt = all(transfer(x1, 0_4, 3) == [1_4, 2_4, 3_4])
+  type(t1), parameter :: x2 = transfer(7_4, t1(-1, -1)) ! a partial, b wholly beyond
+  logical, parameter :: test_derived_lead = transfer(x2, 0_4) == 7_4
+  logical, parameter :: test_derived_zero = x2%b == 0_8
+  type(t1), parameter :: x4 = transfer(1_8, t1(-1, -1)) ! b exactly at the end
+  logical, parameter :: test_at_end = x4%a == 1_8 .and. x4%b == 0_8
+  type t2
+    integer(4) :: x
+    integer(4) :: y ! keeps c beyond a 4-byte SOURCE even where integer(8) has 4-byte alignment
+    integer(8) :: c(4) ! wholly beyond SOURCE's representation
+  end type
+  type(t2), parameter :: x3 = transfer(9_4, t2(0, 0, [0_8, 0_8, 0_8, 0_8]))
+  logical, parameter :: test_beyond = x3%x == 9_4 .and. x3%y == 0_4 .and. all(x3%c == 0_8)
+
+  ! CHARACTER MOLD with elements beyond SOURCE: NUL fill
+  character(1), parameter :: ch(50) = transfer(1_8, 'x', 50)
+  logical, parameter :: test_char_rt = transfer(ch(1:8), 0_8) == 1_8
+  logical, parameter :: test_char_zero = ichar(ch(9)) == 0 .and. ichar(ch(50)) == 0
+  character(8), parameter :: c8 = transfer('AB', 'xxxxxxxx')
+  logical, parameter :: test_char_scalar = c8(1:2) == 'AB' .and. ichar(c8(3:3)) == 0 .and. ichar(c8(8:8)) == 0
+end module

>From 357f861a7cd91f3025154d6d42c4a14564b26907 Mon Sep 17 00:00:00 2001
From: Michael Kruse <llvm-project at meinersbur.de>
Date: Mon, 3 Aug 2026 12:12:13 +0200
Subject: [PATCH 6/6] Add [[maybe_unused]]

---
 flang/include/flang/Evaluate/complex.h |  7 ++++---
 flang/include/flang/Evaluate/integer.h |  7 ++++---
 flang/include/flang/Evaluate/logical.h |  7 ++++---
 flang/include/flang/Evaluate/real.h    |  7 ++++---
 flang/lib/Evaluate/character.h         | 29 --------------------------
 5 files changed, 16 insertions(+), 41 deletions(-)

diff --git a/flang/include/flang/Evaluate/complex.h b/flang/include/flang/Evaluate/complex.h
index 6efc96fbe8372..cb5b8be603c5c 100644
--- a/flang/include/flang/Evaluate/complex.h
+++ b/flang/include/flang/Evaluate/complex.h
@@ -104,7 +104,8 @@ template <typename REAL_TYPE> class Complex {
 
   /// De-serializes a complex from \p raw. \p expectedSize must match the the
   /// number of bytes to be read.
-  static Complex FromRawBytes(const void *raw, std::size_t expectedSize) {
+  static Complex FromRawBytes(
+      const void *raw, [[maybe_unused]] std::size_t expectedSize) {
     CHECK(bytesStored() == expectedSize);
     const char *data{static_cast<const char *>(raw)};
     Part realPart{Part::FromRawBytes(data, Part::bytesStored())};
@@ -116,8 +117,8 @@ template <typename REAL_TYPE> class Complex {
   /// Serializes this complex to \p dst. \p expectedSize must match the the
   /// number of bytes to be written. If \p changed points to a boolean, it will
   /// be set to true if any bytes at \p dst have changed.
-  void StoreRawBytes(
-      void *dst, size_t expectedSize, bool *changed = nullptr) const {
+  void StoreRawBytes(void *dst, [[maybe_unused]] size_t expectedSize,
+      bool *changed = nullptr) const {
     CHECK(expectedSize == bytesStored());
     re_.StoreRawBytes(dst, Part::bytesStored(), changed);
     im_.StoreRawBytes(static_cast<char *>(dst) + Part::bytesStored(),
diff --git a/flang/include/flang/Evaluate/integer.h b/flang/include/flang/Evaluate/integer.h
index 2b237d19adcac..fade8cbcc114f 100644
--- a/flang/include/flang/Evaluate/integer.h
+++ b/flang/include/flang/Evaluate/integer.h
@@ -1022,7 +1022,8 @@ class Integer {
 
   /// De-serializes an integer from \p raw. \p expectedSize must match the the
   /// number of bytes to be read.
-  static Integer FromRawBytes(const void *raw, std::size_t expectedSize) {
+  static Integer FromRawBytes(
+      const void *raw, [[maybe_unused]] std::size_t expectedSize) {
     CHECK(expectedSize == bytesStored());
     Integer result;
     std::memcpy(&result, raw, bytesStored());
@@ -1032,8 +1033,8 @@ class Integer {
   /// Serializes this integer to \p dst. \p expectedSize must match the the
   /// number of bytes to be written. If \p changed points to a boolean, it will
   /// be set to true if any bytes at \p dst have changed.
-  void StoreRawBytes(
-      void *dst, size_t expectedSize, bool *changed = nullptr) const {
+  void StoreRawBytes(void *dst, [[maybe_unused]] size_t expectedSize,
+      bool *changed = nullptr) const {
     CHECK(expectedSize == bytesStored());
     if (changed) {
       if (std::memcmp(dst, this, bytesStored()) == 0) {
diff --git a/flang/include/flang/Evaluate/logical.h b/flang/include/flang/Evaluate/logical.h
index 174c2070a1fa8..eff9674e279ff 100644
--- a/flang/include/flang/Evaluate/logical.h
+++ b/flang/include/flang/Evaluate/logical.h
@@ -98,15 +98,16 @@ template <int BITS, bool IS_LIKE_C = true> class Logical {
 
   /// De-serializes a logical from \p raw. \p expectedSize must match the the
   /// number of bytes to be read.
-  static Logical FromRawBytes(const void *raw, std::size_t expectedSize) {
+  static Logical FromRawBytes(
+      const void *raw, [[maybe_unused]] std::size_t expectedSize) {
     return Logical{Word::FromRawBytes(raw, expectedSize)};
   }
 
   /// Serializes this logical to \p dst. \p expectedSize must match the the
   /// number of bytes to be written. If \p changed points to a boolean, it will
   /// be set to true if any bytes at \p dst have changed.
-  void StoreRawBytes(
-      void *dst, size_t expectedSize, bool *changed = nullptr) const {
+  void StoreRawBytes(void *dst, [[maybe_unused]] size_t expectedSize,
+      bool *changed = nullptr) const {
     word_.StoreRawBytes(dst, expectedSize, changed);
   }
 
diff --git a/flang/include/flang/Evaluate/real.h b/flang/include/flang/Evaluate/real.h
index 1ae274eed982f..4db851734ebb2 100644
--- a/flang/include/flang/Evaluate/real.h
+++ b/flang/include/flang/Evaluate/real.h
@@ -459,15 +459,16 @@ template <typename WORD, int PREC> class Real {
 
   /// De-serializes a real from \p raw. \p expectedSize must match the the
   /// number of bytes to be read.
-  static Real FromRawBytes(const void *raw, std::size_t expectedSize) {
+  static Real FromRawBytes(
+      const void *raw, [[maybe_unused]] std::size_t expectedSize) {
     return Real{Word::FromRawBytes(raw, expectedSize)};
   }
 
   /// Serializes this real to \p dst. \p expectedSize must match the the number
   /// of bytes to be written. If \p changed points to a boolean, it will be set
   /// to true if any bytes at \p dst have changed.
-  void StoreRawBytes(
-      void *dst, size_t expectedSize, bool *changed = nullptr) const {
+  void StoreRawBytes(void *dst, [[maybe_unused]] size_t expectedSize,
+      bool *changed = nullptr) const {
     word_.StoreRawBytes(dst, expectedSize, changed);
   }
 
diff --git a/flang/lib/Evaluate/character.h b/flang/lib/Evaluate/character.h
index d1c9df5afd89e..2d6747741161b 100644
--- a/flang/lib/Evaluate/character.h
+++ b/flang/lib/Evaluate/character.h
@@ -111,35 +111,6 @@ template <int KIND> class CharacterUtils {
     return str.substr(0, LEN_TRIM(str));
   }
 
-  static Character FromRawBytes(const void *raw, std::size_t size) {
-    CHECK(size % sizeof(CharT) == 0);
-    Character s;
-    if (size > 0) {
-      s.assign(static_cast<const CharT *>(raw), size / sizeof(CharT));
-    }
-    return s;
-  }
-
-  static void StoreRawBytes(void *dst, const Character &s, std::size_t size,
-      bool *changed = nullptr) {
-    CHECK(size % sizeof(CharT) == 0);
-    if (size > 0) {
-      std::size_t payloadSize{std::min(size, sizeof(CharT) * s.size())};
-      std::size_t padSize{size - payloadSize};
-
-      Character strWithPadding{s};
-      strWithPadding.append(padSize / sizeof(CharT), ' ');
-
-      if (changed) {
-        if (std::memcmp(dst, strWithPadding.data(), size) == 0) {
-          return;
-        }
-        *changed = true;
-      }
-      std::memcpy(dst, strWithPadding.data(), size);
-    }
-  }
-
 private:
   // Following helpers assume that character encodings contain ASCII
   static constexpr CharT Space() { return 0x20; }



More information about the flang-commits mailing list