[flang-commits] [flang] [Flang][NFCI] Use abstraction for binary scalar data (PR #212956)
Michael Kruse via flang-commits
flang-commits at lists.llvm.org
Thu Jul 30 01:32:59 PDT 2026
https://github.com/Meinersbur created https://github.com/llvm/llvm-project/pull/212956
There is currently the assumption that the binary representation of the scalar data classes (Integer, Real, Complex, Logical) is identical to the binary representation of native types. For instance `Integer<64>` can be reinterpreted casted to a `int64_t` and can be serialized using a `memcpy`. This will not be the case anymore with #206907. This first PR introduces `LoadRawBytes` and `StoreRawBytes` abstractions that can be adapted when the binary data layout of the scalar data classes change. No functional change intended.
The binary representation is assumed for these uses:
1. Data serialization in initial-image.h/.cpp
2. Calling native math functions to constant-fold functions such as `sin` in initial-image.cpp. An abstraction layer has been created in host.h/host.cpp to convert between host-native types and the scalar data classes.
>From a4d1a098d0f7c2b667d2f4572c277797b49ba732 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] [Flang] Use abstraction for binary scalar data
---
.../flang}/Evaluate/character.h | 29 ++++++++++++
flang/include/flang/Evaluate/complex.h | 18 ++++++++
flang/include/flang/Evaluate/initial-image.h | 45 +++++++++++--------
flang/include/flang/Evaluate/integer.h | 21 +++++++++
flang/include/flang/Evaluate/logical.h | 11 +++++
flang/include/flang/Evaluate/real.h | 11 +++++
flang/lib/Evaluate/fold-implementation.h | 2 +-
flang/lib/Evaluate/host.h | 17 +++----
flang/lib/Evaluate/initial-image.cpp | 38 +++++++---------
9 files changed, 141 insertions(+), 51 deletions(-)
rename flang/{lib => include/flang}/Evaluate/character.h (82%)
diff --git a/flang/lib/Evaluate/character.h b/flang/include/flang/Evaluate/character.h
similarity index 82%
rename from flang/lib/Evaluate/character.h
rename to flang/include/flang/Evaluate/character.h
index 2d6747741161b..d1c9df5afd89e 100644
--- a/flang/lib/Evaluate/character.h
+++ b/flang/include/flang/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/include/flang/Evaluate/complex.h b/flang/include/flang/Evaluate/complex.h
index 9781db9a25a64..aeb4e43db3d66 100644
--- a/flang/include/flang/Evaluate/complex.h
+++ b/flang/include/flang/Evaluate/complex.h
@@ -98,6 +98,24 @@ template <typename REAL_TYPE> class Complex {
std::string DumpHexadecimal() const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &, int kind) const;
+ constexpr static std::size_t bytesStored() { return 2 * Part::bytesStored(); }
+
+ 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};
+ }
+
+ 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..bdaff49b8403e 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.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;
}
}
}
@@ -92,18 +106,13 @@ class InitialImage {
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};
+ CharacterUtils<KIND>::StoreRawBytes(
+ to, scalar, 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..75c6ae80da7a1 100644
--- a/flang/include/flang/Evaluate/integer.h
+++ b/flang/include/flang/Evaluate/integer.h
@@ -1017,6 +1017,27 @@ class Integer {
return result;
}
+ static constexpr std::size_t bytesStored() { return sizeof(Integer{}); }
+
+ static Integer FromRawBytes(const void *raw, std::size_t expectedSize) {
+ CHECK(expectedSize == bytesStored());
+ Integer result;
+ std::memcpy(&result, raw, expectedSize);
+ return result;
+ }
+
+ void StoreRawBytes(
+ void *dst, size_t expectedSize, bool *changed = nullptr) const {
+ CHECK(expectedSize == bytesStored());
+ if (changed) {
+ if (std::memcmp(dst, this, expectedSize) == 0) {
+ return;
+ }
+ *changed = true;
+ }
+ std::memcpy(dst, this, expectedSize);
+ }
+
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..62ef53a5b2207 100644
--- a/flang/include/flang/Evaluate/logical.h
+++ b/flang/include/flang/Evaluate/logical.h
@@ -93,6 +93,17 @@ template <int BITS, bool IS_LIKE_C = true> class Logical {
return {word_.IEOR(that.word_)};
}
+ static constexpr std::size_t bytesStored() { return Word::bytesStored(); }
+
+ static Logical FromRawBytes(const void *raw, std::size_t expectedSize) {
+ return Logical{Word::FromRawBytes(raw, expectedSize)};
+ }
+
+ 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..5aaea2cc5e88d 100644
--- a/flang/include/flang/Evaluate/real.h
+++ b/flang/include/flang/Evaluate/real.h
@@ -452,6 +452,17 @@ 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;
+ static constexpr std::size_t bytesStored() { return Word::bytesStored(); }
+
+ static Real FromRawBytes(const void *raw, std::size_t expectedSize) {
+ return Real{Word::FromRawBytes(raw, expectedSize)};
+ }
+
+ 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/fold-implementation.h b/flang/lib/Evaluate/fold-implementation.h
index 0ffcd99c6bc47..712563bf1cfed 100644
--- a/flang/lib/Evaluate/fold-implementation.h
+++ b/flang/lib/Evaluate/fold-implementation.h
@@ -9,12 +9,12 @@
#ifndef FORTRAN_EVALUATE_FOLD_IMPLEMENTATION_H_
#define FORTRAN_EVALUATE_FOLD_IMPLEMENTATION_H_
-#include "character.h"
#include "host.h"
#include "int-power.h"
#include "flang/Common/indirection.h"
#include "flang/Common/template.h"
#include "flang/Common/unwrap.h"
+#include "flang/Evaluate/character.h"
#include "flang/Evaluate/characteristics.h"
#include "flang/Evaluate/common.h"
#include "flang/Evaluate/constant.h"
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..88f55b393badf 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] = CharacterUtils<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_)});
}
}
More information about the flang-commits
mailing list