[llvm-branch-commits] [flang] [llvm] [Flang] Introduce *Value classes with unittests (PR #216958)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Tue Aug 25 01:10:54 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-flang-semantics

Author: Michael Kruse (Meinersbur)

<details>
<summary>Changes</summary>

Add the classes IntegerValue, LogicalValue, RealValue, ComplexValue, CharacterValues. In contrast to the existing classes, the are not template-dependent, but remember which data kind they currently store at runtime. Their intended use is making most of Flang's templates indepdendent of KIND.

Adding unittests for every of their methods as well. Until the KIND-detemplatization, the *Value classes are only used by these unittests.


### Implementation notes

 * The original classes holding scalar constant values (`Integer`, `Real`, `Complex`, `Logical`, `std::basic_string<>`) are heavily template-dependent. They represent the value of a constant. With the code not being instantiated per-KIND anymore, the value must keep enough storage for any possible value. In Clang/LLVM/MLIR this is done with `APInt` and `APFloat`, in this PR it is a `std::variant` of the original scalar scalar.

| Original Scalar Class | KIND-Independent Class | Implementation                                     | pImpl class        |
|---------------|------------------------|----------------------------------------------------|--------------------|
| Integer       | IntegerValue           | `std::variant<I8,I16,I32,I64,I10,I80,I128>`          | IntegerValueImpl   |
| Logical       | LogicalValue           | `IntegerValue`                                       |                    |
| Real          | RealValue              | `std::variant<R2,R3,R4,R8,R10,R16>`                  | RealValueImpl      |
| Complex       | ComplexValue           | `RealValue re,im`                                    |                    |
|               | CharacterValue         | `std::variant<std::string,std::u16string,std::u32string>` | CharacterValueImpl |


 * The `std::variant` also has a "monostate" state which represents a default-initialized value of unknown kind (e.g. `Integer` is default-initialized to zero). To be able to replace `Integer`, `IntegerValue` also needs to default-initializable for uses such as `std::vector<IntegerValue>` to be possible.

 * There is no original class representing values for variable of type CHARACTER. Instead, they have been representing compile-time dependent by `std::string`, `std::u16string`,`std::u32string` and handled with static methods in `CharacterUtils` in `character.h`. This makes it impossible to add a common interface such as a `kind()` method. This PR introduces `CharacterValue` as a value storage for CHARACTER.

 * The endianness customization of the original scalar classes is unused: `IS_LITTLE_ENDIAN` always follows the host's endian format. That's a problem for `initial-image.cpp/.h` which should follow the target's endian. It contains a "TODO endianness". By the design of it, it seems that the idea once was that the binary representation of the scalars should be identical to what it would be on the target machine. This is incompatible with using reinterpret_cast in `host.h`. Even when not considering endianness, this is not true because x87 floating points are aligned to 16 bytes, but some targets align it to 4 bytes only. I think the better approach is to always use host-native alignment, and convert endianness in dedicated data load/store routines only. This allows using the host's native instructions set, and is also what Clang does with `llvm::APInt`, and every networking code I know of (`htonj`, `ntohl`, ...).

 * The scalar classes are all used in `Evaluate/type.h` which is the central header for FortranEvaluate and therefore included into almost everything except maybe the FortranParser. To reduce the build time cost, the implementation details of the scalar classes are hidden using a pImpl-like idiom. Instead of a pointer to a heap-allocated object, it uses a reinterpret_cast of the object itself. For this work the sizes of both objects must be identical. It is determined ahead of the build using the `object-size-probe` executable.

 * The new unittests all use GTest and are intended to subsume the non-GTest unittests.

 * Many unittests compare the result against the same operation computed using the compiler's native type. However, `__int128` is not available with the msvc compiler and the (preexisting) `unit128.h` is used instead. It had to be extended a bit to support all required operations, in particular conversion overloads and `std::numeric_limits`.

 * The new `typekind-traits.h` is used the in lieu of `Type<CAT,KIND>` for translating the Fortran-specific type to the compiler-native type for the unittests. Since the equivalent compiler-native type depends on the bitwidth, it keeps the KIND argument. It is reused for the same purpose in #<!-- -->216960 (translating the binary representation for Fortran's `transfer` function and calling native math functions for constant folding). See also #<!-- -->212956.


### Potential Followup-Work

 * The monostate of the scalar classes could be removed again if we compromise to a default kind. For now I did not want to add such implicit assumptions that would requires a closer review rather than mechanical correctness.

 * IntegerValueImpl could be replaces with APInt and RealValueImpl with APFloat. I don't see the point why Flang should maintain its own arbitrary precision integer/floating point library, if LLVM already has one.


Assisted-by: AI (Claude, ChatGPT, Composer, Grok)

---

Patch is 366.26 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/216958.diff


32 Files Affected:

- (modified) flang/include/flang/Common/template.h (+27) 
- (modified) flang/include/flang/Common/type-kinds.h (+6) 
- (modified) flang/include/flang/Common/uint128.h (+81-16) 
- (added) flang/include/flang/Evaluate/character-value.h (+250) 
- (added) flang/include/flang/Evaluate/complex-value.h (+179) 
- (added) flang/include/flang/Evaluate/integer-value.h (+373) 
- (modified) flang/include/flang/Evaluate/integer.h (+6-2) 
- (added) flang/include/flang/Evaluate/logical-value.h (+166) 
- (added) flang/include/flang/Evaluate/object-sizes.h (+84) 
- (added) flang/include/flang/Evaluate/real-value.h (+261) 
- (added) flang/include/flang/Evaluate/typekind-traits.h (+192) 
- (modified) flang/lib/Evaluate/CMakeLists.txt (+8) 
- (added) flang/lib/Evaluate/character-value-impl.cpp (+615) 
- (added) flang/lib/Evaluate/character-value-impl.h (+256) 
- (added) flang/lib/Evaluate/character-value.cpp (+221) 
- (added) flang/lib/Evaluate/complex-value.cpp (+185) 
- (added) flang/lib/Evaluate/integer-value-impl.cpp (+609) 
- (added) flang/lib/Evaluate/integer-value-impl.h (+331) 
- (added) flang/lib/Evaluate/integer-value.cpp (+315) 
- (added) flang/lib/Evaluate/logical-value.cpp (+33) 
- (added) flang/lib/Evaluate/real-value-impl.cpp (+574) 
- (added) flang/lib/Evaluate/real-value-impl.h (+272) 
- (added) flang/lib/Evaluate/real-value.cpp (+271) 
- (modified) flang/tools/CMakeLists.txt (+1) 
- (added) flang/tools/object-size-probe/CMakeLists.txt (+42) 
- (added) flang/tools/object-size-probe/object-size-probe.cpp (+129) 
- (modified) flang/unittests/Evaluate/CMakeLists.txt (+16) 
- (added) flang/unittests/Evaluate/CharacterValueTest.cpp (+705) 
- (added) flang/unittests/Evaluate/ComplexValueTest.cpp (+402) 
- (added) flang/unittests/Evaluate/IntegerValueTest.cpp (+2352) 
- (added) flang/unittests/Evaluate/LogicalValueTest.cpp (+324) 
- (added) flang/unittests/Evaluate/RealValueTest.cpp (+1142) 


``````````diff
diff --git a/flang/include/flang/Common/template.h b/flang/include/flang/Common/template.h
index 6501994133759..593a8ba804a60 100644
--- a/flang/include/flang/Common/template.h
+++ b/flang/include/flang/Common/template.h
@@ -289,6 +289,33 @@ std::optional<R> MapOptional(R (*f)(A &&...), std::optional<A> &&...x) {
   return MapOptional(std::function<R(A && ...)>{f}, std::move(x)...);
 }
 
+template <typename Target, typename List> struct type_index;
+
+template <typename Target, template <typename...> class List, typename... Ts>
+struct type_index<Target, List<Ts...>> {
+private:
+  template <typename Current, typename... Rest>
+  static constexpr std::size_t find_index(std::size_t current_idx) {
+    if constexpr (std::is_same_v<Target, Current>) {
+      return current_idx;
+    } else if constexpr (sizeof...(Rest) > 0) {
+      return find_index<Rest...>(current_idx + 1);
+    } else {
+      static_assert(std::is_same_v<Target, Current>,
+          "Target type not found in type list!");
+      return 0;
+    }
+  }
+
+public:
+  static constexpr std::size_t value = find_index<Ts...>(0);
+};
+
+/// Get the index in an (typically variadic) template list. Eg.
+/// type_index_v<MyClass, std::tuple<char, int, MyClass, long>> == 2
+template <typename Target, typename List>
+inline constexpr std::size_t type_index_v = type_index<Target, List>::value;
+
 // Given a VISITOR class of the general form
 //   struct VISITOR {
 //     using Result = ...;
diff --git a/flang/include/flang/Common/type-kinds.h b/flang/include/flang/Common/type-kinds.h
index dd100b9dcd17e..926cce6f25909 100644
--- a/flang/include/flang/Common/type-kinds.h
+++ b/flang/include/flang/Common/type-kinds.h
@@ -22,6 +22,12 @@
 
 namespace Fortran::common {
 
+static constexpr int IntegerKinds[] FORTRAN_INTEGER_KINDS;
+static constexpr int UnsignedKinds[] FORTRAN_UNSIGNED_KINDS;
+static constexpr int RealKinds[] FORTRAN_REAL_KINDS;
+static constexpr int LogicalKinds[] FORTRAN_LOGICAL_KINDS;
+static constexpr int CharacterKinds[] FORTRAN_CHARACTER_KINDS;
+
 static constexpr int maxKind{16};
 
 template <typename T, std::size_t N>
diff --git a/flang/include/flang/Common/uint128.h b/flang/include/flang/Common/uint128.h
index c4bc4689a1eaa..f9f20acf6c456 100644
--- a/flang/include/flang/Common/uint128.h
+++ b/flang/include/flang/Common/uint128.h
@@ -22,30 +22,27 @@
 #include "api-attrs.h"
 #include "leading-zero-bit-count.h"
 #include <cstdint>
+#include <limits>
 #include <type_traits>
 
 namespace Fortran::common {
 
 template <bool IS_SIGNED = false> class Int128 {
+  friend class std::numeric_limits<Int128>;
+
 public:
   constexpr Int128() {}
   // This means of definition provides some portability for
   // "size_t" operands.
-  constexpr Int128(unsigned n) : low_{n} {}
-  constexpr Int128(unsigned long n) : low_{n} {}
-  constexpr Int128(unsigned long long n) : low_{n} {}
-  constexpr Int128(int n) {
-    low_ = static_cast<std::uint64_t>(n);
-    high_ = -static_cast<std::uint64_t>(n < 0);
-  }
-  constexpr Int128(long n) {
-    low_ = static_cast<std::uint64_t>(n);
-    high_ = -static_cast<std::uint64_t>(n < 0);
-  }
-  constexpr Int128(long long n) {
+  template <typename T,
+      typename = std::enable_if_t<std::is_integral_v<T> && sizeof(T) <= 8>>
+  constexpr Int128(T n) {
     low_ = static_cast<std::uint64_t>(n);
-    high_ = -static_cast<std::uint64_t>(n < 0);
+    if constexpr (std::is_signed_v<T>) {
+      high_ = -static_cast<std::uint64_t>(n < 0);
+    }
   }
+
   constexpr Int128(const Int128 &) = default;
   constexpr Int128(Int128 &&) = default;
   constexpr Int128 &operator=(const Int128 &) = default;
@@ -61,9 +58,12 @@ template <bool IS_SIGNED = false> class Int128 {
   constexpr Int128 operator-() const { return ~*this + 1; }
   constexpr bool operator!() const { return !low_ && !high_; }
   constexpr explicit operator bool() const { return low_ || high_; }
-  constexpr explicit operator std::uint64_t() const { return low_; }
-  constexpr explicit operator std::int64_t() const { return low_; }
-  constexpr explicit operator int() const { return static_cast<int>(low_); }
+
+  template <typename T,
+      typename = std::enable_if_t<std::is_integral_v<T> && sizeof(T) <= 8>>
+  constexpr explicit operator T() const {
+    return static_cast<T>(low_);
+  }
 
   constexpr std::uint64_t high() const { return high_; }
   constexpr std::uint64_t low() const { return low_; }
@@ -305,4 +305,69 @@ template <int BITS>
 using HostSignedIntType = typename HostSignedIntTypeHelper<BITS>::type;
 
 } // namespace Fortran::common
+
+namespace std {
+// Specializing std::numeric_limits is an intended extension point for
+// user-defined type.
+template <> class numeric_limits<Fortran::common::UnsignedInt128> {
+public:
+  using T = Fortran::common::UnsignedInt128;
+
+  static constexpr bool is_specialized{true};
+  static constexpr bool is_signed{false};
+  static constexpr bool is_integer{true};
+
+  static constexpr T min() { return T{0, 0}; }
+  static constexpr T max() { return T{UINT64_MAX, UINT64_MAX}; }
+};
+
+template <> class numeric_limits<Fortran::common::SignedInt128> {
+public:
+  using T = Fortran::common::SignedInt128;
+
+  static constexpr bool is_specialized{true};
+  static constexpr bool is_signed{true};
+  static constexpr bool is_integer{true};
+
+  static constexpr T min() {
+    return T{static_cast<std::uint64_t>(INT64_MIN), 0};
+  }
+  static constexpr T max() {
+    return T{static_cast<std::uint64_t>(INT64_MAX), UINT64_MAX};
+  }
+};
+
+#if defined(__SIZEOF_INT128__) && defined(_MSVC_STL_VERSION)
+// clang-cl knows __int128 and will be used for (u)int128_t, but the MSVC STL
+// does not define stl::numeric_limits for it.
+
+template <> class numeric_limits<unsigned __int128> {
+public:
+  using T = unsigned __int128;
+
+  static constexpr bool is_specialized{true};
+  static constexpr bool is_signed{false};
+  static constexpr bool is_integer{true};
+
+  static constexpr T min() { return static_cast<T>(0); }
+  static constexpr T max() { return ~static_cast<T>(0); }
+};
+
+template <> class numeric_limits<__int128> {
+public:
+  using T = __int128;
+
+  static constexpr bool is_specialized{true};
+  static constexpr bool is_signed{true};
+  static constexpr bool is_integer{true};
+
+  static constexpr T min() {
+    return static_cast<T>(static_cast<unsigned __int128>(1) << 127u);
+  }
+  static constexpr T max() {
+    return static_cast<T>(~(static_cast<unsigned __int128>(1) << 127u));
+  }
+};
+#endif
+} // namespace std
 #endif
diff --git a/flang/include/flang/Evaluate/character-value.h b/flang/include/flang/Evaluate/character-value.h
new file mode 100644
index 0000000000000..84fa95818c966
--- /dev/null
+++ b/flang/include/flang/Evaluate/character-value.h
@@ -0,0 +1,250 @@
+//===-- include/flang/Evaluate/character-value.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_VALUE_H_
+#define FORTRAN_EVALUATE_CHARACTER_VALUE_H_
+
+#include "flang/Evaluate/common.h"
+#include "flang/Evaluate/object-sizes.h"
+#include "flang/Evaluate/type.h"
+#include "llvm/Support/Compiler.h"
+#include "llvm/Support/raw_ostream.h"
+#include <cstddef>
+#include <iosfwd>
+#include <optional>
+#include <string>
+
+namespace Fortran::evaluate::value {
+class CharacterValueImpl;
+
+/// A character string with dynamic character representation with
+/// std::basic_string-like API.
+///
+/// The character type is dynamic between char, char16_t, and char32_t. As being
+/// able to represent all values, char32_t is used when passing single
+/// characters. It is also kind-aware, i.e. knows which CHARACTER kind it
+/// currently represents.
+///
+/// The implementation is hidden from this header using a pImpl-like idiom.
+class CharacterValue {
+public:
+  // rule-of-five
+  ~CharacterValue();
+  CharacterValue(const CharacterValue &);
+  CharacterValue(CharacterValue &&);
+  CharacterValue &operator=(const CharacterValue &);
+  CharacterValue &operator=(CharacterValue &&);
+
+  // ctors
+
+  /// A default-initialized CharacterValue is in a so-called "monostate"; it
+  /// represents an empty string, but its kind is not yet known. Not all
+  /// operations are supported in this state.
+  CharacterValue();
+
+  explicit CharacterValue(int kind, std::string s);
+  explicit CharacterValue(int kind, std::u16string s);
+  explicit CharacterValue(int kind, std::u32string s);
+
+  /// Fill constructor: create a string of n copies of the given character.
+  CharacterValue(int kind, std::size_t n, char32_t c);
+
+  // Named ctors
+  static CharacterValue Zero(int kind);
+
+  static CharacterValue FromRawBytes(
+      int kind, const void *raw, size_t byteSize);
+
+  void print(llvm::raw_ostream &os) const;
+
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+  LLVM_DUMP_METHOD void dump() const;
+#endif
+
+  /// Whether this object represents a default-initialized value (zero) of
+  /// not-yet-known kind.
+  bool IsMonostate() const;
+
+  /// The kind of the value currently stored.
+  int kind() const;
+
+  bool empty() const;
+  std::size_t size() const;
+  std::size_t length() const { return size(); }
+
+  /// Byte size of one character unit (1, 2, or 4).
+  std::size_t charSize() const { return kind(); }
+
+  /// Number of bytes accessed by FromRawBytes/StoreRawBytes
+  size_t bytesStored() const { return length() * charSize(); }
+
+  // Casting to other representations
+  std::optional<llvm::StringRef> AsStringRef() const;
+  std::optional<std::string> AsStdString() const {
+    if (auto str{AsStringRef()}) {
+      return str->str();
+    }
+    return std::nullopt;
+  }
+  std::optional<std::u16string> AsU16String() const;
+  std::optional<std::u32string> AsU32String() const;
+
+  /// Force conversion to Ascii even if this means loss of information
+  std::string ToStdString() const;
+
+  template <typename CharT, typename = std::void_t<std::basic_string<CharT>>>
+  std::optional<std::basic_string<CharT>> AsBasicString() const {
+    if constexpr (std::is_same_v<char, CharT>) {
+      return AsStdString();
+    } else if constexpr (std::is_same_v<char16_t, CharT>) {
+      return AsU16String();
+    } else if constexpr (std::is_same_v<char32_t, CharT>) {
+      return AsU32String();
+    } else {
+      static_assert(false, "Must be one of the supported character types");
+    }
+  }
+
+  // Comparisons
+  Ordering Compare(const CharacterValue &y) const;
+  bool operator<(const CharacterValue &y) const;
+  bool operator<=(const CharacterValue &y) const { return !(y < *this); }
+  bool operator==(const CharacterValue &y) const;
+  bool operator!=(const CharacterValue &y) const { return !(*this == y); }
+  bool operator>=(const CharacterValue &y) const { return !(*this < y); }
+  bool operator>(const CharacterValue &y) const { return y < *this; }
+
+  CharacterValue ToAscii(int kind) const;
+
+  /// Assign n copies of the given character, fixing the kind from the char
+  /// type.
+  void assign(int kind, std::size_t n, char32_t c);
+
+  /// Assign from a raw character pointer and length.
+  void assign(const char *p, std::size_t n);
+  void assign(const char16_t *p, std::size_t n);
+  void assign(const char32_t *p, std::size_t n);
+
+  /// Erase from position pos to end.
+  void erase(std::size_t pos);
+
+  /// Append n copies of the given character (widened to the stored type).
+  void append(std::size_t n, char32_t c);
+
+  /// Replace the substring [pos, pos+len) with characters from other.
+  CharacterValue &replace(
+      std::size_t pos, std::size_t len, const CharacterValue &other);
+
+  /// Return a suffix starting at pos.
+  CharacterValue substr(std::size_t pos) const;
+
+  /// Return a substring of len characters starting at pos.
+  CharacterValue substr(std::size_t pos, std::size_t len) const;
+
+  /// Reserve storage for at least n characters.
+  void reserve(std::size_t n);
+
+  /// Return the character at position i
+  char32_t operator[](std::size_t i) const;
+
+  /// Concatenate two same-kind strings.
+  CharacterValue operator+(const CharacterValue &y) const;
+
+  /// Append another same-kind string.
+  CharacterValue &operator+=(const CharacterValue &y);
+
+  /// Append a character, converting it to the string's element type.
+  CharacterValue &operator+=(char c);
+
+  /// Sentinel value for "not found" positions (same as std::string::npos).
+  static constexpr std::size_t npos{std::string::npos};
+
+  // Find-family methods; return npos when not found.
+  std::size_t find(const CharacterValue &pattern) const;
+  std::size_t rfind(const CharacterValue &pattern) const;
+  std::size_t find_first_of(const CharacterValue &set) const;
+  std::size_t find_last_of(const CharacterValue &set) const;
+  std::size_t find_first_not_of(char32_t c) const;
+  std::size_t find_last_not_of(char32_t c) const;
+  std::size_t find_first_not_of(const CharacterValue &set) const;
+  std::size_t find_last_not_of(const CharacterValue &set) const;
+
+  /// Raw byte pointer to the underlying character data
+  void *data();
+  const void *data() const;
+
+  /// Like data(), but pre-casted to char
+  char *charData() { return static_cast<char *>(data()); }
+  const char *charData() const { return static_cast<const char *>(data()); }
+
+  void *at(size_t pos) { return &charData()[pos * charSize()]; }
+  const void *at(size_t pos) const { return &charData()[pos * charSize()]; }
+
+  /// 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, size_t size, bool *changed = nullptr) const;
+
+  template <typename F>
+  static auto withCharProto(int kind, F &&f)
+      -> decltype(std::declval<F>()(std::declval<char>())) {
+    switch (kind) {
+    case 1:
+      return f(char{});
+    case 2:
+      return f(char16_t{});
+    case 4:
+      return f(char32_t{});
+    default:
+      llvm_unreachable("unsupported character kind/monostate");
+    }
+  }
+
+  template <typename F> decltype(auto) withStdString(F &&f) const {
+    switch (kind()) {
+    case 1:
+      return f(*AsStdString());
+    case 2:
+      return f(*AsU16String());
+    case 4:
+      return f(*AsU32String());
+    default:
+      llvm_unreachable("unsupported kind/monostate");
+    }
+  }
+
+private:
+  static CharacterValue FromImpl(const CharacterValueImpl &y);
+  static CharacterValue FromImpl(CharacterValueImpl &&y);
+
+  CharacterValueImpl &impl() {
+    return *reinterpret_cast<CharacterValueImpl *>(this);
+  }
+  const CharacterValueImpl &impl() const {
+    return *reinterpret_cast<const CharacterValueImpl *>(this);
+  }
+
+  [[maybe_unused]] alignas(
+      detail::kCharacterObjectAlign) char opaque_[detail::kCharacterObjectSize];
+};
+
+} // namespace Fortran::evaluate::value
+
+namespace llvm {
+/// For pretty printing in GTest
+inline raw_ostream &operator<<(
+    raw_ostream &os, const Fortran::evaluate::value::CharacterValue &v) {
+  v.print(os);
+  return os;
+}
+} // namespace llvm
+
+#endif // FORTRAN_EVALUATE_CHARACTER_VALUE_H_
diff --git a/flang/include/flang/Evaluate/complex-value.h b/flang/include/flang/Evaluate/complex-value.h
new file mode 100644
index 0000000000000..d1501b24f7480
--- /dev/null
+++ b/flang/include/flang/Evaluate/complex-value.h
@@ -0,0 +1,179 @@
+//===-- include/flang/Evaluate/complex-value.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_COMPLEX_VALUE_H_
+#define FORTRAN_EVALUATE_COMPLEX_VALUE_H_
+
+#include "real-value.h"
+#include "llvm/Support/Compiler.h"
+
+namespace llvm {
+class raw_ostream;
+}
+
+namespace Fortran::evaluate::value {
+
+/// A complex floating-point value with dynamic precision.
+///
+/// The precision is dynamic, but only a predefined set of Fortran kinds are
+/// allowed. It is also kind-aware, i.e. knows which COMPLEX kind it currently
+/// represents.
+///
+/// The implementation is a pair of RealValue objects.
+class ComplexValue {
+public:
+  ComplexValue() = default;
+  ComplexValue(const ComplexValue &) = default;
+  ComplexValue(ComplexValue &&) = default;
+  ComplexValue &operator=(const ComplexValue &) = default;
+  ComplexValue &operator=(ComplexValue &&) = default;
+
+  ComplexValue(const RealValue &r, const RealValue &i)
+      : re_{r},
+        im_{r.IsMonostate() ? i : RealValue::Convert(r.kind(), i).value} {}
+
+  explicit ComplexValue(const RealValue &r)
+      : ComplexValue{r, RealValue::Zero(r.kind())} {}
+
+  ComplexValue(int kind, const RealValue &r) : ComplexValue{r} {
+    CHECK(kind == r.kind());
+  }
+
+  ComplexValue(int kind, const ComplexValue &v) : ComplexValue{v} {
+    CHECK(kind == v.kind());
+  }
+
+  ComplexValue(int kind, ComplexValue &&v) : ComplexValue{std::move(v)} {
+    CHECK(kind == v.kind());
+  }
+
+  /// Creates a complex value (+0.0 + +0.0i) of a given kind. This is
+  /// different from the default-ctor which creates a "monostate" that
+  /// represents zero of unknown kind.
+  static ComplexValue Zero(int kind) {
+    RealValue zero{RealValue::Zero(kind)};
+    return ComplexValue{zero, zero};
+  }
+
+  void print(llvm::raw_ostream &os) const;
+
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+  LLVM_DUMP_METHOD void dump() const;
+#endif
+
+  /// Whether this object represents a default-initialized value (zero) of
+  /// not-yet-known kind.
+  bool IsMonostate() const {
+    CHECK(re_.IsMonostate() == im_.IsMonostate());
+    return re_.IsMonostate();
+  }
+
+  /// The kind of the value currently stored.
+  int kind() const {
+    CHECK(re_.kind() == im_.kind());
+    return re_.kind();
+  }
+
+  /// Number of bytes accessed by FromRawBytes/StoreRawBytes
+  std::size_t bytesStored() const {
+    return re_.bytesStored() + im_.bytesStored();
+  }
+  static std::size_t bytesStored(int kind) {
+    return 2 * RealValue::bytesStored(kind);
+  }
+
+  RealValue REAL() const { return re_; }
+
+  RealValue AIMAG() const { return im_; }
+
+  ComplexValue CONJG() const { return ComplexValue{re_, im_.Negate()}; }
+
+  ComplexValue Negate() const {
+    return ComplexValue{re_.Negate(), im_.Negate()};
+  }
+
+  bool Equals(const ComplexValue &y) const {
+    return re_.Compare(y.re_) == Relation::Equal &&
+        im_.Compare(y.im_) == Relation::Equal;
+  }
+
+  bool operator==(const ComplexValue &y) const {
+    return re_ == y.re_ && im_ == y.im_;
+  }
+
+  bool operator!=(const ComplexValue &y) const { return !(*this == y); }
+
+  bool IsZero() const { return re_.IsZero() && im_.IsZero(); }
+
+  bool IsInfinite() const { return re_.IsInfinite() || im_.IsInfinite(); }
+
+  bool IsNotANumber() const { return re_.IsNotANumber() || im_.IsNotANumber(); }
+
+  bool IsSignalingNaN() const {
+    return re_.IsSignalingNaN() || im_.IsSignalingNaN();
+  }
+
+  static ValueWithRealFlags<ComplexValue> FromInteger(int kind,
+      const IntegerValue &n, bool isUnsigned = false,
+      Rounding rounding = TargetCharacteristics::defaultRounding);
+
+  ValueWithRealFlags<ComplexValue> Add(const ComplexValue &,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+
+  ValueWithRealFlags<ComplexValue> Subtract(const ComplexValue &,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+  ValueWithRealFlags<ComplexValue> Multiply(const ComplexValue &,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+  ValueWithRealFlags<ComplexValue> Divide(const ComplexValue &,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+
+  ValueWithRealFlags<ComplexValue> KahanSummation(const ComplexValue &y,
+      ComplexVa...
[truncated]

``````````

</details>


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


More information about the llvm-branch-commits mailing list