[flang-commits] [flang] 2e30c8e - [Flang][NFCI] Use abstraction for binary scalar data (#212956)
via flang-commits
flang-commits at lists.llvm.org
Mon Sep 7 03:28:15 PDT 2026
Author: Michael Kruse
Date: 2026-09-07T12:28:07+02:00
New Revision: 2e30c8e267256e3a2b43985d9be68831c412a1b5
URL: https://github.com/llvm/llvm-project/commit/2e30c8e267256e3a2b43985d9be68831c412a1b5
DIFF: https://github.com/llvm/llvm-project/commit/2e30c8e267256e3a2b43985d9be68831c412a1b5.diff
LOG: [Flang][NFCI] Use abstraction for binary scalar data (#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 reinterpret-casted to a `int64_t` or serialized using `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 intrinsics-library.cpp. An abstraction layer has been created
in host.h/host.cpp to convert between host-native types and the scalar
data classes.
Some implementation notes:
* I originally used `CharacterUtils` but it is in a private header file.
Instead I created `CharacterValue` which similar to what #216960 will
introduce to allow a consistent interface between the scalar value
classes (such as `kind()` returning the current runtime kind)
* `bytesStored` is another such common interface. It it meant to
resemble `llvm::DataLayout::getTypeStoreSize` (there is also
`getTypeAllocSize`, `llvm::Type::getPrimitiveSizeInBits()`,
`llvm::Type::getScalarSizeInBits()`) and must match
`common::TypeSizeInBytes`.
* `Real<10>` uses 16-byte alignment regardless what the host compiler
thinks what `long double` should be. The padding is unitialized data in
`X87IntegerContainer`. Even though x87 is the classic example where
"allocation size" and "store size" would be different, Flang does not
differentiate between them and treats all bytes also as stored bytes,
see `common::TypeSizeInBytes`. As a consequence, `Complex<10>` is 32
bytes. initial-image.h/.cpp will even load/store/compare that
uninitialized data. host.h/host.cpp instead reads/writes the floats
separately to translate to whatever the host compiler does.
* initial-image.cpp/.h is inconsistent when it uses number of bytes and
when it uses number of characters
* For the case the image does not have sufficient bytes to read, I did
not want to insert checks whether another byte can be read into every
ReadRawBytes function, and instead add zero-padding explicitly when
necessary.
Assisted-by: AI
Added:
flang/include/flang/Evaluate/char.h
Modified:
flang/include/flang/Evaluate/complex.h
flang/include/flang/Evaluate/initial-image.h
flang/include/flang/Evaluate/integer.h
flang/include/flang/Evaluate/logical.h
flang/include/flang/Evaluate/real.h
flang/lib/Evaluate/host.h
flang/lib/Evaluate/initial-image.cpp
Removed:
################################################################################
diff --git a/flang/include/flang/Evaluate/char.h b/flang/include/flang/Evaluate/char.h
new file mode 100644
index 0000000000000..75db8d35c0cf3
--- /dev/null
+++ b/flang/include/flang/Evaluate/char.h
@@ -0,0 +1,88 @@
+//===-- include/flang/Evaluate/char.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_CHAR_H_
+#define FORTRAN_EVALUATE_CHAR_H_
+
+#include "flang/Evaluate/type.h"
+#include <string>
+
+namespace Fortran::evaluate::value {
+
+/// Simple wrapper around a std::string/std:u16string/std::u32string
+template <int KIND> class Character {
+ using Word = Scalar<Type<TypeCategory::Character, KIND>>;
+ using CharT = typename Word::value_type;
+
+public:
+ // rule-of-five
+ ~Character() = default;
+ Character(const Character &v) : word_(v) {}
+ Character(Character &&v) : word_(std::move(v)) {}
+ Character &operator=(const Character &v) {
+ word_ = v.word_;
+ return &this;
+ }
+ Character &operator=(Character &&v) {
+ word_ = std::move(v.word_);
+ return *this;
+ }
+
+ // ctors
+ Character() = default;
+ Character(const Word &v) : word_(v) {}
+ Character(Word &&v) : word_(std::move(v)) {}
+ Character &operator=(const Word &v) { word_ = v; }
+ Character &operator=(Word &&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 Word FromRawBytes(const void *raw, std::size_t size) {
+ CHECK(size % sizeof(CharT) == 0);
+ Word 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 the
+ /// string is smaller that \p size, the rest of the memory padded with spaces.
+ /// If the string 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};
+
+ // Pad with spaces
+ Word strWithPadding{word_};
+ strWithPadding.append(padSize / sizeof(CharT), static_cast<CharT>(' '));
+
+ if (changed) {
+ if (std::memcmp(dst, strWithPadding.data(), size) == 0) {
+ return;
+ }
+ *changed = true;
+ }
+ std::memcpy(dst, strWithPadding.data(), size);
+ }
+ }
+
+private:
+ Word word_;
+};
+
+} // namespace Fortran::evaluate::value
+#endif // FORTRAN_EVALUATE_CHAR_H_
diff --git a/flang/include/flang/Evaluate/complex.h b/flang/include/flang/Evaluate/complex.h
index 9781db9a25a64..cb5b8be603c5c 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 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
+ /// number of bytes to be read.
+ 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())};
+ 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, [[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(),
+ 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..4fa7b9307014b 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/char.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
+ typename value::Character<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..fade8cbcc114f 100644
--- a/flang/include/flang/Evaluate/integer.h
+++ b/flang/include/flang/Evaluate/integer.h
@@ -1017,6 +1017,34 @@ 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, [[maybe_unused]] 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, [[maybe_unused]] 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..eff9674e279ff 100644
--- a/flang/include/flang/Evaluate/logical.h
+++ b/flang/include/flang/Evaluate/logical.h
@@ -93,6 +93,24 @@ 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, [[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, [[maybe_unused]] 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..4db851734ebb2 100644
--- a/flang/include/flang/Evaluate/real.h
+++ b/flang/include/flang/Evaluate/real.h
@@ -452,6 +452,26 @@ 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.
+ /// 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
+ /// number of bytes to be read.
+ 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, [[maybe_unused]] 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/host.h b/flang/lib/Evaluate/host.h
index 7f6bf76bb5c53..e032b28ab8a3d 100644
--- a/flang/lib/Evaluate/host.h
+++ b/flang/lib/Evaluate/host.h
@@ -73,13 +73,14 @@ 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);
+ static_assert(Scalar<FTN_T>::bytesStored() == sizeof(HostType<FTN_T>));
+ return Scalar<FTN_T>::FromRawBytes(&x, sizeof(x));
}
}
@@ -87,20 +88,16 @@ 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 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);
+ static_assert(Scalar<FTN_T>::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..5da8018b3b3b5 100644
--- a/flang/lib/Evaluate/initial-image.cpp
+++ b/flang/lib/Evaluate/initial-image.cpp
@@ -160,52 +160,52 @@ 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};
- if (at + chunk > image_.data_.size()) {
- CHECK(padWithZero_);
- if (at >= image_.data_.size()) {
- chunk = 0;
- } else {
- chunk = image_.data_.size() - at;
- }
- }
- if (chunk > 0) {
- const Char *data{reinterpret_cast<const Char *>(&image_.data_[at])};
- typedValue[j].assign(data, chunk);
- }
- if (chunk < length && padWithZero_) {
- typedValue[j].append(length - chunk, Char{});
- }
+ typedValue[j] = value::Character<T::kind>::FromRawBytes(
+ data + j * stride, length * T::kind);
}
return AsGenericExpr(
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);
- }
- }
+ llvm::SmallVector<char, 256> buffer;
+ const char *data{GetTailPaddedData(offset_,
+ elements == 0
+ ? 0
+ : (elements - 1) * stride + evaluate::Scalar<T>::bytesStored(),
+ buffer)};
+ // TODO endianness
+ 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;
+ }
+ CHECK(padWithZero_);
+ 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_;
More information about the flang-commits
mailing list