[flang-commits] [flang] [flang] Build the initial image of equivalence groups in target byte order (PR #228833)

via flang-commits flang-commits at lists.llvm.org
Sat Oct 3 23:09:59 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-flang-semantics

@llvm/pr-subscribers-flang-fir-hlfir

Author: MMS IT GmbH (mms-it-ch)

<details>
<summary>Changes</summary>

`InitialImage` serialized numeric values in host byte order and read them back the same way (three `// TODO endianness`). When the storage of an initialized equivalence group is typed differently from the initialized member, e.g. CHARACTER data over an INTEGER array, the global was emitted in host byte order: compiling on x86-64 for a big-endian target produced `"7654"` instead of `"4567"`. Details in #<!-- -->228832.

This
- sets `TargetCharacteristics::isBigEndian` from the target triple (it kept the host value; this also affects the existing use for substrings of CHARACTER literals with KIND > 1),
- adds an optional swap unit to `StoreSerialValues`/`LoadSerialValues` and uses it in `InitialImage::Add` and `AsConstant` when the byte orders of target and host differ: per value, per part for COMPLEX, per character for CHARACTER with KIND > 1.

Nothing changes when target and host have the same byte order. The new test uses `powerpc64-unknown-linux-gnu` as a big-endian target.

Tests: the same change, written against the code before `StoreRawBytes`/`FromRawBytes` (memcpy), is in my branch based on 85ac560262 with an s390x/z/OS target: the test passes there (with `-triple s390x-ibm-zos`), the gfortran test `arrayio_7.f90` now passes on z/OS 3.1, and the NIST FORTRAN 77 suite still gives the host output. This version for `main` I could only check with `clang++ -fsyntax-only` (including `data-to-inits.cpp`, which instantiates `Add` for all types); I don't have a PowerPC build, so the new test runs in CI.

Fixes #<!-- -->228832.

Assisted-by: Claude Code (Anthropic)

🤖 Generated with [Claude Code](https://claude.com/claude-code)


---
Full diff: https://github.com/llvm/llvm-project/pull/228833.diff


4 Files Affected:

- (modified) flang/include/flang/Evaluate/initial-image.h (+77-11) 
- (modified) flang/include/flang/Tools/TargetSetup.h (+2) 
- (modified) flang/lib/Evaluate/initial-image.cpp (+14-5) 
- (added) flang/test/Lower/equivalence-init-big-endian.f90 (+23) 


``````````diff
diff --git a/flang/include/flang/Evaluate/initial-image.h b/flang/include/flang/Evaluate/initial-image.h
index 4fa7b9307014b07..dad1091605b4f2c 100644
--- a/flang/include/flang/Evaluate/initial-image.h
+++ b/flang/include/flang/Evaluate/initial-image.h
@@ -15,26 +15,63 @@
 
 #include "expression.h"
 #include "flang/Evaluate/char.h"
+#include "llvm/ADT/SmallVector.h"
+#include <algorithm>
+#include <cstring>
 #include <map>
 #include <optional>
 #include <vector>
 
 namespace Fortran::evaluate {
 
+/// Reverses the bytes of each \p unit sized piece of \p p[0..bytes).
+inline void ReverseByteUnits(char *p, std::size_t bytes, std::size_t unit) {
+  if (unit > 1) {
+    for (std::size_t j{0}; j + unit <= bytes; j += unit) {
+      std::reverse(p + j, p + j + unit);
+    }
+  }
+}
+
+/// Serializes \p values to \p dst. If \p swapUnit is greater than one, the
+/// bytes of each \p swapUnit sized piece are reversed (host to target byte
+/// order).
 template <typename SCALAR>
 inline void StoreSerialValues(char *dst, llvm::ArrayRef<SCALAR> values,
-    size_t elementSize, bool *changed = nullptr) {
+    size_t elementSize, bool *changed = nullptr, size_t swapUnit = 0) {
   for (auto [i, v] : llvm::enumerate(values)) {
-    v.StoreRawBytes(dst + i * elementSize, elementSize, changed);
+    char *to{dst + i * elementSize};
+    if (swapUnit > 1) {
+      llvm::SmallVector<char, 32> buffer(elementSize);
+      v.StoreRawBytes(buffer.data(), elementSize);
+      ReverseByteUnits(buffer.data(), elementSize, swapUnit);
+      if (changed) {
+        if (std::memcmp(to, buffer.data(), elementSize) == 0) {
+          continue;
+        }
+        *changed = true;
+      }
+      std::memcpy(to, buffer.data(), elementSize);
+    } else {
+      v.StoreRawBytes(to, elementSize, changed);
+    }
   }
 }
 
+/// De-serializes \p values from \p src; \p swapUnit as for
+/// StoreSerialValues (target to host byte order).
 template <typename SCALAR>
-inline void LoadSerialValues(
-    const char *src, llvm::MutableArrayRef<SCALAR> values, size_t stride) {
+inline void LoadSerialValues(const char *src,
+    llvm::MutableArrayRef<SCALAR> values, size_t stride, size_t swapUnit = 0) {
   for (auto it : llvm::enumerate(values)) {
-    it.value() =
-        SCALAR::FromRawBytes(src + stride * it.index(), SCALAR::bytesStored());
+    const char *from{src + stride * it.index()};
+    if (swapUnit > 1) {
+      llvm::SmallVector<char, 32> buffer(from, from + SCALAR::bytesStored());
+      ReverseByteUnits(buffer.data(), buffer.size(), swapUnit);
+      it.value() = SCALAR::FromRawBytes(buffer.data(), SCALAR::bytesStored());
+    } else {
+      it.value() = SCALAR::FromRawBytes(from, SCALAR::bytesStored());
+    }
   }
 }
 
@@ -55,6 +92,23 @@ class InitialImage {
 
   std::size_t size() const { return data_.size(); }
 
+  /// The image holds the bytes of the values in the byte order of the target.
+  /// Returns the size of the pieces whose bytes have to be reversed when the
+  /// byte orders of the target and the host differ, or 0.
+  template <typename T>
+  static std::size_t ByteSwapUnit(
+      const FoldingContext &context, std::size_t elementBytes) {
+    if (context.targetCharacteristics().isBigEndian() != isHostLittleEndian) {
+      return 0; // same byte order
+    } else if constexpr (T::category == TypeCategory::Character) {
+      return T::kind;
+    } else if constexpr (T::category == TypeCategory::Complex) {
+      return elementBytes / 2; // real and imaginary parts
+    } else {
+      return elementBytes;
+    }
+  }
+
   template <typename A>
   Result Add(ConstantSubscript, std::size_t, const A &, FoldingContext &) {
     return NotAConstant;
@@ -73,10 +127,10 @@ class InitialImage {
       } else if (bytes == 0) {
         return OkNoChange;
       } else {
-        // TODO endianness
         bool changed{false};
         StoreSerialValues<Scalar<T>>(&data_.at(offset),
-            llvm::ArrayRef<Scalar<T>>(x.values()), *elementBytes, &changed);
+            llvm::ArrayRef<Scalar<T>>(x.values()), *elementBytes, &changed,
+            ByteSwapUnit<T>(context, *elementBytes));
         return changed ? Ok : OkNoChange;
       }
     }
@@ -84,7 +138,7 @@ class InitialImage {
   template <int KIND>
   Result Add(ConstantSubscript offset, std::size_t bytes,
       const Constant<Type<TypeCategory::Character, KIND>> &x,
-      FoldingContext &) {
+      FoldingContext &context) {
     if (offset < 0 || offset + bytes > data_.size()) {
       return OutOfRange;
     } else {
@@ -106,10 +160,22 @@ class InitialImage {
           if (scalarBytes != elementBytes) {
             result = LengthMismatch;
           }
-          // TODO endianness
           auto *to{&data_.at(offset)};
           bool changed{false};
-          scalar.StoreRawBytes(to, elementBytes, &changed);
+          if (std::size_t unit{
+                  ByteSwapUnit<Type<TypeCategory::Character, KIND>>(
+                      context, elementBytes)};
+              unit > 1) {
+            llvm::SmallVector<char, 64> buffer(elementBytes);
+            scalar.StoreRawBytes(buffer.data(), elementBytes);
+            ReverseByteUnits(buffer.data(), elementBytes, unit);
+            if (std::memcmp(to, buffer.data(), elementBytes) != 0) {
+              std::memcpy(to, buffer.data(), elementBytes);
+              changed = true;
+            }
+          } else {
+            scalar.StoreRawBytes(to, elementBytes, &changed);
+          }
           if (changed && result == OkNoChange) {
             result = Ok;
           }
diff --git a/flang/include/flang/Tools/TargetSetup.h b/flang/include/flang/Tools/TargetSetup.h
index 47f886141b002a2..995163904dcb092 100644
--- a/flang/include/flang/Tools/TargetSetup.h
+++ b/flang/include/flang/Tools/TargetSetup.h
@@ -24,6 +24,8 @@ namespace Fortran::tools {
 
   const llvm::Triple &targetTriple{targetMachine.getTargetTriple()};
 
+  targetCharacteristics.set_isBigEndian(!targetTriple.isLittleEndian());
+
   if (targetTriple.getArch() == llvm::Triple::ArchType::x86_64) {
     targetCharacteristics.set_hasSubnormalFlushingControl(/*kind=*/3);
     targetCharacteristics.set_hasSubnormalFlushingControl(/*kind=*/4);
diff --git a/flang/lib/Evaluate/initial-image.cpp b/flang/lib/Evaluate/initial-image.cpp
index 5da8018b3b3b598..d207736dc76f441 100644
--- a/flang/lib/Evaluate/initial-image.cpp
+++ b/flang/lib/Evaluate/initial-image.cpp
@@ -162,9 +162,18 @@ class AsConstantHelper {
       auto length{static_cast<ConstantSubscript>(stride) / T::kind};
       llvm::SmallVector<char, 256> buffer;
       const char *data{GetTailPaddedData(offset_, elements * stride, buffer)};
+      std::size_t unit{InitialImage::ByteSwapUnit<T>(context_, stride)};
       for (std::size_t j{0}; j < elements; ++j) {
-        typedValue[j] = value::Character<T::kind>::FromRawBytes(
-            data + j * stride, length * T::kind);
+        if (unit > 1) {
+          llvm::SmallVector<char, 64> chars(
+              data + j * stride, data + j * stride + length * T::kind);
+          ReverseByteUnits(chars.data(), chars.size(), unit);
+          typedValue[j] = value::Character<T::kind>::FromRawBytes(
+              chars.data(), length * T::kind);
+        } else {
+          typedValue[j] = value::Character<T::kind>::FromRawBytes(
+              data + j * stride, length * T::kind);
+        }
       }
       return AsGenericExpr(
           Const{length, std::move(typedValue), std::move(extents_)});
@@ -176,9 +185,9 @@ class AsConstantHelper {
               ? 0
               : (elements - 1) * stride + evaluate::Scalar<T>::bytesStored(),
           buffer)};
-      // TODO endianness
-      LoadSerialValues(
-          data, llvm::MutableArrayRef<evaluate::Scalar<T>>(typedValue), stride);
+      LoadSerialValues(data,
+          llvm::MutableArrayRef<evaluate::Scalar<T>>(typedValue), stride,
+          InitialImage::ByteSwapUnit<T>(context_, stride));
       return AsGenericExpr(Const{std::move(typedValue), std::move(extents_)});
     }
   }
diff --git a/flang/test/Lower/equivalence-init-big-endian.f90 b/flang/test/Lower/equivalence-init-big-endian.f90
new file mode 100755
index 000000000000000..3b7387b290d0874
--- /dev/null
+++ b/flang/test/Lower/equivalence-init-big-endian.f90
@@ -0,0 +1,23 @@
+! The initial image of an equivalence group holds the bytes in the byte order
+! of the target, also when compiling for a big-endian target on a
+! little-endian host (and vice versa).
+! REQUIRES: powerpc-registered-target
+! RUN: %flang_fc1 -triple powerpc64-unknown-linux-gnu -emit-llvm %s -o - | FileCheck %s
+
+subroutine chars_over_integers()
+  character*4, dimension(2) :: c = (/"0123", "4567"/)
+  integer :: k(2)
+  equivalence (k, c)
+  print *, c(2), k(1)
+end subroutine
+! "0123" = 0x30313233, "4567" = 0x34353637 in big-endian memory
+! CHECK: @_QFchars_over_integersEc = internal global [2 x i32] [i32 808530483, i32 875902519]
+
+subroutine integer_over_chars()
+  integer :: k = 1
+  character*4 :: c
+  equivalence (k, c)
+  print *, c, k
+end subroutine
+! The integer is the storage type here: no second byte swap on the way back.
+! CHECK: @_QFinteger_over_charsEc = internal global [1 x i32] [i32 1]

``````````

</details>


https://github.com/llvm/llvm-project/pull/228833


More information about the flang-commits mailing list