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

Michael Kruse via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Tue Aug 18 01:45:59 PDT 2026


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

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.

>From c41089b078833a7e75c56d572a4d20c0b5db9c77 Mon Sep 17 00:00:00 2001
From: Michael Kruse <llvm-project at meinersbur.de>
Date: Mon, 17 Aug 2026 17:13:24 +0200
Subject: [PATCH 1/2] [Flang] Introduce *Value classes with unittests

---
 flang/include/flang/Common/template.h         |   27 +
 flang/include/flang/Common/type-kinds.h       |    6 +
 flang/include/flang/Common/uint128.h          |  108 +-
 .../include/flang/Evaluate/character-value.h  |  250 ++
 flang/include/flang/Evaluate/complex-value.h  |  179 ++
 flang/include/flang/Evaluate/integer-value.h  |  373 +++
 flang/include/flang/Evaluate/integer.h        |    4 +-
 flang/include/flang/Evaluate/logical-value.h  |  166 ++
 flang/include/flang/Evaluate/object-sizes.h   |   84 +
 flang/include/flang/Evaluate/real-value.h     |  264 ++
 flang/include/flang/Evaluate/real.h           |    6 +
 .../include/flang/Evaluate/typekind-traits.h  |   95 +
 flang/lib/Evaluate/CMakeLists.txt             |    8 +
 flang/lib/Evaluate/character-value-impl.cpp   |  615 +++++
 flang/lib/Evaluate/character-value-impl.h     |  256 ++
 flang/lib/Evaluate/character-value.cpp        |  221 ++
 flang/lib/Evaluate/complex-value.cpp          |  185 ++
 flang/lib/Evaluate/integer-value-impl.cpp     |  607 +++++
 flang/lib/Evaluate/integer-value-impl.h       |  331 +++
 flang/lib/Evaluate/integer-value.cpp          |  315 +++
 flang/lib/Evaluate/logical-value.cpp          |   33 +
 flang/lib/Evaluate/real-value-impl.cpp        |  589 +++++
 flang/lib/Evaluate/real-value-impl.h          |  283 ++
 flang/lib/Evaluate/real-value.cpp             |  275 ++
 flang/test/Evaluate/fold-transfer-partial.f90 |   71 +
 flang/tools/CMakeLists.txt                    |    1 +
 flang/tools/object-size-probe/CMakeLists.txt  |   42 +
 .../object-size-probe/object-size-probe.cpp   |  129 +
 flang/unittests/Evaluate/CMakeLists.txt       |   16 +
 .../unittests/Evaluate/CharacterValueTest.cpp |  705 +++++
 flang/unittests/Evaluate/ComplexValueTest.cpp |  402 +++
 flang/unittests/Evaluate/IntegerValueTest.cpp | 2347 +++++++++++++++++
 flang/unittests/Evaluate/LogicalValueTest.cpp |  324 +++
 flang/unittests/Evaluate/RealValueTest.cpp    | 1142 ++++++++
 .../include/gtest/internal/gtest-param-util.h |    4 +
 35 files changed, 10460 insertions(+), 3 deletions(-)
 create mode 100644 flang/include/flang/Evaluate/character-value.h
 create mode 100644 flang/include/flang/Evaluate/complex-value.h
 create mode 100644 flang/include/flang/Evaluate/integer-value.h
 create mode 100644 flang/include/flang/Evaluate/logical-value.h
 create mode 100644 flang/include/flang/Evaluate/object-sizes.h
 create mode 100644 flang/include/flang/Evaluate/real-value.h
 create mode 100644 flang/include/flang/Evaluate/typekind-traits.h
 create mode 100644 flang/lib/Evaluate/character-value-impl.cpp
 create mode 100644 flang/lib/Evaluate/character-value-impl.h
 create mode 100644 flang/lib/Evaluate/character-value.cpp
 create mode 100644 flang/lib/Evaluate/complex-value.cpp
 create mode 100644 flang/lib/Evaluate/integer-value-impl.cpp
 create mode 100644 flang/lib/Evaluate/integer-value-impl.h
 create mode 100644 flang/lib/Evaluate/integer-value.cpp
 create mode 100644 flang/lib/Evaluate/logical-value.cpp
 create mode 100644 flang/lib/Evaluate/real-value-impl.cpp
 create mode 100644 flang/lib/Evaluate/real-value-impl.h
 create mode 100644 flang/lib/Evaluate/real-value.cpp
 create mode 100644 flang/test/Evaluate/fold-transfer-partial.f90
 create mode 100644 flang/tools/object-size-probe/CMakeLists.txt
 create mode 100644 flang/tools/object-size-probe/object-size-probe.cpp
 create mode 100644 flang/unittests/Evaluate/CharacterValueTest.cpp
 create mode 100644 flang/unittests/Evaluate/ComplexValueTest.cpp
 create mode 100644 flang/unittests/Evaluate/IntegerValueTest.cpp
 create mode 100644 flang/unittests/Evaluate/LogicalValueTest.cpp
 create mode 100644 flang/unittests/Evaluate/RealValueTest.cpp

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..955e2999f19c6 100644
--- a/flang/include/flang/Common/uint128.h
+++ b/flang/include/flang/Common/uint128.h
@@ -22,11 +22,14 @@
 #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
@@ -63,7 +66,24 @@ template <bool IS_SIGNED = false> class Int128 {
   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_); }
+  constexpr explicit operator std::uint32_t() const {
+    return static_cast<std::uint32_t>(low_);
+  }
+  constexpr explicit operator std::int32_t() const {
+    return static_cast<std::int32_t>(low_);
+  }
+  constexpr explicit operator std::uint16_t() const {
+    return static_cast<std::uint16_t>(low_);
+  }
+  constexpr explicit operator std::int16_t() const {
+    return static_cast<std::int16_t>(low_);
+  }
+  constexpr explicit operator std::uint8_t() const {
+    return static_cast<std::uint8_t>(low_);
+  }
+  constexpr explicit operator std::int8_t() const {
+    return static_cast<std::int8_t>(low_);
+  }
 
   constexpr std::uint64_t high() const { return high_; }
   constexpr std::uint64_t low() const { return low_; }
@@ -305,4 +325,90 @@ template <int BITS>
 using HostSignedIntType = typename HostSignedIntTypeHelper<BITS>::type;
 
 } // namespace Fortran::common
+
+namespace std {
+
+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 bool is_exact{true};
+  static constexpr bool has_infinity{false};
+  static constexpr bool has_quiet_NaN{false};
+  static constexpr bool has_signaling_NaN{false};
+  static constexpr float_denorm_style has_denorm{denorm_absent};
+  static constexpr bool has_denorm_loss{false};
+  static constexpr float_round_style round_style{round_toward_zero};
+  static constexpr bool is_iec559{false};
+  static constexpr bool is_bounded{true};
+  static constexpr bool is_modulo{true};
+  static constexpr int digits{128};
+  static constexpr int digits10{38};
+  static constexpr int max_digits10{0};
+  static constexpr int radix{2};
+  static constexpr int min_exponent{0};
+  static constexpr int min_exponent10{0};
+  static constexpr int max_exponent{0};
+  static constexpr int max_exponent10{0};
+  static constexpr bool traps{true};
+  static constexpr bool tinyness_before{false};
+
+  static constexpr T min() { return T{0, 0}; }
+  static constexpr T max() { return T{UINT64_MAX, UINT64_MAX}; }
+  static constexpr T lowest() { return min(); }
+  static constexpr T epsilon() { return T{}; }
+  static constexpr T round_error() { return T{}; }
+  static constexpr T infinity() { return T{}; }
+  static constexpr T quiet_NaN() { return T{}; }
+  static constexpr T signaling_NaN() { return T{}; }
+  static constexpr T denorm_min() { return T{}; }
+};
+
+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 bool is_exact{true};
+  static constexpr bool has_infinity{false};
+  static constexpr bool has_quiet_NaN{false};
+  static constexpr bool has_signaling_NaN{false};
+  static constexpr float_denorm_style has_denorm{denorm_absent};
+  static constexpr bool has_denorm_loss{false};
+  static constexpr float_round_style round_style{round_toward_zero};
+  static constexpr bool is_iec559{false};
+  static constexpr bool is_bounded{true};
+  static constexpr bool is_modulo{true};
+  static constexpr int digits{127};
+  static constexpr int digits10{38};
+  static constexpr int max_digits10{0};
+  static constexpr int radix{2};
+  static constexpr int min_exponent{0};
+  static constexpr int min_exponent10{0};
+  static constexpr int max_exponent{0};
+  static constexpr int max_exponent10{0};
+  static constexpr bool traps{true};
+  static constexpr bool tinyness_before{false};
+
+  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};
+  }
+  static constexpr T lowest() { return min(); }
+  static constexpr T epsilon() { return T{}; }
+  static constexpr T round_error() { return T{}; }
+  static constexpr T infinity() { return T{}; }
+  static constexpr T quiet_NaN() { return T{}; }
+  static constexpr T signaling_NaN() { return T{}; }
+  static constexpr T denorm_min() { return T{}; }
+};
+
+} // 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,
+      ComplexValue &correction,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+
+  /// ABS/CABS = HYPOT(re_, imag_) = SQRT(re_**2 + im_**2)
+  ValueWithRealFlags<RealValue> ABS(
+      Rounding rounding = TargetCharacteristics::defaultRounding) const {
+    return re_.HYPOT(im_, rounding);
+  }
+
+  ComplexValue FlushSubnormalToZero() const {
+    return ComplexValue{re_.FlushSubnormalToZero(), im_.FlushSubnormalToZero()};
+  }
+
+  static ComplexValue NotANumber(int kind) {
+    return {RealValue::NotANumber(kind), RealValue::NotANumber(kind)};
+  }
+
+  std::string DumpHexadecimal() const;
+
+  llvm::raw_ostream &AsFortran(llvm::raw_ostream &, int kind) const;
+
+  void StoreRawBytes(void *dst, size_t size, bool *changed = nullptr) const;
+
+  static ComplexValue FromRawBytes(
+      int kind, const void *raw, std::size_t expectedSize);
+
+  // TODO: unit testing
+
+private:
+  RealValue re_, im_;
+};
+
+} // namespace Fortran::evaluate::value
+
+namespace llvm {
+/// For pretty printing in GTest
+inline raw_ostream &operator<<(
+    raw_ostream &os, const Fortran::evaluate::value::ComplexValue &v) {
+  v.print(os);
+  return os;
+}
+} // namespace llvm
+
+#endif // FORTRAN_EVALUATE_COMPLEX_VALUE_H_
diff --git a/flang/include/flang/Evaluate/integer-value.h b/flang/include/flang/Evaluate/integer-value.h
new file mode 100644
index 0000000000000..d8ddeb39f039d
--- /dev/null
+++ b/flang/include/flang/Evaluate/integer-value.h
@@ -0,0 +1,373 @@
+//===-- include/flang/Evaluate/integer-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_INTEGER_VALUE_H_
+#define FORTRAN_EVALUATE_INTEGER_VALUE_H_
+
+#include "flang/Common/uint128.h"
+#include "flang/Evaluate/common.h"
+#include "flang/Evaluate/object-sizes.h"
+#include "llvm/Support/Compiler.h"
+#include "llvm/Support/raw_ostream.h"
+#include <cstdint>
+#include <type_traits>
+
+// Some environments, viz. glibc 2.17 and *BSD, allow the macro HUGE
+// to leak out of <math.h>.
+#undef HUGE
+
+namespace Fortran::evaluate::value {
+class IntegerValueImpl;
+
+/// A two's-complement integer with dynamic bitwidth.
+///
+/// The bitwidth is dynamic, but only a predefined set of Fortran kinds are
+/// allowed. It is also kind-aware, i.e. knows which INTEGER kind it currently
+/// represents.
+///
+/// The implementation is hidden from this header using a pImpl-like idiom.
+class IntegerValue {
+  friend class RealValueImpl;
+
+public:
+  struct ValueWithOverflow;
+  struct ValueWithCarry;
+  struct Product;
+  struct QuotientWithRemainder;
+  struct PowerWithErrors;
+
+  IntegerValue();
+  ~IntegerValue();
+  IntegerValue(const IntegerValue &);
+  IntegerValue(IntegerValue &&);
+  IntegerValue &operator=(const IntegerValue &);
+  IntegerValue &operator=(IntegerValue &&);
+
+  IntegerValue(int kind, const IntegerValue &x) : IntegerValue(x) {
+    CHECK(x.kind() == kind);
+  }
+  IntegerValue(int kind, IntegerValue &&x) : IntegerValue(std::move(x)) {
+    CHECK(x.kind() == kind);
+  }
+
+  // Fortran::common::int128_t/uint128_t are 128-bit values -- either the
+  // host's native __int128/unsigned __int128, or the portable
+  // Fortran::common::Int128<> fallback when there is no native type -- and
+  // are handled by the dedicated branch below rather than by the general
+  // integral case, since some standard libraries don't consider native
+  // __int128 types to satisfy std::is_integral_v, and the portable fallback
+  // is a class type that never does.
+  template <typename INT,
+      typename = std::enable_if_t<std::numeric_limits<INT>::is_integer>>
+  IntegerValue(int kind, INT v) {
+    if constexpr (sizeof(INT) > 8) {
+      static_assert(sizeof(INT) == 16);
+      ConstructFromIntegral(kind, static_cast<Fortran::common::uint128_t>(v));
+    } else if constexpr (std::is_signed_v<INT>) {
+      ConstructFromIntegral(
+          kind, static_cast<uint64_t>(static_cast<int64_t>(v)), true);
+    } else {
+      ConstructFromIntegral(kind, static_cast<uint64_t>(v), false);
+    }
+  }
+
+  /// Creates an integer with value 0 of a given kind. This is different from
+  /// the default-ctor which creates a "monostate" that represents 0 of unknown
+  /// kind.
+  static IntegerValue Zero(int kind);
+
+  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;
+
+  int bits() const { return bits(kind()); }
+  static constexpr int bits(int kind) { return bytesStored(kind) * 8; }
+
+  /// Number of bytes accessed by FromRawBytes/StoreRawBytes
+  std::size_t bytesStored() const { return bytesStored(kind()); }
+  static constexpr std::size_t bytesStored(int kind) {
+    switch (kind) {
+    case 3:
+      return 2;
+    case 10:
+      return 16;
+    default:
+      return kind;
+    }
+  }
+
+  bool operator<(const IntegerValue &y) const {
+    return CompareSigned(y) == Ordering::Less;
+  }
+  bool operator<=(const IntegerValue &y) const { return !(y < *this); }
+  bool operator==(const IntegerValue &y) const;
+  bool operator!=(const IntegerValue &y) const { return !(*this == y); }
+  bool operator>=(const IntegerValue &y) const { return !(*this < y); }
+  bool operator>(const IntegerValue &y) const { return y < *this; }
+
+  /// Left-justified mask (e.g., MASKL(1) has only its sign bit set)
+  static IntegerValue MASKL(int kind, int places);
+
+  /// Right-justified mask (e.g., MASKR(1) == 1, MASKR(2) == 3, &c.)
+  static IntegerValue MASKR(int kind, int places);
+
+  static ValueWithOverflow Read(
+      int kind, const char *&pp, int base, bool isSigned);
+
+  /// ZExt or Trunc
+  static ValueWithOverflow ConvertUnsigned(
+      const IntegerValue &from, int toBits);
+
+  /// SExt or Trunc
+  static ValueWithOverflow ConvertSigned(const IntegerValue &from, int toBits);
+
+  std::string UnsignedDecimal() const;
+
+  std::string SignedDecimal() const;
+
+  /// Omits a leading "0x".
+  std::string Hexadecimal() const;
+
+  static constexpr int DIGITS(int kind) {
+    // don't count the sign bit
+    return bits(kind) - 1;
+  }
+
+  static IntegerValue HUGE(int kind);
+
+  static IntegerValue Least(int kind);
+
+  static int RANGE(int kind);
+
+  static int UnsignedRANGE(int kind);
+
+  bool IsZero() const;
+
+  bool IsNegative() const;
+
+  Ordering CompareToZeroSigned() const;
+
+  /// Count the number of contiguous most-significant bit positions
+  /// that are clear.
+  int LEADZ() const;
+
+  /// Count the number of bit positions that are set.
+  int POPCNT() const;
+
+  /// True when POPCNT is odd.
+  bool POPPAR() const;
+
+  int TRAILZ() const;
+
+  bool BTEST(int pos) const;
+
+  Ordering CompareUnsigned(const IntegerValue &y) const;
+
+  Ordering CompareSigned(const IntegerValue &y) const;
+
+  bool BGE(const IntegerValue &y) const {
+    return CompareUnsigned(y) != Ordering::Less;
+  }
+  bool BGT(const IntegerValue &y) const {
+    return CompareUnsigned(y) == Ordering::Greater;
+  }
+  bool BLE(const IntegerValue &y) const { return !BGT(y); }
+  bool BLT(const IntegerValue &y) const { return !BGE(y); }
+
+  std::uint64_t ToUInt64() const;
+
+  std::int64_t ToInt64() const;
+
+  Fortran::common::uint128_t ToUInt128() const;
+
+  Fortran::common::int128_t ToInt128() const;
+
+  template <typename INT,
+      typename = std::enable_if_t<std::is_signed_v<INT> ||
+          std::is_same_v<INT, Fortran::common::int128_t>>>
+  INT ToSInt() const {
+    if constexpr (std::is_same_v<INT, Fortran::common::int128_t>) {
+      return ToInt128();
+    } else {
+      return ToInt64();
+    }
+  }
+
+  template <typename INT,
+      typename = std::enable_if_t<std::is_unsigned_v<INT> ||
+          std::is_same_v<INT, Fortran::common::uint128_t>>>
+  INT ToUInt() const {
+    if constexpr (std::is_same_v<INT, Fortran::common::uint128_t>) {
+      return ToUInt128();
+    } else {
+      return ToUInt64();
+    }
+  }
+
+  /// Ones'-complement (i.e., C's ~)
+  IntegerValue NOT() const;
+
+  /// Two's-complement negation (-x = ~x + 1).
+  /// An overflow flag accompanies the result, and will be true when the
+  /// operand is the most negative signed number (MASKL(1)).
+  ValueWithOverflow Negate() const;
+
+  ValueWithOverflow ABS() const;
+
+  /// Shifts the operand left when the count is positive, right when negative.
+  /// Vacated bit positions are filled with zeroes.
+  IntegerValue ISHFT(int count) const {
+    return count < 0 ? SHIFTR(-count) : SHIFTL(count);
+  }
+
+  /// Left shift with zero fill.
+  IntegerValue SHIFTL(int count) const;
+
+  /// Circular shift of a field of least-significant bits.  The least-order
+  /// "size" bits are shifted circularly in place by "count" positions;
+  /// the shift is leftward if count is nonnegative, rightward otherwise.
+  /// Higher-order bits are unchanged.
+  IntegerValue ISHFTC(int count, int size) const;
+  IntegerValue ISHFTC(int count) const;
+
+  /// DSHIFTL(I,J) shifts I:J left; the second argument is the right fill.
+  IntegerValue DSHIFTL(const IntegerValue &fill, int count) const;
+
+  /// DSHIFTR(I,J) shifts I:J right; the *first* argument is the left fill.
+  IntegerValue DSHIFTR(const IntegerValue &v2, int count) const;
+
+  /// Vacated upper bits are filled with zeroes.
+  IntegerValue SHIFTR(int count) const;
+
+  /// Be advised, an arithmetic (sign-filling) right shift is not
+  /// the same as a division by a power of two in all cases.
+  IntegerValue SHIFTA(int count) const;
+
+  /// Clears a single bit.
+  IntegerValue IBCLR(int pos) const;
+
+  /// Sets a single bit.
+  IntegerValue IBSET(int pos) const;
+
+  /// Extracts a field.
+  IntegerValue IBITS(int pos, int size) const;
+
+  IntegerValue IAND(const IntegerValue &y) const;
+
+  IntegerValue IOR(const IntegerValue &y) const;
+
+  IntegerValue IEOR(const IntegerValue &y) const;
+
+  IntegerValue MERGE_BITS(
+      const IntegerValue &y, const IntegerValue &mask) const;
+
+  IntegerValue MAX(const IntegerValue &y) const {
+    return CompareSigned(y) == Ordering::Less ? y : *this;
+  }
+
+  IntegerValue MIN(const IntegerValue &y) const {
+    return CompareSigned(y) == Ordering::Less ? *this : y;
+  }
+
+  ValueWithCarry AddUnsigned(const IntegerValue &y, bool carryIn = false) const;
+
+  ValueWithOverflow AddSigned(const IntegerValue &y) const;
+
+  ValueWithOverflow SubtractSigned(const IntegerValue &y) const;
+
+  /// DIM(X,Y)=MAX(X-Y, 0)
+  ValueWithOverflow DIM(const IntegerValue &y) const;
+
+  ValueWithOverflow SIGN(const IntegerValue &sign) const;
+
+  Product MultiplyUnsigned(const IntegerValue &y) const;
+
+  Product MultiplySigned(const IntegerValue &y) const;
+
+  QuotientWithRemainder DivideUnsigned(const IntegerValue &y) const;
+
+  /// A nonzero remainder has the sign of the dividend, i.e., it computes
+  /// the MOD intrinsic (X-INT(X/Y)*Y), not MODULO (which is below).
+  /// 8/5 = 1r3;  -8/5 = -1r-3;  8/-5 = -1r3;  -8/-5 = 1r-3
+  QuotientWithRemainder DivideSigned(const IntegerValue &y) const;
+
+  /// Result has the sign of the divisor argument.
+  /// 8 mod 5 = 3;  -8 mod 5 = 2;  8 mod -5 = -2;  -8 mod -5 = -3
+  ValueWithOverflow MODULO(const IntegerValue &y) const;
+
+  PowerWithErrors Power(const IntegerValue &e) const;
+
+  static IntegerValue FromRawBytes(
+      int kind, const void *raw, std::size_t expectedSize);
+  void StoreRawBytes(void *dst, size_t size, bool *changed = nullptr) const;
+
+private:
+  void ConstructFromIntegral(int kind, std::uint64_t n, bool isSigned);
+  void ConstructFromIntegral(int kind, Fortran::common::uint128_t n);
+
+  static IntegerValue FromImpl(const IntegerValueImpl &x);
+  static IntegerValue FromImpl(IntegerValueImpl &&x);
+
+  IntegerValueImpl &impl() {
+    return *reinterpret_cast<IntegerValueImpl *>(this);
+  }
+  const IntegerValueImpl &impl() const {
+    return *reinterpret_cast<const IntegerValueImpl *>(this);
+  }
+
+  [[maybe_unused]] alignas(
+      detail::kIntegerObjectAlign) char opaque_[detail::kIntegerObjectSize];
+};
+
+struct IntegerValue::ValueWithOverflow {
+  IntegerValue value;
+  bool overflow{false};
+};
+
+struct IntegerValue::ValueWithCarry {
+  IntegerValue value;
+  bool carry{false};
+};
+
+struct IntegerValue::Product {
+  IntegerValue upper, lower;
+  bool SignedMultiplicationOverflowed() const { return overflow; }
+  bool overflow{false};
+};
+
+struct IntegerValue::QuotientWithRemainder {
+  IntegerValue quotient, remainder;
+  bool divisionByZero{false}, overflow{false};
+};
+
+struct IntegerValue::PowerWithErrors {
+  IntegerValue power;
+  bool divisionByZero{false}, overflow{false}, zeroToZero{false};
+};
+
+} // namespace Fortran::evaluate::value
+
+namespace llvm {
+/// For pretty printing in GTest
+inline raw_ostream &operator<<(
+    raw_ostream &os, const Fortran::evaluate::value::IntegerValue &v) {
+  v.print(os);
+  return os;
+}
+} // namespace llvm
+
+#endif // FORTRAN_EVALUATE_INTEGER_VALUE_H_
diff --git a/flang/include/flang/Evaluate/integer.h b/flang/include/flang/Evaluate/integer.h
index 5953fc81cb111..31025d311cf1c 100644
--- a/flang/include/flang/Evaluate/integer.h
+++ b/flang/include/flang/Evaluate/integer.h
@@ -489,12 +489,12 @@ class Integer {
 
   template <typename SINT = std::int64_t, typename UINT = std::uint64_t>
   constexpr SINT ToSInt() const {
-    SINT n = ToUInt<UINT>();
+    SINT n(ToUInt<UINT>());
     constexpr std::size_t maxBits{CHAR_BIT * sizeof n};
     if constexpr (bits < maxBits) {
       // Avoid left shifts of negative signed values (that's an undefined
       // behavior in C++).
-      auto u{std::make_unsigned_t<SINT>(ToUInt())};
+      UINT u{ToUInt<UINT>()};
       u = (u >> (bits - 1)) << (bits - 1); // Get the sign bit only.
       u = ~u + 1; // Negate top bits if not 0.
       n |= static_cast<SINT>(u);
diff --git a/flang/include/flang/Evaluate/logical-value.h b/flang/include/flang/Evaluate/logical-value.h
new file mode 100644
index 0000000000000..f313e88cff5a0
--- /dev/null
+++ b/flang/include/flang/Evaluate/logical-value.h
@@ -0,0 +1,166 @@
+//===-- include/flang/Evaluate/logical-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_LOGICAL_VALUE_H_
+#define FORTRAN_EVALUATE_LOGICAL_VALUE_H_
+
+#include "integer-value.h"
+#include "llvm/Support/Compiler.h"
+#include <utility>
+
+namespace Fortran::evaluate::value {
+
+/// A Fortran LOGICAL value.
+///
+/// The kind is dynamic, but only a predefined set of Fortran kinds are
+/// allowed. It is also kind-aware, i.e. knows which LOGICAL kind it currently
+/// represents.
+///
+/// It is implemented as a wrapper around IntegerValue.
+class LogicalValue {
+public:
+  using Word = IntegerValue;
+
+  LogicalValue() {}
+  LogicalValue(const LogicalValue &) = default;
+  LogicalValue(LogicalValue &&) = default;
+  LogicalValue &operator=(const LogicalValue &) = default;
+  LogicalValue &operator=(LogicalValue &&) = default;
+
+  LogicalValue(int kind, const LogicalValue &v) : LogicalValue{v} {
+    CHECK(kind == v.kind());
+  }
+
+  LogicalValue(int kind, LogicalValue &&v) : LogicalValue{std::move(v)} {
+    CHECK(kind == v.kind());
+  }
+
+  LogicalValue(int kind, bool truth) : word_(Represent(kind, truth)) {}
+
+  LogicalValue(int kind, const Word &w) : word_(kind, w) {}
+
+  /// Creates a logical with value 'false' of a given kind. This is in contrast
+  /// to the default-ctor which creates a "monostate" that represents 'false' of
+  /// a not-yet-known kind.
+  static LogicalValue Zero(int kind) { return LogicalValue{kind, false}; }
+
+  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 ('false') of
+  /// unknown kind.
+  bool IsMonostate() const { return word_.IsMonostate(); }
+
+  /// The kind of the value currently stored.
+  int kind() const { return word_.kind(); }
+
+  int bits() const { return bits(kind()); }
+  static constexpr int bits(int kind) { return Word::bits(kind); }
+
+  /// Number of bytes accessed by FromRawBytes/StoreRawBytes
+  std::size_t bytesStored() const { return bytesStored(kind()); }
+  static constexpr std::size_t bytesStored(int kind) {
+    return Word::bytesStored(kind);
+  }
+
+  Word word() const { return word_; }
+
+  bool IsCanonical() const {
+    const int kind{this->kind()};
+    return word_ == canonicalFalse(kind) || word_ == canonicalTrue(kind);
+  }
+
+  /// Fortran actually has only .EQV. & .NEQV. relational operations
+  /// for LOGICAL, but this class supports more so that it can be used
+  /// with the STL for sorting and as a key type for std::set<> & std::map<>.
+  bool operator<(const LogicalValue &that) const {
+    return !IsTrue() && that.IsTrue();
+  }
+  bool operator<=(const LogicalValue &that) const { return !IsTrue(); }
+  bool operator==(const LogicalValue &that) const {
+    return IsTrue() == that.IsTrue();
+  }
+  bool operator!=(const LogicalValue &that) const {
+    return IsTrue() != that.IsTrue();
+  }
+
+  bool operator>=(const LogicalValue &that) const { return IsTrue(); }
+
+  bool operator>(const LogicalValue &that) const {
+    return IsTrue() && !that.IsTrue();
+  }
+
+  bool IsTrue() const { return !word_.IsZero(); }
+
+  LogicalValue NOT() const {
+    return FromWord(word_.IEOR(canonicalTrue(kind())));
+  }
+
+  LogicalValue AND(const LogicalValue &that) const {
+    return FromWord(word_.IAND(that.word()));
+  }
+
+  LogicalValue OR(const LogicalValue &that) const {
+    return FromWord(word_.IOR(that.word()));
+  }
+
+  LogicalValue EQV(const LogicalValue &that) const { return NEQV(that).NOT(); }
+
+  LogicalValue NEQV(const LogicalValue &that) const {
+    return FromWord(word_.IEOR(that.word()));
+  }
+
+  static LogicalValue FromRawBytes(
+      int kind, const void *raw, std::size_t expectedSize) {
+    Word w{Word::FromRawBytes(kind, raw, expectedSize)};
+    return LogicalValue{w.kind(), w};
+  }
+
+  void StoreRawBytes(void *dst, size_t size, bool *changed = nullptr) const {
+    word_.StoreRawBytes(dst, size, changed);
+  }
+
+private:
+  static Word canonicalTrue(int kind) { return Word{kind, 1}; }
+
+  static Word canonicalFalse(int kind) { return Word{kind, 0}; }
+
+  static Word Represent(int kind, bool x) {
+    return x ? canonicalTrue(kind) : canonicalFalse(kind);
+  }
+
+  static LogicalValue FromWord(const Word &w) {
+    LogicalValue v;
+    v.word_ = w;
+    return v;
+  }
+
+  static LogicalValue FromWord(Word &&w) {
+    LogicalValue v;
+    v.word_ = std::move(w);
+    return v;
+  }
+
+  Word word_;
+};
+
+} // namespace Fortran::evaluate::value
+
+namespace llvm {
+/// For pretty printing in GTest
+inline raw_ostream &operator<<(
+    raw_ostream &os, const Fortran::evaluate::value::LogicalValue &v) {
+  v.print(os);
+  return os;
+}
+} // namespace llvm
+
+#endif // FORTRAN_EVALUATE_LOGICAL_VALUE_H_
diff --git a/flang/include/flang/Evaluate/object-sizes.h b/flang/include/flang/Evaluate/object-sizes.h
new file mode 100644
index 0000000000000..bf1290e425014
--- /dev/null
+++ b/flang/include/flang/Evaluate/object-sizes.h
@@ -0,0 +1,84 @@
+//===-- include/flang/Evaluate/object-sizes.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Object size/alignment for the opaque facades IntegerValue, RealValue,
+// CharacterValue and their variant-backed implementations IntegerValueImpl,
+// RealValueImpl, CharacterValueImpl.
+//
+// When not cross-compiling, flang-evaluate-object-size-probe measures these
+// with the very toolchain (and per build configuration) used for the build and
+// emits object-sizes-generated.h into the build tree's include
+// directory. Those values directly measured are preferred whenever that header
+// is available on the include path, regardless of -I ordering. The constants
+// below are the fallback used otherwise -- in particular when cross-compiling,
+// where the probe cannot run on the build host.  They are verified against the
+// implementation classes by static_asserts in integer-value.cpp, real-value.cpp
+// and character-value.cpp.
+//
+// The probe itself (object-size-probe.cpp) compiles with
+// FLANG_OBJECT_SIZE_PROBE defined: it generates the header, so it
+// must not depend on it.  The dedicated #if branch below omits __has_include so
+// dependency scanners do not record the generated header (probe -> generated
+// header -> probe cycle).
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef FORTRAN_EVALUATE_OBJECT_SIZES_H_
+#define FORTRAN_EVALUATE_OBJECT_SIZES_H_
+
+#include <cstddef>
+
+#ifdef FLANG_OBJECT_SIZE_PROBE
+#error This header must not be included into the object-size-probe executable itself (in particular, integer-value-impl.h, real-value-impl.h, character-value-impl.h); it would cause a dependency cycle in incremental builds.
+#endif
+
+#if __has_include(<flang/Evaluate/object-sizes-generated.h>)
+// Measured object sizes
+#include <flang/Evaluate/object-sizes-generated.h>
+#else
+// Fallback known object sizes
+//
+// These fallbacks assume a 64-bit (LP64/LLP64) host, which covers the targets
+// flang is built for (x86_64, AArch64, PowerPC64).
+namespace Fortran::evaluate::value::detail {
+
+inline constexpr std::size_t kIntegerObjectSize{20};
+inline constexpr std::size_t kIntegerObjectAlign{4};
+
+inline constexpr std::size_t kRealObjectSize{32};
+inline constexpr std::size_t kRealObjectAlign{16};
+
+// CharacterValueImpl is a
+// std::variant<std::string, std::u16string, std::u32string>.
+//
+//  * MSVC STL:  48 bytes with _ITERATOR_DEBUG_LEVEL==2
+//               40 bytes otherwise
+//  * libc++:    32 bytes
+//               invariant to _LIBCPP_HARDENING_MODE
+//  * libstdc++: 40 bytes
+//               invariant to _GLIBCXX_ASSERTIONS or _GLIBCXX_DEBUG
+#if defined(_MSC_VER)
+#if ((defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL >= 2) || \
+    (!defined(_ITERATOR_DEBUG_LEVEL) && defined(_DEBUG)))
+inline constexpr std::size_t kCharacterObjectSize{48};
+#else
+inline constexpr std::size_t kCharacterObjectSize{40};
+#endif
+#elif defined(_LIBCPP_VERSION)
+inline constexpr std::size_t kCharacterObjectSize{32};
+#elif defined(__GLIBCXX__) || defined(__GLIBCPP__)
+inline constexpr std::size_t kCharacterObjectSize{40};
+#else
+#error Unknown STL implementation
+#endif
+inline constexpr std::size_t kCharacterObjectAlign{8};
+
+} // namespace Fortran::evaluate::value::detail
+#endif
+
+#endif // FORTRAN_EVALUATE_OBJECT_SIZES_H_
diff --git a/flang/include/flang/Evaluate/real-value.h b/flang/include/flang/Evaluate/real-value.h
new file mode 100644
index 0000000000000..f30c65d41478d
--- /dev/null
+++ b/flang/include/flang/Evaluate/real-value.h
@@ -0,0 +1,264 @@
+//===-- include/flang/Evaluate/real-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_REAL_VALUE_H_
+#define FORTRAN_EVALUATE_REAL_VALUE_H_
+
+#include "flang/Evaluate/integer-value.h"
+#include "flang/Evaluate/object-sizes.h"
+#include "flang/Evaluate/target.h"
+#include "llvm/Support/Compiler.h"
+
+// Some environments, viz. glibc 2.17 and *BSD, allow the macro HUGE
+// to leak out of <math.h>.
+#undef HUGE
+
+namespace Fortran::evaluate::value {
+class RealValueImpl;
+
+/// A 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 REAL kind it currently
+/// represents.
+///
+/// The implementation is hidden from this header using a pImpl-like idiom.
+class RealValue {
+public:
+  using Word = IntegerValue;
+
+  RealValue();
+  ~RealValue();
+  RealValue(const RealValue &);
+  RealValue(RealValue &&);
+  RealValue &operator=(const RealValue &);
+  RealValue &operator=(RealValue &&);
+
+  RealValue(int kind, const RealValue &v) : RealValue(v) {
+    CHECK(kind == v.kind());
+  }
+  RealValue(int kind, RealValue &&v) : RealValue(std::move(v)) {
+    CHECK(kind == v.kind());
+  }
+
+  /// Interpret w as the raw bit pattern for the given runtime kind.
+  RealValue(int kind, const Word &w);
+
+  /// Creates a floating-point value of a given kind from a host double,
+  /// rounded to the target kind's precision (per the default rounding mode).
+  /// Portable: does not assume that the host "double" shares any bit layout
+  /// with the target kind, only that <cmath>'s frexp()/ldexp() are available.
+  RealValue(int kind, double x);
+
+  /// Creates a floating-point with value +0.0 of a given kind. In contrast, the
+  /// default ctor creates a "monostate" that represents +0.0 of unknown kind.
+  static RealValue Zero(int kind);
+
+  /// Creates a floating-point with value -0.0 of a given kind.
+  static RealValue NegativeZero(int kind);
+
+  static RealValue Infinity(int kind, bool negative = false);
+
+  /// A signaling NaN, as opposed to the quiet NaN returned by NotANumber().
+  static RealValue SignalingNaN(int kind);
+
+  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) or
+  /// unknown value.
+  bool IsMonostate() const;
+
+  /// The kind of the value currently stored.
+  int kind() const;
+
+  int bits() const { return bits(kind()); }
+  static constexpr int bits(int kind) { return bytesStored(kind) * 8; }
+
+  /// Number of bytes accessed by FromRawBytes/StoreRawBytes
+  std::size_t bytesStored() const { return bytesStored(kind()); }
+  static constexpr std::size_t bytesStored(int kind) {
+    switch (kind) {
+    case 3:
+      return 2;
+    case 10:
+      return 16;
+    default:
+      return kind;
+    }
+  }
+
+  bool operator==(const RealValue &y) const;
+  bool operator!=(const RealValue &y) const { return !operator==(y); }
+
+  bool IsNegative() const;
+
+  bool IsNotANumber() const;
+
+  bool IsSignalingNaN() const;
+
+  bool IsInfinite() const;
+
+  bool IsFinite() const;
+
+  bool IsZero() const;
+
+  bool IsNormal() const;
+
+  RealValue ABS() const;
+
+  RealValue SetSign(bool toNegative) const;
+
+  RealValue SIGN(const RealValue &x) const;
+
+  RealValue Negate() const;
+
+  Relation Compare(const RealValue &y) const;
+
+  ValueWithRealFlags<RealValue> Add(const RealValue &y,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+
+  ValueWithRealFlags<RealValue> Subtract(const RealValue &y,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+
+  ValueWithRealFlags<RealValue> Multiply(const RealValue &y,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+
+  ValueWithRealFlags<RealValue> Divide(const RealValue &y,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+
+  ValueWithRealFlags<RealValue> SQRT(
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+
+  ///  NEAREST(), IEEE_NEXT_AFTER(), IEEE_NEXT_UP(), and IEEE_NEXT_DOWN()
+  ValueWithRealFlags<RealValue> NEAREST(bool upward) const;
+
+  /// HYPOT(x,y)=SQRT(x**2 + y**2) computed so as to avoid spurious
+  /// intermediate overflows.
+  ValueWithRealFlags<RealValue> HYPOT(const RealValue &y,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+
+  /// DIM(X,Y) = MAX(X-Y, 0)
+  ValueWithRealFlags<RealValue> DIM(const RealValue &y,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+
+  /// MOD(x,y) = x - AINT(x/y)*y (in the standard)
+  ValueWithRealFlags<RealValue> MOD(const RealValue &y,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+
+  /// MODULO(x,y) = x - FLOOR(x/y)*y (in the standard)
+  ValueWithRealFlags<RealValue> MODULO(const RealValue &y,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+
+  ValueWithRealFlags<RealValue> KahanSummation(const RealValue &y,
+      RealValue &correction,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+
+  IntegerValue EXPONENT() const;
+
+  static RealValue EPSILON(int kind);
+
+  static RealValue HUGE(int kind);
+
+  static RealValue TINY(int kind);
+
+  static int DIGITS(int kind);
+
+  static int PRECISION(int kind);
+
+  static int RANGE(int kind);
+
+  static int MAXEXPONENT(int kind);
+
+  static int MINEXPONENT(int kind);
+
+  RealValue RRSPACING() const;
+
+  RealValue SPACING() const;
+
+  RealValue SET_EXPONENT(std::int64_t e) const;
+
+  RealValue FRACTION() const;
+
+  /// SCALE(); also known as IEEE_SCALB and (in IEEE-754 '08) ScaleB.
+  ValueWithRealFlags<RealValue> SCALE(const IntegerValue &by,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+
+  RealValue FlushSubnormalToZero() const;
+
+  // TODO: Configurable NotANumber representations
+  static RealValue NotANumber(int kind);
+
+  static ValueWithRealFlags<RealValue> FromInteger(int kind,
+      const IntegerValue &n, bool isUnsigned = false,
+      Rounding rounding = TargetCharacteristics::defaultRounding);
+
+  /// Conversion to integer in the same real format (AINT(), ANINT())
+  ValueWithRealFlags<RealValue> ToWholeNumber(
+      common::RoundingMode mode = common::RoundingMode::ToZero) const;
+
+  /// Conversion to an integer (INT(), NINT(), FLOOR(), CEILING())
+  ValueWithRealFlags<IntegerValue> ToInteger(
+      common::RoundingMode mode = common::RoundingMode::ToZero,
+      int toBits = 0) const;
+
+  static ValueWithRealFlags<RealValue> Convert(int kind, const RealValue &from,
+      Rounding rounding = TargetCharacteristics::defaultRounding);
+
+  Word RawBits() const;
+
+  /// Extracts "raw" biased exponent field.
+  int Exponent() const;
+
+  static ValueWithRealFlags<RealValue> Read(int kind, const char *&pp,
+      Rounding rounding = TargetCharacteristics::defaultRounding);
+
+  std::string DumpHexadecimal() const;
+
+  /// Emits a character representation for an equivalent Fortran constant
+  /// or parenthesized constant expression that produces this value.
+  llvm::raw_ostream &AsFortran(
+      llvm::raw_ostream &o, int kind, bool minimal = false) const;
+
+  static RealValue FromRawBytes(
+      int kind, const void *raw, std::size_t expectedSize);
+
+  void StoreRawBytes(void *dst, size_t size, bool *changed = nullptr) const;
+
+private:
+  static RealValue FromImpl(const RealValueImpl &x);
+  static RealValue FromImpl(RealValueImpl &&x);
+  static ValueWithRealFlags<RealValue> FromImpl(
+      const ValueWithRealFlags<RealValueImpl> &x);
+  static ValueWithRealFlags<RealValue> FromImpl(
+      ValueWithRealFlags<RealValueImpl> &&x);
+
+  RealValueImpl &impl() { return *reinterpret_cast<RealValueImpl *>(this); }
+  const RealValueImpl &impl() const {
+    return *reinterpret_cast<const RealValueImpl *>(this);
+  }
+
+  [[maybe_unused]] alignas(
+      detail::kRealObjectAlign) char opaque_[detail::kRealObjectSize];
+};
+
+} // namespace Fortran::evaluate::value
+
+namespace llvm {
+/// For pretty printing in GTest
+inline raw_ostream &operator<<(
+    raw_ostream &os, const Fortran::evaluate::value::RealValue &v) {
+  v.print(os);
+  return os;
+}
+} // namespace llvm
+
+#endif // FORTRAN_EVALUATE_REAL_VALUE_H_
diff --git a/flang/include/flang/Evaluate/real.h b/flang/include/flang/Evaluate/real.h
index 391d4e057f134..b14f2b1935b9e 100644
--- a/flang/include/flang/Evaluate/real.h
+++ b/flang/include/flang/Evaluate/real.h
@@ -277,6 +277,12 @@ template <typename WORD, int PREC> class Real {
                 .IBSET(significandBits - 2)};
   }
 
+  // A signaling NaN: like NotANumber(), but with the most significant
+  // significand bit clear so that IsSignalingNaN() holds.
+  static constexpr Real SignalingNaN() {
+    return {Word{maxExponent}.SHIFTL(significandBits).IBSET(0)};
+  }
+
   static constexpr Real PositiveZero() { return Real{}; }
 
   static constexpr Real NegativeZero() { return {Word{}.MASKL(1)}; }
diff --git a/flang/include/flang/Evaluate/typekind-traits.h b/flang/include/flang/Evaluate/typekind-traits.h
new file mode 100644
index 0000000000000..184de18666e24
--- /dev/null
+++ b/flang/include/flang/Evaluate/typekind-traits.h
@@ -0,0 +1,95 @@
+//===-- include/flang/Evaluate/typekind-traits.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_TYPEKINDTRAITS_H_
+#define FORTRAN_EVALUATE_TYPEKINDTRAITS_H_
+
+#include "flang/Common/Fortran-consts.h"
+#include "flang/Evaluate/common.h"
+#include "flang/Evaluate/integer-value.h"
+#include "flang/Evaluate/real-value.h"
+
+namespace Fortran::evaluate::value {
+class CharacterValue;
+class IntegerValue;
+class ComplexValue;
+} // namespace Fortran::evaluate::value
+
+namespace Fortran::evaluate {
+
+template <common::TypeCategory CAT, int KIND> struct TypeKind;
+
+template <> struct TypeKind<common::TypeCategory::Character, 1> {
+  using CharT = char;
+  using StringT = std::basic_string<CharT>;
+  using Scalar = value::CharacterValue;
+  static constexpr int kind{1};
+};
+
+template <> struct TypeKind<common::TypeCategory::Character, 2> {
+  using CharT = char16_t;
+  using StringT = std::basic_string<CharT>;
+  using Scalar = value::CharacterValue;
+  static constexpr int kind{2};
+};
+
+template <> struct TypeKind<common::TypeCategory::Character, 4> {
+  using CharT = char32_t;
+  using StringT = std::basic_string<CharT>;
+  using Scalar = value::CharacterValue;
+  static constexpr int kind{4};
+};
+
+template <int KIND> struct TypeKind<common::TypeCategory::Integer, KIND> {
+  static constexpr int kind{KIND};
+  static constexpr int bits{value::IntegerValue::bits(KIND)};
+  using UnsignedT = common::HostUnsignedIntType<bits>;
+  using SignedT = common::HostSignedIntType<bits>;
+  using HostT = SignedT;
+  using Scalar = value::IntegerValue;
+};
+
+template <int KIND> struct TypeKind<common::TypeCategory::Unsigned, KIND> {
+  static constexpr int kind{KIND};
+  static constexpr int bits{value::IntegerValue::bits(KIND)};
+  using UnsignedT = common::HostUnsignedIntType<bits>;
+  using SignedT = common::HostSignedIntType<bits>;
+  using HostT = UnsignedT;
+  using Scalar = value::IntegerValue;
+};
+
+namespace detail {
+// Only REAL(4) and REAL(8) have a portable native host arithmetic type
+// (float and double, respectively); every other kind maps to void.
+template <int BITS> struct RealHostType {
+  using type = void;
+};
+template <> struct RealHostType<32> {
+  using type = float;
+};
+template <> struct RealHostType<64> {
+  using type = double;
+};
+} // namespace detail
+
+template <int KIND> struct TypeKind<common::TypeCategory::Real, KIND> {
+  static constexpr int kind{KIND};
+  static constexpr int bits{value::RealValue::bits(KIND)};
+  using UnsignedT = common::HostUnsignedIntType<bits>;
+  using SignedT = common::HostSignedIntType<bits>;
+  using HostT = typename detail::RealHostType<bits>::type;
+  using Scalar = value::RealValue;
+};
+
+template <int KIND> struct TypeKind<common::TypeCategory::Complex, KIND> {
+  static constexpr int kind{KIND};
+  using Scalar = value::ComplexValue;
+};
+
+} // namespace Fortran::evaluate
+#endif // FORTRAN_EVALUATE_TYPEKINDTRAITS_H_
diff --git a/flang/lib/Evaluate/CMakeLists.txt b/flang/lib/Evaluate/CMakeLists.txt
index 472ecb6d8d079..fc5bbda50461e 100644
--- a/flang/lib/Evaluate/CMakeLists.txt
+++ b/flang/lib/Evaluate/CMakeLists.txt
@@ -30,10 +30,13 @@ endif ()
 
 add_flang_library(FortranEvaluate
   call.cpp
+  character-value.cpp
+  character-value-impl.cpp
   characteristics.cpp
   check-expression.cpp
   common.cpp
   complex.cpp
+  complex-value.cpp
   constant.cpp
   expression.cpp
   fold.cpp
@@ -48,10 +51,15 @@ add_flang_library(FortranEvaluate
   host.cpp
   initial-image.cpp
   integer.cpp
+  integer-value.cpp
+  integer-value-impl.cpp
   intrinsics.cpp
   intrinsics-library.cpp
   logical.cpp
+  logical-value.cpp
   real.cpp
+  real-value.cpp
+  real-value-impl.cpp
   shape.cpp
   static-data.cpp
   target.cpp
diff --git a/flang/lib/Evaluate/character-value-impl.cpp b/flang/lib/Evaluate/character-value-impl.cpp
new file mode 100644
index 0000000000000..44f5b1c46d0b5
--- /dev/null
+++ b/flang/lib/Evaluate/character-value-impl.cpp
@@ -0,0 +1,615 @@
+//===-- lib/Evaluate/character-value-impl.cpp -----------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "character-value-impl.h"
+#include "flang/Common/idioms.h"
+#include "flang/Evaluate/common.h"
+#include "llvm/Support/ErrorHandling.h"
+#include <algorithm>
+#include <cstring>
+
+namespace Fortran::evaluate::value {
+
+CharacterValueImpl::CharacterValueImpl(int kind, std::size_t n, char32_t c) {
+  withCharProto(kind, [this, n, c](auto ct) {
+    using CharT = std::decay_t<decltype(ct)>;
+    storage_ = std::basic_string<CharT>(n, static_cast<CharT>(c));
+  });
+}
+
+CharacterValueImpl CharacterValueImpl::Zero(int kind) {
+  return withCharProto(kind, [kind](auto c) {
+    using Char = std::decay_t<decltype(c)>;
+    return CharacterValueImpl{kind, std::basic_string<Char>{}};
+  });
+}
+
+CharacterValueImpl CharacterValueImpl::FromRawBytes(
+    int kind, const void *raw, size_t size) {
+  return withCharProto(kind, [kind, raw, size](auto charProto) {
+    using CharT = decltype(charProto);
+    CHECK(size % sizeof(CharT) == 0);
+    std::basic_string<CharT> s;
+    if (size > 0) {
+      s.assign(static_cast<const CharT *>(raw), size / sizeof(CharT));
+    }
+    return CharacterValueImpl{kind, std::move(s)};
+  });
+}
+
+void CharacterValueImpl::print(llvm::raw_ostream &os) const {
+  os << kind() << '_';
+  withStdString(
+      [&](const auto &s) { os << parser::QuoteCharacterLiteral(s, true); });
+}
+
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+LLVM_DUMP_METHOD void CharacterValueImpl::dump() const {
+  print(llvm::errs());
+  llvm::errs() << '\n';
+}
+#endif
+
+std::size_t CharacterValueImpl::charSize() const {
+  return common::visit(
+      [](const auto &s) -> std::size_t {
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          llvm_unreachable("operation not supported on uninitialized value");
+        } else {
+          return sizeof(typename std::decay_t<decltype(s)>::value_type);
+        }
+      },
+      storage_);
+}
+
+std::size_t CharacterValueImpl::size() const {
+  return common::visit(
+      [](const auto &s) -> std::size_t {
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          return 0;
+        } else {
+          return s.size();
+        }
+      },
+      storage_);
+}
+
+void *CharacterValueImpl::charData() {
+  return common::visit(
+      [](auto &s) -> void * {
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          // No data available in monostate
+          return nullptr;
+        } else {
+          return static_cast<void *>(s.data());
+        }
+      },
+      storage_);
+}
+
+const void *CharacterValueImpl::charData() const {
+  return common::visit(
+      [](const auto &s) -> const void * {
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          // No data available in monostate
+          return nullptr;
+        } else {
+          return static_cast<const void *>(s.data());
+        }
+      },
+      storage_);
+}
+
+Ordering CharacterValueImpl::Compare(const CharacterValueImpl &y) const {
+  return common::visit(
+      [](const auto &xs, const auto &ys) -> Ordering {
+        using XS = std::decay_t<decltype(xs)>;
+        using YS = std::decay_t<decltype(ys)>;
+
+        // monostate represents an empty string of any type; here it is
+        // polymorhpic to what it is compared to
+        if constexpr (std::is_same_v<XS, YS>) {
+          return Fortran::evaluate::Compare(xs, ys);
+        } else if constexpr (std::is_same_v<XS, std::monostate> &&
+            !std::is_same_v<YS, std::monostate>) {
+          return Fortran::evaluate::Compare(YS{}, ys);
+        } else if constexpr (!std::is_same_v<XS, std::monostate> &&
+            std::is_same_v<YS, std::monostate>) {
+          return Fortran::evaluate::Compare(xs, XS{});
+        } else {
+          llvm_unreachable("character comparison across differing kinds");
+        }
+      },
+      this->storage_, y.storage_);
+}
+
+bool CharacterValueImpl::operator<(const CharacterValueImpl &y) const {
+  return common::visit(
+      [](const auto &xs, const auto &ys) -> bool {
+        using XS = std::decay_t<decltype(xs)>;
+        using YS = std::decay_t<decltype(ys)>;
+
+        // monostate represents an empty string of any type; here it is
+        // polymorphic to what it is compared to
+        if constexpr (std::is_same_v<XS, YS>) {
+          return xs < ys;
+        } else if constexpr (std::is_same_v<XS, std::monostate> &&
+            !std::is_same_v<YS, std::monostate>) {
+          return YS{} < ys;
+        } else if constexpr (!std::is_same_v<XS, std::monostate> &&
+            std::is_same_v<YS, std::monostate>) {
+          return xs < XS{};
+        } else {
+          llvm_unreachable("character comparison across differing kinds");
+        }
+      },
+      this->storage_, y.storage_);
+}
+
+bool CharacterValueImpl::operator==(const CharacterValueImpl &y) const {
+  return common::visit(
+      [](const auto &xs, const auto &ys) -> bool {
+        using XS = std::decay_t<decltype(xs)>;
+        using YS = std::decay_t<decltype(ys)>;
+
+        // monostate represents an empty string of any type; here it is
+        // polymorhpic to what it is compared to
+        if constexpr (std::is_same_v<XS, YS>) {
+          return xs == ys;
+        } else if constexpr (std::is_same_v<XS, std::monostate> &&
+            !std::is_same_v<YS, std::monostate>) {
+          return YS{} == ys;
+        } else if constexpr (!std::is_same_v<XS, std::monostate> &&
+            std::is_same_v<YS, std::monostate>) {
+          return xs == XS{};
+        } else {
+          llvm_unreachable("character comparison across differing kinds");
+        }
+      },
+      this->storage_, y.storage_);
+}
+
+void CharacterValueImpl::assign(int kind, std::size_t n, char32_t c) {
+  return withCharProto(kind, [this, n, c](auto ct) {
+    using CharT = decltype(ct);
+    storage_ = std::basic_string<CharT>(n, static_cast<CharT>(c));
+  });
+}
+
+void CharacterValueImpl::erase(std::size_t pos) {
+  common::visit(
+      [pos](auto &s) {
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          llvm_unreachable("operation not supported on uninitialized value");
+        } else {
+          s.erase(pos);
+        }
+      },
+      storage_);
+}
+
+void CharacterValueImpl::append(std::size_t n, char32_t c) {
+  common::visit(
+      [n, c](auto &s) {
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          llvm_unreachable("operation not supported on uninitialized value");
+        } else {
+          using CharT = typename std::decay_t<decltype(s)>::value_type;
+          s.append(n, static_cast<CharT>(c));
+        }
+      },
+      storage_);
+}
+
+CharacterValueImpl &CharacterValueImpl::replace(
+    std::size_t pos, std::size_t len, const CharacterValueImpl &other) {
+  common::visit(
+      [pos, len](auto &s, const auto &o) {
+        if constexpr (!std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate> &&
+            !std::is_same_v<std::decay_t<decltype(o)>, std::monostate> &&
+            std::is_same_v<std::decay_t<decltype(s)>,
+                std::decay_t<decltype(o)>>) {
+          s.replace(pos, len, o);
+        } else {
+          llvm_unreachable("operation not supported on uninitialized value or "
+                           "values of different kinds");
+        }
+      },
+      storage_, other.storage_);
+  return *this;
+}
+
+CharacterValueImpl CharacterValueImpl::substr(std::size_t pos) const {
+  return common::visit(
+      [pos](const auto &s) -> CharacterValueImpl {
+        using StringT = std::decay_t<decltype(s)>;
+        if constexpr (std::is_same_v<StringT, std::monostate>) {
+          llvm_unreachable("operation not supported on uninitialized value");
+        } else {
+          return CharacterValueImpl{
+              sizeof(typename StringT::value_type), s.substr(pos)};
+        }
+      },
+      storage_);
+}
+
+CharacterValueImpl CharacterValueImpl::substr(
+    std::size_t pos, std::size_t len) const {
+  return common::visit(
+      [pos, len](const auto &s) -> CharacterValueImpl {
+        using StringT = std::decay_t<decltype(s)>;
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          llvm_unreachable("operation not supported on uninitialized value");
+        } else {
+          return CharacterValueImpl{
+              sizeof(typename StringT::value_type), s.substr(pos, len)};
+        }
+      },
+      storage_);
+}
+
+std::optional<llvm::StringRef> CharacterValueImpl::AsStringRef() const {
+  if (IsMonostate()) {
+    return llvm::StringRef{};
+  }
+  if (const auto *s{std::get_if<std::string>(&storage_)}) {
+    return *s;
+  }
+  return std::nullopt;
+}
+
+/// Return the string as std::string if kind==1, or nullopt otherwise.
+std::optional<std::string> CharacterValueImpl::AsStdString() const {
+  if (IsMonostate()) {
+    return std::string{};
+  }
+
+  if (const auto *s{std::get_if<std::string>(&storage_)}) {
+    return *s;
+  } else {
+    return std::nullopt;
+  }
+}
+
+std::optional<std::u16string> CharacterValueImpl::AsU16String() const {
+  if (IsMonostate()) {
+    return std::u16string{};
+  }
+
+  if (const auto *s{std::get_if<std::u16string>(&storage_)}) {
+    return *s;
+  } else {
+    return std::nullopt;
+  }
+}
+
+std::optional<std::u32string> CharacterValueImpl::AsU32String() const {
+  if (IsMonostate()) {
+    return std::u32string{};
+  }
+
+  if (const auto *s{std::get_if<std::u32string>(&storage_)}) {
+    return *s;
+  } else {
+    return std::nullopt;
+  }
+}
+
+std::string CharacterValueImpl::ToStdString() const {
+  return common::visit(
+      [](const auto &s) {
+        using StringT = std::decay_t<decltype(s)>;
+        if constexpr (std::is_same_v<StringT, std::monostate>) {
+          return std::string{};
+        } else if constexpr (std::is_same_v<StringT, std::string>) {
+          return s;
+        } else {
+          std::string result(s.size(), '\0');
+          for (auto [i, c] : llvm::enumerate(s)) {
+            result[i] = c;
+          }
+          return result;
+        }
+      },
+      storage_);
+}
+
+CharacterValueImpl CharacterValueImpl::ToAscii(int kind) const {
+  if (IsMonostate()) {
+    return Zero(kind);
+  }
+
+  return withStdString([kind](const auto &s) -> CharacterValueImpl {
+    return withCharProto(kind, [&s](auto ct) -> CharacterValueImpl {
+      using CharT = std::decay_t<decltype(ct)>;
+      using StringT = std::basic_string<CharT>;
+      // Fortran character conversion is well defined between distinct kinds
+      // only when the actual characters are valid 7-bit ASCII.
+      StringT str;
+      for (auto iter{s.cbegin()}; iter != s.cend(); ++iter) {
+        if (static_cast<std::uint64_t>(*iter) > 127) {
+          return Zero(sizeof(ct));
+        }
+        str.push_back(static_cast<CharT>(*iter));
+      }
+      return CharacterValueImpl{sizeof(CharT), str};
+    });
+  });
+}
+
+void CharacterValueImpl::reserve(std::size_t n) {
+  common::visit(
+      [n](auto &s) {
+        if constexpr (!std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          s.reserve(n);
+        }
+      },
+      storage_);
+}
+
+char32_t CharacterValueImpl::operator[](std::size_t i) const {
+  return common::visit(
+      [i](const auto &s) -> char32_t {
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          llvm_unreachable("operation not supported on uninitialized value");
+        } else {
+          return static_cast<char32_t>(s[i]);
+        }
+        return 0;
+      },
+      storage_);
+}
+
+CharacterValueImpl CharacterValueImpl::operator+(
+    const CharacterValueImpl &y) const {
+  return common::visit(
+      [](const auto &a, const auto &b) -> CharacterValueImpl {
+        if constexpr (std::is_same_v<std::decay_t<decltype(a)>,
+                          std::decay_t<decltype(b)>> &&
+            !std::is_same_v<std::decay_t<decltype(a)>, std::monostate>) {
+          using StringT = std::decay_t<decltype(a)>;
+          return CharacterValueImpl{
+              sizeof(typename StringT::value_type), a + b};
+        } else {
+          llvm_unreachable("operation not supported on uninitialized value or "
+                           "values of different kinds");
+        }
+        return CharacterValueImpl{};
+      },
+      storage_, y.storage_);
+}
+
+CharacterValueImpl &CharacterValueImpl::operator+=(
+    const CharacterValueImpl &y) {
+  common::visit(
+      [](auto &a, const auto &b) {
+        if constexpr (std::is_same_v<std::decay_t<decltype(a)>,
+                          std::decay_t<decltype(b)>> &&
+            !std::is_same_v<std::decay_t<decltype(a)>, std::monostate>) {
+          a += b;
+        } else {
+          llvm_unreachable("operation not supported on uninitialized value or "
+                           "values of different kinds");
+        }
+      },
+      storage_, y.storage_);
+  return *this;
+}
+
+CharacterValueImpl &CharacterValueImpl::operator+=(char c) {
+  common::visit(
+      [c](auto &s) {
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          llvm_unreachable("operation not supported on uninitialized value");
+        } else {
+          using CharT = typename std::decay_t<decltype(s)>::value_type;
+          s.push_back(static_cast<CharT>(c));
+        }
+      },
+      storage_);
+  return *this;
+}
+
+std::size_t CharacterValueImpl::find_first_not_of(char32_t c) const {
+  return common::visit(
+      [c](const auto &s) -> std::size_t {
+        if constexpr (!std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          using CharT = typename std::decay_t<decltype(s)>::value_type;
+          return s.find_first_not_of(static_cast<CharT>(c));
+        } else {
+          llvm_unreachable("Unsupported combination of character kinds");
+          return std::string::npos;
+        }
+      },
+      storage_);
+}
+
+std::size_t CharacterValueImpl::find_last_not_of(char32_t c) const {
+  return common::visit(
+      [c](const auto &s) -> std::size_t {
+        if constexpr (!std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          using CharT = typename std::decay_t<decltype(s)>::value_type;
+          return s.find_last_not_of(static_cast<CharT>(c));
+        } else {
+          llvm_unreachable("Unsupported combination of character kinds");
+          return std::string::npos;
+        }
+      },
+      storage_);
+}
+
+std::size_t CharacterValueImpl::find_first_not_of(
+    const CharacterValueImpl &set) const {
+  return common::visit(
+      [](const auto &s, const auto &p) -> std::size_t {
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          // Nothing to find in an empty string
+          return std::string::npos;
+        } else if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                                 std::decay_t<decltype(p)>> &&
+            !std::is_same_v<std::decay_t<decltype(s)>, std::monostate>) {
+          return s.find_first_not_of(p);
+        } else {
+          llvm_unreachable("Unsupported combination of character kinds");
+          return std::string::npos;
+        }
+      },
+      storage_, set.storage_);
+}
+
+std::size_t CharacterValueImpl::find_last_not_of(
+    const CharacterValueImpl &set) const {
+  return common::visit(
+      [](const auto &s, const auto &p) -> std::size_t {
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          // Nothing to find in an empty string
+          return std::string::npos;
+        } else if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                                 std::decay_t<decltype(p)>> &&
+            !std::is_same_v<std::decay_t<decltype(s)>, std::monostate>) {
+          return s.find_last_not_of(p);
+        } else {
+          llvm_unreachable("Unsupported combination of character kinds");
+          return std::string::npos;
+        }
+      },
+      storage_, set.storage_);
+}
+
+std::size_t CharacterValueImpl::find(const CharacterValueImpl &pattern) const {
+  return common::visit(
+      [](const auto &s, const auto &p) -> std::size_t {
+        if constexpr (std::is_same_v<std::decay_t<decltype(p)>,
+                          std::monostate>) {
+          // Empty string always matches beginning
+          return 0;
+        } else if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                                 std::monostate>) {
+          // Nothing to find in an empty string, unless the pattern is itself an
+          // empty string
+          return p.empty() ? 0 : std::string::npos;
+        } else if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                                 std::decay_t<decltype(p)>> &&
+            !std::is_same_v<std::decay_t<decltype(s)>, std::monostate>) {
+          return s.find(p);
+        } else {
+          llvm_unreachable("Unsupported combination of character kinds");
+          return std::string::npos;
+        }
+      },
+      storage_, pattern.storage_);
+}
+
+std::size_t CharacterValueImpl::rfind(const CharacterValueImpl &pattern) const {
+  return common::visit(
+      [](const auto &s, const auto &p) -> std::size_t {
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          // Nothing to find in an empty string
+          return std::string::npos;
+        } else if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                                 std::decay_t<decltype(p)>> &&
+            !std::is_same_v<std::decay_t<decltype(s)>, std::monostate>) {
+          return s.rfind(p);
+        }
+        llvm_unreachable("Unsupported combination of character kinds");
+        return std::string::npos;
+      },
+      storage_, pattern.storage_);
+}
+
+std::size_t CharacterValueImpl::find_first_of(
+    const CharacterValueImpl &set) const {
+  return common::visit(
+      [](const auto &s, const auto &p) -> std::size_t {
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          // Nothing to find in an empty string
+          return std::string::npos;
+        } else if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                                 std::decay_t<decltype(p)>> &&
+            !std::is_same_v<std::decay_t<decltype(s)>, std::monostate>) {
+          return s.find_first_of(p);
+        } else {
+          llvm_unreachable("Unsupported combination of character kinds");
+          return std::string::npos;
+        }
+      },
+      storage_, set.storage_);
+}
+
+std::size_t CharacterValueImpl::find_last_of(
+    const CharacterValueImpl &set) const {
+  return common::visit(
+      [](const auto &s, const auto &p) -> std::size_t {
+        if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                          std::monostate>) {
+          // Nothing to find in an empty string
+          return std::string::npos;
+        } else if constexpr (std::is_same_v<std::decay_t<decltype(s)>,
+                                 std::decay_t<decltype(p)>> &&
+            !std::is_same_v<std::decay_t<decltype(s)>, std::monostate>) {
+          return s.find_last_of(p);
+        } else {
+          llvm_unreachable("Unsupported combination of character kinds");
+          return std::string::npos;
+        }
+      },
+      storage_, set.storage_);
+}
+
+void CharacterValueImpl::StoreRawBytes(
+    void *dst, std::size_t size, bool *changed) const {
+  common::visit(
+      [&](const auto &word) {
+        if constexpr (std::is_same_v<std::decay_t<decltype(word)>,
+                          std::monostate>) {
+          CHECK(size == 0);
+          // Nothing to store
+        } else {
+          using Character = std::decay_t<decltype(word)>;
+          using CharT = typename Character::value_type;
+          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), static_cast<CharT>(' '));
+
+            if (changed) {
+              if (std::memcmp(dst, strWithPadding.data(), size) == 0) {
+                return;
+              }
+              *changed = true;
+            }
+            std::memcpy(dst, strWithPadding.data(), size);
+          }
+        }
+      },
+      storage_);
+}
+
+} // namespace Fortran::evaluate::value
diff --git a/flang/lib/Evaluate/character-value-impl.h b/flang/lib/Evaluate/character-value-impl.h
new file mode 100644
index 0000000000000..9e51587e657c0
--- /dev/null
+++ b/flang/lib/Evaluate/character-value-impl.h
@@ -0,0 +1,256 @@
+//===-- include/flang/Evaluate/character-value-impl.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_IMPL_H_
+#define FORTRAN_EVALUATE_CHARACTER_VALUE_IMPL_H_
+
+#include "flang/Evaluate/common.h"
+#include "llvm/Support/ErrorHandling.h"
+#include <cstddef>
+#include <optional>
+#include <string>
+#include <utility>
+#include <variant>
+
+namespace Fortran::evaluate::value {
+
+class CharacterValueImpl {
+  using Storage =
+      std::variant<std::monostate, std::string, std::u16string, std::u32string>;
+
+public:
+  // rule-of-five
+  ~CharacterValueImpl() = default;
+  CharacterValueImpl(const CharacterValueImpl &) = default;
+  CharacterValueImpl(CharacterValueImpl &&) = default;
+  CharacterValueImpl &operator=(const CharacterValueImpl &) = default;
+  CharacterValueImpl &operator=(CharacterValueImpl &&) = default;
+
+  CharacterValueImpl() = default;
+  explicit CharacterValueImpl(int kind, std::string s) {
+    withCharProto(kind, [&](auto c) {
+      using CharT = std::decay_t<decltype(c)>;
+      using StringT = std::basic_string<CharT>;
+      if (std::is_same_v<StringT, std::string>) {
+        storage_ = std::move(s);
+      } else {
+        StringT buf;
+        buf.resize(s.length());
+        for (auto [i, c] : llvm::enumerate(s)) {
+          buf[i] = c;
+        }
+        storage_ = std::move(buf);
+      }
+    });
+
+    CHECK(this->kind() == kind);
+  }
+
+  explicit CharacterValueImpl(int kind, std::u16string s)
+      : storage_{std::move(s)} {
+    CHECK(kind == 2);
+    CHECK(this->kind() == kind);
+  }
+
+  explicit CharacterValueImpl(int kind, std::u32string s)
+      : storage_{std::move(s)} {
+    CHECK(kind == 4);
+    CHECK(this->kind() == kind);
+  }
+
+  /// Fill constructors: create a string of n copies of the given character.
+  CharacterValueImpl(int kind, std::size_t n, char32_t c);
+
+  static CharacterValueImpl Zero(int kind);
+
+  static CharacterValueImpl 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
+
+  std::optional<llvm::StringRef> AsStringRef() const;
+
+  /// Return the string as std::string if kind==1, or nullopt otherwise.
+  std::optional<std::string> AsStdString() const;
+  std::optional<std::u16string> AsU16String() const;
+  std::optional<std::u32string> AsU32String() const;
+
+  std::string ToStdString() const;
+
+  bool IsMonostate() const { return storage_.index() == 0; }
+  int kind() const {
+    return withCharProto([](auto ct) { return sizeof(ct); });
+  }
+
+  /// Byte size of one character unit (1, 2, or 4).
+  std::size_t charSize() const;
+
+  /// Number of characters in this string.
+  std::size_t size() const;
+
+  /// String length (synonym for size()).
+  std::size_t length() const { return size(); }
+
+  /// True when the string is empty.
+  bool empty() const { return size() == 0; }
+
+  /// Raw byte pointer to the underlying character data.
+  void *data() { return charData(); }
+  const void *data() const { return charData(); }
+  void *charData();
+  const void *charData() const;
+
+  // Comparison operators
+  Ordering Compare(const CharacterValueImpl &y) const;
+  bool operator<(const CharacterValueImpl &y) const;
+  bool operator<=(const CharacterValueImpl &y) const { return !(y < *this); }
+  bool operator==(const CharacterValueImpl &y) const;
+  bool operator!=(const CharacterValueImpl &y) const { return !(*this == y); }
+  bool operator>=(const CharacterValueImpl &y) const { return !(*this < y); }
+  bool operator>(const CharacterValueImpl &y) const { return y < *this; }
+
+  /// Assign n copies of the given character.
+  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) { storage_ = std::string(p, n); }
+  void assign(const char16_t *p, std::size_t n) {
+    storage_ = std::u16string(p, n);
+  }
+  void assign(const char32_t *p, std::size_t n) {
+    storage_ = std::u32string(p, n);
+  }
+
+  /// Erase from position pos to end.
+  void erase(std::size_t pos);
+
+  /// Append n copies of the given character.
+  void append(std::size_t n, char32_t c);
+
+  /// Replace the substring [pos, pos+len) with characters from other.
+  CharacterValueImpl &replace(
+      std::size_t pos, std::size_t len, const CharacterValueImpl &other);
+
+  /// Return a suffix starting at pos.
+  CharacterValueImpl substr(std::size_t pos) const;
+
+  /// Return a substring of len characters starting at pos.
+  CharacterValueImpl substr(std::size_t pos, std::size_t len) const;
+
+  CharacterValueImpl ToAscii(int kind) const;
+
+  /// Reserve storage for at least n characters.
+  void reserve(std::size_t n);
+
+  /// Return the character at position i as char32_t (safe for all kinds).
+  char32_t operator[](std::size_t i) const;
+
+  /// Concatenate two same-kind strings.
+  CharacterValueImpl operator+(const CharacterValueImpl &y) const;
+
+  /// Append another same-kind string.
+  CharacterValueImpl &operator+=(const CharacterValueImpl &y);
+
+  /// Append a character, converting it to the string's element type.
+  CharacterValueImpl &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_first_not_of(char c) const {
+    return find_first_not_of(static_cast<char32_t>(c));
+  }
+  std::size_t find_first_not_of(char16_t c) const {
+    return find_first_not_of(static_cast<char32_t>(c));
+  }
+  std::size_t find_first_not_of(char32_t c) const;
+  std::size_t find_last_not_of(char c) const {
+    return find_last_not_of(static_cast<char32_t>(c));
+  }
+  std::size_t find_last_not_of(char16_t c) const {
+    return find_last_not_of(static_cast<char32_t>(c));
+  }
+  std::size_t find_last_not_of(char32_t c) const;
+  std::size_t find_first_not_of(const CharacterValueImpl &set) const;
+  std::size_t find_last_not_of(const CharacterValueImpl &set) const;
+  std::size_t find(const CharacterValueImpl &pattern) const;
+  std::size_t rfind(const CharacterValueImpl &pattern) const;
+  std::size_t find_first_of(const CharacterValueImpl &set) const;
+  std::size_t find_last_of(const CharacterValueImpl &set) const;
+
+  void StoreRawBytes(
+      void *dst, std::size_t size, bool *changed = nullptr) const;
+
+  // Compile-time dispatchers to current/specified kind
+
+  template <typename F>
+  auto withCharProto(F &&f) const
+      -> decltype(std::declval<F>()(std::declval<char>())) {
+    switch (storage_.index()) {
+    case 1:
+      return f(char{});
+    case 2:
+      return f(char16_t{});
+    case 3:
+      return f(char32_t{});
+    default:
+      llvm_unreachable("unsupported character kind/monostate");
+    }
+  }
+
+  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>
+  auto withStdString(F &&f) const
+      -> decltype(std::declval<F>()(std::declval<const std::string &>())) {
+    switch (storage_.index()) {
+    case 1:
+      return f(std::get<std::string>(storage_));
+    case 2:
+      return f(std::get<std::u16string>(storage_));
+    case 3:
+      return f(std::get<std::u32string>(storage_));
+    default:
+      llvm_unreachable("operation on uninitialized CharacterValue");
+    }
+  }
+
+private:
+  Storage storage_;
+};
+
+} // namespace Fortran::evaluate::value
+
+namespace llvm {
+/// For pretty printing in GTest
+inline raw_ostream &operator<<(
+    raw_ostream &os, const Fortran::evaluate::value::CharacterValueImpl &v) {
+  v.print(os);
+  return os;
+}
+} // namespace llvm
+
+#endif // FORTRAN_EVALUATE_CHARACTER_VALUE_IMPL_H_
diff --git a/flang/lib/Evaluate/character-value.cpp b/flang/lib/Evaluate/character-value.cpp
new file mode 100644
index 0000000000000..303fa80b175e6
--- /dev/null
+++ b/flang/lib/Evaluate/character-value.cpp
@@ -0,0 +1,221 @@
+//===-- lib/Evaluate/character-value.cpp ----------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "flang/Evaluate/character-value.h"
+#include "character-value-impl.h"
+#include "flang/Evaluate/common.h"
+#include "llvm/Support/ErrorHandling.h"
+#include <new>
+#include <string>
+
+namespace Fortran::evaluate::value {
+static_assert(sizeof(CharacterValueImpl) == detail::kCharacterObjectSize);
+static_assert(alignof(CharacterValueImpl) == detail::kCharacterObjectAlign);
+static_assert(sizeof(CharacterValue) == sizeof(CharacterValueImpl));
+static_assert(alignof(CharacterValue) == alignof(CharacterValueImpl));
+
+CharacterValue::CharacterValue() { new (this) CharacterValueImpl(); }
+
+CharacterValue::~CharacterValue() { impl().~CharacterValueImpl(); }
+
+CharacterValue::CharacterValue(const CharacterValue &x) {
+  new (this) CharacterValueImpl(x.impl());
+}
+
+CharacterValue::CharacterValue(CharacterValue &&x) {
+  new (this) CharacterValueImpl(std::move(x.impl()));
+}
+
+CharacterValue &CharacterValue::operator=(const CharacterValue &x) {
+  impl() = x.impl();
+  return *this;
+}
+
+CharacterValue &CharacterValue::operator=(CharacterValue &&x) {
+  impl() = std::move(x.impl());
+  return *this;
+}
+
+CharacterValue::CharacterValue(int kind, std::string s) {
+  new (this) CharacterValueImpl(kind, std::move(s));
+}
+
+CharacterValue::CharacterValue(int kind, std::u16string s) {
+  CHECK(kind == 2);
+  new (this) CharacterValueImpl(kind, std::move(s));
+}
+
+CharacterValue::CharacterValue(int kind, std::u32string s) {
+  CHECK(kind == 4);
+  new (this) CharacterValueImpl(kind, std::move(s));
+}
+
+CharacterValue::CharacterValue(int kind, std::size_t n, char32_t c) {
+  new (this) CharacterValueImpl(kind, n, c);
+}
+
+CharacterValue CharacterValue::Zero(int kind) {
+  return FromImpl(CharacterValueImpl::Zero(kind));
+}
+
+CharacterValue CharacterValue::FromRawBytes(
+    int kind, const void *raw, size_t byteSize) {
+  return FromImpl(CharacterValueImpl::FromRawBytes(kind, raw, byteSize));
+}
+
+void CharacterValue::print(llvm::raw_ostream &os) const { impl().print(os); }
+
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+LLVM_DUMP_METHOD void CharacterValue::dump() const { impl().dump(); }
+#endif
+
+bool CharacterValue::IsMonostate() const { return impl().IsMonostate(); }
+
+bool CharacterValue::empty() const { return impl().empty(); }
+
+std::size_t CharacterValue::size() const { return impl().size(); }
+
+int CharacterValue::kind() const { return impl().kind(); }
+
+std::optional<llvm::StringRef> CharacterValue::AsStringRef() const {
+  return impl().AsStringRef();
+}
+
+std::optional<std::u16string> CharacterValue::AsU16String() const {
+  return impl().AsU16String();
+}
+
+std::optional<std::u32string> CharacterValue::AsU32String() const {
+  return impl().AsU32String();
+}
+
+std::string CharacterValue::ToStdString() const { return impl().ToStdString(); }
+
+Ordering CharacterValue::Compare(const CharacterValue &y) const {
+  return impl().Compare(y.impl());
+}
+
+bool CharacterValue::operator<(const CharacterValue &y) const {
+  return impl() < y.impl();
+}
+
+bool CharacterValue::operator==(const CharacterValue &y) const {
+  return impl() == y.impl();
+}
+
+CharacterValue CharacterValue::ToAscii(int kind) const {
+  return FromImpl(impl().ToAscii(kind));
+}
+
+void CharacterValue::assign(int kind, std::size_t n, char32_t c) {
+  impl().assign(kind, n, c);
+}
+
+void CharacterValue::assign(const char *p, std::size_t n) {
+  impl().assign(p, n);
+}
+
+void CharacterValue::assign(const char16_t *p, std::size_t n) {
+  impl().assign(p, n);
+}
+
+void CharacterValue::assign(const char32_t *p, std::size_t n) {
+  impl().assign(p, n);
+}
+
+void CharacterValue::erase(std::size_t pos) { impl().erase(pos); }
+
+void CharacterValue::append(std::size_t n, char32_t c) { impl().append(n, c); }
+
+CharacterValue &CharacterValue::replace(
+    std::size_t pos, std::size_t len, const CharacterValue &other) {
+  impl().replace(pos, len, other.impl());
+  return *this;
+}
+
+CharacterValue CharacterValue::substr(std::size_t pos) const {
+  return FromImpl(impl().substr(pos));
+}
+
+CharacterValue CharacterValue::substr(std::size_t pos, std::size_t len) const {
+  return FromImpl(impl().substr(pos, len));
+}
+
+void CharacterValue::reserve(std::size_t n) { impl().reserve(n); }
+
+char32_t CharacterValue::operator[](std::size_t i) const {
+  return impl().operator[](i);
+}
+
+CharacterValue CharacterValue::operator+(const CharacterValue &y) const {
+  return FromImpl(impl() + y.impl());
+}
+
+CharacterValue &CharacterValue::operator+=(const CharacterValue &y) {
+  impl() += y.impl();
+  return *this;
+}
+
+CharacterValue &CharacterValue::operator+=(char c) {
+  impl() += c;
+  return *this;
+}
+
+std::size_t CharacterValue::find(const CharacterValue &pattern) const {
+  return impl().find(pattern.impl());
+}
+
+std::size_t CharacterValue::rfind(const CharacterValue &pattern) const {
+  return impl().rfind(pattern.impl());
+}
+
+std::size_t CharacterValue::find_first_of(const CharacterValue &set) const {
+  return impl().find_first_of(set.impl());
+}
+
+std::size_t CharacterValue::find_last_of(const CharacterValue &set) const {
+  return impl().find_last_of(set.impl());
+}
+
+std::size_t CharacterValue::find_first_not_of(char32_t c) const {
+  return impl().find_first_not_of(c);
+}
+
+std::size_t CharacterValue::find_last_not_of(char32_t c) const {
+  return impl().find_last_not_of(c);
+}
+
+std::size_t CharacterValue::find_first_not_of(const CharacterValue &set) const {
+  return impl().find_first_not_of(set.impl());
+}
+
+std::size_t CharacterValue::find_last_not_of(const CharacterValue &set) const {
+  return impl().find_last_not_of(set.impl());
+}
+
+void *CharacterValue::data() { return impl().data(); }
+const void *CharacterValue::data() const { return impl().data(); }
+
+void CharacterValue::StoreRawBytes(
+    void *dst, size_t size, bool *changed) const {
+  impl().StoreRawBytes(dst, size, changed);
+}
+
+CharacterValue CharacterValue::FromImpl(const CharacterValueImpl &y) {
+  CharacterValue result;
+  result.impl() = y;
+  return result;
+}
+
+CharacterValue CharacterValue::FromImpl(CharacterValueImpl &&y) {
+  CharacterValue result;
+  result.impl() = std::move(y);
+  return result;
+}
+
+} // namespace Fortran::evaluate::value
diff --git a/flang/lib/Evaluate/complex-value.cpp b/flang/lib/Evaluate/complex-value.cpp
new file mode 100644
index 0000000000000..40ba3aeaefa2f
--- /dev/null
+++ b/flang/lib/Evaluate/complex-value.cpp
@@ -0,0 +1,185 @@
+//===-- lib/Evaluate/complex-value.cpp ------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "flang/Evaluate/complex-value.h"
+#include "flang/Common/idioms.h"
+#include "llvm/Support/raw_ostream.h"
+#include <string>
+
+namespace Fortran::evaluate::value {
+
+void ComplexValue::print(llvm::raw_ostream &os) const { AsFortran(os, kind()); }
+
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+LLVM_DUMP_METHOD void ComplexValue::dump() const {
+  print(llvm::errs());
+  llvm::errs() << '\n';
+}
+#endif
+
+ValueWithRealFlags<ComplexValue> ComplexValue::FromInteger(
+    int kind, const IntegerValue &n, bool isUnsigned, Rounding rounding) {
+  CHECK(!n.IsMonostate());
+
+  ValueWithRealFlags<ComplexValue> result;
+  result.value.re_ = RealValue::FromInteger(kind, n, isUnsigned, rounding)
+                         .AccumulateFlags(result.flags);
+  result.value.im_ = RealValue::Zero(kind);
+  return result;
+}
+
+ValueWithRealFlags<ComplexValue> ComplexValue::Add(
+    const ComplexValue &y, Rounding rounding) const {
+  CHECK(!IsMonostate());
+
+  RealFlags flags;
+  RealValue reSum{re_.Add(y.re_, rounding).AccumulateFlags(flags)};
+  RealValue imSum{im_.Add(y.im_, rounding).AccumulateFlags(flags)};
+  return {ComplexValue{reSum, imSum}, flags};
+}
+
+ValueWithRealFlags<ComplexValue> ComplexValue::Subtract(
+    const ComplexValue &y, Rounding rounding) const {
+  CHECK(!IsMonostate());
+
+  RealFlags flags;
+  RealValue reDiff{re_.Subtract(y.re_, rounding).AccumulateFlags(flags)};
+  RealValue imDiff{im_.Subtract(y.im_, rounding).AccumulateFlags(flags)};
+  return {ComplexValue{reDiff, imDiff}, flags};
+}
+
+ValueWithRealFlags<ComplexValue> ComplexValue::Multiply(
+    const ComplexValue &y, Rounding rounding) const {
+  CHECK(!IsMonostate());
+
+  // (a + ib)*(c + id) -> ac - bd + i(ad + bc)
+  RealFlags flags;
+  RealValue ac{re_.Multiply(y.re_, rounding).AccumulateFlags(flags)};
+  RealValue bd{im_.Multiply(y.im_, rounding).AccumulateFlags(flags)};
+  RealValue ad{re_.Multiply(y.im_, rounding).AccumulateFlags(flags)};
+  RealValue bc{im_.Multiply(y.re_, rounding).AccumulateFlags(flags)};
+  RealValue acbd{ac.Subtract(bd, rounding).AccumulateFlags(flags)};
+  RealValue adbc{ad.Add(bc, rounding).AccumulateFlags(flags)};
+  return {ComplexValue{acbd, adbc}, flags};
+}
+
+ValueWithRealFlags<ComplexValue> ComplexValue::Divide(
+    const ComplexValue &that, Rounding rounding) const {
+  CHECK(!IsMonostate());
+
+  // (a + ib)/(c + id) -> [(a+ib)*(c-id)] / [(c+id)*(c-id)]
+  //   -> [ac+bd+i(bc-ad)] / (cc+dd)  -- note (cc+dd) is real
+  //   -> ((ac+bd)/(cc+dd)) + i((bc-ad)/(cc+dd))
+  RealFlags flags;
+  RealValue cc{that.re_.Multiply(that.re_, rounding).AccumulateFlags(flags)};
+  RealValue dd{that.im_.Multiply(that.im_, rounding).AccumulateFlags(flags)};
+  RealValue ccPdd{cc.Add(dd, rounding).AccumulateFlags(flags)};
+  if (!flags.test(RealFlag::Overflow) && !flags.test(RealFlag::Underflow)) {
+    // den = (cc+dd) did not overflow or underflow; try the naive
+    // sequence without scaling to avoid extra roundings.
+    RealValue ac{re_.Multiply(that.re_, rounding).AccumulateFlags(flags)};
+    RealValue ad{re_.Multiply(that.im_, rounding).AccumulateFlags(flags)};
+    RealValue bc{im_.Multiply(that.re_, rounding).AccumulateFlags(flags)};
+    RealValue bd{im_.Multiply(that.im_, rounding).AccumulateFlags(flags)};
+    RealValue acPbd{ac.Add(bd, rounding).AccumulateFlags(flags)};
+    RealValue bcSad{bc.Subtract(ad, rounding).AccumulateFlags(flags)};
+    RealValue re{acPbd.Divide(ccPdd, rounding).AccumulateFlags(flags)};
+    RealValue im{bcSad.Divide(ccPdd, rounding).AccumulateFlags(flags)};
+    if (!flags.test(RealFlag::Overflow) && !flags.test(RealFlag::Underflow)) {
+      return {ComplexValue{re, im}, flags};
+    }
+  }
+  // Scale numerator and denominator by d/c (if c>=d) or c/d (if c<d)
+  flags.clear();
+  RealValue scale; // will be <= 1.0 in magnitude
+  bool cGEd{that.re_.ABS().Compare(that.im_.ABS()) != Relation::Less};
+  if (cGEd) {
+    scale = that.im_.Divide(that.re_, rounding).AccumulateFlags(flags);
+  } else {
+    scale = that.re_.Divide(that.im_, rounding).AccumulateFlags(flags);
+  }
+  RealValue den;
+  if (cGEd) {
+    RealValue dS{scale.Multiply(that.im_, rounding).AccumulateFlags(flags)};
+    den = dS.Add(that.re_, rounding).AccumulateFlags(flags);
+  } else {
+    RealValue cS{scale.Multiply(that.re_, rounding).AccumulateFlags(flags)};
+    den = cS.Add(that.im_, rounding).AccumulateFlags(flags);
+  }
+  RealValue aS{scale.Multiply(re_, rounding).AccumulateFlags(flags)};
+  RealValue bS{scale.Multiply(im_, rounding).AccumulateFlags(flags)};
+  RealValue re1, im1;
+  if (cGEd) {
+    re1 = re_.Add(bS, rounding).AccumulateFlags(flags);
+    im1 = im_.Subtract(aS, rounding).AccumulateFlags(flags);
+  } else {
+    re1 = aS.Add(im_, rounding).AccumulateFlags(flags);
+    im1 = bS.Subtract(re_, rounding).AccumulateFlags(flags);
+  }
+  RealValue re{re1.Divide(den, rounding).AccumulateFlags(flags)};
+  RealValue im{im1.Divide(den, rounding).AccumulateFlags(flags)};
+  return {ComplexValue{re, im}, flags};
+}
+
+ValueWithRealFlags<ComplexValue> ComplexValue::KahanSummation(
+    const ComplexValue &y, ComplexValue &correction, Rounding rounding) const {
+  CHECK(!y.IsMonostate());
+  CHECK(!correction.IsMonostate());
+
+  RealFlags flags;
+  RealValue reSum{re_.KahanSummation(y.re_, correction.re_, rounding)
+          .AccumulateFlags(flags)};
+  RealValue imSum{im_.KahanSummation(y.im_, correction.im_, rounding)
+          .AccumulateFlags(flags)};
+  return {ComplexValue{reSum, imSum}, flags};
+}
+
+std::string ComplexValue::DumpHexadecimal() const {
+  CHECK(!IsMonostate());
+
+  std::string result{'('};
+  result += re_.DumpHexadecimal();
+  result += ',';
+  result += im_.DumpHexadecimal();
+  result += ')';
+  return result;
+}
+
+llvm::raw_ostream &ComplexValue::AsFortran(
+    llvm::raw_ostream &o, int kind) const {
+  CHECK(!IsMonostate());
+
+  re_.AsFortran(o << '(', kind);
+  im_.AsFortran(o << ',', kind);
+  return o << ')';
+}
+
+void ComplexValue::StoreRawBytes(
+    void *dst, [[maybe_unused]] size_t expectedSize, bool *changed) const {
+  CHECK(!IsMonostate());
+  CHECK(re_.bits() == im_.bits());
+  CHECK(expectedSize == re_.bytesStored() + im_.bytesStored());
+
+  re_.StoreRawBytes(dst, re_.bytesStored(), changed);
+  im_.StoreRawBytes(
+      static_cast<char *>(dst) + re_.bytesStored(), im_.bytesStored(), changed);
+}
+
+ComplexValue ComplexValue::FromRawBytes(
+    int kind, const void *raw, std::size_t expectedSize) {
+  CHECK(expectedSize == static_cast<size_t>(-1) ||
+      expectedSize == bytesStored(kind));
+  std::size_t partBytes{RealValue::bytesStored(kind)};
+  const char *data{static_cast<const char *>(raw)};
+  RealValue realPart{RealValue::FromRawBytes(kind, data, partBytes)};
+  RealValue imagPart{
+      RealValue::FromRawBytes(kind, data + partBytes, partBytes)};
+  return {realPart, imagPart};
+}
+
+} // namespace Fortran::evaluate::value
diff --git a/flang/lib/Evaluate/integer-value-impl.cpp b/flang/lib/Evaluate/integer-value-impl.cpp
new file mode 100644
index 0000000000000..bc1a1d1c8cf73
--- /dev/null
+++ b/flang/lib/Evaluate/integer-value-impl.cpp
@@ -0,0 +1,607 @@
+//===-- lib/Evaluate/integer-value.cpp ------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "integer-value-impl.h"
+#include "flang/Evaluate/integer-value.h"
+#include <new>
+
+namespace Fortran::evaluate::value {
+
+IntegerValueImpl IntegerValueImpl::Zero(int kind) {
+  return withWordProto(kind, [](auto proto) {
+    using T = decltype(proto);
+    return FromWord(T{});
+  });
+}
+
+IntegerValueImpl IntegerValueImpl::FromRawBytes(
+    int kind, const void *raw, std::size_t expectedSize) {
+  CHECK(expectedSize == IntegerValue::bytesStored(kind));
+
+  return withWordProto(kind, [&](auto proto) {
+    assert(IntegerValue::bytesStored(kind) == sizeof(proto));
+    std::decay_t<decltype(proto)> t{};
+    memcpy(&t, raw, sizeof(proto));
+    return FromWord(t);
+  });
+}
+
+void IntegerValueImpl::print(llvm::raw_ostream &os) const {
+  os << SignedDecimal() << '_' << kind();
+}
+
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+LLVM_DUMP_METHOD void IntegerValueImpl::dump() const {
+  print(llvm::errs());
+  llvm::errs() << '\n';
+}
+#endif
+
+int IntegerValueImpl::kind() const {
+  if (IsMonostate()) {
+    llvm_unreachable("default-initialized value representing 0 with unknown "
+                     "width does not know its kind");
+    return 0;
+  }
+  return withWord(
+      [](const auto &x) -> int { return std::decay_t<decltype(x)>::bits / 8; });
+}
+
+int IntegerValueImpl::bits() const {
+  if (IsMonostate()) {
+    return 0;
+  }
+  return withWord(
+      [](const auto &x) -> int { return std::decay_t<decltype(x)>::bits; });
+}
+
+bool IntegerValueImpl::IsZero() const {
+  if (IsMonostate()) {
+    return true; // uninitialized int representing 0 is zero
+  }
+  return withWord([](const auto &x) { return x.IsZero(); });
+}
+
+bool IntegerValueImpl::operator==(const IntegerValueImpl &y) const {
+  if (IsMonostate() && y.IsMonostate()) {
+    return true;
+  }
+  if (IsMonostate() != y.IsMonostate() || bits() != y.bits()) {
+    llvm_unreachable("uncomparable integers");
+    return false;
+  }
+  return withWord([&](const auto &x) -> bool {
+    using T = std::decay_t<decltype(x)>;
+    return x == std::get<T>(y.storage_);
+  });
+}
+
+IntegerValueImpl IntegerValueImpl::MASKL(int kind, int places) {
+  return withWordProto(kind, [&](auto proto) {
+    using T = decltype(proto);
+    return FromWord(T::MASKL(places));
+  });
+}
+
+IntegerValueImpl IntegerValueImpl::MASKR(int kind, int places) {
+  return withWordProto(kind, [&](auto proto) {
+    using T = decltype(proto);
+    return FromWord(T::MASKR(places));
+  });
+}
+
+IntegerValueImpl IntegerValueImpl::HUGE(int kind) {
+  return withWordProto(kind, [&](auto proto) {
+    using T = decltype(proto);
+    return FromWord(T::HUGE());
+  });
+}
+
+IntegerValueImpl IntegerValueImpl::Least(int kind) {
+  return withWordProto(kind, [&](auto proto) {
+    using T = decltype(proto);
+    return FromWord(T::Least());
+  });
+}
+
+bool IntegerValueImpl::IsNegative() const {
+  if (IsMonostate()) {
+    return false; // uninitialized int representing 0 is not negative
+  }
+  return withWord([](const auto &x) { return x.IsNegative(); });
+}
+
+std::uint64_t IntegerValueImpl::ToUInt64() const {
+  if (IsMonostate()) {
+    return 0;
+  }
+  return withWord([](const auto &x) { return x.ToUInt64(); });
+}
+
+std::int64_t IntegerValueImpl::ToInt64() const {
+  if (IsMonostate()) {
+    return 0;
+  }
+  return withWord([](const auto &x) { return x.ToInt64(); });
+}
+
+Fortran::common::uint128_t IntegerValueImpl::ToUInt128() const {
+  if (IsMonostate()) {
+    return 0;
+  }
+  return withWord([](const auto &x) {
+    return x.template ToUInt<Fortran::common::uint128_t>();
+  });
+}
+
+Fortran::common::int128_t IntegerValueImpl::ToInt128() const {
+  if (IsMonostate()) {
+    return 0;
+  }
+  return withWord([](const auto &x) {
+    return x.template ToSInt<Fortran::common::int128_t,
+        Fortran::common::uint128_t>();
+  });
+}
+
+Ordering IntegerValueImpl::CompareSigned(const IntegerValueImpl &y) const {
+  if (IsMonostate()) {
+    llvm_unreachable("uncomparable ints");
+    return Ordering::Equal;
+  }
+  return withWord([&](const auto &x) -> Ordering {
+    using T = std::decay_t<decltype(x)>;
+    return x.CompareSigned(Coerce<T>(y));
+  });
+}
+
+Ordering IntegerValueImpl::CompareUnsigned(const IntegerValueImpl &y) const {
+  if (IsMonostate()) {
+    llvm_unreachable("uncomparable ints; cast bitwidth first");
+    return Ordering::Equal;
+  }
+  return withWord([&](const auto &x) -> Ordering {
+    using T = std::decay_t<decltype(x)>;
+    return x.CompareUnsigned(Coerce<T>(y));
+  });
+}
+
+Ordering IntegerValueImpl::CompareToZeroSigned() const {
+  if (IsMonostate()) {
+    llvm_unreachable("uncomparable ints");
+    return Ordering::Equal;
+  }
+  return withWord([](const auto &x) { return x.CompareToZeroSigned(); });
+}
+
+typename IntegerValueImpl::ValueWithOverflow IntegerValueImpl::Negate() const {
+  if (IsMonostate()) {
+    return ValueWithOverflow{}; // negation of uninitialized int 0 is zero
+  }
+  return withWord([](const auto &x) -> ValueWithOverflow {
+    auto r{x.Negate()};
+    return {FromWord(r.value), r.overflow};
+  });
+}
+
+typename IntegerValueImpl::ValueWithOverflow IntegerValueImpl::ABS() const {
+  if (IsMonostate()) {
+    return ValueWithOverflow{}; // absolute of uninitialized int 0 is zero
+  }
+  return withWord([](const auto &x) -> ValueWithOverflow {
+    auto r{x.ABS()};
+    return {FromWord(r.value), r.overflow};
+  });
+}
+
+typename IntegerValueImpl::ValueWithCarry IntegerValueImpl::AddUnsigned(
+    const IntegerValueImpl &y, bool carryIn) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatiable ints");
+    return ValueWithCarry{};
+  }
+  return withWord([&](const auto &x) -> ValueWithCarry {
+    using T = std::decay_t<decltype(x)>;
+    auto r{x.AddUnsigned(Coerce<T>(y), carryIn)};
+    return {FromWord(r.value), r.carry};
+  });
+}
+
+typename IntegerValueImpl::ValueWithOverflow IntegerValueImpl::AddSigned(
+    const IntegerValueImpl &y) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatible ints");
+    return ValueWithOverflow{};
+  }
+  return withWord([&](const auto &x) -> ValueWithOverflow {
+    using T = std::decay_t<decltype(x)>;
+    auto r{x.AddSigned(Coerce<T>(y))};
+    return {FromWord(r.value), r.overflow};
+  });
+}
+
+typename IntegerValueImpl::ValueWithOverflow IntegerValueImpl::SubtractSigned(
+    const IntegerValueImpl &y) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatiable ints");
+    return ValueWithOverflow{};
+  }
+  return withWord([&](const auto &x) -> ValueWithOverflow {
+    using T = std::decay_t<decltype(x)>;
+    auto r{x.SubtractSigned(Coerce<T>(y))};
+    return {FromWord(r.value), r.overflow};
+  });
+}
+
+typename IntegerValueImpl::ValueWithOverflow IntegerValueImpl::DIM(
+    const IntegerValueImpl &y) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatiable ints");
+    return ValueWithOverflow{};
+  }
+  // DIM(X,Y) = MAX(X-Y, 0)
+  if (CompareSigned(y) != Ordering::Greater) {
+    return {Zero(kind()), false};
+  }
+  return SubtractSigned(y);
+}
+
+typename IntegerValueImpl::ValueWithOverflow IntegerValueImpl::SIGN(
+    const IntegerValueImpl &sign) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatiable ints");
+    return ValueWithOverflow{};
+  }
+  bool toNegative{sign.IsNegative()};
+  if (toNegative == IsNegative()) {
+    return {*this, false};
+  }
+  if (toNegative) {
+    return Negate();
+  }
+  return ABS();
+}
+
+typename IntegerValueImpl::Product IntegerValueImpl::MultiplySigned(
+    const IntegerValueImpl &y) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatiable ints");
+    return Product{};
+  }
+  return withWord([&](const auto &x) -> Product {
+    using T = std::decay_t<decltype(x)>;
+    auto r{x.MultiplySigned(Coerce<T>(y))};
+    return {FromWord(r.upper), FromWord(r.lower),
+        r.SignedMultiplicationOverflowed()};
+  });
+}
+
+typename IntegerValueImpl::Product IntegerValueImpl::MultiplyUnsigned(
+    const IntegerValueImpl &y) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatiable ints");
+    return Product{};
+  }
+  return withWord([&](const auto &x) -> Product {
+    using T = std::decay_t<decltype(x)>;
+    auto r{x.MultiplyUnsigned(Coerce<T>(y))};
+    return {FromWord(r.upper), FromWord(r.lower), false};
+  });
+}
+
+typename IntegerValueImpl::QuotientWithRemainder IntegerValueImpl::DivideSigned(
+    const IntegerValueImpl &y) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatiable ints");
+    return QuotientWithRemainder{};
+  }
+  return withWord([&](const auto &x) -> QuotientWithRemainder {
+    using T = std::decay_t<decltype(x)>;
+    auto r{x.DivideSigned(Coerce<T>(y))};
+    return {FromWord(r.quotient), FromWord(r.remainder), r.divisionByZero,
+        r.overflow};
+  });
+}
+
+typename IntegerValueImpl::QuotientWithRemainder
+IntegerValueImpl::DivideUnsigned(const IntegerValueImpl &y) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatiable ints");
+    return QuotientWithRemainder{};
+  }
+  return withWord([&](const auto &x) -> QuotientWithRemainder {
+    using T = std::decay_t<decltype(x)>;
+    auto r{x.DivideUnsigned(Coerce<T>(y))};
+    return {FromWord(r.quotient), FromWord(r.remainder), r.divisionByZero,
+        r.overflow};
+  });
+}
+
+typename IntegerValueImpl::ValueWithOverflow IntegerValueImpl::MODULO(
+    const IntegerValueImpl &y) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatiable ints");
+    return ValueWithOverflow{};
+  }
+  return withWord([&](const auto &x) -> ValueWithOverflow {
+    using T = std::decay_t<decltype(x)>;
+    auto r{x.MODULO(Coerce<T>(y))};
+    return {FromWord(r.value), r.overflow};
+  });
+}
+
+typename IntegerValueImpl::PowerWithErrors IntegerValueImpl::Power(
+    const IntegerValueImpl &e) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatiable ints");
+    return PowerWithErrors{};
+  }
+  return withWord([&](const auto &x) -> PowerWithErrors {
+    using T = std::decay_t<decltype(x)>;
+    auto r{x.Power(Coerce<T>(e))};
+    return {FromWord(r.power), r.divisionByZero, r.overflow, r.zeroToZero};
+  });
+}
+
+IntegerValueImpl IntegerValueImpl::NOT() const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatible ints");
+    return IntegerValueImpl{};
+  }
+  return withWord([](const auto &x) { return FromWord(x.NOT()); });
+}
+
+IntegerValueImpl IntegerValueImpl::IAND(const IntegerValueImpl &y) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatiable ints");
+    return IntegerValueImpl{};
+  }
+  return withWord([&](const auto &x) {
+    using T = std::decay_t<decltype(x)>;
+    return FromWord(x.IAND(Coerce<T>(y)));
+  });
+}
+
+IntegerValueImpl IntegerValueImpl::IOR(const IntegerValueImpl &y) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatible ints");
+    return IntegerValueImpl{};
+  }
+  return withWord([&](const auto &x) {
+    using T = std::decay_t<decltype(x)>;
+    return FromWord(x.IOR(Coerce<T>(y)));
+  });
+}
+
+IntegerValueImpl IntegerValueImpl::IEOR(const IntegerValueImpl &y) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatible ints");
+    return IntegerValueImpl{};
+  }
+  return withWord([&](const auto &x) {
+    using T = std::decay_t<decltype(x)>;
+    return FromWord(x.IEOR(Coerce<T>(y)));
+  });
+}
+
+IntegerValueImpl IntegerValueImpl::MERGE_BITS(
+    const IntegerValueImpl &y, const IntegerValueImpl &mask) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatible ints");
+    return IntegerValueImpl{};
+  }
+  return withWord([&](const auto &x) {
+    using T = std::decay_t<decltype(x)>;
+    return FromWord(x.MERGE_BITS(Coerce<T>(y), Coerce<T>(mask)));
+  });
+}
+
+IntegerValueImpl IntegerValueImpl::SHIFTL(int count) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatible ints");
+    return IntegerValueImpl{};
+  }
+  return withWord([&](const auto &x) { return FromWord(x.SHIFTL(count)); });
+}
+
+IntegerValueImpl IntegerValueImpl::SHIFTR(int count) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatible ints");
+    return IntegerValueImpl{};
+  }
+  return withWord([&](const auto &x) { return FromWord(x.SHIFTR(count)); });
+}
+
+IntegerValueImpl IntegerValueImpl::SHIFTA(int count) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatible ints");
+    return IntegerValueImpl{};
+  }
+  return withWord([&](const auto &x) { return FromWord(x.SHIFTA(count)); });
+}
+
+IntegerValueImpl IntegerValueImpl::ISHFTC(int count, int size) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatible ints");
+    return IntegerValueImpl{};
+  }
+  return withWord([&](const auto &x) {
+    using T = std::decay_t<decltype(x)>;
+    return FromWord(x.ISHFTC(count, size <= 0 ? T::bits : size));
+  });
+}
+
+IntegerValueImpl IntegerValueImpl::IBITS(int pos, int size) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatible ints");
+    return IntegerValueImpl{};
+  }
+  return withWord([&](const auto &x) { return FromWord(x.IBITS(pos, size)); });
+}
+
+IntegerValueImpl IntegerValueImpl::IBSET(int pos) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatible ints");
+    return IntegerValueImpl{};
+  }
+  return withWord([&](const auto &x) { return FromWord(x.IBSET(pos)); });
+}
+
+IntegerValueImpl IntegerValueImpl::IBCLR(int pos) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatible ints");
+    return IntegerValueImpl{};
+  }
+  return withWord([&](const auto &x) { return FromWord(x.IBCLR(pos)); });
+}
+
+IntegerValueImpl IntegerValueImpl::DSHIFTL(
+    const IntegerValueImpl &fill, int count) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatible ints");
+    return IntegerValueImpl{};
+  }
+  // DSHIFTL(I,J) shifts I:J left; the second argument is the right fill.
+  return withWord([&](const auto &x) {
+    using T = std::decay_t<decltype(x)>;
+    return FromWord(x.SHIFTLWithFill(Coerce<T>(fill), count));
+  });
+}
+
+IntegerValueImpl IntegerValueImpl::DSHIFTR(
+    const IntegerValueImpl &v2, int count) const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatible ints");
+    return IntegerValueImpl{};
+  }
+  // DSHIFTR(I,J) shifts I:J right; the *first* argument (this) is the left
+  // fill, and the receiver of the shift is v2 (mirrors value::Integer's
+  // DSHIFTR, whose *this is the shifted operand and whose argument is the
+  // fill).
+  return v2.withWord([&](const auto &x2) {
+    using T = std::decay_t<decltype(x2)>;
+    return FromWord(x2.SHIFTRWithFill(Coerce<T>(*this), count));
+  });
+}
+
+bool IntegerValueImpl::BTEST(int pos) const {
+  if (IsMonostate()) {
+    return false; // uninitialized int representing 0 has no bits set
+  }
+  return withWord([&](const auto &x) { return x.BTEST(pos); });
+}
+
+int IntegerValueImpl::LEADZ() const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatible ints");
+    return 0;
+  }
+  return withWord([](const auto &x) { return x.LEADZ(); });
+}
+
+int IntegerValueImpl::TRAILZ() const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatible ints");
+    return 0;
+  }
+  return withWord([](const auto &x) { return x.TRAILZ(); });
+}
+
+int IntegerValueImpl::POPCNT() const {
+  if (IsMonostate()) {
+    return 0; // uninitialized int representing 0 has no bits set
+  }
+  return withWord([](const auto &x) { return x.POPCNT(); });
+}
+
+bool IntegerValueImpl::POPPAR() const {
+  if (IsMonostate()) {
+    llvm_unreachable("incompatible ints");
+    return false;
+  }
+  return withWord([](const auto &x) { return x.POPPAR(); });
+}
+
+typename IntegerValueImpl::ValueWithOverflow IntegerValueImpl::ConvertSigned(
+    const IntegerValueImpl &from, int toBits) {
+  if (from.IsMonostate()) {
+    return {};
+  }
+  return from.withWord([&](const auto &x) -> ValueWithOverflow {
+    using S = std::decay_t<decltype(x)>;
+    return withWordProto(toBits / 8, [&](auto proto) -> ValueWithOverflow {
+      using T = decltype(proto);
+      auto r{T::template ConvertSigned<S>(x)};
+      return {FromWord(r.value), r.overflow};
+    });
+  });
+}
+
+typename IntegerValueImpl::ValueWithOverflow IntegerValueImpl::ConvertUnsigned(
+    const IntegerValueImpl &from, int toBits) {
+  if (from.IsMonostate()) {
+    return {};
+  }
+  return from.withWord([&](const auto &x) -> ValueWithOverflow {
+    using S = std::decay_t<decltype(x)>;
+    return withWordProto(toBits / 8, [&](auto proto) -> ValueWithOverflow {
+      using T = decltype(proto);
+      auto r{T::template ConvertUnsigned<S>(x)};
+      return {FromWord(r.value), r.overflow};
+    });
+  });
+}
+
+typename IntegerValueImpl::ValueWithOverflow IntegerValueImpl::Read(
+    int kind, const char *&pp, int base, bool isSigned) {
+  return withWordProto(kind, [&](auto proto) -> ValueWithOverflow {
+    using T = decltype(proto);
+    auto r{T::Read(pp, base, isSigned)};
+    return {FromWord(r.value), r.overflow};
+  });
+}
+
+std::string IntegerValueImpl::SignedDecimal() const {
+  if (IsMonostate()) {
+    return "0";
+  }
+  return withWord([](const auto &x) { return x.SignedDecimal(); });
+}
+
+std::string IntegerValueImpl::UnsignedDecimal() const {
+  if (IsMonostate()) {
+    return "0";
+  }
+  return withWord([](const auto &x) { return x.UnsignedDecimal(); });
+}
+
+std::string IntegerValueImpl::Hexadecimal() const {
+  if (IsMonostate()) {
+    return "0";
+  }
+  return withWord([](const auto &x) { return x.Hexadecimal(); });
+}
+
+void IntegerValueImpl::StoreRawBytes(
+    void *dst, size_t expectedSize, bool *changed) const {
+  CHECK(expectedSize == bytesStored());
+
+  withWord([dst, changed, bytesStored = bytesStored()](auto w) {
+    assert(bytesStored == sizeof(w));
+
+    if (changed) {
+      if (std::memcmp(dst, &w, bytesStored) == 0) {
+        return;
+      }
+      *changed = true;
+    }
+    std::memcpy(dst, &w, bytesStored);
+  });
+}
+
+} // namespace Fortran::evaluate::value
diff --git a/flang/lib/Evaluate/integer-value-impl.h b/flang/lib/Evaluate/integer-value-impl.h
new file mode 100644
index 0000000000000..0ad4608619046
--- /dev/null
+++ b/flang/lib/Evaluate/integer-value-impl.h
@@ -0,0 +1,331 @@
+//===-- lib/Evaluate/integer-value-impl.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_INTEGER_VALUE_IMPL_H_
+#define FORTRAN_EVALUATE_INTEGER_VALUE_IMPL_H_
+
+#include "flang/Evaluate/integer.h"
+#include "llvm/Support/ErrorHandling.h"
+#include <cstdint>
+#include <cstring>
+#include <string>
+#include <type_traits>
+#include <variant>
+
+// Some environments, viz. glibc 2.17 and *BSD, allow the macro HUGE
+// to leak out of <math.h>.
+#undef HUGE
+
+namespace Fortran::evaluate::value {
+
+class IntegerValueImpl {
+public:
+  // Per-KIND fixed-width backing formats.  I80 (X87IntegerContainer) is not
+  // itself a Fortran INTEGER kind, but used as REAL(10) storage. While
+  // RealValue has its own RealValueImpl, IntegerValue still needs to able to
+  // hold it with conversions such as RealValue::IntegerValue().
+  using I8 = Integer<8>;
+  using I16 = Integer<16>;
+  using I32 = Integer<32>;
+  using I64 = Integer<64>;
+  using I80 = X87IntegerContainer;
+  using I128 = Integer<128>;
+  using Storage = std::variant<std::monostate, I8, I16, I32, I64, I80, I128>;
+
+  struct ValueWithOverflow;
+  struct ValueWithCarry;
+  struct Product;
+  struct QuotientWithRemainder;
+  struct PowerWithErrors;
+
+  // rule-of-five
+  ~IntegerValueImpl() = default;
+  IntegerValueImpl(const IntegerValueImpl &) = default;
+  IntegerValueImpl(IntegerValueImpl &&) = default;
+  IntegerValueImpl &operator=(const IntegerValueImpl &) = default;
+  IntegerValueImpl &operator=(IntegerValueImpl &&) = default;
+
+  IntegerValueImpl() = default;
+  IntegerValueImpl(int kind, const IntegerValueImpl &x) : IntegerValueImpl(x) {
+    CHECK(x.kind() == kind);
+  }
+
+  static IntegerValueImpl Zero(int kind);
+
+  IntegerValueImpl(int kind, uint64_t v, bool isSigned) {
+    withWordProto(kind, [=](auto wordProto) {
+      using T = decltype(wordProto);
+      storage_ = isSigned ? T{static_cast<int64_t>(v)} : T{v};
+    });
+  }
+
+  IntegerValueImpl(int kind, Fortran::common::uint128_t v) {
+    withWordProto(kind, [=](auto wordProto) {
+      using T = decltype(wordProto);
+      std::uint64_t lo{static_cast<std::uint64_t>(v)};
+      std::uint64_t hi{static_cast<std::uint64_t>(v >> 64)};
+      storage_ = T{lo}.IOR(T{hi}.SHIFTL(64));
+    });
+  }
+
+  template <typename T> static IntegerValueImpl FromWord(const T &n) {
+    IntegerValueImpl v;
+    v.storage_ = n;
+    return v;
+  }
+
+  static IntegerValueImpl FromRawBytes(
+      int kind, const void *raw, std::size_t expectedSize);
+
+  void print(llvm::raw_ostream &os) const;
+
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+  LLVM_DUMP_METHOD void dump() const;
+#endif
+
+  bool IsMonostate() const { return storage_.index() == 0; }
+  int kind() const;
+
+  int bits() const;
+
+  std::size_t bytesStored() const { return bytesStored(kind()); }
+  static constexpr std::size_t bytesStored(int kind) {
+    switch (kind) {
+    case 3:
+      return 2;
+    case 10:
+      return 16;
+    default:
+      return kind;
+    }
+  }
+
+  bool IsZero() const;
+
+  // Comparison operators
+  bool operator<(const IntegerValueImpl &y) const {
+    return CompareSigned(y) == Ordering::Less;
+  }
+  bool operator<=(const IntegerValueImpl &y) const { return !(y < *this); }
+  bool operator==(const IntegerValueImpl &y) const;
+  bool operator!=(const IntegerValueImpl &y) const { return !(*this == y); }
+  bool operator>=(const IntegerValueImpl &y) const { return !(*this < y); }
+  bool operator>(const IntegerValueImpl &y) const { return y < *this; }
+
+  /// Left-justified mask (e.g., MASKL(1) has only its sign bit set)
+  static IntegerValueImpl MASKL(int kind, int places);
+  /// Right-justified mask (e.g., MASKR(1) == 1, MASKR(2) == 3, &c.)
+  static IntegerValueImpl MASKR(int kind, int places);
+  static IntegerValueImpl HUGE(int kind);
+  static IntegerValueImpl Least(int kind);
+
+  bool IsNegative() const;
+
+  std::uint64_t ToUInt64() const;
+  std::int64_t ToInt64() const;
+
+  Fortran::common::uint128_t ToUInt128() const;
+  Fortran::common::int128_t ToInt128() const;
+
+  // Signed/unsigned comparisons
+  Ordering CompareSigned(const IntegerValueImpl &y) const;
+  Ordering CompareUnsigned(const IntegerValueImpl &y) const;
+  Ordering CompareToZeroSigned() const;
+
+  // Arithmetic
+  ValueWithOverflow Negate() const;
+  ValueWithOverflow ABS() const;
+
+  ValueWithCarry AddUnsigned(
+      const IntegerValueImpl &y, bool carryIn = false) const;
+  ValueWithOverflow AddSigned(const IntegerValueImpl &y) const;
+  ValueWithOverflow SubtractSigned(const IntegerValueImpl &y) const;
+  ValueWithOverflow DIM(const IntegerValueImpl &y) const;
+  ValueWithOverflow SIGN(const IntegerValueImpl &sign) const;
+
+  Product MultiplySigned(const IntegerValueImpl &y) const;
+  Product MultiplyUnsigned(const IntegerValueImpl &y) const;
+  QuotientWithRemainder DivideSigned(const IntegerValueImpl &y) const;
+  QuotientWithRemainder DivideUnsigned(const IntegerValueImpl &y) const;
+  ValueWithOverflow MODULO(const IntegerValueImpl &y) const;
+  PowerWithErrors Power(const IntegerValueImpl &e) const;
+
+  // Bitwise operations
+  IntegerValueImpl NOT() const;
+  IntegerValueImpl IAND(const IntegerValueImpl &y) const;
+  IntegerValueImpl IOR(const IntegerValueImpl &y) const;
+  IntegerValueImpl IEOR(const IntegerValueImpl &y) const;
+  IntegerValueImpl MERGE_BITS(
+      const IntegerValueImpl &y, const IntegerValueImpl &mask) const;
+  IntegerValueImpl MAX(const IntegerValueImpl &y) const {
+    return CompareSigned(y) == Ordering::Less ? y : *this;
+  }
+  IntegerValueImpl MIN(const IntegerValueImpl &y) const {
+    return CompareSigned(y) == Ordering::Less ? *this : y;
+  }
+
+  // Shift operations
+  IntegerValueImpl ISHFT(int count) const {
+    return count < 0 ? SHIFTR(-count) : SHIFTL(count);
+  }
+  IntegerValueImpl SHIFTL(int count) const;
+  IntegerValueImpl SHIFTR(int count) const;
+  IntegerValueImpl SHIFTA(int count) const;
+  IntegerValueImpl ISHFTC(int count, int size) const;
+  IntegerValueImpl ISHFTC(int count) const { return ISHFTC(count, bits()); }
+  IntegerValueImpl IBITS(int pos, int size) const;
+  IntegerValueImpl IBSET(int pos) const;
+  IntegerValueImpl IBCLR(int pos) const;
+  IntegerValueImpl DSHIFTL(const IntegerValueImpl &fill, int count) const;
+  IntegerValueImpl DSHIFTR(const IntegerValueImpl &v2, int count) const;
+  bool BTEST(int pos) const;
+  int LEADZ() const;
+  int TRAILZ() const;
+  int POPCNT() const;
+  bool POPPAR() const;
+
+  static ValueWithOverflow ConvertSigned(
+      const IntegerValueImpl &from, int toBits);
+  static ValueWithOverflow ConvertUnsigned(
+      const IntegerValueImpl &from, int toBits);
+
+  static ValueWithOverflow Read(
+      int kind, const char *&pp, int base, bool isSigned);
+
+  // Formatting
+  std::string SignedDecimal() const;
+  std::string UnsignedDecimal() const;
+  std::string Hexadecimal() const;
+
+  // y converted (sign-preserving) to T, so that binary operations operate on
+  // operands of equal width.  A monostate operand is treated as a zero of
+  // that width.
+  template <typename T> static T Coerce(const IntegerValueImpl &y) {
+    if (y.IsMonostate()) {
+      return T{};
+    }
+    return y.withWord([](const auto &yv) -> T {
+      using S = std::decay_t<decltype(yv)>;
+      if constexpr (std::is_same_v<S, T>) {
+        return yv;
+      } else {
+        return T::template ConvertSigned<S>(yv).value;
+      }
+    });
+  }
+
+  // Same as Coerce, but zero-extending rather than sign-extending.
+  template <typename T> static T CoerceUnsigned(const IntegerValueImpl &y) {
+    if (y.IsMonostate()) {
+      return T{};
+    }
+    return y.withWord([](const auto &yv) -> T {
+      using S = std::decay_t<decltype(yv)>;
+      if constexpr (std::is_same_v<S, T>) {
+        return yv;
+      } else {
+        return T::template ConvertUnsigned<S>(yv).value;
+      }
+    });
+  }
+
+  void StoreRawBytes(void *dst, size_t size, bool *changed) const;
+
+  // Compile-time dispatchers to current/specified kind
+
+  template <typename F>
+  auto withWordProto(F &&f) const
+      -> decltype(std::declval<F>()(std::declval<I64>())) {
+    return withWordProto(kind(), std::forward<F>(f));
+  }
+
+  template <typename F>
+  static auto withWordProto(int kind, F &&f)
+      -> decltype(std::declval<F>()(std::declval<I64>())) {
+    switch (kind) {
+    case 1:
+      return f(I8{});
+    case 2:
+    case 3:
+      return f(I16{});
+    case 4:
+      return f(I32{});
+    case 8:
+      return f(I64{});
+    case 10:
+      return f(I80{});
+    case 16:
+      return f(I128{});
+    default:
+      llvm_unreachable("unsupported integer width");
+    }
+  }
+
+  template <typename F>
+  auto withWord(F &&f) const
+      -> decltype(std::declval<F>()(std::declval<I64>())) {
+    switch (storage_.index()) {
+    case 1:
+      return f(std::get<I8>(storage_));
+    case 2:
+      return f(std::get<I16>(storage_));
+    case 3:
+      return f(std::get<I32>(storage_));
+    case 4:
+      return f(std::get<I64>(storage_));
+    case 5:
+      return f(std::get<I80>(storage_));
+    case 6:
+      return f(std::get<I128>(storage_));
+    default:
+      llvm_unreachable("operation on uninitialized IntegerValueImpl");
+    }
+  }
+
+private:
+  Storage storage_;
+};
+
+struct IntegerValueImpl::ValueWithOverflow {
+  IntegerValueImpl value;
+  bool overflow{false};
+};
+
+struct IntegerValueImpl::ValueWithCarry {
+  IntegerValueImpl value;
+  bool carry{false};
+};
+
+struct IntegerValueImpl::Product {
+  IntegerValueImpl upper, lower;
+  bool SignedMultiplicationOverflowed() const { return overflow; }
+  bool overflow{false};
+};
+
+struct IntegerValueImpl::QuotientWithRemainder {
+  IntegerValueImpl quotient, remainder;
+  bool divisionByZero{false}, overflow{false};
+};
+
+struct IntegerValueImpl::PowerWithErrors {
+  IntegerValueImpl power;
+  bool divisionByZero{false}, overflow{false}, zeroToZero{false};
+};
+
+} // namespace Fortran::evaluate::value
+
+namespace llvm {
+/// For pretty printing in GTest
+inline raw_ostream &operator<<(
+    raw_ostream &os, const Fortran::evaluate::value::IntegerValueImpl &v) {
+  v.print(os);
+  return os;
+}
+} // namespace llvm
+
+#endif // FORTRAN_EVALUATE_INTEGER_VALUE_IMPL_H_
diff --git a/flang/lib/Evaluate/integer-value.cpp b/flang/lib/Evaluate/integer-value.cpp
new file mode 100644
index 0000000000000..2ec567a0423ac
--- /dev/null
+++ b/flang/lib/Evaluate/integer-value.cpp
@@ -0,0 +1,315 @@
+//===-- lib/Evaluate/integer-value.cpp ------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "flang/Evaluate/integer-value.h"
+#include "integer-value-impl.h"
+#include <new>
+
+namespace Fortran::evaluate::value {
+static_assert(sizeof(IntegerValueImpl) == detail::kIntegerObjectSize);
+static_assert(alignof(IntegerValueImpl) == detail::kIntegerObjectAlign);
+static_assert(sizeof(IntegerValue) == sizeof(IntegerValueImpl));
+static_assert(alignof(IntegerValue) == alignof(IntegerValueImpl));
+
+IntegerValue::IntegerValue() { new (this) IntegerValueImpl(); }
+
+IntegerValue::~IntegerValue() { impl().~IntegerValueImpl(); }
+
+IntegerValue::IntegerValue(const IntegerValue &x) {
+  new (this) IntegerValueImpl(x.impl());
+}
+
+IntegerValue::IntegerValue(IntegerValue &&x) {
+  new (this) IntegerValueImpl(std::move(x.impl()));
+}
+
+IntegerValue &IntegerValue::operator=(const IntegerValue &x) {
+  impl() = x.impl();
+  return *this;
+}
+
+IntegerValue &IntegerValue::operator=(IntegerValue &&x) {
+  impl() = std::move(x.impl());
+  return *this;
+}
+
+IntegerValue IntegerValue::Zero(int kind) {
+  return FromImpl(IntegerValueImpl::Zero(kind));
+}
+
+bool IntegerValue::IsMonostate() const { return impl().IsMonostate(); }
+
+int IntegerValue::kind() const { return impl().kind(); }
+
+void IntegerValue::print(llvm::raw_ostream &os) const { impl().print(os); }
+
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+LLVM_DUMP_METHOD void IntegerValue::dump() const { impl().dump(); }
+#endif
+
+bool IntegerValue::operator==(const IntegerValue &y) const {
+  return impl() == y.impl();
+}
+
+IntegerValue IntegerValue::MASKL(int kind, int places) {
+  return FromImpl(IntegerValueImpl::MASKL(kind, places));
+}
+
+IntegerValue IntegerValue::MASKR(int kind, int places) {
+  return FromImpl(IntegerValueImpl::MASKR(kind, places));
+}
+
+IntegerValue::ValueWithOverflow IntegerValue::Read(
+    int kind, const char *&pp, int base, bool isSigned) {
+  auto r{IntegerValueImpl::Read(kind, pp, base, isSigned)};
+  return {FromImpl(std::move(r.value)), r.overflow};
+}
+
+IntegerValue::ValueWithOverflow IntegerValue::ConvertUnsigned(
+    const IntegerValue &from, int toBits) {
+  auto r{IntegerValueImpl::ConvertUnsigned(from.impl(), toBits)};
+  return {FromImpl(std::move(r.value)), r.overflow};
+}
+
+typename IntegerValue::ValueWithOverflow IntegerValue::ConvertSigned(
+    const IntegerValue &from, int toBits) {
+  auto r{IntegerValueImpl::ConvertSigned(from.impl(), toBits)};
+  return {FromImpl(std::move(r.value)), r.overflow};
+}
+
+std::string IntegerValue::UnsignedDecimal() const {
+  return impl().UnsignedDecimal();
+}
+
+std::string IntegerValue::SignedDecimal() const {
+  return impl().SignedDecimal();
+}
+
+std::string IntegerValue::Hexadecimal() const { return impl().Hexadecimal(); }
+
+IntegerValue IntegerValue::HUGE(int kind) {
+  return FromImpl(IntegerValueImpl::HUGE(kind));
+}
+
+IntegerValue IntegerValue::Least(int kind) {
+  return FromImpl(IntegerValueImpl::Least(kind));
+}
+
+int IntegerValue::RANGE(int kind) { return DecimalRange(kind * 8 - 1); }
+
+int IntegerValue::UnsignedRANGE(int kind) { return DecimalRange(kind * 8); }
+
+bool IntegerValue::IsZero() const { return impl().IsZero(); }
+
+bool IntegerValue::IsNegative() const { return impl().IsNegative(); }
+
+int IntegerValue::LEADZ() const { return impl().LEADZ(); }
+
+int IntegerValue::POPCNT() const { return impl().POPCNT(); }
+
+bool IntegerValue::POPPAR() const { return impl().POPPAR(); }
+
+int IntegerValue::TRAILZ() const { return impl().TRAILZ(); }
+
+bool IntegerValue::BTEST(int pos) const { return impl().BTEST(pos); }
+
+Ordering IntegerValue::CompareToZeroSigned() const {
+  return impl().CompareToZeroSigned();
+}
+
+Ordering IntegerValue::CompareUnsigned(const IntegerValue &y) const {
+  return impl().CompareUnsigned(y.impl());
+}
+
+Ordering IntegerValue::CompareSigned(const IntegerValue &y) const {
+  return impl().CompareSigned(y.impl());
+}
+
+std::uint64_t IntegerValue::ToUInt64() const { return impl().ToUInt64(); }
+
+std::int64_t IntegerValue::ToInt64() const { return impl().ToInt64(); }
+
+Fortran::common::uint128_t IntegerValue::ToUInt128() const {
+  return impl().ToUInt128();
+}
+
+Fortran::common::int128_t IntegerValue::ToInt128() const {
+  return impl().ToInt128();
+}
+
+IntegerValue IntegerValue::NOT() const { return FromImpl(impl().NOT()); }
+
+typename IntegerValue::ValueWithOverflow IntegerValue::Negate() const {
+  auto r{impl().Negate()};
+  return {FromImpl(std::move(r.value)), r.overflow};
+}
+typename IntegerValue::ValueWithOverflow IntegerValue::ABS() const {
+  auto r{impl().ABS()};
+  return {FromImpl(std::move(r.value)), r.overflow};
+}
+
+IntegerValue IntegerValue::SHIFTL(int count) const {
+  return FromImpl(impl().SHIFTL(count));
+}
+
+IntegerValue IntegerValue::ISHFTC(int count, int size) const {
+  return FromImpl(impl().ISHFTC(count, size));
+}
+
+IntegerValue IntegerValue::ISHFTC(int count) const {
+  return FromImpl(impl().ISHFTC(count));
+}
+
+IntegerValue IntegerValue::DSHIFTL(const IntegerValue &fill, int count) const {
+  return FromImpl(impl().DSHIFTL(fill.impl(), count));
+}
+
+IntegerValue IntegerValue::DSHIFTR(const IntegerValue &v2, int count) const {
+  return FromImpl(impl().DSHIFTR(v2.impl(), count));
+}
+
+IntegerValue IntegerValue::SHIFTR(int count) const {
+  return FromImpl(impl().SHIFTR(count));
+}
+
+IntegerValue IntegerValue::SHIFTA(int count) const {
+  return FromImpl(impl().SHIFTA(count));
+}
+
+IntegerValue IntegerValue::IBCLR(int pos) const {
+  return FromImpl(impl().IBCLR(pos));
+}
+
+IntegerValue IntegerValue::IBSET(int pos) const {
+  return FromImpl(impl().IBSET(pos));
+}
+
+IntegerValue IntegerValue::IBITS(int pos, int size) const {
+  return FromImpl(impl().IBITS(pos, size));
+}
+
+IntegerValue IntegerValue::IAND(const IntegerValue &y) const {
+  return FromImpl(impl().IAND(y.impl()));
+}
+
+IntegerValue IntegerValue::IOR(const IntegerValue &y) const {
+  return FromImpl(impl().IOR(y.impl()));
+}
+
+IntegerValue IntegerValue::IEOR(const IntegerValue &y) const {
+  return FromImpl(impl().IEOR(y.impl()));
+}
+
+IntegerValue IntegerValue::MERGE_BITS(
+    const IntegerValue &y, const IntegerValue &mask) const {
+  return FromImpl(impl().MERGE_BITS(y.impl(), mask.impl()));
+}
+
+typename IntegerValue::ValueWithCarry IntegerValue::AddUnsigned(
+    const IntegerValue &y, bool carryIn) const {
+  auto r{impl().AddUnsigned(y.impl(), carryIn)};
+  return {FromImpl(std::move(r.value)), r.carry};
+}
+
+typename IntegerValue::ValueWithOverflow IntegerValue::AddSigned(
+    const IntegerValue &y) const {
+  auto r{impl().AddSigned(y.impl())};
+  return {FromImpl(std::move(r.value)), r.overflow};
+}
+
+typename IntegerValue::ValueWithOverflow IntegerValue::SubtractSigned(
+    const IntegerValue &y) const {
+  auto r{impl().SubtractSigned(y.impl())};
+  return {FromImpl(std::move(r.value)), r.overflow};
+}
+
+typename IntegerValue::ValueWithOverflow IntegerValue::DIM(
+    const IntegerValue &y) const {
+  auto r{impl().DIM(y.impl())};
+  return {FromImpl(std::move(r.value)), r.overflow};
+}
+
+typename IntegerValue::ValueWithOverflow IntegerValue::SIGN(
+    const IntegerValue &sign) const {
+  auto r{impl().SIGN(sign.impl())};
+  return {FromImpl(std::move(r.value)), r.overflow};
+}
+
+typename IntegerValue::Product IntegerValue::MultiplyUnsigned(
+    const IntegerValue &y) const {
+  auto r{impl().MultiplyUnsigned(y.impl())};
+  return {
+      FromImpl(std::move(r.upper)), FromImpl(std::move(r.lower)), r.overflow};
+}
+
+typename IntegerValue::Product IntegerValue::MultiplySigned(
+    const IntegerValue &y) const {
+  auto r{impl().MultiplySigned(y.impl())};
+  return {
+      FromImpl(std::move(r.upper)), FromImpl(std::move(r.lower)), r.overflow};
+}
+
+typename IntegerValue::QuotientWithRemainder IntegerValue::DivideUnsigned(
+    const IntegerValue &y) const {
+  auto r{impl().DivideUnsigned(y.impl())};
+  return {FromImpl(std::move(r.quotient)), FromImpl(std::move(r.remainder)),
+      r.divisionByZero, r.overflow};
+}
+
+typename IntegerValue::QuotientWithRemainder IntegerValue::DivideSigned(
+    const IntegerValue &y) const {
+  auto r{impl().DivideSigned(y.impl())};
+  return {FromImpl(std::move(r.quotient)), FromImpl(std::move(r.remainder)),
+      r.divisionByZero, r.overflow};
+}
+
+typename IntegerValue::ValueWithOverflow IntegerValue::MODULO(
+    const IntegerValue &y) const {
+  auto r{impl().MODULO(y.impl())};
+  return {FromImpl(std::move(r.value)), r.overflow};
+}
+
+typename IntegerValue::PowerWithErrors IntegerValue::Power(
+    const IntegerValue &e) const {
+  auto r{impl().Power(e.impl())};
+  return {
+      FromImpl(std::move(r.power)), r.divisionByZero, r.overflow, r.zeroToZero};
+}
+
+IntegerValue IntegerValue::FromRawBytes(
+    int kind, const void *raw, std::size_t expectedSize) {
+  return FromImpl(IntegerValueImpl::FromRawBytes(kind, raw, expectedSize));
+}
+
+void IntegerValue::StoreRawBytes(void *dst, size_t size, bool *changed) const {
+  impl().StoreRawBytes(dst, size, changed);
+}
+
+void IntegerValue::ConstructFromIntegral(
+    int kind, std::uint64_t v, bool isSigned) {
+  new (this) IntegerValueImpl(kind, v, isSigned);
+}
+
+void IntegerValue::ConstructFromIntegral(
+    int kind, Fortran::common::uint128_t v) {
+  new (this) IntegerValueImpl(kind, v);
+}
+
+IntegerValue IntegerValue::FromImpl(const IntegerValueImpl &x) {
+  IntegerValue r;
+  r.impl() = x;
+  return r;
+}
+
+IntegerValue IntegerValue::FromImpl(IntegerValueImpl &&x) {
+  IntegerValue r;
+  r.impl() = std::move(x);
+  return r;
+}
+
+} // namespace Fortran::evaluate::value
diff --git a/flang/lib/Evaluate/logical-value.cpp b/flang/lib/Evaluate/logical-value.cpp
new file mode 100644
index 0000000000000..46ffa811ece43
--- /dev/null
+++ b/flang/lib/Evaluate/logical-value.cpp
@@ -0,0 +1,33 @@
+//===-- lib/Evaluate/logical-value.cpp ------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "flang/Evaluate/logical-value.h"
+
+namespace Fortran::evaluate::value {
+
+void LogicalValue::print(llvm::raw_ostream &os) const {
+  if (!IsCanonical()) {
+    // PAPAYA: This was modified from formatting.cpp where kind 8 is hardcoded
+    os << "transfer(";
+    word().print(os);
+    os << ",.false._" << kind() << ')';
+  } else if (IsTrue()) {
+    os << ".true." << '_' << kind();
+  } else {
+    os << ".false." << '_' << kind();
+  }
+}
+
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+LLVM_DUMP_METHOD void LogicalValue::dump() const {
+  print(llvm::errs());
+  llvm::errs() << '\n';
+}
+#endif
+
+} // namespace Fortran::evaluate::value
diff --git a/flang/lib/Evaluate/real-value-impl.cpp b/flang/lib/Evaluate/real-value-impl.cpp
new file mode 100644
index 0000000000000..80d6654f439a3
--- /dev/null
+++ b/flang/lib/Evaluate/real-value-impl.cpp
@@ -0,0 +1,589 @@
+//===-- lib/Evaluate/real-value-impl.cpp ----------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "real-value-impl.h"
+#include "integer-value-impl.h"
+#include "flang/Common/idioms.h"
+#include "flang/Evaluate/integer-value.h"
+#include "llvm/Support/raw_ostream.h"
+#include <cmath>
+#include <cstring>
+#include <string>
+
+namespace Fortran::evaluate::value {
+
+RealValueImpl::RealValueImpl(int kind, const Word &w) {
+  withWordProto(kind, [&](auto proto) {
+    using R = decltype(proto);
+    if (w.IsMonostate()) {
+      storage_ = R{};
+    } else {
+      storage_ = R{FixedIntegerFromValue<typename R::Word>(w)};
+    }
+  });
+}
+
+RealValueImpl::RealValueImpl(int kind, double x) {
+  if (x == 0.0) {
+    storage_ = std::signbit(x) ? RealValueImpl::NegativeZero(kind).storage_
+                               : RealValueImpl::Zero(kind).storage_;
+  } else if (std::isnan(x)) {
+    storage_ = RealValueImpl::NotANumber(kind).storage_;
+  } else if (std::isinf(x)) {
+    storage_ = RealValueImpl::Infinity(kind, x < 0).storage_;
+  } else {
+    const bool negative{x < 0};
+    int exp{0};
+    const double frac{std::frexp(std::fabs(x), &exp)}; // x == +/-frac * 2**exp
+    constexpr int fracBits{53}; // exact for any host "double" mantissa
+    const auto mantissa{static_cast<std::int64_t>(std::ldexp(frac, fracBits))};
+    // Materialize the value in a kind with ample exponent range (IEEE double)
+    // first: some target kinds (e.g. REAL(2), a 5-bit-exponent IEEE half) have
+    // far too little range to hold the unscaled 53-bit mantissa, and would
+    // spuriously overflow to infinity before SCALE() could bring it back down.
+    // Convert() then applies the target kind's own IEEE rounding/overflow
+    // semantics for the final narrowing (or widening).
+    constexpr int wideKind{8};
+    RealValueImpl magnitude{
+        RealValueImpl::FromInteger(wideKind, IntegerValue{8, mantissa}).value};
+    magnitude = magnitude.SCALE(IntegerValue{4, exp - fracBits}).value;
+    if (negative) {
+      magnitude = magnitude.SetSign(true);
+    }
+    storage_ = (kind == wideKind)
+        ? magnitude.storage_
+        : RealValueImpl::Convert(kind, magnitude).value.storage_;
+  }
+}
+
+RealValueImpl RealValueImpl::Zero(int kind) {
+  RealValueImpl result;
+  withWordProto(kind, [&](auto proto) { result.storage_ = decltype(proto){}; });
+  return result;
+}
+
+RealValueImpl RealValueImpl::FromRawBytes(
+    int kind, const void *raw, std::size_t expectedSize) {
+  return RealValueImpl{
+      kind, IntegerValue::FromRawBytes(kind, raw, expectedSize)};
+}
+
+void RealValueImpl::print(llvm::raw_ostream &os) const {
+  AsFortran(os, kind());
+}
+
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+LLVM_DUMP_METHOD void RealValueImpl::dump() const {
+  print(llvm::errs());
+  llvm::errs() << '\n';
+}
+#endif
+
+int RealValueImpl::kind() const {
+  if (IsMonostate()) {
+    llvm_unreachable("uninitialized value has not a defined kind");
+  }
+
+  return withWord([](const auto &v) -> int {
+    using R = std::decay_t<decltype(v)>;
+    if constexpr (std::is_same_v<R, R3>) {
+      return 3;
+    }
+    return R::bits / 8;
+  });
+}
+
+int RealValueImpl::bits() const {
+  if (IsMonostate()) {
+    return 0;
+  }
+
+  return withWord(
+      [](const auto &v) -> int { return std::decay_t<decltype(v)>::bits; });
+}
+
+bool RealValueImpl::IsZero() const {
+  if (IsMonostate()) {
+    return true;
+  }
+  return withWord([](const auto &v) { return v.IsZero(); });
+}
+
+bool RealValueImpl::operator==(const RealValueImpl &y) const {
+  return withWord([&y](const auto &v1) -> bool {
+    return y.withWord([&v1](const auto &v2) -> bool {
+      if constexpr (std::is_same_v<std::decay_t<decltype(v1)>,
+                        std::decay_t<decltype(v2)>>) {
+        return v1 == v2;
+      }
+      llvm_unreachable("Uncomparable reals");
+    });
+  });
+}
+
+int RealValueImpl::DIGITS(int kind) {
+  return withWordProto(kind, [](auto p) { return decltype(p)::DIGITS; });
+}
+
+int RealValueImpl::PRECISION(int kind) {
+  return withWordProto(kind, [](auto p) { return decltype(p)::PRECISION; });
+}
+
+int RealValueImpl::RANGE(int kind) {
+  return withWordProto(kind, [](auto p) { return decltype(p)::RANGE; });
+}
+
+int RealValueImpl::MAXEXPONENT(int kind) {
+  return withWordProto(kind, [](auto p) { return decltype(p)::MAXEXPONENT; });
+}
+
+int RealValueImpl::MINEXPONENT(int kind) {
+  return withWordProto(kind, [](auto p) { return decltype(p)::MINEXPONENT; });
+}
+
+RealValueImpl RealValueImpl::HUGE(int kind) {
+  return withWordProto(
+      kind, [](auto p) { return FromWord(decltype(p)::HUGE()); });
+}
+
+RealValueImpl RealValueImpl::EPSILON(int kind) {
+  return withWordProto(
+      kind, [](auto p) { return FromWord(decltype(p)::EPSILON()); });
+}
+
+RealValueImpl RealValueImpl::TINY(int kind) {
+  return withWordProto(
+      kind, [](auto p) { return FromWord(decltype(p)::TINY()); });
+}
+
+RealValueImpl RealValueImpl::NotANumber(int kind) {
+  return withWordProto(
+      kind, [](auto p) { return FromWord(decltype(p)::NotANumber()); });
+}
+
+RealValueImpl RealValueImpl::SignalingNaN(int kind) {
+  return withWordProto(
+      kind, [](auto p) { return FromWord(decltype(p)::SignalingNaN()); });
+}
+
+RealValueImpl RealValueImpl::Infinity(int kind, bool negative) {
+  return withWordProto(kind,
+      [negative](auto p) { return FromWord(decltype(p)::Infinity(negative)); });
+}
+
+RealValueImpl RealValueImpl::NegativeZero(int kind) {
+  return withWordProto(
+      kind, [](auto p) { return FromWord(decltype(p)::NegativeZero()); });
+}
+
+bool RealValueImpl::IsNegative() const {
+  if (IsMonostate()) {
+    return false;
+  }
+  return withWord([](const auto &v) { return v.IsNegative(); });
+}
+
+bool RealValueImpl::IsNotANumber() const {
+  if (IsMonostate()) {
+    return false;
+  }
+  return withWord([](const auto &v) { return v.IsNotANumber(); });
+}
+
+bool RealValueImpl::IsSignalingNaN() const {
+  if (IsMonostate()) {
+    return false;
+  }
+  return withWord([](const auto &v) { return v.IsSignalingNaN(); });
+}
+
+bool RealValueImpl::IsInfinite() const {
+  if (IsMonostate()) {
+    return false;
+  }
+  return withWord([](const auto &v) { return v.IsInfinite(); });
+}
+
+bool RealValueImpl::IsFinite() const {
+  if (IsMonostate()) {
+    return true;
+  }
+  return withWord([](const auto &v) { return v.IsFinite(); });
+}
+
+bool RealValueImpl::IsNormal() const {
+  if (IsMonostate()) {
+    return true;
+  }
+  return withWord([](const auto &v) { return v.IsNormal(); });
+}
+
+int RealValueImpl::Exponent() const {
+  if (IsMonostate()) {
+    return 0;
+  }
+  return withWord([](const auto &v) { return v.Exponent(); });
+}
+
+void RealValueImpl::StoreRawBytes(
+    void *dst, size_t expectedSize, bool *changed) const {
+  CHECK(bytesStored() == expectedSize);
+  withWord([=](const auto &v) {
+    auto data{v.RawBits()};
+    CHECK(sizeof(data) == expectedSize);
+    if (std::memcmp(dst, &data, sizeof(data))) {
+      std::memcpy(dst, &data, sizeof(data));
+      if (changed)
+        *changed = true;
+    }
+  });
+}
+
+IntegerValue RealValueImpl::RawBits() const {
+  if (IsMonostate()) {
+    return {};
+  }
+
+  return withWord(
+      [](const auto &v) { return IntegerValueFromFixed(v.RawBits()); });
+}
+
+Relation RealValueImpl::Compare(const RealValueImpl &y) const {
+  if (IsMonostate()) {
+    llvm_unreachable("uncomparable value");
+  }
+  return withWord([&](const auto &v) {
+    using R = std::decay_t<decltype(v)>;
+    return v.Compare(AsWord<R>(y));
+  });
+}
+
+RealValueImpl RealValueImpl::ABS() const {
+  if (IsMonostate()) {
+    return RealValueImpl{};
+  }
+  return withWord([](const auto &v) { return FromWord(v.ABS()); });
+}
+
+RealValueImpl RealValueImpl::Negate() const {
+  if (IsMonostate()) {
+    return RealValueImpl{};
+  }
+  return withWord([](const auto &v) { return FromWord(v.Negate()); });
+}
+
+RealValueImpl RealValueImpl::SIGN(const RealValueImpl &x) const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord([&](const auto &v) {
+    using R = std::decay_t<decltype(v)>;
+    return FromWord(v.SIGN(AsWord<R>(x)));
+  });
+}
+
+RealValueImpl RealValueImpl::SetSign(bool toNegative) const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord(
+      [&](const auto &v) { return FromWord(v.SetSign(toNegative)); });
+}
+
+RealValueImpl RealValueImpl::FlushSubnormalToZero() const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord(
+      [](const auto &v) { return FromWord(v.FlushSubnormalToZero()); });
+}
+
+ValueWithRealFlags<RealValueImpl> RealValueImpl::Add(
+    const RealValueImpl &y, Rounding rounding) const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord([&](const auto &v) {
+    using R = std::decay_t<decltype(v)>;
+    return FromWord(v.Add(AsWord<R>(y), rounding));
+  });
+}
+
+ValueWithRealFlags<RealValueImpl> RealValueImpl::Subtract(
+    const RealValueImpl &y, Rounding rounding) const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord([&](const auto &v) {
+    using R = std::decay_t<decltype(v)>;
+    return FromWord(v.Subtract(AsWord<R>(y), rounding));
+  });
+}
+
+ValueWithRealFlags<RealValueImpl> RealValueImpl::Multiply(
+    const RealValueImpl &y, Rounding rounding) const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord([&](const auto &v) {
+    using R = std::decay_t<decltype(v)>;
+    return FromWord(v.Multiply(AsWord<R>(y), rounding));
+  });
+}
+
+ValueWithRealFlags<RealValueImpl> RealValueImpl::Divide(
+    const RealValueImpl &y, Rounding rounding) const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord([&](const auto &v) {
+    using R = std::decay_t<decltype(v)>;
+    return FromWord(v.Divide(AsWord<R>(y), rounding));
+  });
+}
+
+ValueWithRealFlags<RealValueImpl> RealValueImpl::SQRT(Rounding rounding) const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord([&](const auto &v) { return FromWord(v.SQRT(rounding)); });
+}
+
+ValueWithRealFlags<RealValueImpl> RealValueImpl::HYPOT(
+    const RealValueImpl &y, Rounding rounding) const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord([&](const auto &v) {
+    using R = std::decay_t<decltype(v)>;
+    return FromWord(v.HYPOT(AsWord<R>(y), rounding));
+  });
+}
+
+ValueWithRealFlags<RealValueImpl> RealValueImpl::MOD(
+    const RealValueImpl &y, Rounding rounding) const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord([&](const auto &v) {
+    using R = std::decay_t<decltype(v)>;
+    return FromWord(v.MOD(AsWord<R>(y), rounding));
+  });
+}
+
+ValueWithRealFlags<RealValueImpl> RealValueImpl::MODULO(
+    const RealValueImpl &y, Rounding rounding) const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord([&](const auto &v) {
+    using R = std::decay_t<decltype(v)>;
+    return FromWord(v.MODULO(AsWord<R>(y), rounding));
+  });
+}
+
+ValueWithRealFlags<RealValueImpl> RealValueImpl::DIM(
+    const RealValueImpl &y, Rounding rounding) const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord([&](const auto &v) {
+    using R = std::decay_t<decltype(v)>;
+    return FromWord(v.DIM(AsWord<R>(y), rounding));
+  });
+}
+
+RealValueImpl RealValueImpl::FRACTION() const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord([](const auto &v) { return FromWord(v.FRACTION()); });
+}
+
+RealValueImpl RealValueImpl::RRSPACING() const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord([](const auto &v) { return FromWord(v.RRSPACING()); });
+}
+
+RealValueImpl RealValueImpl::SPACING() const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord([](const auto &v) { return FromWord(v.SPACING()); });
+}
+
+RealValueImpl RealValueImpl::SET_EXPONENT(std::int64_t e) const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord([&](const auto &v) { return FromWord(v.SET_EXPONENT(e)); });
+}
+
+ValueWithRealFlags<RealValueImpl> RealValueImpl::NEAREST(bool upward) const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord([&](const auto &v) { return FromWord(v.NEAREST(upward)); });
+}
+
+ValueWithRealFlags<RealValueImpl> RealValueImpl::ToWholeNumber(
+    common::RoundingMode mode) const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord(
+      [&](const auto &v) { return FromWord(v.ToWholeNumber(mode)); });
+}
+
+ValueWithRealFlags<IntegerValue> RealValueImpl::ToInteger(
+    common::RoundingMode mode, int toBits) const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord([&](const auto &v) -> ValueWithRealFlags<IntegerValue> {
+    auto pick{[&](auto target) -> ValueWithRealFlags<IntegerValue> {
+      using W = decltype(target);
+      auto r{v.template ToInteger<W>(mode)};
+      ValueWithRealFlags<IntegerValue> result;
+      result.value = IntegerValueFromFixed(r.value);
+      result.flags = r.flags;
+      return result;
+    }};
+    switch (toBits) {
+    case 8:
+      return pick(Integer<8>{});
+    case 16:
+      return pick(Integer<16>{});
+    case 32:
+      return pick(Integer<32>{});
+    case 64:
+      return pick(Integer<64>{});
+    case 128:
+      return pick(Integer<128>{});
+    default:
+      return pick(Integer<64>{});
+    }
+  });
+}
+
+ValueWithRealFlags<RealValueImpl> RealValueImpl::SCALE(
+    const IntegerValue &by, Rounding rounding) const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord([&](const auto &v) -> ValueWithRealFlags<RealValueImpl> {
+    return FromWord(v.SCALE(Integer<64>{by.ToInt64()}, rounding));
+  });
+}
+
+ValueWithRealFlags<RealValueImpl> RealValueImpl::KahanSummation(
+    const RealValueImpl &y, RealValueImpl &correction,
+    Rounding rounding) const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord([&](const auto &v) {
+    using R = std::decay_t<decltype(v)>;
+    R corr{AsWord<R>(correction)};
+    auto r{v.KahanSummation(AsWord<R>(y), corr, rounding)};
+    correction = FromWord(corr);
+    return FromWord(r);
+  });
+}
+
+IntegerValue RealValueImpl::EXPONENT() const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord([](const auto &v) -> IntegerValue {
+    return IntegerValueFromFixed(v.template EXPONENT<Integer<32>>());
+  });
+}
+
+ValueWithRealFlags<RealValueImpl> RealValueImpl::FromInteger(
+    int kind, const IntegerValue &n, bool isUnsigned, Rounding rounding) {
+  if (n.IsMonostate()) {
+    return ValueWithRealFlags<RealValueImpl>{};
+  }
+  return withWordProto(
+      kind, [&](auto proto) -> ValueWithRealFlags<RealValueImpl> {
+        auto r{FromIntegerValue<decltype(proto)>(n, isUnsigned, rounding)};
+        return {FromWord(r.value), r.flags};
+      });
+}
+
+ValueWithRealFlags<RealValueImpl> RealValueImpl::Convert(
+    int kind, const RealValueImpl &from, Rounding rounding) {
+  return withWordProto(
+      kind, [&](auto proto) -> ValueWithRealFlags<RealValueImpl> {
+        using R = decltype(proto);
+        if (from.IsMonostate()) {
+          return FromWord(R::Convert(R{}, rounding));
+        }
+        return from.withWord(
+            [&](const auto &v) -> ValueWithRealFlags<RealValueImpl> {
+              return FromWord(R::Convert(v, rounding));
+            });
+      });
+}
+
+ValueWithRealFlags<RealValueImpl> RealValueImpl::Read(
+    int kind, const char *&pp, Rounding rounding) {
+  return withWordProto(
+      kind, [&](auto proto) -> ValueWithRealFlags<RealValueImpl> {
+        auto r{decltype(proto)::Read(pp, rounding)};
+        ValueWithRealFlags<RealValueImpl> result;
+        result.value = FromWord(r.value);
+        result.flags = r.flags;
+        return result;
+      });
+}
+
+std::string RealValueImpl::DumpHexadecimal() const {
+  if (IsMonostate()) {
+    llvm_unreachable("unsupported operation over uninitialized value");
+  }
+  return withWord([](const auto &v) { return v.DumpHexadecimal(); });
+}
+
+llvm::raw_ostream &RealValueImpl::AsFortran(
+    llvm::raw_ostream &o, int kind, bool minimal) const {
+  if (IsMonostate()) {
+    o << "0";
+    return o;
+  }
+  withWord([&](const auto &v) {
+    v.AsFortran(o, kind, minimal);
+    return 0;
+  });
+  return o;
+}
+
+template <typename INT>
+IntegerValue RealValueImpl::IntegerValueFromFixed(const INT &n) {
+  IntegerValue result;
+  result.impl() = IntegerValueImpl::FromWord(n);
+  return result;
+}
+
+template <typename INT>
+INT RealValueImpl::FixedIntegerFromValue(const IntegerValue &v) {
+  return IntegerValueImpl::CoerceUnsigned<INT>(v.impl());
+}
+
+template <typename R>
+ValueWithRealFlags<R> RealValueImpl::FromIntegerValue(
+    const IntegerValue &n, bool isUnsigned, Rounding rounding) {
+  return n.impl().withWord([&](const auto &concrete) {
+    return R::FromInteger(concrete, isUnsigned, rounding);
+  });
+}
+
+} // namespace Fortran::evaluate::value
diff --git a/flang/lib/Evaluate/real-value-impl.h b/flang/lib/Evaluate/real-value-impl.h
new file mode 100644
index 0000000000000..1ab80ad2fab45
--- /dev/null
+++ b/flang/lib/Evaluate/real-value-impl.h
@@ -0,0 +1,283 @@
+//===-- lib/Evaluate/real-value-impl.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_REAL_VALUE_IMPL_H_
+#define FORTRAN_EVALUATE_REAL_VALUE_IMPL_H_
+
+#include "flang/Evaluate/real.h"
+#include "llvm/Support/ErrorHandling.h"
+#include <type_traits>
+#include <utility>
+#include <variant>
+
+// Some environments, viz. glibc 2.17 and *BSD, allow the macro HUGE
+// to leak out of <math.h>.
+#undef HUGE
+
+namespace llvm {
+class raw_ostream;
+}
+
+namespace Fortran::evaluate::value {
+class IntegerValue;
+
+class RealValueImpl {
+public:
+  using R2 = Real<Integer<16>, 11>; // IEEE half
+  using R3 = Real<Integer<16>, 8>; // bfloat16
+  using R4 = Real<Integer<32>, 24>; // IEEE single
+  using R8 = Real<Integer<64>, 53>; // IEEE double
+  using R10 = Real<X87IntegerContainer, 64>; // 80387 extended precision
+  using R16 = Real<Integer<128>, 113>; // IEEE quad
+  using Storage = std::variant<std::monostate, R2, R3, R4, R8, R10, R16>;
+  using Word = IntegerValue;
+
+  // rule-of-five
+  ~RealValueImpl() = default;
+  RealValueImpl(const RealValueImpl &) = default;
+  RealValueImpl(RealValueImpl &&) = default;
+  RealValueImpl &operator=(const RealValueImpl &) = default;
+  RealValueImpl &operator=(RealValueImpl &&) = default;
+
+  RealValueImpl() = default;
+
+  // Interpret w as the raw bit pattern of a value of the given runtime kind.
+  RealValueImpl(int kind, const Word &w);
+
+  RealValueImpl(int kind, double x);
+
+  static RealValueImpl Zero(int kind);
+
+  template <typename T> static RealValueImpl FromWord(const T &r) {
+    RealValueImpl v;
+    v.storage_ = r;
+    return v;
+  }
+
+  template <typename T>
+  static ValueWithRealFlags<RealValueImpl> FromWord(
+      const ValueWithRealFlags<T> &x) {
+    ValueWithRealFlags<RealValueImpl> r;
+    r.value = FromWord(x.value);
+    r.flags = x.flags;
+    return r;
+  }
+
+  static RealValueImpl FromRawBytes(
+      int kind, const void *raw, std::size_t expectedSize);
+
+  void print(llvm::raw_ostream &os) const;
+
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+  LLVM_DUMP_METHOD void dump() const;
+#endif
+
+  bool IsMonostate() const { return storage_.index() == 0; }
+  int kind() const;
+
+  int bits() const;
+
+  std::size_t bytesStored() const { return bytesStored(kind()); }
+  static constexpr std::size_t bytesStored(int kind) {
+    switch (kind) {
+    case 3:
+      return 2;
+    case 10:
+      return 16;
+    default:
+      return kind;
+    }
+  }
+
+  bool IsZero() const;
+
+  // Comparison operators
+  bool operator==(const RealValueImpl &y) const;
+  bool operator!=(const RealValueImpl &y) const { return !(*this == y); }
+
+  // Kind-property inquiries, formerly compile-time constants derived from the
+  // PREC template parameter; now selected by the runtime KIND.
+  static int DIGITS(int kind);
+  static int PRECISION(int kind);
+  static int RANGE(int kind);
+  static int MAXEXPONENT(int kind);
+  static int MINEXPONENT(int kind);
+
+  static RealValueImpl HUGE(int kind);
+  static RealValueImpl EPSILON(int kind);
+  static RealValueImpl TINY(int kind);
+  static RealValueImpl NotANumber(int kind);
+  static RealValueImpl SignalingNaN(int kind);
+  static RealValueImpl Infinity(int kind, bool negative = false);
+  static RealValueImpl NegativeZero(int kind);
+
+  // Runtime kind / width accessors
+  bool IsNegative() const;
+  bool IsNotANumber() const;
+  bool IsSignalingNaN() const;
+  bool IsInfinite() const;
+  bool IsFinite() const;
+  bool IsNormal() const;
+  int Exponent() const;
+  void StoreRawBytes(void *dst, size_t size, bool *changed) const;
+
+  // The raw bit pattern at the value's runtime width.
+  IntegerValue RawBits() const;
+
+  // Comparisons
+  Relation Compare(const RealValueImpl &y) const;
+
+  // Unary operations
+  RealValueImpl ABS() const;
+  RealValueImpl Negate() const;
+  RealValueImpl SIGN(const RealValueImpl &x) const;
+  RealValueImpl SetSign(bool toNegative) const;
+  RealValueImpl FlushSubnormalToZero() const;
+
+  // Binary arithmetic
+  ValueWithRealFlags<RealValueImpl> Add(const RealValueImpl &y,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+  ValueWithRealFlags<RealValueImpl> Subtract(const RealValueImpl &y,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+  ValueWithRealFlags<RealValueImpl> Multiply(const RealValueImpl &y,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+  ValueWithRealFlags<RealValueImpl> Divide(const RealValueImpl &y,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+  ValueWithRealFlags<RealValueImpl> SQRT(
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+  ValueWithRealFlags<RealValueImpl> HYPOT(const RealValueImpl &y,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+  ValueWithRealFlags<RealValueImpl> MOD(const RealValueImpl &y,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+  ValueWithRealFlags<RealValueImpl> MODULO(const RealValueImpl &y,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+  ValueWithRealFlags<RealValueImpl> DIM(const RealValueImpl &y,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+
+  RealValueImpl FRACTION() const;
+  RealValueImpl RRSPACING() const;
+  RealValueImpl SPACING() const;
+  RealValueImpl SET_EXPONENT(std::int64_t e) const;
+
+  ValueWithRealFlags<RealValueImpl> NEAREST(bool upward) const;
+  ValueWithRealFlags<RealValueImpl> ToWholeNumber(
+      common::RoundingMode mode = common::RoundingMode::ToZero) const;
+  // Convert this real to an integer of the given bit width.
+  ValueWithRealFlags<IntegerValue> ToInteger(
+      common::RoundingMode mode = common::RoundingMode::ToZero,
+      int toBits = 0) const;
+
+  ValueWithRealFlags<RealValueImpl> SCALE(const IntegerValue &by,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+
+  ValueWithRealFlags<RealValueImpl> KahanSummation(const RealValueImpl &y,
+      RealValueImpl &correction,
+      Rounding rounding = TargetCharacteristics::defaultRounding) const;
+
+  IntegerValue EXPONENT() const;
+
+  // Conversion from an integer facade (REAL()).
+  static ValueWithRealFlags<RealValueImpl> FromInteger(int kind,
+      const IntegerValue &n, bool isUnsigned = false,
+      Rounding rounding = TargetCharacteristics::defaultRounding);
+
+  // Conversion between real kinds.
+  static ValueWithRealFlags<RealValueImpl> Convert(int kind,
+      const RealValueImpl &from,
+      Rounding rounding = TargetCharacteristics::defaultRounding);
+
+  static ValueWithRealFlags<RealValueImpl> Read(int kind, const char *&pp,
+      Rounding rounding = TargetCharacteristics::defaultRounding);
+
+  std::string DumpHexadecimal() const;
+  llvm::raw_ostream &AsFortran(
+      llvm::raw_ostream &o, int kind, bool minimal = false) const;
+
+  template <typename V> static std::decay_t<V> AsWord(const RealValueImpl &y) {
+    using R = std::decay_t<V>;
+    if (y.IsMonostate()) {
+      return R{};
+    }
+
+    return y.withWord([](const auto &yv) -> R {
+      using YR = std::decay_t<decltype(yv)>;
+      if constexpr (std::is_same_v<YR, R>) {
+        return yv;
+      } else {
+        return R::Convert(yv).value;
+      }
+    });
+  }
+
+  // Compile-time dispatchers to current/specified kind
+
+  template <typename F> static inline auto withWordProto(int kind, F &&f) {
+    using namespace Fortran::evaluate::value;
+    switch (kind) {
+    case 2:
+      return f(RealValueImpl::R2{});
+    case 3:
+      return f(RealValueImpl::R3{});
+    case 4:
+      return f(RealValueImpl::R4{});
+    case 8:
+      return f(RealValueImpl::R8{});
+    case 10:
+      return f(RealValueImpl::R10{});
+    case 16:
+      return f(RealValueImpl::R16{});
+    default:
+      llvm_unreachable("arbitrary bits not yet supported");
+    }
+  }
+
+  template <typename F> auto withWord(F &&f) const {
+    switch (storage_.index()) {
+    case 1:
+      return f(std::get<R2>(storage_));
+    case 2:
+      return f(std::get<R3>(storage_));
+    case 3:
+      return f(std::get<R4>(storage_));
+    case 4:
+      return f(std::get<R8>(storage_));
+    case 5:
+      return f(std::get<R10>(storage_));
+    case 6:
+      return f(std::get<R16>(storage_));
+    default:
+      llvm_unreachable("operation on uninitialized RealValueImpl");
+    }
+  }
+
+private:
+  template <typename INT>
+  static IntegerValue IntegerValueFromFixed(const INT &);
+
+  template <typename INT>
+  static INT FixedIntegerFromValue(const IntegerValue &);
+
+  template <typename R>
+  static ValueWithRealFlags<R> FromIntegerValue(
+      const IntegerValue &v, bool isUnsigned, Rounding rounding);
+
+  Storage storage_;
+};
+
+} // namespace Fortran::evaluate::value
+
+namespace llvm {
+/// For pretty printing in GTest
+inline raw_ostream &operator<<(
+    raw_ostream &os, const Fortran::evaluate::value::RealValueImpl &v) {
+  v.print(os);
+  return os;
+}
+} // namespace llvm
+
+#endif // FORTRAN_EVALUATE_REAL_VALUE_IMPL_H_
diff --git a/flang/lib/Evaluate/real-value.cpp b/flang/lib/Evaluate/real-value.cpp
new file mode 100644
index 0000000000000..8f8f9bbaefd99
--- /dev/null
+++ b/flang/lib/Evaluate/real-value.cpp
@@ -0,0 +1,275 @@
+//===-- lib/Evaluate/real-value.cpp ---------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "flang/Evaluate/real-value.h"
+#include "real-value-impl.h"
+#include "llvm/Support/raw_ostream.h"
+#include <new>
+#include <string>
+
+namespace Fortran::evaluate::value {
+static_assert(sizeof(RealValueImpl) == detail::kRealObjectSize);
+static_assert(alignof(RealValueImpl) == detail::kRealObjectAlign);
+static_assert(sizeof(RealValue) == sizeof(RealValueImpl));
+static_assert(alignof(RealValue) == alignof(RealValueImpl));
+
+RealValue::RealValue() { new (this) RealValueImpl(); }
+
+RealValue::~RealValue() { impl().~RealValueImpl(); }
+
+RealValue::RealValue(const RealValue &x) { new (this) RealValueImpl(x.impl()); }
+
+RealValue::RealValue(RealValue &&x) {
+  new (this) RealValueImpl(std::move(x.impl()));
+}
+
+RealValue &RealValue::operator=(const RealValue &x) {
+  impl() = x.impl();
+  return *this;
+}
+
+RealValue &RealValue::operator=(RealValue &&x) {
+  impl() = std::move(x.impl());
+  return *this;
+}
+
+RealValue::RealValue(int kind, const Word &w) {
+  new (this) RealValueImpl(kind, w);
+}
+
+RealValue::RealValue(int kind, double x) { new (this) RealValueImpl(kind, x); }
+
+RealValue RealValue::Zero(int kind) {
+  return FromImpl(RealValueImpl::Zero(kind));
+}
+
+RealValue RealValue::NegativeZero(int kind) {
+  return FromImpl(RealValueImpl::NegativeZero(kind));
+}
+
+RealValue RealValue::Infinity(int kind, bool negative) {
+  return FromImpl(RealValueImpl::Infinity(kind, negative));
+}
+
+RealValue RealValue::SignalingNaN(int kind) {
+  return FromImpl(RealValueImpl::SignalingNaN(kind));
+}
+
+bool RealValue::IsMonostate() const { return impl().IsMonostate(); }
+
+int RealValue::kind() const { return impl().kind(); }
+
+void RealValue::print(llvm::raw_ostream &os) const { impl().print(os); }
+
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+LLVM_DUMP_METHOD void RealValue::dump() const { impl().dump(); }
+#endif
+
+bool RealValue::operator==(const RealValue &y) const {
+  return impl() == y.impl();
+}
+
+bool RealValue::IsNegative() const { return impl().IsNegative(); }
+
+bool RealValue::IsNotANumber() const { return impl().IsNotANumber(); }
+
+bool RealValue::IsSignalingNaN() const { return impl().IsSignalingNaN(); }
+
+bool RealValue::IsInfinite() const { return impl().IsInfinite(); }
+
+bool RealValue::IsFinite() const { return impl().IsFinite(); }
+
+bool RealValue::IsZero() const { return impl().IsZero(); }
+
+bool RealValue::IsNormal() const { return impl().IsNormal(); }
+
+RealValue RealValue::ABS() const { return FromImpl(impl().ABS()); }
+
+RealValue RealValue::SetSign(bool toNegative) const {
+  return FromImpl(impl().SetSign(toNegative));
+}
+
+RealValue RealValue::SIGN(const RealValue &x) const {
+  return FromImpl(impl().SIGN(x.impl()));
+}
+
+RealValue RealValue::Negate() const { return FromImpl(impl().Negate()); }
+
+Relation RealValue::Compare(const RealValue &y) const {
+  return impl().Compare(y.impl());
+}
+
+ValueWithRealFlags<RealValue> RealValue::Add(
+    const RealValue &y, Rounding rounding) const {
+  return FromImpl(impl().Add(y.impl(), rounding));
+}
+ValueWithRealFlags<RealValue> RealValue::Subtract(
+    const RealValue &y, Rounding rounding) const {
+  return FromImpl(impl().Subtract(y.impl(), rounding));
+}
+ValueWithRealFlags<RealValue> RealValue::Multiply(
+    const RealValue &y, Rounding rounding) const {
+  return FromImpl(impl().Multiply(y.impl(), rounding));
+}
+ValueWithRealFlags<RealValue> RealValue::Divide(
+    const RealValue &y, Rounding rounding) const {
+  return FromImpl(impl().Divide(y.impl(), rounding));
+}
+ValueWithRealFlags<RealValue> RealValue::SQRT(Rounding rounding) const {
+  return FromImpl(impl().SQRT(rounding));
+}
+ValueWithRealFlags<RealValue> RealValue::NEAREST(bool upward) const {
+  return FromImpl(impl().NEAREST(upward));
+}
+ValueWithRealFlags<RealValue> RealValue::HYPOT(
+    const RealValue &y, Rounding rounding) const {
+  return FromImpl(impl().HYPOT(y.impl(), rounding));
+}
+ValueWithRealFlags<RealValue> RealValue::DIM(
+    const RealValue &y, Rounding rounding) const {
+  return FromImpl(impl().DIM(y.impl(), rounding));
+}
+ValueWithRealFlags<RealValue> RealValue::MOD(
+    const RealValue &y, Rounding rounding) const {
+  return FromImpl(impl().MOD(y.impl(), rounding));
+}
+ValueWithRealFlags<RealValue> RealValue::MODULO(
+    const RealValue &y, Rounding rounding) const {
+  return FromImpl(impl().MODULO(y.impl(), rounding));
+}
+ValueWithRealFlags<RealValue> RealValue::KahanSummation(
+    const RealValue &y, RealValue &correction, Rounding rounding) const {
+  return FromImpl(impl().KahanSummation(y.impl(), correction.impl(), rounding));
+}
+
+IntegerValue RealValue::EXPONENT() const { return impl().EXPONENT(); }
+
+RealValue RealValue::EPSILON(int kind) {
+  return FromImpl(RealValueImpl::EPSILON(kind));
+}
+
+RealValue RealValue::HUGE(int kind) {
+  return FromImpl(RealValueImpl::HUGE(kind));
+}
+
+RealValue RealValue::TINY(int kind) {
+  return FromImpl(RealValueImpl::TINY(kind));
+}
+
+int RealValue::DIGITS(int kind) { return RealValueImpl::DIGITS(kind); }
+
+int RealValue::PRECISION(int kind) { return RealValueImpl::PRECISION(kind); }
+
+int RealValue::RANGE(int kind) { return RealValueImpl::RANGE(kind); }
+
+int RealValue::MAXEXPONENT(int kind) {
+  return RealValueImpl::MAXEXPONENT(kind);
+}
+
+int RealValue::MINEXPONENT(int kind) {
+  return RealValueImpl::MINEXPONENT(kind);
+}
+
+RealValue RealValue::RRSPACING() const { return FromImpl(impl().RRSPACING()); }
+
+RealValue RealValue::SPACING() const { return FromImpl(impl().SPACING()); }
+
+RealValue RealValue::SET_EXPONENT(std::int64_t e) const {
+  return FromImpl(impl().SET_EXPONENT(e));
+}
+
+RealValue RealValue::FRACTION() const { return FromImpl(impl().FRACTION()); }
+
+ValueWithRealFlags<RealValue> RealValue::SCALE(
+    const IntegerValue &by, Rounding rounding) const {
+  return FromImpl(impl().SCALE(by, rounding));
+}
+
+RealValue RealValue::FlushSubnormalToZero() const {
+  return FromImpl(impl().FlushSubnormalToZero());
+}
+
+RealValue RealValue::NotANumber(int kind) {
+  return FromImpl(RealValueImpl::NotANumber(kind));
+}
+
+ValueWithRealFlags<RealValue> RealValue::FromInteger(
+    int kind, const IntegerValue &n, bool isUnsigned, Rounding rounding) {
+  return FromImpl(RealValueImpl::FromInteger(kind, n, isUnsigned, rounding));
+}
+
+ValueWithRealFlags<RealValue> RealValue::ToWholeNumber(
+    common::RoundingMode mode) const {
+  return FromImpl(impl().ToWholeNumber(mode));
+}
+ValueWithRealFlags<IntegerValue> RealValue::ToInteger(
+    common::RoundingMode mode, int toBits) const {
+  return impl().ToInteger(mode, toBits);
+}
+
+ValueWithRealFlags<RealValue> RealValue::Convert(
+    int kind, const RealValue &from, Rounding rounding) {
+  return FromImpl(RealValueImpl::Convert(kind, from.impl(), rounding));
+}
+
+IntegerValue RealValue::RawBits() const { return impl().RawBits(); }
+
+int RealValue::Exponent() const { return impl().Exponent(); }
+
+ValueWithRealFlags<RealValue> RealValue::Read(
+    int kind, const char *&pp, Rounding rounding) {
+  return FromImpl(RealValueImpl::Read(kind, pp, rounding));
+}
+
+std::string RealValue::DumpHexadecimal() const {
+  return impl().DumpHexadecimal();
+}
+
+llvm::raw_ostream &RealValue::AsFortran(
+    llvm::raw_ostream &o, int kind, bool minimal) const {
+  return impl().AsFortran(o, kind, minimal);
+}
+
+RealValue RealValue::FromRawBytes(
+    int kind, const void *raw, std::size_t expectedSize) {
+  return FromImpl(RealValueImpl::FromRawBytes(kind, raw, expectedSize));
+}
+
+void RealValue::StoreRawBytes(void *dst, size_t size, bool *changed) const {
+  impl().StoreRawBytes(dst, size, changed);
+}
+
+RealValue RealValue::FromImpl(const RealValueImpl &x) {
+  RealValue r;
+  r.impl() = x;
+  return r;
+}
+
+RealValue RealValue::FromImpl(RealValueImpl &&x) {
+  RealValue r;
+  r.impl() = std::move(x);
+  return r;
+}
+
+ValueWithRealFlags<RealValue> RealValue::FromImpl(
+    const ValueWithRealFlags<RealValueImpl> &x) {
+  ValueWithRealFlags<RealValue> r;
+  r.value.impl() = std::move(x.value);
+  r.flags = x.flags;
+  return r;
+}
+
+ValueWithRealFlags<RealValue> RealValue::FromImpl(
+    ValueWithRealFlags<RealValueImpl> &&x) {
+  ValueWithRealFlags<RealValue> r;
+  r.value.impl() = x.value;
+  r.flags = x.flags;
+  return r;
+}
+
+} // namespace Fortran::evaluate::value
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
diff --git a/flang/tools/CMakeLists.txt b/flang/tools/CMakeLists.txt
index 975eaa29343fc..9f919ffe6e5d3 100644
--- a/flang/tools/CMakeLists.txt
+++ b/flang/tools/CMakeLists.txt
@@ -16,3 +16,4 @@ add_subdirectory(tco)
 add_subdirectory(f18-parse-demo)
 add_subdirectory(fir-opt)
 add_subdirectory(fir-lsp-server)
+add_subdirectory(object-size-probe)
diff --git a/flang/tools/object-size-probe/CMakeLists.txt b/flang/tools/object-size-probe/CMakeLists.txt
new file mode 100644
index 0000000000000..79233e1a46c05
--- /dev/null
+++ b/flang/tools/object-size-probe/CMakeLists.txt
@@ -0,0 +1,42 @@
+#===-- tools/object-size-probe/CMakeLists.txt ------------------------------===#
+#
+# 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
+#
+#===------------------------------------------------------------------------===#
+
+if (CMAKE_CROSSCOMPILING)
+  # Cannot execute the probe when cross-compiling
+  # Unlike tblgen, building it separately is no solution because we need the
+  # sizeof(...)/alignof(...) to be processed for the target, not the host.
+  # include/flang/Evaluate/object-sizes.h contains backup values when the
+  # generated header does not exist
+  return ()
+endif ()
+
+
+set(LLVM_LINK_COMPONENTS
+  Support
+  )
+
+add_llvm_executable(flang-object-size-probe object-size-probe.cpp)
+target_include_directories(flang-object-size-probe PRIVATE
+  "${FLANG_SOURCE_DIR}/lib/Evaluate"
+  )
+
+set(_object_sizes_dir "${FLANG_BINARY_DIR}/include/object-sizes/$<CONFIG>/flang/Evaluate")
+set(_object_sizes_h "${_object_sizes_dir}/object-sizes-generated.h")
+
+add_custom_command(
+    OUTPUT "${_object_sizes_h}"
+    COMMAND "${CMAKE_COMMAND}" -E make_directory "${_object_sizes_dir}"
+    COMMAND "$<TARGET_FILE:flang-object-size-probe>" "--write-if-changed" "-o" "${_object_sizes_h}"
+    DEPENDS flang-object-size-probe
+    COMMENT "Deducing IntegerValueImpl/RealValueImpl/CharacterValueImpl object size and alignment ($<CONFIG>)"
+    VERBATIM)
+add_custom_target(flang-generated-object-sizes DEPENDS "${_object_sizes_h}")
+
+add_dependencies(FortranEvaluate flang-generated-object-sizes)
+add_dependencies(FortranSemantics flang-generated-object-sizes)
+add_dependencies(FortranLower flang-generated-object-sizes)
diff --git a/flang/tools/object-size-probe/object-size-probe.cpp b/flang/tools/object-size-probe/object-size-probe.cpp
new file mode 100644
index 0000000000000..e0e6ddbdb6b6d
--- /dev/null
+++ b/flang/tools/object-size-probe/object-size-probe.cpp
@@ -0,0 +1,129 @@
+//===-- tools/object-size-probe/object-size-probe.cpp -----------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Automatic deduction of the opaque object size/alignment used by the
+// IntegerValue, RealValue, and CharacterValue facades (integer-value.h,
+// real-value.h, character-value.h).
+//
+// These are similar to the pImpl-idiom, except that instead of the facade
+// storing a pointer to the implementation-object (IntegerValueImpl,
+// RealValueImpl, CharacterValueImpl), it is reinterpret-casted over the facade
+// object. This requires both to have the same object sizes. A `sizeof(*Impl)`
+// would defeat the purpose of hiding the implementation. Instead, we probe the
+// object size at build time.
+//
+// This program is compiled and executed to generate a header file containing
+// sizes of the implementation objects.
+//
+//===----------------------------------------------------------------------===//
+
+#define FLANG_OBJECT_SIZE_PROBE
+
+#include "character-value-impl.h"
+#include "integer-value-impl.h"
+#include "real-value-impl.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Format.h"
+#include "llvm/Support/InitLLVM.h"
+#include "llvm/Support/ToolOutputFile.h"
+#include "llvm/Support/raw_ostream.h"
+#include <cstddef>
+#include <cstdlib>
+
+using Fortran::evaluate::value::CharacterValueImpl;
+using Fortran::evaluate::value::IntegerValueImpl;
+using Fortran::evaluate::value::RealValueImpl;
+using namespace llvm;
+
+static cl::opt<std::string> OutputFilename("o", cl::desc("Output filename"),
+    cl::value_desc("filename"), cl::init("-"));
+
+static cl::opt<bool> WriteIfChanged(
+    "write-if-changed", cl::desc("Only write output if it changed"));
+
+static int reportError(const char *ProgName, Twine Msg) {
+  errs() << ProgName << ": " << Msg;
+  errs().flush();
+  return 1;
+}
+
+static int WriteOutput(
+    const char *argv0, StringRef Filename, StringRef Content) {
+  if (WriteIfChanged) {
+    // Only updates the real output file if there are any differences.
+    // This prevents recompilation of all the files depending on it if there
+    // aren't any.
+    if (auto ExistingOrErr = MemoryBuffer::getFile(Filename, /*IsText=*/true))
+      if (std::move(ExistingOrErr.get())->getBuffer() == Content)
+        return 0;
+  }
+  std::error_code EC;
+  ToolOutputFile OutFile(Filename, EC, sys::fs::OF_Text);
+  if (EC)
+    return reportError(
+        argv0, "error opening " + Filename + ": " + EC.message() + "\n");
+  OutFile.os() << Content;
+  OutFile.keep();
+
+  return 0;
+}
+
+int main(int argc, char **argv) {
+  InitLLVM X(argc, argv);
+  cl::ParseCommandLineOptions(argc, argv);
+
+  SmallString<1024> Buffer;
+  raw_svector_ostream OS(Buffer);
+
+  OS << llvm::format(
+      R"(
+//===-- object-sizes-generated.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Generated at build time by flang-object-size-probe.
+// Do not edit; edit flang/tools/object-size-probe/object-size-probe.cpp instead.
+// Included by flang/Evaluate/object-sizes.h when present on the path.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef FORTRAN_EVALUATE_OBJECT_SIZES_GENERATED_H_
+#define FORTRAN_EVALUATE_OBJECT_SIZES_GENERATED_H_
+
+#include <cstddef>
+
+namespace Fortran::evaluate::value::detail {
+
+// Object size/alignment for IntegerValue / IntegerValueImpl
+inline constexpr std::size_t kIntegerObjectSize{%zu};
+inline constexpr std::size_t kIntegerObjectAlign{%zu};
+
+// Object size/alignment for RealValue / RealValueImpl
+inline constexpr std::size_t kRealObjectSize{%zu};
+inline constexpr std::size_t kRealObjectAlign{%zu};
+
+// Object size/alignment for CharacterValue / CharacterValueImpl
+inline constexpr std::size_t kCharacterObjectSize{%zu};
+inline constexpr std::size_t kCharacterObjectAlign{%zu};
+
+} // namespace Fortran::evaluate::value::detail
+#endif // FORTRAN_EVALUATE_OBJECT_SIZES_GENERATED_H_
+)",
+      sizeof(IntegerValueImpl), alignof(IntegerValueImpl),
+      sizeof(RealValueImpl), alignof(RealValueImpl), sizeof(CharacterValueImpl),
+      alignof(CharacterValueImpl));
+
+  WriteOutput("object-size-probe", OutputFilename, OS.str());
+
+  return EXIT_SUCCESS;
+}
diff --git a/flang/unittests/Evaluate/CMakeLists.txt b/flang/unittests/Evaluate/CMakeLists.txt
index ed012828a7258..2a404ed1c0ee5 100644
--- a/flang/unittests/Evaluate/CMakeLists.txt
+++ b/flang/unittests/Evaluate/CMakeLists.txt
@@ -41,6 +41,22 @@ add_flang_nongtest_unittest(logical
   FortranSemantics
 )
 
+add_flang_unittest(FlangEvaluateTests
+  PARTIAL_SOURCES_INTENDED
+  CharacterValueTest.cpp
+  ComplexValueTest.cpp
+  IntegerValueTest.cpp
+  LogicalValueTest.cpp
+  RealValueTest.cpp
+)
+
+target_link_libraries(FlangEvaluateTests
+  PRIVATE
+  FortranEvaluate
+  FortranDecimal
+  FortranSemantics
+)
+
 # GCC -fno-exceptions breaks the fenv.h interfaces needed to capture
 # IEEE exception flags (different use of the word "exception")
 # in the actual hardware floating-point status register, so ensure that
diff --git a/flang/unittests/Evaluate/CharacterValueTest.cpp b/flang/unittests/Evaluate/CharacterValueTest.cpp
new file mode 100644
index 0000000000000..e5e3ef8bb4e72
--- /dev/null
+++ b/flang/unittests/Evaluate/CharacterValueTest.cpp
@@ -0,0 +1,705 @@
+//===-- flang/unittests/Evaluate/CharacterValueTest.cpp -------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "gtest/gtest.h"
+#include "flang/Common/template.h"
+#include "flang/Common/type-kinds.h"
+#include "flang/Evaluate/character-value.h"
+#include "flang/Evaluate/typekind-traits.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/ErrorHandling.h"
+#include <cstring>
+#include <initializer_list>
+#include <string>
+
+using namespace Fortran::common;
+using namespace Fortran::evaluate;
+using namespace Fortran::evaluate::value;
+
+namespace {
+
+using CharacterTypedKinds = testing::Types<TypeKind<TypeCategory::Character, 1>,
+    TypeKind<TypeCategory::Character, 2>, TypeKind<TypeCategory::Character, 4>>;
+template <typename Target>
+inline constexpr std::size_t CharacterKindPos =
+    type_index<Target, CharacterTypedKinds>::value;
+struct KindName {
+  template <typename TP> static std::string GetName(int) {
+    return "CHARACTER(" + std::to_string(TP::kind) + ")";
+  }
+};
+
+template <typename T> class CharacterValueTypedKind : public testing::Test {};
+TYPED_TEST_SUITE(CharacterValueTypedKind, CharacterTypedKinds, KindName);
+
+class CharacterValueKind : public testing::TestWithParam<int> {};
+INSTANTIATE_TEST_SUITE_P(CharacterValueKind, CharacterValueKind,
+    testing::ValuesIn(CharacterKinds),
+    [](const testing::TestParamInfo<int> &info) {
+      return "CHARACTER(" + std::to_string(info.param) + ")";
+    });
+
+//===----------------------------------------------------------------------===//
+// Helpers
+//===----------------------------------------------------------------------===//
+
+static testing::AssertionResult CharsEqual(const char *expectedExpr,
+    const char *valueExpr, llvm::StringRef expected, const CharacterValue &v) {
+  std::string actual{v.ToStdString()};
+  if (expected == actual) {
+    return testing::AssertionSuccess();
+  }
+  return testing::AssertionFailure()
+      << valueExpr << " is \"" << actual << "\", expected " << expectedExpr
+      << " (\"" << expected << "\")";
+}
+
+#define EXPECT_CHARS_EQ(expected, value) \
+  EXPECT_PRED_FORMAT2(CharsEqual, expected, value)
+
+/// Writes one character of the value's own character type at "dst".
+static void PutChar(int kind, void *dst, char32_t c) {
+  CharacterValue::withCharProto(kind, [=](auto proto) {
+    using CharT = std::decay_t<decltype(proto)>;
+    CharT raw{static_cast<CharT>(c)};
+    std::memcpy(dst, &raw, sizeof(raw));
+  });
+}
+
+//===----------------------------------------------------------------------===//
+// Construction, assignment and kind inquiries
+//===----------------------------------------------------------------------===//
+
+TEST(CharacterValue, Monostate) {
+  CharacterValue v;
+  EXPECT_TRUE(v.IsMonostate());
+
+  // Monostate behaves like an empty string
+  EXPECT_TRUE(v.empty());
+  EXPECT_EQ(0u, v.size());
+  EXPECT_EQ(0u, v.length());
+
+  // A monostate is converted to an empty string of any representation
+  EXPECT_CHARS_EQ("", v);
+  EXPECT_EQ(llvm::StringRef{}, *v.AsStringRef());
+  EXPECT_EQ(std::string{}, *v.AsStdString());
+  EXPECT_EQ(std::u16string{}, *v.AsU16String());
+  EXPECT_EQ(std::u32string{}, *v.AsU32String());
+  EXPECT_EQ(std::string{}, v.ToStdString());
+}
+
+TYPED_TEST(CharacterValueTypedKind, ConstructFromStdBasicString) {
+  using CharT = typename TypeParam::CharT;
+  using StringT = typename TypeParam::StringT;
+  constexpr int kind{TypeParam::kind};
+
+  CharT buffer[] = {'a', 'b', 'c', '\0'};
+  CharacterValue v{kind, StringT{buffer}};
+
+  EXPECT_FALSE(v.IsMonostate());
+  EXPECT_EQ(StringT{buffer}, v.AsBasicString<CharT>());
+}
+
+TEST_P(CharacterValueKind, Zero) {
+  const int kind{GetParam()};
+  CharacterValue zero{CharacterValue::Zero(kind)};
+  CharacterValue empty{kind, ""};
+  CharacterValue monostate;
+
+  EXPECT_FALSE(zero.IsMonostate());
+  EXPECT_EQ(kind, zero.kind());
+  EXPECT_TRUE(zero.empty());
+  EXPECT_EQ(0u, zero.bytesStored());
+  EXPECT_CHARS_EQ("", zero);
+  EXPECT_EQ(empty, zero);
+  EXPECT_EQ(monostate, empty);
+}
+
+TEST_P(CharacterValueKind, FillConstructor) {
+  const int kind{GetParam()};
+
+  CharacterValue v(kind, 3, U'x');
+  EXPECT_FALSE(v.IsMonostate());
+  EXPECT_EQ(kind, v.kind());
+  EXPECT_EQ(3u, v.size());
+  EXPECT_CHARS_EQ("xxx", v);
+
+  // A zero-length fill is still kind-typed
+  CharacterValue none(kind, 0, U'x');
+  EXPECT_FALSE(none.IsMonostate());
+  EXPECT_TRUE(none.empty());
+  EXPECT_EQ(kind, none.kind());
+}
+
+TEST(CharacterValue, SubscriptWidensToChar32) {
+  CharacterValue u{1, std::string{"\x80"}};
+  EXPECT_EQ('\x80', u[0]);
+
+  CharacterValue w{2, std::u16string{u"\u0100"}};
+  EXPECT_EQ(u'\u0100', w[0]);
+
+  CharacterValue v{4, std::u32string{U"\U0001F600"}};
+  EXPECT_EQ(U'\U0001F600', v[0]);
+}
+
+TEST_P(CharacterValueKind, CopyAndMove) {
+  const int kind{GetParam()};
+  CharacterValue v{kind, "abc"};
+
+  CharacterValue copyConstructed{v};
+  EXPECT_EQ(kind, v.kind());
+  EXPECT_TRUE(v == copyConstructed);
+
+  CharacterValue moveConstructed{std::move(copyConstructed)};
+  EXPECT_EQ(kind, moveConstructed.kind());
+  EXPECT_TRUE(v == moveConstructed);
+
+  CharacterValue copyAssigned;
+  copyAssigned = v;
+  EXPECT_EQ(kind, copyAssigned.kind());
+  EXPECT_TRUE(v == copyAssigned);
+
+  CharacterValue moveAssigned;
+  moveAssigned = std::move(copyAssigned);
+  EXPECT_EQ(kind, moveAssigned.kind());
+  EXPECT_TRUE(v == moveAssigned);
+}
+
+TEST_P(CharacterValueKind, CharSize) {
+  const int kind{GetParam()};
+
+  CharacterValue v{kind, "abcd"};
+  EXPECT_EQ(kind, v.kind());
+  EXPECT_EQ(static_cast<std::size_t>(kind), v.charSize());
+}
+
+TEST_P(CharacterValueKind, SizeAndLength) {
+  const int kind{GetParam()};
+
+  CharacterValue v{kind, "hello"};
+  EXPECT_FALSE(v.empty());
+  EXPECT_EQ(5u, v.size());
+  EXPECT_EQ(5u, v.length());
+}
+
+//===----------------------------------------------------------------------===//
+// Conversions to host string types
+//===----------------------------------------------------------------------===//
+
+TEST_P(CharacterValueKind, AsStringConversions) {
+  const int kind{GetParam()};
+  CharacterValue v{kind, "abc"};
+
+  // Only the conversion matching the stored character type is available.
+  EXPECT_EQ(kind == 1, v.AsStringRef().has_value());
+  EXPECT_EQ(kind == 1, v.AsStdString().has_value());
+  EXPECT_EQ(kind == 2, v.AsU16String().has_value());
+  EXPECT_EQ(kind == 4, v.AsU32String().has_value());
+
+  switch (kind) {
+  case 1:
+    EXPECT_EQ("abc", *v.AsStringRef());
+    EXPECT_EQ("abc", *v.AsStdString());
+    break;
+  case 2:
+    EXPECT_EQ(std::u16string{u"abc"}, *v.AsU16String());
+    break;
+  case 4:
+    EXPECT_EQ(std::u32string{U"abc"}, *v.AsU32String());
+    break;
+  }
+  EXPECT_EQ("abc", v.ToStdString());
+}
+
+TYPED_TEST(CharacterValueTypedKind, ToBasicString) {
+  using CharT = typename TypeParam::CharT;
+  using StringT = typename TypeParam::StringT;
+  constexpr int kind{TypeParam::kind};
+
+  const CharT data[] = {'a', 'b', 'c', '\0'};
+  CharacterValue v1{kind, data};
+  EXPECT_EQ(StringT{data}, v1.AsBasicString<CharT>());
+}
+
+TEST_P(CharacterValueKind, WithStdString) {
+  const int kind{GetParam()};
+  CharacterValue v{kind, "abcde"};
+
+  // The callable sees the concrete std::basic_string<> for the stored kind.
+  EXPECT_EQ(5u, v.withStdString([](const auto &s) { return s.size(); }));
+  EXPECT_EQ(static_cast<std::size_t>(kind), v.withStdString([](const auto &s) {
+    return sizeof(typename std::decay_t<decltype(s)>::value_type);
+  }));
+}
+
+TEST_P(CharacterValueKind, ToAscii) {
+  const int kind{GetParam()};
+
+  // conversion to possible kinds
+  CharacterValue v{kind, "abc"};
+  for (int to : std::initializer_list<int> FORTRAN_CHARACTER_KINDS) {
+    CharacterValue converted{v.ToAscii(to)};
+    EXPECT_EQ(to, converted.kind());
+    EXPECT_CHARS_EQ("abc", converted);
+  }
+
+  // Conversion between kinds is defined only for 7-bit ASCII; anything else
+  // yields an empty string.
+  CharacterValue nonascii{4, std::u32string{U"a\u0100b"}};
+  EXPECT_TRUE(nonascii.ToAscii(kind).empty());
+  EXPECT_EQ(kind, nonascii.ToAscii(kind).kind());
+
+  // Converting a monostate yields an empty string of the target kind.
+  CharacterValue empty{CharacterValue{}.ToAscii(kind)};
+  EXPECT_EQ(kind, empty.kind());
+  EXPECT_TRUE(empty.empty());
+}
+
+//===----------------------------------------------------------------------===//
+// Comparisons
+//===----------------------------------------------------------------------===//
+
+TEST_P(CharacterValueKind, Compare) {
+  const int kind{GetParam()};
+
+  CharacterValue abc{kind, "abc"};
+  CharacterValue abd{kind, "abd"};
+  CharacterValue ab{kind, "ab"};
+  CharacterValue ab_{kind, "ab "};
+  CharacterValue empty{kind, ""};
+
+  EXPECT_EQ(Ordering::Equal, abc.Compare(abc));
+  EXPECT_EQ(Ordering::Less, abc.Compare(abd));
+  EXPECT_EQ(Ordering::Greater, abd.Compare(abc));
+
+  // Fortran CHARACTER comparison blank-pads the shorter operand, so a trailing
+  // blank does not make a difference ...
+  EXPECT_EQ(Ordering::Equal, ab.Compare(ab_));
+
+  // ... whereas any other trailing character does.
+  EXPECT_EQ(Ordering::Less, ab.Compare(abc));
+
+  // A monostate compares as an empty string of the other operand's kind.
+  CharacterValue monostate;
+  EXPECT_EQ(Ordering::Equal, monostate.Compare(empty));
+  EXPECT_EQ(Ordering::Less, monostate.Compare(abc));
+  EXPECT_EQ(Ordering::Greater, abc.Compare(monostate));
+}
+
+TEST_P(CharacterValueKind, RelationalOperators) {
+  const int kind{GetParam()};
+  CharacterValue abc{kind, "abc"};
+  CharacterValue abd{kind, "abd"};
+
+  EXPECT_TRUE(abc == abc);
+  EXPECT_FALSE(abc != abc);
+  EXPECT_TRUE(abc != abd);
+  EXPECT_TRUE(abc < abd);
+  EXPECT_TRUE(abc <= abd);
+  EXPECT_TRUE(abc <= abc);
+  EXPECT_TRUE(abd > abc);
+  EXPECT_TRUE(abd >= abc);
+  EXPECT_TRUE(abc >= abc);
+  EXPECT_FALSE(abd < abc);
+
+  // The operators have std::basic_string semantics, which - unlike Compare() -
+  // do not blank-pad the shorter operand.
+  CharacterValue ab{kind, "ab"};
+  CharacterValue ab_{kind, "ab "};
+  EXPECT_TRUE(ab != ab_);
+  EXPECT_TRUE(ab < ab_);
+
+  // A monostate is an empty string here too.
+  CharacterValue monostate;
+  CharacterValue empty{kind, ""};
+  EXPECT_TRUE(monostate == empty);
+  EXPECT_TRUE(monostate < abc);
+}
+
+//===----------------------------------------------------------------------===//
+// Mutation
+//===----------------------------------------------------------------------===//
+
+TEST_P(CharacterValueKind, AssignFill) {
+  const int kind{GetParam()};
+  CharacterValue v{kind, "abc"};
+
+  v.assign(kind, 2, 'z');
+  EXPECT_EQ(kind, v.kind());
+  EXPECT_CHARS_EQ("zz", v);
+
+  // assign() also fixes the kind of a monostate, and can change the kind.
+  CharacterValue fresh;
+  fresh.assign(kind, 1, 'q');
+  EXPECT_EQ(kind, fresh.kind());
+  EXPECT_CHARS_EQ("q", fresh);
+}
+
+TEST_P(CharacterValueKind, AssignFromPointerAndLength) {
+  CharacterValue v;
+
+  // char
+  v.assign("abcd", 3);
+  EXPECT_EQ(1, v.kind());
+  EXPECT_CHARS_EQ("abc", v);
+
+  // char16_t
+  v.assign(u"abcd", 2);
+  EXPECT_EQ(2, v.kind());
+  EXPECT_CHARS_EQ("ab", v);
+
+  // char32_t
+  v.assign(U"abcd", 4);
+  EXPECT_EQ(4, v.kind());
+  EXPECT_CHARS_EQ("abcd", v);
+}
+
+TEST_P(CharacterValueKind, Erase) {
+  const int kind{GetParam()};
+  CharacterValue v{kind, "abcdef"};
+
+  v.erase(3);
+  EXPECT_EQ(kind, v.kind());
+  EXPECT_CHARS_EQ("abc", v);
+
+  v.erase(0);
+  EXPECT_EQ(kind, v.kind());
+  EXPECT_TRUE(v.empty());
+}
+
+TEST_P(CharacterValueKind, Append) {
+  const int kind{GetParam()};
+  CharacterValue v{kind, "ab"};
+
+  v.append(3, '!');
+  EXPECT_CHARS_EQ("ab!!!", v);
+
+  v.append(0, '?');
+  EXPECT_CHARS_EQ("ab!!!", v);
+}
+
+TEST_P(CharacterValueKind, Replace) {
+  const int kind{GetParam()};
+  CharacterValue v{kind, "abcdef"};
+
+  CharacterValue xy{kind, "XY"};
+  EXPECT_EQ(&v, &v.replace(1, 2, xy));
+  EXPECT_CHARS_EQ("aXYdef", v);
+
+  // The replacement need not have the same length as the replaced substring.
+  CharacterValue hyph{kind, "-"};
+  v.replace(0, 3, hyph);
+  EXPECT_CHARS_EQ("-def", v);
+}
+
+TEST_P(CharacterValueKind, Substr) {
+  const int kind{GetParam()};
+  CharacterValue v{kind, "abcdef"};
+
+  EXPECT_CHARS_EQ("cdef", v.substr(2));
+  EXPECT_EQ(kind, v.substr(2).kind());
+  EXPECT_CHARS_EQ("cd", v.substr(2, 2));
+
+  // A length reaching past the end is clamped.
+  EXPECT_CHARS_EQ("ef", v.substr(4, 100));
+  EXPECT_TRUE(v.substr(6).empty());
+
+  // The original is unchanged.
+  EXPECT_CHARS_EQ("abcdef", v);
+}
+
+TEST_P(CharacterValueKind, Reserve) {
+  const int kind{GetParam()};
+  CharacterValue v{kind, "abc"};
+
+  // Reserving capacity does not change the value.
+  v.reserve(100);
+  EXPECT_EQ(kind, v.kind());
+  EXPECT_CHARS_EQ("abc", v);
+  EXPECT_EQ(3u, v.size());
+}
+
+TEST_P(CharacterValueKind, Subscript) {
+  const int kind{GetParam()};
+  CharacterValue v{kind, "abc"};
+  EXPECT_EQ(U'a', v[0]);
+  EXPECT_EQ(U'b', v[1]);
+  EXPECT_EQ(U'c', v[2]);
+}
+
+TEST_P(CharacterValueKind, Concatenation) {
+  const int kind{GetParam()};
+  CharacterValue ab{kind, "ab"};
+  CharacterValue cd{kind, "cd"};
+  CharacterValue empty = CharacterValue::Zero(kind);
+
+  CharacterValue sum{ab + cd};
+  EXPECT_EQ(kind, sum.kind());
+  EXPECT_CHARS_EQ("abcd", sum);
+
+  // Concatenating an empty string is the identity.
+  EXPECT_CHARS_EQ("ab", ab + empty);
+}
+
+TEST_P(CharacterValueKind, AppendAssignString) {
+  const int kind{GetParam()};
+
+  CharacterValue v{kind, "ab"};
+  CharacterValue cd{kind, "cd"};
+  EXPECT_EQ(&v, &(v += cd));
+  EXPECT_CHARS_EQ("abcd", v);
+}
+
+TEST_P(CharacterValueKind, AppendAssignChar) {
+  const int kind{GetParam()};
+
+  CharacterValue v{kind, "ab"};
+  EXPECT_EQ(kind, v.kind());
+  EXPECT_EQ(&v, &(v += 'c'));
+  EXPECT_CHARS_EQ("abc", v);
+}
+
+//===----------------------------------------------------------------------===//
+// Searching
+//===----------------------------------------------------------------------===//
+
+TEST(CharacterValue, Npos) {
+  EXPECT_EQ(std::string::npos, CharacterValue::npos);
+}
+
+TEST_P(CharacterValueKind, Find) {
+  const int kind{GetParam()};
+  CharacterValue abcabc{kind, "abcabc"};
+  CharacterValue bc{kind, "bc"};
+  CharacterValue abc{kind, "abc"};
+  CharacterValue empty{kind, ""};
+  CharacterValue xyz{kind, "xyz"};
+  CharacterValue a{kind, "a"};
+  CharacterValue monostate;
+
+  EXPECT_EQ(1u, abcabc.find(bc));
+  EXPECT_EQ(0u, abcabc.find(abc));
+  EXPECT_EQ(CharacterValue::npos, abcabc.find(xyz));
+
+  // Find empty string at begnning
+  EXPECT_EQ(0u, abcabc.find(empty));
+  EXPECT_EQ(0u, abcabc.find(monostate));
+  EXPECT_EQ(0u, empty.find(empty));
+  EXPECT_EQ(0u, monostate.find(empty));
+  EXPECT_EQ(0u, monostate.find(monostate));
+
+  // Nothing is ever found in a value of unknown kind
+  EXPECT_EQ(CharacterValue::npos, monostate.find(a));
+}
+
+TEST_P(CharacterValueKind, RFind) {
+  const int kind{GetParam()};
+  CharacterValue v{kind, "abcabc"};
+  CharacterValue bc{kind, "bc"};
+  CharacterValue abc{kind, "abc"};
+  CharacterValue xyz{kind, "xyz"};
+
+  EXPECT_EQ(4u, v.rfind(bc));
+  EXPECT_EQ(3u, v.rfind(abc));
+  EXPECT_EQ(CharacterValue::npos, v.rfind(xyz));
+}
+
+TEST_P(CharacterValueKind, FindFirstOf) {
+  const int kind{GetParam()};
+  CharacterValue v{kind, "hello"};
+  CharacterValue le{kind, "le"};
+  CharacterValue h{kind, "he"};
+  CharacterValue xyz{kind, "xyz"};
+  CharacterValue empty{kind, ""};
+
+  EXPECT_EQ(1u, v.find_first_of(le));
+  EXPECT_EQ(0u, v.find_first_of(h));
+  EXPECT_EQ(CharacterValue::npos, v.find_first_of(xyz));
+  EXPECT_EQ(CharacterValue::npos, v.find_first_of(empty));
+}
+
+TEST_P(CharacterValueKind, FindLastOf) {
+  const int kind{GetParam()};
+  CharacterValue v{kind, "hello"};
+  CharacterValue le{kind, "le"};
+  CharacterValue o{kind, "o"};
+  CharacterValue xyz{kind, "xyz"};
+
+  EXPECT_EQ(3u, v.find_last_of(le));
+  EXPECT_EQ(4u, v.find_last_of(o));
+  EXPECT_EQ(CharacterValue::npos, v.find_last_of(xyz));
+}
+
+TEST_P(CharacterValueKind, FindFirstNotOfCharacter) {
+  const int kind{GetParam()};
+  CharacterValue aab{kind, "aab"};
+  CharacterValue aaa{kind, "aaa"};
+
+  EXPECT_EQ(2u, aab.find_first_not_of(U'a'));
+  EXPECT_EQ(0u, aab.find_first_not_of(U'b'));
+  EXPECT_EQ(CharacterValue::npos, aaa.find_first_not_of(U'a'));
+}
+
+TEST_P(CharacterValueKind, FindLastNotOfCharacter) {
+  const int kind{GetParam()};
+  CharacterValue abb{kind, "abb"};
+  CharacterValue bbb{kind, "bbb"};
+
+  EXPECT_EQ(0u, abb.find_last_not_of(U'b'));
+  EXPECT_EQ(2u, abb.find_last_not_of(U'a'));
+  EXPECT_EQ(CharacterValue::npos, bbb.find_last_not_of(U'b'));
+}
+
+TEST_P(CharacterValueKind, FindFirstNotOfSet) {
+  const int kind{GetParam()};
+  CharacterValue v{kind, "aabbc"};
+  CharacterValue ab{kind, "ab"};
+  CharacterValue abc{kind, "abc"};
+  CharacterValue xyz{kind, "xyz"};
+  CharacterValue a{kind, "a"};
+  CharacterValue empty{kind, ""};
+  CharacterValue monostate;
+
+  EXPECT_EQ(4u, v.find_first_not_of(ab));
+  EXPECT_EQ(0u, v.find_first_not_of(xyz));
+  EXPECT_EQ(CharacterValue::npos, v.find_first_not_of(abc));
+  EXPECT_EQ(CharacterValue::npos, empty.find_first_not_of(a));
+  EXPECT_EQ(CharacterValue::npos, monostate.find_first_not_of(a));
+}
+
+TEST_P(CharacterValueKind, FindLastNotOfSet) {
+  const int kind{GetParam()};
+  CharacterValue v{kind, "aabbc"};
+  CharacterValue abc{kind, "abc"};
+  CharacterValue bc{kind, "bc"};
+  CharacterValue xyz{kind, "xyz"};
+  CharacterValue a{kind, "a"};
+  CharacterValue empty{kind, ""};
+  CharacterValue monostate;
+
+  EXPECT_EQ(1u, v.find_last_not_of(bc));
+  EXPECT_EQ(4u, v.find_last_not_of(xyz));
+  EXPECT_EQ(CharacterValue::npos, v.find_last_not_of(abc));
+  EXPECT_EQ(CharacterValue::npos, empty.find_last_not_of(a));
+  EXPECT_EQ(CharacterValue::npos, monostate.find_last_not_of(a));
+}
+
+//===----------------------------------------------------------------------===//
+// Raw storage
+//===----------------------------------------------------------------------===//
+
+TEST_P(CharacterValueKind, Data) {
+  const int kind{GetParam()};
+  CharacterValue abc{kind, "abc"};
+  CharacterValue same{abc};
+  CharacterValue v{kind, "abc"};
+  const CharacterValue &constRef{v};
+
+  ASSERT_EQ(v.bytesStored(), 3 * kind);
+  ASSERT_EQ(same.bytesStored(), v.bytesStored());
+  EXPECT_EQ(v.data(), static_cast<void *>(v.charData()));
+  EXPECT_EQ(constRef.data(), static_cast<const void *>(constRef.charData()));
+  EXPECT_EQ(0, std::memcmp(v.data(), same.data(), v.bytesStored()));
+
+  // Writing through data() is visible in the value.
+  PutChar(kind, v.data(), U'A');
+  EXPECT_EQ(U'A', v[0]);
+}
+
+TYPED_TEST(CharacterValueTypedKind, At) {
+  constexpr int kind{TypeParam::kind};
+  CharacterValue v{kind, "abc"};
+  const CharacterValue &constRef{v};
+
+  EXPECT_EQ(v.data(), v.at(0));
+  EXPECT_EQ(static_cast<void *>(v.charData() + 2 * v.charSize()), v.at(2));
+  EXPECT_EQ(static_cast<const void *>(constRef.charData() + v.charSize()),
+      constRef.at(1));
+
+  // The character at that address is the one reported by operator[].
+  PutChar(kind, v.at(1), U'Z');
+  EXPECT_EQ(U'Z', v[1]);
+  EXPECT_CHARS_EQ("aZc", v);
+}
+
+TYPED_TEST(CharacterValueTypedKind, StoreRawBytes) {
+  using CharT = typename TypeParam::CharT;
+  constexpr int kind{TypeParam::kind};
+  CharacterValue v{kind, "abc"};
+
+  CharT buffer[4]{};
+
+  bool changed1{false};
+  v.StoreRawBytes(buffer, 3 * sizeof(CharT), &changed1);
+  EXPECT_TRUE(changed1);
+  EXPECT_EQ('a', buffer[0]);
+  EXPECT_EQ('b', buffer[1]);
+  EXPECT_EQ('c', buffer[2]);
+
+  // Storing the same bytes again reports no change.
+  bool changed2{false};
+  v.StoreRawBytes(buffer, 3 * sizeof(CharT), &changed2);
+  EXPECT_FALSE(changed2);
+
+  // Storing fewer than available chars
+  bool changed3{false};
+  buffer[1] = 'X';
+  buffer[2] = 'X';
+  v.StoreRawBytes(buffer, 2 * sizeof(CharT), &changed3);
+  EXPECT_TRUE(changed3);
+  EXPECT_EQ('b', buffer[1]);
+  EXPECT_EQ('X', buffer[2]);
+
+  // A larger destination is zero-filled beyond the payload, and that padding
+  // counts towards whether anything changed.
+  bool changed4{false};
+  buffer[3] = 'X';
+  v.StoreRawBytes(buffer, 4 * sizeof(CharT), &changed4);
+  EXPECT_TRUE(changed4);
+  EXPECT_EQ(U' ', buffer[3]);
+
+  // No change reported even with padding
+  bool changed5{false};
+  v.StoreRawBytes(buffer, 4 * sizeof(CharT), &changed5);
+  EXPECT_FALSE(changed5);
+}
+
+TYPED_TEST(CharacterValueTypedKind, FromRawBytes) {
+  using CharT = typename TypeParam::CharT;
+  constexpr int kind{TypeParam::kind};
+
+  CharT data[] = {'a', 'b', 'c', '\0'};
+  CharacterValue reference{kind, std::basic_string<CharT>(data)};
+
+  CharacterValue restored{
+      CharacterValue::FromRawBytes(kind, data, 3 * sizeof(CharT))};
+  EXPECT_EQ(kind, restored.kind());
+  EXPECT_EQ(reference, restored);
+
+  // Read an empty string
+  CharacterValue empty{CharacterValue::FromRawBytes(kind, data, 0)};
+  EXPECT_EQ(kind, empty.kind());
+  EXPECT_TRUE(empty.empty());
+}
+
+TYPED_TEST(CharacterValueTypedKind, Print) {
+  using CharT = typename TypeParam::CharT;
+  constexpr int kind{TypeParam::kind};
+  constexpr int pos{CharacterKindPos<TypeParam>};
+
+  llvm::SmallString<128> buf;
+  llvm::raw_svector_ostream os{buf};
+  const CharT data[] = {'a', 'b', 'c', '\0'};
+  CharacterValue abc{kind, data};
+  abc.print(os);
+
+  const char *results[]{"1_\"abc\"", "2_\"abc\"", "4_\"abc\""};
+  EXPECT_EQ(results[pos], os.str());
+}
+
+} // namespace
diff --git a/flang/unittests/Evaluate/ComplexValueTest.cpp b/flang/unittests/Evaluate/ComplexValueTest.cpp
new file mode 100644
index 0000000000000..93a882f551d4a
--- /dev/null
+++ b/flang/unittests/Evaluate/ComplexValueTest.cpp
@@ -0,0 +1,402 @@
+//===-- flang/unittests/Evaluate/ComplexValueTest.cpp ---------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "gtest/gtest.h"
+#include "flang/Common/type-kinds.h"
+#include "flang/Evaluate/complex-value.h"
+#include "llvm/Support/raw_ostream.h"
+#include <string>
+
+using namespace Fortran::common;
+using namespace Fortran::evaluate;
+using namespace Fortran::evaluate::value;
+
+namespace {
+
+class ComplexValueKind : public testing::TestWithParam<int> {};
+INSTANTIATE_TEST_SUITE_P(ComplexValueKind, ComplexValueKind,
+    testing::ValuesIn(RealKinds), [](const testing::TestParamInfo<int> &info) {
+      return "COMPLEX(" + std::to_string(info.param) + ")";
+    });
+
+RealValue Real(int kind, std::int64_t n) {
+  return RealValue::FromInteger(kind, IntegerValue{8, n}).value;
+}
+
+ComplexValue Complex(int kind, std::int64_t re, std::int64_t im) {
+  return ComplexValue{Real(kind, re), Real(kind, im)};
+}
+
+testing::AssertionResult ComplexValuesEqual(const char *lhsExpr,
+    const char *rhsExpr, const ComplexValue &lhs, const ComplexValue &rhs) {
+  if (lhs == rhs) {
+    return testing::AssertionSuccess();
+  }
+  return testing::AssertionFailure()
+      << lhsExpr << " (" << lhs.DumpHexadecimal() << ") != " << rhsExpr << " ("
+      << rhs.DumpHexadecimal() << ")";
+}
+
+#define EXPECT_COMPLEX_EQ(lhs, rhs) \
+  EXPECT_PRED_FORMAT2(ComplexValuesEqual, lhs, rhs)
+
+std::string AsFortranString(const ComplexValue &z, int kind) {
+  std::string s;
+  llvm::raw_string_ostream os{s};
+  z.AsFortran(os, kind);
+  return s;
+}
+
+constexpr int KindPos(int kind) {
+  for (std::size_t i{0}; i < std::size(RealKinds); ++i) {
+    if (RealKinds[i] == kind) {
+      return static_cast<int>(i);
+    }
+  }
+  return -1;
+}
+
+//===----------------------------------------------------------------------===//
+// Construction and kind inquiries
+//===----------------------------------------------------------------------===//
+
+TEST(ComplexValue, DefaultConstructionIsMonostate) {
+  ComplexValue z;
+  EXPECT_TRUE(z.IsMonostate());
+  EXPECT_TRUE(z.IsZero());
+  EXPECT_FALSE(z.IsInfinite());
+  EXPECT_FALSE(z.IsNotANumber());
+  EXPECT_FALSE(z.IsSignalingNaN());
+}
+
+TEST_P(ComplexValueKind, ConstructFromParts) {
+  const int kind{GetParam()};
+  ComplexValue z{Real(kind, 1), Real(kind, 2)};
+  EXPECT_FALSE(z.IsMonostate());
+  EXPECT_EQ(kind, z.kind());
+  EXPECT_TRUE(z.REAL() == Real(kind, 1));
+  EXPECT_TRUE(z.AIMAG() == Real(kind, 2));
+}
+
+TEST_P(ComplexValueKind, ConstructFromRealPartOnly) {
+  const int kind{GetParam()};
+  ComplexValue z{Real(kind, 3)};
+  EXPECT_EQ(kind, z.kind());
+  EXPECT_TRUE(z.REAL() == Real(kind, 3));
+  EXPECT_TRUE(z.AIMAG().IsZero());
+  // The kind-checking form agrees.
+  EXPECT_COMPLEX_EQ(z, ComplexValue(kind, Real(kind, 3)));
+}
+
+TEST_P(ComplexValueKind, ImaginaryPartIsConvertedToTheRealPartsKind) {
+  const int kind{GetParam()};
+  // The imaginary operand is converted to the kind of the real operand.
+  ComplexValue z{Real(kind, 1), Real(8, 2)};
+  EXPECT_EQ(kind, z.kind());
+  EXPECT_TRUE(z.AIMAG() == Real(kind, 2));
+}
+
+TEST(ComplexValue, CopyAndMove) {
+  ComplexValue z{Complex(4, 1, 2)};
+  ComplexValue copyConstructed{z};
+  EXPECT_COMPLEX_EQ(z, copyConstructed);
+  ComplexValue copyAssigned;
+  copyAssigned = z;
+  EXPECT_COMPLEX_EQ(z, copyAssigned);
+  ComplexValue moveConstructed{std::move(copyConstructed)};
+  EXPECT_COMPLEX_EQ(z, moveConstructed);
+  ComplexValue moveAssigned;
+  moveAssigned = std::move(copyAssigned);
+  EXPECT_COMPLEX_EQ(z, moveAssigned);
+}
+
+TEST(ComplexValue, KindCheckingConstructors) {
+  ComplexValue z{Complex(4, 1, 2)};
+  EXPECT_EQ(4, ComplexValue(4, z).kind());
+  EXPECT_COMPLEX_EQ(z, ComplexValue(4, z));
+  ComplexValue y{Complex(8, 1, 2)};
+  ComplexValue moved{8, std::move(y)};
+  EXPECT_EQ(8, moved.kind());
+}
+
+TEST_P(ComplexValueKind, Zero) {
+  const int kind{GetParam()};
+  ComplexValue zero{ComplexValue::Zero(kind)};
+  EXPECT_FALSE(zero.IsMonostate());
+  EXPECT_EQ(kind, zero.kind());
+  EXPECT_TRUE(zero.IsZero());
+  EXPECT_FALSE(zero.REAL().IsNegative());
+  EXPECT_FALSE(zero.AIMAG().IsNegative());
+}
+
+TEST(ComplexValue, BytesStored) {
+  EXPECT_EQ(4u, ComplexValue::bytesStored(2));
+  EXPECT_EQ(4u, ComplexValue::bytesStored(3));
+  EXPECT_EQ(8u, ComplexValue::bytesStored(4));
+  EXPECT_EQ(16u, ComplexValue::bytesStored(8));
+  EXPECT_EQ(32u, ComplexValue::bytesStored(10));
+  EXPECT_EQ(32u, ComplexValue::bytesStored(16));
+  EXPECT_EQ(8u, Complex(4, 1, 2).bytesStored());
+}
+
+//===----------------------------------------------------------------------===//
+// Component access and sign manipulation
+//===----------------------------------------------------------------------===//
+
+TEST_P(ComplexValueKind, REAL) {
+  const int kind{GetParam()};
+  EXPECT_TRUE(Complex(kind, 1, 2).REAL() == Real(kind, 1));
+  EXPECT_EQ(kind, Complex(kind, 1, 2).REAL().kind());
+}
+
+TEST_P(ComplexValueKind, AIMAG) {
+  const int kind{GetParam()};
+  EXPECT_TRUE(Complex(kind, 1, 2).AIMAG() == Real(kind, 2));
+  EXPECT_EQ(kind, Complex(kind, 1, 2).AIMAG().kind());
+}
+
+TEST_P(ComplexValueKind, CONJG) {
+  const int kind{GetParam()};
+  EXPECT_COMPLEX_EQ(Complex(kind, 1, -2), Complex(kind, 1, 2).CONJG());
+  EXPECT_COMPLEX_EQ(Complex(kind, 1, 2), Complex(kind, 1, 2).CONJG().CONJG());
+}
+
+TEST_P(ComplexValueKind, Negate) {
+  const int kind{GetParam()};
+  EXPECT_COMPLEX_EQ(Complex(kind, -1, -2), Complex(kind, 1, 2).Negate());
+  // Negating a zero flips both sign bits.
+  ComplexValue negZero{ComplexValue::Zero(kind).Negate()};
+  EXPECT_TRUE(negZero.IsZero());
+  EXPECT_TRUE(negZero.REAL().IsNegative());
+  EXPECT_TRUE(negZero.AIMAG().IsNegative());
+}
+
+//===----------------------------------------------------------------------===//
+// Comparison and classification
+//===----------------------------------------------------------------------===//
+
+TEST_P(ComplexValueKind, Equals) {
+  const int kind{GetParam()};
+  // Equals() compares numerically, so +0.0 and -0.0 are equal ...
+  EXPECT_TRUE(
+      ComplexValue::Zero(kind).Equals(ComplexValue::Zero(kind).Negate()));
+  EXPECT_TRUE(Complex(kind, 1, 2).Equals(Complex(kind, 1, 2)));
+  EXPECT_FALSE(Complex(kind, 1, 2).Equals(Complex(kind, 1, 3)));
+  // ... and a NaN is equal to nothing, not even itself.
+  EXPECT_FALSE(
+      ComplexValue::NotANumber(kind).Equals(ComplexValue::NotANumber(kind)));
+}
+
+TEST_P(ComplexValueKind, EqualityOperators) {
+  const int kind{GetParam()};
+  // The operators compare bit patterns, so -0.0 differs from +0.0 ...
+  EXPECT_FALSE(ComplexValue::Zero(kind) == ComplexValue::Zero(kind).Negate());
+  EXPECT_TRUE(ComplexValue::Zero(kind) != ComplexValue::Zero(kind).Negate());
+  // ... and a NaN equals itself.
+  EXPECT_TRUE(ComplexValue::NotANumber(kind) == ComplexValue::NotANumber(kind));
+  EXPECT_TRUE(Complex(kind, 1, 2) == Complex(kind, 1, 2));
+  EXPECT_TRUE(Complex(kind, 1, 2) != Complex(kind, 2, 1));
+}
+
+TEST_P(ComplexValueKind, IsZero) {
+  const int kind{GetParam()};
+  EXPECT_TRUE(ComplexValue::Zero(kind).IsZero());
+  EXPECT_FALSE(Complex(kind, 1, 0).IsZero());
+  EXPECT_FALSE(Complex(kind, 0, 1).IsZero());
+}
+
+TEST_P(ComplexValueKind, IsInfinite) {
+  const int kind{GetParam()};
+  RealValue inf{Real(kind, 1).Divide(RealValue::Zero(kind)).value};
+  ASSERT_TRUE(inf.IsInfinite());
+  EXPECT_FALSE(ComplexValue::Zero(kind).IsInfinite());
+  // Either part being infinite suffices.
+  EXPECT_TRUE(ComplexValue(inf, Real(kind, 1)).IsInfinite());
+  EXPECT_TRUE(ComplexValue(Real(kind, 1), inf).IsInfinite());
+}
+
+TEST_P(ComplexValueKind, IsNotANumber) {
+  const int kind{GetParam()};
+  RealValue nan{RealValue::NotANumber(kind)};
+  EXPECT_FALSE(ComplexValue::Zero(kind).IsNotANumber());
+  EXPECT_TRUE(ComplexValue::NotANumber(kind).IsNotANumber());
+  // Either part being a NaN suffices.
+  EXPECT_TRUE(ComplexValue(nan, Real(kind, 1)).IsNotANumber());
+  EXPECT_TRUE(ComplexValue(Real(kind, 1), nan).IsNotANumber());
+}
+
+TEST_P(ComplexValueKind, IsSignalingNaN) {
+  const int kind{GetParam()};
+  EXPECT_FALSE(ComplexValue::Zero(kind).IsSignalingNaN());
+  // NotANumber() produces quiet NaNs.
+  EXPECT_FALSE(ComplexValue::NotANumber(kind).IsSignalingNaN());
+}
+
+TEST_P(ComplexValueKind, NotANumber) {
+  const int kind{GetParam()};
+  ComplexValue nan{ComplexValue::NotANumber(kind)};
+  EXPECT_EQ(kind, nan.kind());
+  EXPECT_TRUE(nan.REAL().IsNotANumber());
+  EXPECT_TRUE(nan.AIMAG().IsNotANumber());
+}
+
+//===----------------------------------------------------------------------===//
+// Arithmetic
+//===----------------------------------------------------------------------===//
+
+TEST_P(ComplexValueKind, FromInteger) {
+  const int kind{GetParam()};
+  auto z{ComplexValue::FromInteger(kind, IntegerValue{8, 3})};
+  EXPECT_TRUE(z.flags.empty());
+  EXPECT_EQ(kind, z.value.kind());
+  EXPECT_COMPLEX_EQ(Complex(kind, 3, 0), z.value);
+  auto negative{ComplexValue::FromInteger(kind, IntegerValue{8, -3})};
+  EXPECT_COMPLEX_EQ(Complex(kind, -3, 0), negative.value);
+  // Reading the same bits as unsigned gives a large positive real part.
+  auto asUnsigned{ComplexValue::FromInteger(
+      kind, IntegerValue{8, -1}, /*isUnsigned=*/true)};
+  EXPECT_FALSE(asUnsigned.value.REAL().IsNegative());
+  EXPECT_TRUE(asUnsigned.value.AIMAG().IsZero());
+}
+
+TEST_P(ComplexValueKind, Add) {
+  const int kind{GetParam()};
+  auto sum{Complex(kind, 1, 2).Add(Complex(kind, 3, 4))};
+  EXPECT_TRUE(sum.flags.empty());
+  EXPECT_COMPLEX_EQ(Complex(kind, 4, 6), sum.value);
+  // Flags from either part are accumulated.
+  auto overflowed{ComplexValue(RealValue::HUGE(kind))
+          .Add(ComplexValue(RealValue::HUGE(kind)))};
+  EXPECT_TRUE(overflowed.flags.test(RealFlag::Overflow));
+  EXPECT_TRUE(overflowed.value.IsInfinite());
+}
+
+TEST_P(ComplexValueKind, Subtract) {
+  const int kind{GetParam()};
+  auto diff{Complex(kind, 1, 2).Subtract(Complex(kind, 3, 4))};
+  EXPECT_TRUE(diff.flags.empty());
+  EXPECT_COMPLEX_EQ(Complex(kind, -2, -2), diff.value);
+}
+
+TEST_P(ComplexValueKind, Multiply) {
+  const int kind{GetParam()};
+  // (1+2i)*(3+4i) = (3-8) + (4+6)i
+  auto product{Complex(kind, 1, 2).Multiply(Complex(kind, 3, 4))};
+  EXPECT_TRUE(product.flags.empty());
+  EXPECT_COMPLEX_EQ(Complex(kind, -5, 10), product.value);
+  // Multiplying by i rotates by a quarter turn.
+  EXPECT_COMPLEX_EQ(Complex(kind, -2, 1),
+      Complex(kind, 1, 2).Multiply(Complex(kind, 0, 1)).value);
+}
+
+TEST_P(ComplexValueKind, Divide) {
+  const int kind{GetParam()};
+  // (-5+10i)/(3+4i) = 1+2i
+  auto quotient{Complex(kind, -5, 10).Divide(Complex(kind, 3, 4))};
+  EXPECT_COMPLEX_EQ(Complex(kind, 1, 2), quotient.value);
+  // Dividing by a real number divides both parts.
+  EXPECT_COMPLEX_EQ(Complex(kind, 1, 2),
+      Complex(kind, 4, 8).Divide(Complex(kind, 4, 0)).value);
+  // Dividing by a purely imaginary number.
+  EXPECT_COMPLEX_EQ(Complex(kind, 2, 0),
+      Complex(kind, 0, 4).Divide(Complex(kind, 0, 2)).value);
+  // Dividing by zero reaches (0/0) in the numerator, hence a NaN rather than
+  // an infinity.
+  auto byZero{Complex(kind, 1, 0).Divide(ComplexValue::Zero(kind))};
+  EXPECT_TRUE(byZero.flags.test(RealFlag::InvalidArgument));
+  EXPECT_TRUE(byZero.value.IsNotANumber());
+}
+
+TEST_P(ComplexValueKind, ABS) {
+  const int kind{GetParam()};
+  auto abs{Complex(kind, 3, 4).ABS()};
+  EXPECT_TRUE(abs.value == Real(kind, 5));
+  EXPECT_EQ(kind, abs.value.kind());
+  EXPECT_TRUE(Complex(kind, -3, -4).ABS().value == Real(kind, 5));
+  EXPECT_TRUE(ComplexValue::Zero(kind).ABS().value.IsZero());
+}
+
+TEST_P(ComplexValueKind, KahanSummation) {
+  const int kind{GetParam()};
+  ComplexValue correction{ComplexValue::Zero(kind)};
+  auto sum{Complex(kind, 1, 2).KahanSummation(Complex(kind, 3, 4), correction)};
+  EXPECT_COMPLEX_EQ(Complex(kind, 4, 6), sum.value);
+  EXPECT_TRUE(correction.IsZero());
+  // A contribution too small to appear in the sum survives in the correction.
+  RealValue tooSmall{RealValue::EPSILON(kind).Divide(Real(kind, 4)).value};
+  correction = ComplexValue::Zero(kind);
+  auto lossy{Complex(kind, 1, 1)
+          .KahanSummation(ComplexValue{tooSmall, tooSmall}, correction)};
+  EXPECT_COMPLEX_EQ(Complex(kind, 1, 1), lossy.value);
+  EXPECT_FALSE(correction.IsZero());
+}
+
+TEST_P(ComplexValueKind, FlushSubnormalToZero) {
+  const int kind{GetParam()};
+  RealValue subnormal{RealValue{kind, IntegerValue{kind, 1}}};
+  ASSERT_FALSE(subnormal.IsZero());
+  ComplexValue z{subnormal, subnormal};
+  EXPECT_FALSE(z.IsZero());
+  EXPECT_TRUE(z.FlushSubnormalToZero().IsZero());
+  // Normal values pass through unchanged.
+  EXPECT_COMPLEX_EQ(
+      Complex(kind, 1, 2), Complex(kind, 1, 2).FlushSubnormalToZero());
+}
+
+//===----------------------------------------------------------------------===//
+// Formatting and raw storage
+//===----------------------------------------------------------------------===//
+
+TEST(ComplexValue, DumpHexadecimal) {
+  EXPECT_EQ("(0.0,0.0)", ComplexValue::Zero(4).DumpHexadecimal());
+  EXPECT_EQ("(0x1.0p0,-0x1.0p1)", Complex(4, 1, -2).DumpHexadecimal());
+}
+
+TEST_P(ComplexValueKind, AsFortran) {
+  const int kind{GetParam()};
+  std::string s{AsFortranString(Complex(kind, 1, 2), kind)};
+  // The components are emitted as a parenthesized, comma-separated pair.
+  EXPECT_EQ('(', s.front());
+  EXPECT_EQ(')', s.back());
+  EXPECT_NE(std::string::npos, s.find(','));
+}
+
+TEST_P(ComplexValueKind, RawBytesRoundTrip) {
+  const int kind{GetParam()};
+  ComplexValue original{Complex(kind, 1, -2)};
+  char buffer[32]{};
+  ASSERT_EQ(ComplexValue::bytesStored(kind), original.bytesStored());
+  bool changed{false};
+  original.StoreRawBytes(buffer, original.bytesStored(), &changed);
+  EXPECT_TRUE(changed);
+  ComplexValue restored{
+      ComplexValue::FromRawBytes(kind, buffer, original.bytesStored())};
+  EXPECT_EQ(kind, restored.kind());
+  EXPECT_COMPLEX_EQ(original, restored);
+  changed = false;
+  original.StoreRawBytes(buffer, original.bytesStored(), &changed);
+  EXPECT_FALSE(changed);
+}
+
+TEST_P(ComplexValueKind, Print) {
+  const int kind{GetParam()};
+  const int pos{KindPos(kind)};
+
+  llvm::SmallString<128> buf;
+  llvm::raw_svector_ostream os{buf};
+  ComplexValue v{RealValue::FromInteger(kind, IntegerValue{kind, 42}).value,
+      RealValue::FromInteger(kind, IntegerValue{kind, 21}).value};
+  v.print(os);
+
+  const char *results[]{"(4.2e1_2,2.1e1_2)", "(4.2e1_3,2.1e1_3)",
+      "(4.2e1_4,2.1e1_4)", "(4.2e1_8,2.1e1_8)", "(4.2e1_10,2.1e1_10)",
+      "(4.2e1_16,2.1e1_16)"};
+  EXPECT_EQ(results[pos], os.str());
+}
+
+} // namespace
diff --git a/flang/unittests/Evaluate/IntegerValueTest.cpp b/flang/unittests/Evaluate/IntegerValueTest.cpp
new file mode 100644
index 0000000000000..84e6f28690718
--- /dev/null
+++ b/flang/unittests/Evaluate/IntegerValueTest.cpp
@@ -0,0 +1,2347 @@
+//===-- flang/unittests/Evaluate/IntegerValueTest.cpp ---------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "gtest/gtest.h"
+#include "flang/Common/Fortran-consts.h"
+#include "flang/Common/template.h"
+#include "flang/Common/type-kinds.h"
+#include "flang/Common/uint128.h"
+#include "flang/Evaluate/integer-value.h"
+#include "flang/Evaluate/typekind-traits.h"
+#include "llvm/ADT/Sequence.h"
+#include <algorithm>
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <initializer_list>
+#include <ostream>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+using namespace Fortran::common;
+using namespace Fortran::evaluate;
+using namespace Fortran::evaluate::value;
+
+namespace {
+
+//===----------------------------------------------------------------------===//
+// Parameterization over the INTEGER kinds
+//===----------------------------------------------------------------------===//
+
+using IntegerTypedKinds = testing::Types<TypeKind<TypeCategory::Integer, 1>,
+    TypeKind<TypeCategory::Integer, 2>, TypeKind<TypeCategory::Integer, 4>,
+    TypeKind<TypeCategory::Integer, 8>, TypeKind<TypeCategory::Integer, 16>>;
+template <typename Target>
+inline constexpr std::size_t IntKindPos =
+    type_index<Target, IntegerTypedKinds>::value;
+struct KindName {
+  template <typename TK> static std::string GetName(int) {
+    return "INTEGER(" + std::to_string(TK::kind) + ")";
+  }
+};
+
+template <typename T> class IntegerValueTypedKind : public testing::Test {};
+TYPED_TEST_SUITE(IntegerValueTypedKind, IntegerTypedKinds, KindName);
+
+class IntegerValueKind : public testing::TestWithParam<int> {};
+INSTANTIATE_TEST_SUITE_P(IntegerValueKind, IntegerValueKind,
+    testing::ValuesIn(IntegerKinds),
+    [](const testing::TestParamInfo<int> &info) {
+      return "INTEGER(" + std::to_string(info.param) + ")";
+    });
+
+//===----------------------------------------------------------------------===//
+// Construction, assignment and kind inquiries
+//===----------------------------------------------------------------------===//
+
+TEST(IntegerValue, Monostate) {
+  IntegerValue x;
+  EXPECT_TRUE(x.IsMonostate());
+  EXPECT_TRUE(x.IsZero());
+  EXPECT_FALSE(x.IsNegative());
+  EXPECT_EQ(0u, x.ToUInt64());
+  EXPECT_EQ(0, x.ToInt64());
+  EXPECT_EQ(0, x.POPCNT());
+  EXPECT_FALSE(x.BTEST(0));
+  EXPECT_EQ("0", x.SignedDecimal());
+  EXPECT_EQ("0", x.UnsignedDecimal());
+  EXPECT_EQ("0", x.Hexadecimal());
+}
+
+TYPED_TEST(IntegerValueTypedKind, ConstructFromIntegral) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue positive{kind, 42};
+  EXPECT_EQ(42, positive.ToInt64());
+  IntegerValue negative{kind, -42};
+  EXPECT_EQ(-42, negative.ToInt64());
+
+  // The signedness of the C++ operand decides between sign- and zero-extension.
+  IntegerValue sext{kind, int8_t{-1}};
+  EXPECT_EQ(SignedT{-1}, sext.ToSInt<SignedT>());
+  EXPECT_EQ(std::numeric_limits<UnsignedT>::max(), sext.ToUInt<UnsignedT>());
+  IntegerValue zext{kind, uint8_t{255}};
+  EXPECT_EQ(UnsignedT{255}, zext.ToUInt<UnsignedT>());
+
+  // A value too wide for the kind is truncated silently.
+  constexpr uint64_t w{0x123456789abcdefu};
+  IntegerValue wide{kind, w};
+  EXPECT_EQ(UnsignedT(w), wide.ToUInt<UnsignedT>());
+  EXPECT_EQ(SignedT(w), wide.ToSInt<SignedT>());
+  EXPECT_EQ(UnsignedT(w), wide.ToUInt<UnsignedT>());
+  EXPECT_EQ(SignedT(w), wide.ToSInt<SignedT>());
+}
+
+TEST_P(IntegerValueKind, CopyAndMove) {
+  const int kind{GetParam()};
+  const IntegerValue x{IntegerValue::HUGE(kind)};
+
+  IntegerValue copyConstructed{x};
+  EXPECT_EQ(kind, copyConstructed.kind());
+  EXPECT_EQ(x, copyConstructed);
+
+  IntegerValue copyAssigned;
+  copyAssigned = x;
+  EXPECT_EQ(kind, copyAssigned.kind());
+  EXPECT_EQ(x, copyAssigned);
+
+  IntegerValue moveConstructed{std::move(copyConstructed)};
+  EXPECT_EQ(kind, moveConstructed.kind());
+  EXPECT_EQ(x, moveConstructed);
+
+  IntegerValue moveAssigned;
+  moveAssigned = std::move(copyAssigned);
+  EXPECT_EQ(kind, moveAssigned.kind());
+  EXPECT_EQ(x, moveAssigned);
+}
+
+TYPED_TEST(IntegerValueTypedKind, KindCheckingConstructors) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue x{kind, 7};
+  IntegerValue copied{kind, x};
+  EXPECT_EQ(SignedT(7), copied.ToSInt<SignedT>());
+  EXPECT_EQ(UnsignedT(7), copied.ToUInt<UnsignedT>());
+
+  IntegerValue y{kind, 7};
+  IntegerValue moved{kind, std::move(y)};
+  EXPECT_EQ(kind, moved.kind());
+  EXPECT_EQ(SignedT(7), moved.ToSInt<SignedT>());
+  EXPECT_EQ(UnsignedT(7), moved.ToUInt<UnsignedT>());
+}
+
+TYPED_TEST(IntegerValueTypedKind, Zero) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  EXPECT_EQ(kind, zero.kind());
+  EXPECT_FALSE(zero.IsMonostate());
+  EXPECT_TRUE(zero.IsZero());
+  EXPECT_EQ(SignedT(0), zero.ToSInt<SignedT>());
+}
+
+TEST(IntegerValue, Bits) {
+  EXPECT_EQ(8, IntegerValue::bits(1));
+  EXPECT_EQ(16, IntegerValue::bits(2));
+  EXPECT_EQ(16, IntegerValue::bits(3));
+  EXPECT_EQ(32, IntegerValue::bits(4));
+  EXPECT_EQ(64, IntegerValue::bits(8));
+  EXPECT_EQ(128, IntegerValue::bits(10)); // 80 significant bits, 128 stored
+  EXPECT_EQ(128, IntegerValue::bits(16));
+
+  IntegerValue v{4, 0};
+  EXPECT_EQ(32, v.bits());
+}
+
+TEST(IntegerValue, BytesStored) {
+  EXPECT_EQ(1u, IntegerValue::bytesStored(1));
+  EXPECT_EQ(2u, IntegerValue::bytesStored(2));
+  EXPECT_EQ(2u, IntegerValue::bytesStored(3));
+  EXPECT_EQ(4u, IntegerValue::bytesStored(4));
+  EXPECT_EQ(8u, IntegerValue::bytesStored(8));
+  EXPECT_EQ(16u, IntegerValue::bytesStored(10));
+  EXPECT_EQ(16u, IntegerValue::bytesStored(16));
+
+  IntegerValue v{4, 0};
+  EXPECT_EQ(4u, v.bytesStored());
+}
+
+TYPED_TEST(IntegerValueTypedKind, DIGITS) {
+  constexpr int kind{TypeParam::kind};
+  EXPECT_EQ(TypeParam::bits - 1, IntegerValue::DIGITS(kind));
+}
+
+TEST(IntegerValue, RANGE) {
+  EXPECT_EQ(2, IntegerValue::RANGE(1));
+  EXPECT_EQ(4, IntegerValue::RANGE(2));
+  EXPECT_EQ(9, IntegerValue::RANGE(4));
+  EXPECT_EQ(18, IntegerValue::RANGE(8));
+  EXPECT_EQ(38, IntegerValue::RANGE(16));
+}
+
+TEST(IntegerValue, UnsignedRANGE) {
+  EXPECT_EQ(2, IntegerValue::UnsignedRANGE(1));
+  EXPECT_EQ(4, IntegerValue::UnsignedRANGE(2));
+  EXPECT_EQ(9, IntegerValue::UnsignedRANGE(4));
+  EXPECT_EQ(19, IntegerValue::UnsignedRANGE(8));
+  EXPECT_EQ(38, IntegerValue::UnsignedRANGE(16));
+}
+
+//===----------------------------------------------------------------------===//
+// Formatting and parsing
+//===----------------------------------------------------------------------===//
+
+TYPED_TEST(IntegerValueTypedKind, UnsignedDecimal) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(TypeParam::kind)};
+  EXPECT_EQ("0", zero.UnsignedDecimal());
+
+  IntegerValue one{kind, 1};
+  EXPECT_EQ("1", one.UnsignedDecimal());
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_EQ("42", theanswer.UnsignedDecimal());
+
+  IntegerValue maxv{kind, std::numeric_limits<UnsignedT>::max()};
+  static constexpr const char *maxstr[]{"255", "65535", "4294967295",
+      "18446744073709551615", "340282366920938463463374607431768211455"};
+  EXPECT_EQ(maxstr[IntKindPos<TypeParam>], maxv.UnsignedDecimal());
+
+  IntegerValue beforemaxv{kind, std::numeric_limits<UnsignedT>::max() - 1};
+  static constexpr const char *beforemaxstr[]{"254", "65534", "4294967294",
+      "18446744073709551614", "340282366920938463463374607431768211454"};
+  EXPECT_EQ(beforemaxstr[IntKindPos<TypeParam>], beforemaxv.UnsignedDecimal());
+
+  IntegerValue hugev{kind, IntegerValue::HUGE(kind)};
+  static constexpr const char *hugestr[]{"127", "32767", "2147483647",
+      "9223372036854775807", "170141183460469231731687303715884105727"};
+  EXPECT_EQ(hugestr[IntKindPos<TypeParam>], hugev.UnsignedDecimal());
+
+  IntegerValue leastv{kind, IntegerValue::Least(kind)};
+  static constexpr const char *leaststr[]{"128", "32768", "2147483648",
+      "9223372036854775808", "170141183460469231731687303715884105728"};
+  EXPECT_EQ(leaststr[IntKindPos<TypeParam>], leastv.UnsignedDecimal());
+
+  IntegerValue patternv{kind, 0x0123456789abcdefull};
+  static constexpr const char *patternstr[]{
+      "239", "52719", "2309737967", "81985529216486895", "81985529216486895"};
+  EXPECT_EQ(patternstr[IntKindPos<TypeParam>], patternv.UnsignedDecimal());
+
+  IntegerValue invpatternv{kind, ~UnsignedT(0x0123456789abcdefull)};
+  static constexpr const char *invpatternstr[]{"16", "12816", "1985229328",
+      "18364758544493064720", "340282366920938463463292621902551724560"};
+  EXPECT_EQ(
+      invpatternstr[IntKindPos<TypeParam>], invpatternv.UnsignedDecimal());
+}
+
+TYPED_TEST(IntegerValueTypedKind, SignedDecimal) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(TypeParam::kind)};
+  EXPECT_EQ("0", zero.SignedDecimal());
+
+  IntegerValue one{kind, 1};
+  EXPECT_EQ("1", one.SignedDecimal());
+
+  IntegerValue minusone{kind, -1};
+  EXPECT_EQ("-1", minusone.SignedDecimal());
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_EQ("42", theanswer.SignedDecimal());
+
+  IntegerValue maxv{kind, std::numeric_limits<SignedT>::max()};
+  static constexpr const char *maxstr[]{"127", "32767", "2147483647",
+      "9223372036854775807", "170141183460469231731687303715884105727"};
+  EXPECT_EQ(maxstr[IntKindPos<TypeParam>], maxv.SignedDecimal());
+
+  IntegerValue beforemaxv{kind, std::numeric_limits<SignedT>::max() - 1};
+  static constexpr const char *beforemaxstr[]{"126", "32766", "2147483646",
+      "9223372036854775806", "170141183460469231731687303715884105726"};
+  EXPECT_EQ(beforemaxstr[IntKindPos<TypeParam>], beforemaxv.SignedDecimal());
+
+  IntegerValue hugev{kind, IntegerValue::HUGE(kind)};
+  static constexpr const char *hugestr[]{"127", "32767", "2147483647",
+      "9223372036854775807", "170141183460469231731687303715884105727"};
+  EXPECT_EQ(hugestr[IntKindPos<TypeParam>], hugev.SignedDecimal());
+
+  IntegerValue leastv{kind, IntegerValue::Least(kind)};
+  static constexpr const char *leaststr[]{"-128", "-32768", "-2147483648",
+      "-9223372036854775808", "-170141183460469231731687303715884105728"};
+  EXPECT_EQ(leaststr[IntKindPos<TypeParam>], leastv.SignedDecimal());
+
+  IntegerValue patternv{kind, 0x0123456789abcdefull};
+  static constexpr const char *patternstr[]{
+      "-17", "-12817", "-1985229329", "81985529216486895", "81985529216486895"};
+  EXPECT_EQ(patternstr[IntKindPos<TypeParam>], patternv.SignedDecimal());
+
+  IntegerValue invpatternv{kind, ~SignedT(0x0123456789abcdefull)};
+  static constexpr const char *invpatternstr[]{
+      "16", "12816", "1985229328", "-81985529216486896", "-81985529216486896"};
+  EXPECT_EQ(invpatternstr[IntKindPos<TypeParam>], invpatternv.SignedDecimal());
+}
+
+TYPED_TEST(IntegerValueTypedKind, Hexadecimal) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(TypeParam::kind)};
+  EXPECT_EQ("0", zero.Hexadecimal());
+
+  IntegerValue one{kind, 1};
+  EXPECT_EQ("1", one.Hexadecimal());
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_EQ("2a", theanswer.Hexadecimal());
+
+  IntegerValue maxv{kind, std::numeric_limits<UnsignedT>::max()};
+  static constexpr const char *maxstr[]{"ff", "ffff", "ffffffff",
+      "ffffffffffffffff", "ffffffffffffffffffffffffffffffff"};
+  EXPECT_EQ(maxstr[IntKindPos<TypeParam>], maxv.Hexadecimal());
+
+  IntegerValue beforemaxv{kind, std::numeric_limits<UnsignedT>::max() - 1};
+  static constexpr const char *beforemaxstr[]{"fe", "fffe", "fffffffe",
+      "fffffffffffffffe", "fffffffffffffffffffffffffffffffe"};
+  EXPECT_EQ(beforemaxstr[IntKindPos<TypeParam>], beforemaxv.Hexadecimal());
+
+  IntegerValue hugev{kind, IntegerValue::HUGE(kind)};
+  static constexpr const char *hugestr[]{"7f", "7fff", "7fffffff",
+      "7fffffffffffffff", "7fffffffffffffffffffffffffffffff"};
+  EXPECT_EQ(hugestr[IntKindPos<TypeParam>], hugev.Hexadecimal());
+
+  IntegerValue leastv{kind, IntegerValue::Least(kind)};
+  static constexpr const char *leaststr[]{"80", "8000", "80000000",
+      "8000000000000000", "80000000000000000000000000000000"};
+  EXPECT_EQ(leaststr[IntKindPos<TypeParam>], leastv.Hexadecimal());
+
+  IntegerValue patternv{kind, 0x0123456789abcdefull};
+  static constexpr const char *patternstr[]{
+      "ef", "cdef", "89abcdef", "123456789abcdef", "123456789abcdef"};
+  EXPECT_EQ(patternstr[IntKindPos<TypeParam>], patternv.Hexadecimal());
+
+  IntegerValue invpatternv{kind, ~UnsignedT(0x0123456789abcdefull)};
+  static constexpr const char *invpatternstr[]{"10", "3210", "76543210",
+      "fedcba9876543210", "fffffffffffffffffedcba9876543210"};
+  EXPECT_EQ(invpatternstr[IntKindPos<TypeParam>], invpatternv.Hexadecimal());
+}
+
+TYPED_TEST(IntegerValueTypedKind, Read) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  {
+    // Leading blanks are skipped and trailing text is left for the caller.
+    const char *p{"  42tail"};
+    auto decimal{IntegerValue::Read(kind, p, 10, /*isSigned=*/false)};
+    EXPECT_FALSE(decimal.overflow);
+    EXPECT_EQ(kind, decimal.value.kind());
+    EXPECT_EQ(UnsignedT(42), decimal.value.ToUInt<UnsignedT>());
+    EXPECT_STREQ("tail", p);
+  }
+
+  {
+    const char *p{"  -42tail"};
+    auto decimal{IntegerValue::Read(kind, p, 10, /*isSigned=*/true)};
+    EXPECT_FALSE(decimal.overflow);
+    EXPECT_EQ(kind, decimal.value.kind());
+    EXPECT_EQ(SignedT(-42), decimal.value.ToSInt<SignedT>());
+    EXPECT_STREQ("tail", p);
+  }
+
+  {
+    const char *p{"-42"};
+    auto decimal{IntegerValue::Read(kind, p, 10, /*isSigned=*/false)};
+    EXPECT_FALSE(decimal.overflow);
+    EXPECT_EQ(kind, decimal.value.kind());
+    EXPECT_EQ(UnsignedT(-42), decimal.value.ToUInt<UnsignedT>());
+    EXPECT_STREQ("", p);
+  }
+
+  {
+    // More f's than can fit into the largest unsigned int
+    const char *p = "fffffffffffffffffffffffffffffffff";
+    auto unsignedRead{
+        IntegerValue::Read(kind, p, /*base=*/16, /*isSigned=*/false)};
+    EXPECT_TRUE(unsignedRead.overflow);
+    EXPECT_EQ(kind, unsignedRead.value.kind());
+    EXPECT_EQ(std::numeric_limits<UnsignedT>::max(),
+        unsignedRead.value.ToUInt<UnsignedT>());
+    EXPECT_EQ(p[0], '\0');
+  }
+
+  {
+    // Fits unsigned representations, but not signed
+    static constexpr const char *signedstr[]{"ff", "ffff", "ffffffff",
+        "ffffffffffffffff", "ffffffffffffffffffffffffffffffff"};
+    const char *p = signedstr[IntKindPos<TypeParam>];
+    auto signedRead{
+        IntegerValue::Read(kind, p, /*base=*/16, /*isSigned=*/true)};
+    EXPECT_TRUE(signedRead.overflow);
+    EXPECT_EQ(kind, signedRead.value.kind());
+    EXPECT_EQ(SignedT(-1), signedRead.value.ToSInt<SignedT>());
+    EXPECT_EQ(p[0], '\0');
+  }
+}
+
+//===----------------------------------------------------------------------===//
+// Bit masks and kind-specific constants
+//===----------------------------------------------------------------------===//
+
+TYPED_TEST(IntegerValueTypedKind, MASKL) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  constexpr int kind{TypeParam::kind};
+  constexpr int bits{TypeParam::bits};
+  static constexpr UnsignedT nobits{0};
+  static constexpr UnsignedT allbits{UnsignedT(~UnsignedT(0))};
+
+  IntegerValue signBit{IntegerValue::MASKL(kind, 1)};
+  EXPECT_EQ(kind, signBit.kind());
+  EXPECT_EQ(1, signBit.POPCNT());
+  EXPECT_TRUE(signBit.IsNegative());
+  EXPECT_EQ(0, signBit.LEADZ());
+
+  IntegerValue maskedunderflow{IntegerValue::MASKL(kind, -1)};
+  EXPECT_EQ(kind, maskedunderflow.kind());
+  EXPECT_EQ(nobits, maskedunderflow.ToUInt<UnsignedT>());
+
+  IntegerValue nomask{IntegerValue::MASKL(kind, 0)};
+  EXPECT_EQ(kind, nomask.kind());
+  EXPECT_EQ(nobits, nomask.ToUInt<UnsignedT>());
+
+  for (auto places : llvm::seq<int>(1, bits)) {
+    IntegerValue masked{IntegerValue::MASKL(kind, places)};
+    UnsignedT reference =
+        UnsignedT(UnsignedT(~UnsignedT(0)) << (bits - places));
+    EXPECT_EQ(kind, masked.kind());
+    EXPECT_EQ(reference, masked.ToUInt<UnsignedT>()) << "places=" << places;
+  }
+
+  IntegerValue fullmask{IntegerValue::MASKL(kind, bits)};
+  EXPECT_EQ(kind, fullmask.kind());
+  EXPECT_EQ(allbits, fullmask.ToUInt<UnsignedT>());
+
+  IntegerValue maskedoverflow{IntegerValue::MASKL(kind, bits + 1)};
+  EXPECT_EQ(kind, maskedoverflow.kind());
+  EXPECT_EQ(allbits, maskedoverflow.ToUInt<UnsignedT>());
+}
+
+TYPED_TEST(IntegerValueTypedKind, MASKR) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  constexpr int kind{TypeParam::kind};
+  constexpr int bits{TypeParam::bits};
+  static constexpr UnsignedT nobits{0};
+  static constexpr UnsignedT allbits{UnsignedT(~UnsignedT(0))};
+
+  IntegerValue maskedunderflow{IntegerValue::MASKR(kind, -1)};
+  EXPECT_EQ(kind, maskedunderflow.kind());
+  EXPECT_EQ(nobits, maskedunderflow.ToUInt<UnsignedT>());
+
+  IntegerValue nomask{IntegerValue::MASKR(kind, 0)};
+  EXPECT_EQ(kind, nomask.kind());
+  EXPECT_EQ(nobits, nomask.ToUInt<UnsignedT>());
+
+  for (auto places : llvm::seq<int>(1, bits)) {
+    IntegerValue masked{IntegerValue::MASKR(kind, places)};
+    UnsignedT reference = allbits >> (bits - places);
+    EXPECT_EQ(kind, masked.kind());
+    EXPECT_EQ(reference, masked.ToUInt<UnsignedT>()) << "places=" << places;
+  }
+
+  IntegerValue fullmask{IntegerValue::MASKR(kind, bits)};
+  EXPECT_EQ(kind, fullmask.kind());
+  EXPECT_EQ(allbits, fullmask.ToUInt<UnsignedT>());
+
+  IntegerValue maskedoverflow{IntegerValue::MASKR(kind, bits + 1)};
+  EXPECT_EQ(kind, maskedoverflow.kind());
+  EXPECT_EQ(allbits, maskedoverflow.ToUInt<UnsignedT>());
+}
+
+TYPED_TEST(IntegerValueTypedKind, HUGE) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue huge{IntegerValue::HUGE(kind)};
+  EXPECT_EQ(kind, huge.kind());
+  EXPECT_EQ(std::numeric_limits<SignedT>::max(), huge.ToSInt<SignedT>());
+}
+
+TYPED_TEST(IntegerValueTypedKind, Least) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue least{IntegerValue::Least(kind)};
+  EXPECT_EQ(kind, least.kind());
+  EXPECT_EQ(std::numeric_limits<SignedT>::min(), least.ToSInt<SignedT>());
+}
+
+//===----------------------------------------------------------------------===//
+// Predicates and comparisons
+//===----------------------------------------------------------------------===//
+
+TYPED_TEST(IntegerValueTypedKind, IsZero) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(TypeParam::kind)};
+  EXPECT_TRUE(zero.IsZero());
+
+  IntegerValue one{kind, 1};
+  EXPECT_FALSE(one.IsZero());
+
+  IntegerValue negone{kind, -1};
+  EXPECT_FALSE(negone.IsZero());
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_FALSE(theanswer.IsZero());
+
+  IntegerValue maxv{kind, std::numeric_limits<UnsignedT>::max()};
+  EXPECT_FALSE(maxv.IsZero());
+
+  IntegerValue minv{kind, std::numeric_limits<UnsignedT>::min()};
+  EXPECT_TRUE(minv.IsZero());
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  EXPECT_FALSE(smaxv.IsZero());
+
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_FALSE(smaxv.IsZero());
+
+  IntegerValue patternv{kind, 0x0123456789abcdefull};
+  EXPECT_FALSE(patternv.IsZero());
+
+  IntegerValue invpatternv{kind, ~UnsignedT(0x0123456789abcdefull)};
+  EXPECT_FALSE(invpatternv.IsZero());
+}
+
+TYPED_TEST(IntegerValueTypedKind, IsNegative) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(TypeParam::kind)};
+  EXPECT_FALSE(zero.IsNegative());
+
+  IntegerValue one{kind, 1};
+  EXPECT_FALSE(one.IsNegative());
+
+  IntegerValue negone{kind, -1};
+  EXPECT_TRUE(negone.IsNegative());
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_FALSE(theanswer.IsNegative());
+
+  IntegerValue maxv{kind, std::numeric_limits<UnsignedT>::max()};
+  EXPECT_TRUE(maxv.IsNegative());
+
+  IntegerValue minv{kind, std::numeric_limits<UnsignedT>::min()};
+  EXPECT_FALSE(minv.IsNegative());
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  EXPECT_FALSE(smaxv.IsNegative());
+
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_FALSE(smaxv.IsNegative());
+
+  IntegerValue patternv{kind, 0x7FFFFFFF7FFF7F7Full};
+  EXPECT_FALSE(patternv.IsNegative());
+
+  IntegerValue invpatternv{kind, ~UnsignedT(0x7FFFFFFF7FFF7F7Full)};
+  EXPECT_TRUE(invpatternv.IsNegative());
+}
+
+TYPED_TEST(IntegerValueTypedKind, CompareToZeroSigned) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(TypeParam::kind)};
+  EXPECT_EQ(Ordering::Equal, zero.CompareToZeroSigned());
+
+  IntegerValue one{kind, 1};
+  EXPECT_EQ(Ordering::Greater, one.CompareToZeroSigned());
+
+  IntegerValue negone{kind, -1};
+  EXPECT_EQ(Ordering::Less, negone.CompareToZeroSigned());
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_EQ(Ordering::Greater, theanswer.CompareToZeroSigned());
+
+  IntegerValue maxv{kind, std::numeric_limits<UnsignedT>::max()};
+  EXPECT_EQ(Ordering::Less, maxv.CompareToZeroSigned());
+
+  IntegerValue minv{kind, std::numeric_limits<UnsignedT>::min()};
+  EXPECT_EQ(Ordering::Equal, minv.CompareToZeroSigned());
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  EXPECT_EQ(Ordering::Greater, smaxv.CompareToZeroSigned());
+
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_EQ(Ordering::Less, sminv.CompareToZeroSigned());
+
+  IntegerValue patternv{kind, 0x7FFFFFFF7FFF7F7Full};
+  EXPECT_EQ(Ordering::Greater, patternv.CompareToZeroSigned());
+
+  IntegerValue invpatternv{kind, ~UnsignedT(0x7FFFFFFF7FFF7F7Full)};
+  EXPECT_EQ(Ordering::Less, invpatternv.CompareToZeroSigned());
+}
+
+TYPED_TEST(IntegerValueTypedKind, LEADZ) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+  constexpr int bits{TypeParam::bits};
+
+  IntegerValue zero{IntegerValue::Zero(TypeParam::kind)};
+  EXPECT_EQ(bits, zero.LEADZ());
+
+  IntegerValue one{kind, 1};
+  EXPECT_EQ(bits - 1, one.LEADZ());
+
+  IntegerValue negone{kind, -1};
+  EXPECT_EQ(0, negone.LEADZ());
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_EQ(bits - 6, theanswer.LEADZ());
+
+  IntegerValue maxv{kind, std::numeric_limits<UnsignedT>::max()};
+  EXPECT_EQ(0, maxv.LEADZ());
+
+  IntegerValue minv{kind, std::numeric_limits<UnsignedT>::min()};
+  EXPECT_EQ(bits, minv.LEADZ());
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  EXPECT_EQ(1, smaxv.LEADZ());
+
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_EQ(0, sminv.LEADZ());
+
+  IntegerValue patternv{kind, 0x7FFFFFFF7FFF7F7Full};
+  EXPECT_EQ((kind == 16) ? 65 : 1, patternv.LEADZ());
+
+  IntegerValue invpatternv{kind, ~UnsignedT(0x7FFFFFFF7FFF7F7Full)};
+  EXPECT_EQ(0, invpatternv.LEADZ());
+}
+
+TYPED_TEST(IntegerValueTypedKind, POPCNT) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+  constexpr int bits{TypeParam::bits};
+
+  IntegerValue zero{IntegerValue::Zero(TypeParam::kind)};
+  EXPECT_EQ(0, zero.POPCNT());
+
+  IntegerValue one{kind, 1};
+  EXPECT_EQ(1, one.POPCNT());
+
+  IntegerValue negone{kind, -1};
+  EXPECT_EQ(bits, negone.POPCNT());
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_EQ(3, theanswer.POPCNT());
+
+  IntegerValue maxv{kind, std::numeric_limits<UnsignedT>::max()};
+  EXPECT_EQ(bits, maxv.POPCNT());
+
+  IntegerValue minv{kind, std::numeric_limits<UnsignedT>::min()};
+  EXPECT_EQ(0, minv.POPCNT());
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  EXPECT_EQ(bits - 1, smaxv.POPCNT());
+
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_EQ(1, sminv.POPCNT());
+
+  IntegerValue patternv{kind, 0x7FFFFFFF7FFF7F7Full};
+  EXPECT_EQ(
+      (kind == 16) ? 60 : bits - IntKindPos<TypeParam> - 1, patternv.POPCNT());
+
+  IntegerValue invpatternv{kind, ~UnsignedT(0x7FFFFFFF7FFF7F7Full)};
+  EXPECT_EQ(
+      (kind == 16) ? 68 : 1 + IntKindPos<TypeParam>, invpatternv.POPCNT());
+}
+
+TYPED_TEST(IntegerValueTypedKind, POPPAR) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+  constexpr int bits{TypeParam::bits};
+
+  IntegerValue zero{IntegerValue::Zero(TypeParam::kind)};
+  EXPECT_FALSE(zero.POPPAR());
+
+  IntegerValue one{kind, 1};
+  EXPECT_TRUE(one.POPPAR());
+
+  IntegerValue negone{kind, -1};
+  EXPECT_EQ(false, negone.POPPAR());
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_EQ(true, theanswer.POPPAR());
+
+  IntegerValue maxv{kind, std::numeric_limits<UnsignedT>::max()};
+  EXPECT_EQ(bits & 1, maxv.POPPAR());
+
+  IntegerValue minv{kind, std::numeric_limits<UnsignedT>::min()};
+  EXPECT_EQ(0, minv.POPPAR());
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  EXPECT_EQ(bits % 2 == 0, smaxv.POPPAR());
+
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_EQ(true, sminv.POPPAR());
+
+  IntegerValue patternv{kind, 0x5555555555555554ull};
+  EXPECT_TRUE(patternv.POPPAR());
+
+  IntegerValue invpatternv{kind, 0xAAAAAAAAAAAAAAABull};
+  EXPECT_TRUE(invpatternv.POPPAR());
+}
+
+TYPED_TEST(IntegerValueTypedKind, TRAILZ) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+  constexpr int bits{TypeParam::bits};
+
+  IntegerValue zero{IntegerValue::Zero(TypeParam::kind)};
+  EXPECT_EQ(bits, zero.TRAILZ());
+
+  IntegerValue one{kind, 1};
+  EXPECT_EQ(0, one.TRAILZ());
+
+  IntegerValue negone{kind, -1};
+  EXPECT_EQ(0, negone.TRAILZ());
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_EQ(1, theanswer.TRAILZ());
+
+  IntegerValue maxv{kind, std::numeric_limits<UnsignedT>::max()};
+  EXPECT_EQ(0, maxv.TRAILZ());
+
+  IntegerValue minv{kind, std::numeric_limits<UnsignedT>::min()};
+  EXPECT_EQ(bits, minv.TRAILZ());
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  EXPECT_EQ(0, smaxv.TRAILZ());
+
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_EQ(bits - 1, sminv.TRAILZ());
+
+  IntegerValue patternv{kind, 0x0123456789abcdefull};
+  EXPECT_EQ(0, patternv.TRAILZ());
+
+  IntegerValue invpatternv{kind, ~UnsignedT(0x0123456789abcdefull)};
+  EXPECT_EQ(4, invpatternv.TRAILZ());
+}
+
+TYPED_TEST(IntegerValueTypedKind, BTEST) {
+  constexpr int kind{TypeParam::kind};
+  constexpr int bits{TypeParam::bits};
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  for (auto place : llvm::seq<int>(-2, bits + 2)) {
+    EXPECT_FALSE(zero.BTEST(place)) << "place=" << place;
+  }
+
+  // Out-of-range positions read as clear.
+  IntegerValue negone{kind, -1};
+  EXPECT_FALSE(negone.BTEST(-1));
+  for (auto place : llvm::seq<int>(0, bits)) {
+    EXPECT_TRUE(negone.BTEST(place)) << "place=" << place;
+  }
+  EXPECT_FALSE(negone.BTEST(bits));
+}
+
+TYPED_TEST(IntegerValueTypedKind, CompareUnsigned) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  EXPECT_EQ(Ordering::Equal, zero.CompareUnsigned(zero));
+
+  IntegerValue one{kind, 1};
+  EXPECT_EQ(Ordering::Less, zero.CompareUnsigned(one));
+  EXPECT_EQ(Ordering::Greater, one.CompareUnsigned(zero));
+
+  // -1 is the largest unsigned value.
+  IntegerValue negone{kind, -1};
+  EXPECT_EQ(Ordering::Less, one.CompareUnsigned(negone));
+  EXPECT_EQ(Ordering::Greater, negone.CompareUnsigned(one));
+
+  // As an unsigned pattern, the sign bit outweighs the rest of the word.
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_EQ(Ordering::Less, smaxv.CompareUnsigned(sminv));
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_EQ(Ordering::Equal, theanswer.CompareUnsigned(theanswer));
+  EXPECT_EQ(Ordering::Less, one.CompareUnsigned(theanswer));
+}
+
+TYPED_TEST(IntegerValueTypedKind, CompareSigned) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  EXPECT_EQ(Ordering::Equal, zero.CompareSigned(zero));
+
+  IntegerValue one{kind, 1};
+  EXPECT_EQ(Ordering::Less, zero.CompareSigned(one));
+  EXPECT_EQ(Ordering::Greater, one.CompareSigned(zero));
+
+  IntegerValue negone{kind, -1};
+  EXPECT_EQ(Ordering::Less, negone.CompareSigned(one));
+  EXPECT_EQ(Ordering::Greater, one.CompareSigned(negone));
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_EQ(Ordering::Greater, smaxv.CompareSigned(sminv));
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_EQ(Ordering::Equal, theanswer.CompareSigned(theanswer));
+  EXPECT_EQ(Ordering::Less, one.CompareSigned(theanswer));
+}
+
+TYPED_TEST(IntegerValueTypedKind, BitwiseComparisons) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  IntegerValue one{kind, 1};
+  EXPECT_FALSE(zero.BGE(one));
+  EXPECT_FALSE(zero.BGT(one));
+  EXPECT_TRUE(zero.BLE(one));
+  EXPECT_TRUE(zero.BLT(one));
+
+  IntegerValue negone{kind, -1};
+  EXPECT_TRUE(negone.BGE(one));
+  EXPECT_TRUE(negone.BGT(one));
+  EXPECT_FALSE(negone.BLE(one));
+  EXPECT_FALSE(negone.BLT(one));
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_FALSE(smaxv.BGE(sminv));
+  EXPECT_FALSE(smaxv.BGT(sminv));
+  EXPECT_TRUE(smaxv.BLE(sminv));
+  EXPECT_TRUE(smaxv.BLT(sminv));
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_TRUE(theanswer.BGE(theanswer));
+  EXPECT_FALSE(theanswer.BGT(theanswer));
+  EXPECT_TRUE(theanswer.BLE(theanswer));
+  EXPECT_FALSE(theanswer.BLT(theanswer));
+}
+
+TYPED_TEST(IntegerValueTypedKind, RelationalOperators) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  EXPECT_FALSE(zero < zero);
+  EXPECT_TRUE(zero <= zero);
+  EXPECT_TRUE(zero == zero);
+  EXPECT_FALSE(zero != zero);
+  EXPECT_TRUE(zero >= zero);
+  EXPECT_FALSE(zero > zero);
+
+  IntegerValue one{kind, 1};
+  EXPECT_TRUE(zero < one);
+  EXPECT_TRUE(zero <= one);
+  EXPECT_FALSE(zero == one);
+  EXPECT_TRUE(zero != one);
+  EXPECT_FALSE(zero >= one);
+  EXPECT_FALSE(zero > one);
+
+  IntegerValue negone{kind, -1};
+  EXPECT_TRUE(negone < one);
+  EXPECT_TRUE(negone <= one);
+  EXPECT_FALSE(negone == one);
+  EXPECT_TRUE(negone != one);
+  EXPECT_FALSE(negone >= one);
+  EXPECT_FALSE(negone > one);
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_FALSE(smaxv < sminv);
+  EXPECT_FALSE(smaxv <= sminv);
+  EXPECT_FALSE(smaxv == sminv);
+  EXPECT_TRUE(smaxv != sminv);
+  EXPECT_TRUE(smaxv >= sminv);
+  EXPECT_TRUE(smaxv > sminv);
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_TRUE(one < theanswer);
+  EXPECT_TRUE(one <= theanswer);
+  EXPECT_FALSE(one == theanswer);
+  EXPECT_TRUE(one != theanswer);
+  EXPECT_FALSE(one >= theanswer);
+  EXPECT_FALSE(one > theanswer);
+}
+
+TYPED_TEST(IntegerValueTypedKind, ToUInt64) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  EXPECT_EQ(0u, zero.ToUInt64());
+
+  IntegerValue one{kind, 1};
+  EXPECT_EQ(1u, one.ToUInt64());
+
+  // Only the least-significant 64 bits of a value survive conversion to a host
+  // 64-bit integer; wider kinds can therefore lose information.
+  IntegerValue negone{kind, -1};
+  static constexpr uint64_t moneu64[]{255ull, 65535ull, 4294967295ull,
+      18446744073709551615ull, 18446744073709551615ull};
+  EXPECT_EQ(moneu64[IntKindPos<TypeParam>], negone.ToUInt64());
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_EQ(42u, theanswer.ToUInt64());
+
+  IntegerValue maxv{kind, std::numeric_limits<UnsignedT>::max()};
+  EXPECT_EQ(moneu64[IntKindPos<TypeParam>], maxv.ToUInt64());
+
+  IntegerValue minv{kind, std::numeric_limits<UnsignedT>::min()};
+  EXPECT_EQ(0u, minv.ToUInt64());
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  static constexpr uint64_t smaxu64[]{127ull, 32767ull, 2147483647ull,
+      9223372036854775807ull, 18446744073709551615ull};
+  EXPECT_EQ(smaxu64[IntKindPos<TypeParam>], smaxv.ToUInt64());
+
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  static constexpr uint64_t sminu64[]{
+      128ull, 32768ull, 2147483648ull, 9223372036854775808ull, 0ull};
+  EXPECT_EQ(sminu64[IntKindPos<TypeParam>], sminv.ToUInt64());
+
+  IntegerValue patternv{kind, 0x0123456789abcdefull};
+  static constexpr uint64_t patternu64[]{239ull, 52719ull, 2309737967ull,
+      81985529216486895ull, 81985529216486895ull};
+  EXPECT_EQ(patternu64[IntKindPos<TypeParam>], patternv.ToUInt64());
+
+  IntegerValue invpatternv{kind, ~UnsignedT(0x0123456789abcdefull)};
+  static constexpr uint64_t invpatternu64[]{16ull, 12816ull, 1985229328ull,
+      18364758544493064720ull, 18364758544493064720ull};
+  EXPECT_EQ(invpatternu64[IntKindPos<TypeParam>], invpatternv.ToUInt64());
+}
+
+TYPED_TEST(IntegerValueTypedKind, ToInt64) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  EXPECT_EQ(0, zero.ToInt64());
+  EXPECT_EQ(0, zero.template ToSInt<int64_t>()); // a synonym
+
+  IntegerValue one{kind, 1};
+  EXPECT_EQ(1, one.ToInt64());
+
+  // -1 is all-ones regardless of width, so its low 64 bits read back as -1.
+  IntegerValue negone{kind, -1};
+  EXPECT_EQ(-1, negone.ToInt64());
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_EQ(42, theanswer.ToInt64());
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  static constexpr int64_t smaxi64[]{
+      127, 32767, 2147483647, 9223372036854775807ll, -1ll};
+  EXPECT_EQ(smaxi64[IntKindPos<TypeParam>], smaxv.ToInt64());
+
+  // For kinds up to 8 bytes, ToInt64() recovers the exact signed value.
+  // For 16-byte kind, only the low 8 bytes survive, reread as signed.
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  static constexpr int64_t smini64[]{
+      -128, -32768, -2147483648ll, -9223372036854775807ll - 1, 0};
+  EXPECT_EQ(smini64[IntKindPos<TypeParam>], sminv.ToInt64());
+
+  IntegerValue patternv{kind, 0x0123456789abcdefull};
+  static constexpr int64_t patterni64[]{
+      -17, -12817, -1985229329, 81985529216486895ll, 81985529216486895ll};
+  EXPECT_EQ(patterni64[IntKindPos<TypeParam>], patternv.ToInt64());
+
+  IntegerValue invpatternv{kind, ~UnsignedT(0x0123456789abcdefull)};
+  static constexpr int64_t invpatterni64[]{
+      16, 12816, 1985229328, -81985529216486896ll, -81985529216486896ll};
+  EXPECT_EQ(invpatterni64[IntKindPos<TypeParam>], invpatternv.ToInt64());
+}
+
+TYPED_TEST(IntegerValueTypedKind, ToUInt) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  // Small values fit in every host width, regardless of kind.
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  EXPECT_EQ(uint8_t{0}, zero.ToUInt<uint8_t>());
+  EXPECT_EQ(uint16_t{0}, zero.ToUInt<uint16_t>());
+  EXPECT_EQ(uint32_t{0}, zero.ToUInt<uint32_t>());
+  EXPECT_EQ(uint64_t{0}, zero.ToUInt<uint64_t>());
+  EXPECT_EQ(uint128_t{0}, zero.ToUInt<uint128_t>());
+
+  IntegerValue one{kind, 1};
+  EXPECT_EQ(uint8_t{1}, one.ToUInt<uint8_t>());
+  EXPECT_EQ(uint16_t{1}, one.ToUInt<uint16_t>());
+  EXPECT_EQ(uint32_t{1}, one.ToUInt<uint32_t>());
+  EXPECT_EQ(uint64_t{1}, one.ToUInt<uint64_t>());
+  EXPECT_EQ(uint128_t{1}, one.ToUInt<uint128_t>());
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_EQ(uint8_t{42}, theanswer.ToUInt<uint8_t>());
+  EXPECT_EQ(uint16_t{42}, theanswer.ToUInt<uint16_t>());
+  EXPECT_EQ(uint32_t{42}, theanswer.ToUInt<uint32_t>());
+  EXPECT_EQ(uint64_t{42}, theanswer.ToUInt<uint64_t>());
+  EXPECT_EQ(uint128_t{42}, theanswer.ToUInt<uint128_t>());
+
+  // -1 is all-ones within the kind's own width. A host type at least as wide
+  // as the kind therefore also reads back all-ones, but a host type wider
+  // than a narrower kind sees that kind's value zero-extended instead.
+  // A width as wide as the widest kind (16) always sees the exact value.
+  IntegerValue negone{kind, -1};
+  EXPECT_EQ(uint8_t{0xff}, negone.ToUInt<uint8_t>());
+  EXPECT_EQ(UnsignedT(0xffffu), negone.ToUInt<uint16_t>());
+  EXPECT_EQ(UnsignedT(0xffffffffu), negone.ToUInt<uint32_t>());
+  EXPECT_EQ(uint128_t{std::numeric_limits<UnsignedT>::max()},
+      negone.ToUInt<uint128_t>());
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  EXPECT_EQ(UnsignedT(std::numeric_limits<SignedT>::max()),
+      smaxv.ToUInt<UnsignedT>());
+  EXPECT_EQ(uint128_t{std::numeric_limits<SignedT>::max()},
+      smaxv.ToUInt<uint128_t>());
+
+  // Least truncates to zero in any host width narrower than the kind.
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_EQ(kind == 1 ? 0x80u : 0, sminv.ToUInt<uint8_t>());
+  EXPECT_EQ(uint128_t{UnsignedT(std::numeric_limits<SignedT>::min())},
+      sminv.ToUInt<uint128_t>());
+
+  IntegerValue patternv{kind, 0x0123456789abcdefull};
+  EXPECT_EQ(uint8_t{0xef}, patternv.ToUInt<uint8_t>());
+  EXPECT_EQ(UnsignedT(0xcdefu), patternv.ToUInt<uint16_t>());
+  EXPECT_EQ(UnsignedT(0x89abcdefu), patternv.ToUInt<uint32_t>());
+  EXPECT_EQ(uint128_t{UnsignedT(0x0123456789abcdefull)},
+      patternv.ToUInt<uint128_t>());
+
+  IntegerValue invpatternv{kind, ~UnsignedT(0x0123456789abcdefull)};
+  EXPECT_EQ(uint8_t{0x10}, invpatternv.ToUInt<uint8_t>());
+  EXPECT_EQ(UnsignedT(0x3210u), invpatternv.ToUInt<uint16_t>());
+  EXPECT_EQ(invpatternv.ToUInt64(), invpatternv.ToUInt<uint64_t>()); // synonym
+  // The inner UnsignedT cast undoes ~'s integer promotion for narrow kinds
+  // before widening, so only the kind's own bits are zero-extended.
+  EXPECT_EQ(uint128_t{UnsignedT(~UnsignedT(0x0123456789abcdefull))},
+      invpatternv.ToUInt<uint128_t>());
+}
+
+TYPED_TEST(IntegerValueTypedKind, ToSInt) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  // Small values fit in every host width, regardless of kind.
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  EXPECT_EQ(int8_t{0}, zero.ToSInt<int8_t>());
+  EXPECT_EQ(int16_t{0}, zero.ToSInt<int16_t>());
+  EXPECT_EQ(int32_t{0}, zero.ToSInt<int32_t>());
+  EXPECT_EQ(int64_t{0}, zero.ToSInt<int64_t>());
+  EXPECT_EQ(int128_t{0}, zero.ToSInt<int128_t>());
+
+  IntegerValue one{kind, 1};
+  EXPECT_EQ(int8_t{1}, one.ToSInt<int8_t>());
+  EXPECT_EQ(int16_t{1}, one.ToSInt<int16_t>());
+  EXPECT_EQ(int32_t{1}, one.ToSInt<int32_t>());
+  EXPECT_EQ(int64_t{1}, one.ToSInt<int64_t>());
+  EXPECT_EQ(int128_t{1}, one.ToSInt<int128_t>());
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_EQ(int8_t{42}, theanswer.ToSInt<int8_t>());
+  EXPECT_EQ(int16_t{42}, theanswer.ToSInt<int16_t>());
+  EXPECT_EQ(int32_t{42}, theanswer.ToSInt<int32_t>());
+  EXPECT_EQ(int64_t{42}, theanswer.ToSInt<int64_t>());
+  EXPECT_EQ(int128_t{42}, theanswer.ToSInt<int128_t>());
+
+  // -1 is all-ones at every width, in every kind.
+  IntegerValue negone{kind, -1};
+  EXPECT_EQ(int8_t{-1}, negone.ToSInt<int8_t>());
+  EXPECT_EQ(int16_t(-1), negone.ToSInt<int16_t>());
+  EXPECT_EQ(int32_t(-1), negone.ToSInt<int32_t>());
+  EXPECT_EQ(int64_t(-1), negone.ToSInt<int64_t>());
+  EXPECT_EQ(int128_t{-1}, negone.ToSInt<int128_t>());
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  EXPECT_EQ(std::numeric_limits<SignedT>::max(), smaxv.ToSInt<SignedT>());
+  EXPECT_EQ(
+      int8_t(std::numeric_limits<SignedT>::max()), smaxv.ToSInt<int8_t>());
+  EXPECT_EQ(
+      int16_t(std::numeric_limits<SignedT>::max()), smaxv.ToSInt<int16_t>());
+  EXPECT_EQ(
+      int32_t(std::numeric_limits<SignedT>::max()), smaxv.ToSInt<int32_t>());
+  EXPECT_EQ(
+      int64_t(std::numeric_limits<SignedT>::max()), smaxv.ToSInt<int64_t>());
+  EXPECT_EQ(
+      int128_t{std::numeric_limits<SignedT>::max()}, smaxv.ToSInt<int128_t>());
+
+  // Least's sign bit only survives in a host width no narrower than the
+  // kind; a narrower width truncates it away, along with the sign. Widening
+  // to the widest kind's own width (16) always sign-extends the true value.
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_EQ(kind == 1 ? -128 : 0, sminv.ToSInt<int8_t>());
+  EXPECT_EQ(
+      int8_t(std::numeric_limits<SignedT>::min()), sminv.ToSInt<int8_t>());
+  EXPECT_EQ(
+      int16_t(std::numeric_limits<SignedT>::min()), sminv.ToSInt<int16_t>());
+  EXPECT_EQ(
+      int32_t(std::numeric_limits<SignedT>::min()), sminv.ToSInt<int32_t>());
+  EXPECT_EQ(
+      int64_t(std::numeric_limits<SignedT>::min()), sminv.ToSInt<int64_t>());
+  EXPECT_EQ(
+      int128_t{std::numeric_limits<SignedT>::min()}, sminv.ToSInt<int128_t>());
+
+  IntegerValue patternv{kind, 0x0123456789abcdefull};
+  SignedT patternvref{SignedT(UnsignedT(0x0123456789abcdefull))};
+  EXPECT_EQ(int8_t(patternvref), patternv.ToSInt<int8_t>());
+  EXPECT_EQ(int16_t(patternvref), patternv.ToSInt<int16_t>());
+  EXPECT_EQ(int32_t(patternvref), patternv.ToSInt<int32_t>());
+  EXPECT_EQ(int64_t(patternvref), patternv.ToSInt<int64_t>());
+  EXPECT_EQ(int128_t{patternvref}, patternv.ToSInt<int128_t>());
+
+  IntegerValue invpatternv{kind, ~UnsignedT(0x0123456789abcdefull)};
+  SignedT invpatternvref{SignedT(~UnsignedT(0x0123456789abcdefull))};
+  EXPECT_EQ(int8_t(invpatternvref), invpatternv.ToSInt<int8_t>());
+  EXPECT_EQ(int16_t(invpatternvref), invpatternv.ToSInt<int16_t>());
+  EXPECT_EQ(int32_t(invpatternvref), invpatternv.ToSInt<int32_t>());
+  EXPECT_EQ(int64_t(invpatternvref), invpatternv.ToSInt<int64_t>());
+  EXPECT_EQ(int128_t{invpatternvref}, invpatternv.ToSInt<int128_t>());
+}
+
+//===----------------------------------------------------------------------===//
+// Bitwise operations
+//===----------------------------------------------------------------------===//
+
+TYPED_TEST(IntegerValueTypedKind, NOT) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  EXPECT_EQ(UnsignedT(~UnsignedT{0}), zero.NOT().ToUInt<UnsignedT>());
+
+  IntegerValue one{kind, 1};
+  EXPECT_EQ(UnsignedT(~UnsignedT{1}), one.NOT().ToUInt<UnsignedT>());
+
+  IntegerValue negone{kind, -1};
+  EXPECT_EQ(UnsignedT{0}, negone.NOT().ToUInt<UnsignedT>());
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_EQ(UnsignedT(~UnsignedT{42}), theanswer.NOT().ToUInt<UnsignedT>());
+
+  // Complementing HUGE (a leading zero followed by all ones) yields Least.
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_EQ(sminv, smaxv.NOT());
+  EXPECT_EQ(smaxv, sminv.NOT());
+
+  IntegerValue patternv{kind, 0x0123456789abcdefull};
+  IntegerValue invpatternv{kind, ~UnsignedT(0x0123456789abcdefull)};
+  EXPECT_EQ(invpatternv, patternv.NOT());
+  EXPECT_EQ(patternv, invpatternv.NOT());
+}
+
+TYPED_TEST(IntegerValueTypedKind, IAND) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_EQ(theanswer, theanswer.IAND(theanswer));
+
+  // A pattern and its complement share no set bits.
+  IntegerValue patternv{kind, 0x0123456789abcdefull};
+  IntegerValue invpatternv{kind, ~UnsignedT(0x0123456789abcdefull)};
+  EXPECT_TRUE(patternv.IAND(invpatternv).IsZero());
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  EXPECT_TRUE(zero.IAND(patternv).IsZero());
+
+  // ANDing with all-ones is the identity.
+  IntegerValue negone{kind, -1};
+  EXPECT_EQ(patternv, negone.IAND(patternv));
+}
+
+TYPED_TEST(IntegerValueTypedKind, IOR) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_EQ(theanswer, theanswer.IOR(theanswer));
+
+  IntegerValue negone{kind, -1};
+  IntegerValue patternv{kind, 0x0123456789abcdefull};
+  EXPECT_EQ(negone, negone.IOR(patternv));
+
+  // A pattern and its complement together cover every bit.
+  IntegerValue invpatternv{kind, ~UnsignedT(0x0123456789abcdefull)};
+  EXPECT_EQ(negone, patternv.IOR(invpatternv));
+
+  // ORing with zero is the identity; ORing with all-ones saturates.
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  EXPECT_EQ(patternv, zero.IOR(patternv));
+}
+
+TYPED_TEST(IntegerValueTypedKind, IEOR) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  constexpr int kind{TypeParam::kind};
+  IntegerValue zero{IntegerValue::Zero(kind)};
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_TRUE(theanswer.IEOR(theanswer).IsZero());
+
+  IntegerValue patternv{kind, 0x0123456789abcdefull};
+  EXPECT_EQ(patternv, zero.IEOR(patternv));
+
+  IntegerValue negone{kind, -1};
+  IntegerValue invpatternv{kind, ~UnsignedT(0x0123456789abcdefull)};
+  EXPECT_EQ(negone, patternv.IEOR(invpatternv));
+  EXPECT_EQ(invpatternv, negone.IEOR(patternv));
+}
+
+TYPED_TEST(IntegerValueTypedKind, MERGE_BITS) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue negone{kind, -1};
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  IntegerValue patternv{kind, 0x0123456789abcdefull};
+  IntegerValue invpatternv{kind, ~UnsignedT(0x0123456789abcdefull)};
+
+  EXPECT_EQ(patternv, negone.MERGE_BITS(zero, patternv));
+  EXPECT_EQ(invpatternv, zero.MERGE_BITS(negone, patternv));
+  EXPECT_EQ(patternv, patternv.MERGE_BITS(invpatternv, negone));
+  EXPECT_EQ(invpatternv, patternv.MERGE_BITS(invpatternv, zero));
+}
+
+TYPED_TEST(IntegerValueTypedKind, MAX) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  IntegerValue one{kind, 1};
+  EXPECT_EQ(one, zero.MAX(one));
+
+  IntegerValue negone{kind, -1};
+  EXPECT_EQ(one, negone.MAX(one));
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_EQ(smaxv, smaxv.MAX(sminv));
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_EQ(theanswer, theanswer.MAX(theanswer));
+}
+
+TYPED_TEST(IntegerValueTypedKind, MIN) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  IntegerValue one{kind, 1};
+  EXPECT_EQ(zero, zero.MIN(one));
+
+  IntegerValue negone{kind, -1};
+  EXPECT_EQ(negone, negone.MIN(one));
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_EQ(sminv, smaxv.MIN(sminv));
+
+  IntegerValue theanswer{kind, 42};
+  EXPECT_EQ(theanswer, theanswer.MIN(theanswer));
+}
+
+TYPED_TEST(IntegerValueTypedKind, IBCLR) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+  constexpr int bits{TypeParam::bits};
+
+  IntegerValue theanswer{kind, 0b101010};
+  EXPECT_EQ(40, theanswer.IBCLR(1).ToInt64()); // clears the bit worth 2
+  EXPECT_EQ(34, theanswer.IBCLR(3).ToInt64()); // clears the bit worth 8
+  EXPECT_EQ(42, theanswer.IBCLR(0).ToInt64()); // bit 0 is already clear
+  // Out-of-range positions are ignored.
+  EXPECT_EQ(theanswer, theanswer.IBCLR(-1));
+  EXPECT_EQ(theanswer, theanswer.IBCLR(bits));
+
+  IntegerValue negone{kind, -1};
+  EXPECT_EQ(UnsignedT(~UnsignedT{1}), negone.IBCLR(0).ToUInt<UnsignedT>());
+  // Clearing the sign bit of all-ones yields HUGE.
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  EXPECT_EQ(smaxv, negone.IBCLR(bits - 1));
+}
+
+TYPED_TEST(IntegerValueTypedKind, IBSET) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+  constexpr int bits{TypeParam::bits};
+
+  IntegerValue theanswer{kind, 42}; // 0b101010
+  EXPECT_EQ(43, theanswer.IBSET(0).ToInt64()); // sets the bit worth 1
+  EXPECT_EQ(46, theanswer.IBSET(2).ToInt64()); // sets the bit worth 4
+  EXPECT_EQ(42, theanswer.IBSET(1).ToInt64()); // bit 1 is already set
+  EXPECT_EQ(theanswer, theanswer.IBSET(-1));
+  EXPECT_EQ(theanswer, theanswer.IBSET(bits));
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  IntegerValue one{kind, 1};
+  EXPECT_EQ(one, zero.IBSET(0));
+  // Setting the sign bit of zero yields Least.
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_EQ(sminv, zero.IBSET(bits - 1));
+}
+
+TYPED_TEST(IntegerValueTypedKind, IBITS) {
+  constexpr int kind{TypeParam::kind};
+  constexpr int bits{TypeParam::bits};
+
+  // 0x...ef: low byte is 0b11101111.
+  IntegerValue patternv{kind, 0x0123456789abcdefull};
+  EXPECT_EQ(0xf, patternv.IBITS(0, 4).ToInt64());
+  EXPECT_EQ(0xe, patternv.IBITS(4, 4).ToInt64());
+  // Bit fields are unsigned; for kind 1 this extracts the whole byte, whose
+  // top bit would read as negative through the signed accessor.
+  EXPECT_EQ(0xef, patternv.IBITS(0, 8).ToUInt64());
+  EXPECT_TRUE(patternv.IBITS(0, 0).IsZero());
+  // A zero-based field spanning the full width extracts the whole value.
+  EXPECT_EQ(patternv, patternv.IBITS(0, bits));
+}
+
+//===----------------------------------------------------------------------===//
+// Shifts
+//===----------------------------------------------------------------------===//
+
+TYPED_TEST(IntegerValueTypedKind, ISHFT) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+  constexpr int bits{TypeParam::bits};
+
+  // A positive count shifts left; a negative count shifts right.
+  IntegerValue one{kind, 1};
+  EXPECT_EQ(1, one.ISHFT(0).ToInt64());
+  EXPECT_EQ(2, one.ISHFT(1).ToInt64());
+  EXPECT_EQ(0, one.ISHFT(-1).ToInt64());
+
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_EQ(sminv, one.ISHFT(bits - 1));
+  EXPECT_TRUE(one.ISHFT(bits).IsZero());
+  EXPECT_TRUE(one.ISHFT(bits + 1).IsZero());
+
+  IntegerValue negone{kind, -1};
+  EXPECT_EQ(UnsignedT(~UnsignedT{1}), negone.ISHFT(1).ToUInt<UnsignedT>());
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  EXPECT_EQ(smaxv, negone.ISHFT(-1));
+  EXPECT_TRUE(negone.ISHFT(bits).IsZero());
+  EXPECT_TRUE(negone.ISHFT(-bits).IsZero());
+}
+
+TYPED_TEST(IntegerValueTypedKind, SHIFTL) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+  constexpr int bits{TypeParam::bits};
+
+  IntegerValue one{kind, 1};
+  EXPECT_EQ(1, one.SHIFTL(-1).ToInt64()); // nonpositive count: no shift
+  EXPECT_EQ(1, one.SHIFTL(0).ToInt64());
+  EXPECT_EQ(2, one.SHIFTL(1).ToInt64());
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_EQ(sminv, one.SHIFTL(bits - 1));
+  EXPECT_TRUE(one.SHIFTL(bits).IsZero());
+  EXPECT_TRUE(one.SHIFTL(bits + 1).IsZero());
+
+  IntegerValue negone{kind, -1};
+  EXPECT_EQ(negone, negone.SHIFTL(0));
+  EXPECT_EQ(UnsignedT(~UnsignedT{1}), negone.SHIFTL(1).ToUInt<UnsignedT>());
+}
+
+TYPED_TEST(IntegerValueTypedKind, SHIFTR) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+  constexpr int bits{TypeParam::bits};
+
+  IntegerValue negone{kind, -1};
+  EXPECT_EQ(negone, negone.SHIFTR(-1)); // nonpositive count: no shift
+  EXPECT_EQ(negone, negone.SHIFTR(0));
+
+  // Zero fill, so a negative value becomes positive.
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  EXPECT_EQ(smaxv, negone.SHIFTR(1));
+  EXPECT_TRUE(negone.SHIFTR(bits).IsZero());
+  EXPECT_TRUE(negone.SHIFTR(bits + 1).IsZero());
+
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_EQ(1, sminv.SHIFTR(bits - 1).ToInt64());
+}
+
+TYPED_TEST(IntegerValueTypedKind, SHIFTA) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+  constexpr int bits{TypeParam::bits};
+
+  IntegerValue negone{kind, -1};
+  EXPECT_EQ(negone, negone.SHIFTA(-1)); // nonpositive count: no shift
+  EXPECT_EQ(negone, negone.SHIFTA(0));
+  // Sign fill keeps a negative value negative.
+  EXPECT_EQ(negone, negone.SHIFTA(1));
+  EXPECT_EQ(negone, negone.SHIFTA(bits - 1));
+
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_EQ(negone, sminv.SHIFTA(bits - 1));
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  EXPECT_TRUE(smaxv.SHIFTA(bits - 1).IsZero());
+}
+
+TYPED_TEST(IntegerValueTypedKind, ISHFTC) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+  constexpr int bits{TypeParam::bits};
+
+  // Rotating a uniform bit pattern leaves it unchanged.
+  IntegerValue negone{kind, -1};
+  EXPECT_EQ(negone, negone.ISHFTC(1));
+  EXPECT_EQ(negone, negone.ISHFTC(-1));
+
+  // Rotating the single set bit off one end wraps it to the other.
+  IntegerValue one{kind, 1};
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  EXPECT_EQ(2, one.ISHFTC(1).ToInt64());
+  EXPECT_EQ(sminv, one.ISHFTC(-1));
+  EXPECT_EQ(one, sminv.ISHFTC(1));
+
+  // A full-word rotation by the width is the identity.
+  IntegerValue patternv{kind, 0x0123456789abcdefull};
+  EXPECT_EQ(patternv, patternv.ISHFTC(bits));
+
+  // Rotating within a narrower field of least-significant bits leaves the
+  // higher-order bits unchanged; a nonpositive size selects the full width.
+  EXPECT_EQ(2, one.ISHFTC(1, 4).ToInt64());
+  EXPECT_EQ(8, one.ISHFTC(-1, 4).ToInt64());
+  EXPECT_EQ(2, one.ISHFTC(1, 0).ToInt64());
+}
+
+TYPED_TEST(IntegerValueTypedKind, DSHIFTL) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  constexpr int kind{TypeParam::kind};
+  constexpr int bits{TypeParam::bits};
+  const UnsignedT i{UnsignedT(0x0123456789abcdefull)};
+  const UnsignedT j{UnsignedT(~i)};
+  IntegerValue a{kind, i}, b{kind, j};
+
+  // The leading `bits` of the doubled-width value i:j shifted left by count.
+  EXPECT_EQ(a, a.DSHIFTL(b, 0)); // count==0 selects i unchanged
+  EXPECT_EQ(b, a.DSHIFTL(b, bits)); // count==bits selects j unchanged
+  EXPECT_TRUE(a.DSHIFTL(b, 2 * bits).IsZero()); // shifted entirely out
+
+  constexpr int half{bits / 2};
+  const UnsignedT expected{
+      UnsignedT(UnsignedT(i << half) | UnsignedT(j >> (bits - half)))};
+  EXPECT_EQ(expected, a.DSHIFTL(b, half).ToUInt<UnsignedT>());
+}
+
+TYPED_TEST(IntegerValueTypedKind, DSHIFTR) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  constexpr int kind{TypeParam::kind};
+  constexpr int bits{TypeParam::bits};
+  const UnsignedT i{UnsignedT(0x0123456789abcdefull)};
+  const UnsignedT j{UnsignedT(~i)};
+  IntegerValue a{kind, i}, b{kind, j};
+
+  // The trailing `bits` of the doubled-width value i:j shifted right by
+  // count.
+  EXPECT_EQ(b, a.DSHIFTR(b, 0)); // count==0 selects j unchanged
+  EXPECT_EQ(a, a.DSHIFTR(b, bits)); // count==bits selects i unchanged
+  EXPECT_TRUE(a.DSHIFTR(b, 2 * bits).IsZero()); // shifted entirely out
+
+  constexpr int half{bits / 2};
+  const UnsignedT expected(
+      UnsignedT(j >> half) | UnsignedT(i << (bits - half)));
+  EXPECT_EQ(expected, a.DSHIFTR(b, half).ToUInt<UnsignedT>());
+}
+
+//===----------------------------------------------------------------------===//
+// Arithmetic
+//===----------------------------------------------------------------------===//
+
+TYPED_TEST(IntegerValueTypedKind, Negate) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  auto negZero{zero.Negate()};
+  EXPECT_TRUE(negZero.value.IsZero());
+  EXPECT_FALSE(negZero.overflow);
+
+  IntegerValue one{kind, 1};
+  IntegerValue negone{kind, -1};
+  auto negOne{one.Negate()};
+  EXPECT_EQ(negone, negOne.value);
+  EXPECT_FALSE(negOne.overflow);
+  auto negMOne{negone.Negate()};
+  EXPECT_EQ(one, negMOne.value);
+  EXPECT_FALSE(negMOne.overflow);
+
+  IntegerValue theanswer{kind, 42};
+  auto negAnswer{theanswer.Negate()};
+  EXPECT_EQ(-42, negAnswer.value.ToInt64());
+  EXPECT_FALSE(negAnswer.overflow);
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  auto negHuge{smaxv.Negate()};
+  EXPECT_EQ(-smaxv.ToInt64(), negHuge.value.ToInt64());
+  EXPECT_FALSE(negHuge.overflow);
+
+  // Only the most negative number cannot be negated; it wraps back to
+  // itself.
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  auto negLeast{sminv.Negate()};
+  EXPECT_EQ(sminv, negLeast.value);
+  EXPECT_TRUE(negLeast.overflow);
+}
+
+TYPED_TEST(IntegerValueTypedKind, ABS) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  auto absZero{zero.ABS()};
+  EXPECT_TRUE(absZero.value.IsZero());
+  EXPECT_FALSE(absZero.overflow);
+
+  IntegerValue one{kind, 1};
+  auto absOne{one.ABS()};
+  EXPECT_EQ(one, absOne.value);
+  EXPECT_FALSE(absOne.overflow);
+
+  IntegerValue negone{kind, -1};
+  auto absMOne{negone.ABS()};
+  EXPECT_EQ(one, absMOne.value);
+  EXPECT_FALSE(absMOne.overflow);
+
+  IntegerValue theanswer{kind, 42};
+  auto absAnswer{theanswer.ABS()};
+  EXPECT_EQ(42, absAnswer.value.ToInt64());
+  EXPECT_FALSE(absAnswer.overflow);
+
+  // HUGE is already nonnegative.
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  auto absHuge{smaxv.ABS()};
+  EXPECT_EQ(smaxv, absHuge.value);
+  EXPECT_FALSE(absHuge.overflow);
+
+  // Taking the magnitude of the most negative number overflows; it stays
+  // unchanged.
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  auto absLeast{sminv.ABS()};
+  EXPECT_EQ(sminv, absLeast.value);
+  EXPECT_TRUE(absLeast.overflow);
+}
+
+TYPED_TEST(IntegerValueTypedKind, AddUnsigned) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  auto zeroPlusZero{zero.AddUnsigned(zero)};
+  EXPECT_TRUE(zeroPlusZero.value.IsZero());
+  EXPECT_FALSE(zeroPlusZero.carry);
+
+  // All-ones plus one wraps around to zero with a carry out.
+  IntegerValue negone{kind, -1};
+  IntegerValue one{kind, 1};
+  auto wrapped{negone.AddUnsigned(one)};
+  EXPECT_TRUE(wrapped.value.IsZero());
+  EXPECT_TRUE(wrapped.carry);
+  // A carry in has the same effect as adding one.
+  auto wrappedByCarryIn{negone.AddUnsigned(zero, /*carryIn=*/true)};
+  EXPECT_TRUE(wrappedByCarryIn.value.IsZero());
+  EXPECT_TRUE(wrappedByCarryIn.carry);
+
+  IntegerValue theanswer{kind, 42};
+  auto doubled{theanswer.AddUnsigned(theanswer)};
+  EXPECT_EQ(84, doubled.value.ToInt64());
+  EXPECT_FALSE(doubled.carry);
+
+  // A pattern and its complement sum exactly to all-ones.
+  IntegerValue patternv{kind, 0x0123456789abcdefull};
+  IntegerValue invpatternv{kind, ~UnsignedT(0x0123456789abcdefull)};
+  auto complementary{patternv.AddUnsigned(invpatternv)};
+  EXPECT_EQ(negone, complementary.value);
+  EXPECT_FALSE(complementary.carry);
+  auto complementaryPlusOne{
+      patternv.AddUnsigned(invpatternv, /*carryIn=*/true)};
+  EXPECT_TRUE(complementaryPlusOne.value.IsZero());
+  EXPECT_TRUE(complementaryPlusOne.carry);
+}
+
+TYPED_TEST(IntegerValueTypedKind, AddSigned) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  auto zeroPlusZero{zero.AddSigned(zero)};
+  EXPECT_TRUE(zeroPlusZero.value.IsZero());
+  EXPECT_FALSE(zeroPlusZero.overflow);
+
+  // Operands of unlike sign can never overflow.
+  IntegerValue one{kind, 1};
+  IntegerValue negone{kind, -1};
+  auto onePlusMOne{one.AddSigned(negone)};
+  EXPECT_TRUE(onePlusMOne.value.IsZero());
+  EXPECT_FALSE(onePlusMOne.overflow);
+
+  IntegerValue theanswer{kind, 42};
+  auto doubled{theanswer.AddSigned(theanswer)};
+  EXPECT_EQ(84, doubled.value.ToInt64());
+  EXPECT_FALSE(doubled.overflow);
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  // HUGE+1 overflows and wraps around to the most negative number.
+  auto hugePlusOne{smaxv.AddSigned(one)};
+  EXPECT_EQ(sminv, hugePlusOne.value);
+  EXPECT_TRUE(hugePlusOne.overflow);
+
+  // Least-1 underflows and wraps around to HUGE.
+  auto leastMinusOne{sminv.AddSigned(negone)};
+  EXPECT_EQ(smaxv, leastMinusOne.value);
+  EXPECT_TRUE(leastMinusOne.overflow);
+}
+
+TYPED_TEST(IntegerValueTypedKind, SubtractSigned) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  auto zeroMinusZero{zero.SubtractSigned(zero)};
+  EXPECT_TRUE(zeroMinusZero.value.IsZero());
+  EXPECT_FALSE(zeroMinusZero.overflow);
+
+  IntegerValue theanswer{kind, 42};
+  auto selfMinusSelf{theanswer.SubtractSigned(theanswer)};
+  EXPECT_TRUE(selfMinusSelf.value.IsZero());
+  EXPECT_FALSE(selfMinusSelf.overflow);
+
+  IntegerValue one{kind, 1};
+  IntegerValue negone{kind, -1};
+  auto oneMinusMOne{one.SubtractSigned(negone)};
+  EXPECT_EQ(2, oneMinusMOne.value.ToInt64());
+  EXPECT_FALSE(oneMinusMOne.overflow);
+
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  // HUGE-(-1) overflows and wraps around to the most negative number.
+  auto hugeMinusMOne{smaxv.SubtractSigned(negone)};
+  EXPECT_EQ(sminv, hugeMinusMOne.value);
+  EXPECT_TRUE(hugeMinusMOne.overflow);
+
+  // Least-1 underflows and wraps around to HUGE.
+  auto leastMinusOne{sminv.SubtractSigned(one)};
+  EXPECT_EQ(smaxv, leastMinusOne.value);
+  EXPECT_TRUE(leastMinusOne.overflow);
+}
+
+TYPED_TEST(IntegerValueTypedKind, DIM) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue one{kind, 1};
+  IntegerValue theanswer{kind, 42};
+  // x <= y clamps at zero rather than going negative.
+  auto smallMinusBig{one.DIM(theanswer)};
+  EXPECT_TRUE(smallMinusBig.value.IsZero());
+  EXPECT_FALSE(smallMinusBig.overflow);
+  EXPECT_EQ(kind, smallMinusBig.value.kind());
+
+  auto selfMinusSelf{theanswer.DIM(theanswer)};
+  EXPECT_TRUE(selfMinusSelf.value.IsZero());
+  EXPECT_FALSE(selfMinusSelf.overflow);
+
+  auto bigMinusSmall{theanswer.DIM(one)};
+  EXPECT_EQ(41, bigMinusSmall.value.ToInt64());
+  EXPECT_FALSE(bigMinusSmall.overflow);
+
+  // HUGE-Least overflows the representable range.
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  IntegerValue negone{kind, -1};
+  auto hugeMinusLeast{smaxv.DIM(sminv)};
+  EXPECT_EQ(negone, hugeMinusLeast.value);
+  EXPECT_TRUE(hugeMinusLeast.overflow);
+
+  auto leastMinusHuge{sminv.DIM(smaxv)};
+  EXPECT_TRUE(leastMinusHuge.value.IsZero());
+  EXPECT_FALSE(leastMinusHuge.overflow);
+}
+
+TYPED_TEST(IntegerValueTypedKind, SIGN) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue one{kind, 1};
+  IntegerValue negone{kind, -1};
+  IntegerValue theanswer{kind, 42};
+
+  // Same sign as the second operand: the value is unchanged.
+  auto samePos{one.SIGN(theanswer)};
+  EXPECT_EQ(one, samePos.value);
+  EXPECT_FALSE(samePos.overflow);
+  auto sameNeg{negone.SIGN(negone)};
+  EXPECT_EQ(negone, sameNeg.value);
+  EXPECT_FALSE(sameNeg.overflow);
+
+  // Differing sign: the value is negated.
+  auto flipToNeg{one.SIGN(negone)};
+  EXPECT_EQ(negone, flipToNeg.value);
+  EXPECT_FALSE(flipToNeg.overflow);
+  auto flipToPos{negone.SIGN(one)};
+  EXPECT_EQ(one, flipToPos.value);
+  EXPECT_FALSE(flipToPos.overflow);
+
+  // Negating the most negative number overflows and wraps back to itself.
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  auto flipLeast{sminv.SIGN(smaxv)};
+  EXPECT_EQ(sminv, flipLeast.value);
+  EXPECT_TRUE(flipLeast.overflow);
+  auto sameLeast{sminv.SIGN(sminv)};
+  EXPECT_EQ(sminv, sameLeast.value);
+  EXPECT_FALSE(sameLeast.overflow);
+}
+
+TYPED_TEST(IntegerValueTypedKind, MultiplyUnsigned) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  constexpr int kind{TypeParam::kind};
+  constexpr int bits{TypeParam::bits};
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  IntegerValue patternv{kind, 0x0123456789abcdefull};
+  auto zeroProduct{zero.MultiplyUnsigned(patternv)};
+  EXPECT_TRUE(zeroProduct.lower.IsZero());
+  EXPECT_TRUE(zeroProduct.upper.IsZero());
+  EXPECT_FALSE(zeroProduct.overflow);
+
+  IntegerValue one{kind, 1};
+  auto identityProduct{one.MultiplyUnsigned(patternv)};
+  EXPECT_EQ(patternv, identityProduct.lower);
+  EXPECT_TRUE(identityProduct.upper.IsZero());
+  EXPECT_FALSE(identityProduct.overflow);
+
+  IntegerValue theanswer{kind, 42};
+  auto answerSquared{theanswer.MultiplyUnsigned(theanswer)};
+  EXPECT_EQ(UnsignedT(42 * 42), answerSquared.lower.ToUInt<UnsignedT>());
+  EXPECT_FALSE(answerSquared.overflow);
+
+  // All-ones squared: (2^bits-1)^2 == 1 (mod 2^bits), with the high half
+  // holding the rest of the product.
+  IntegerValue negone{kind, -1};
+  auto moneSquared{negone.MultiplyUnsigned(negone)};
+  EXPECT_EQ(1, moneSquared.lower.ToInt64());
+  EXPECT_FALSE(moneSquared.overflow);
+  // Only up to INTEGER(8) is there a host type wide enough to check the
+  // full product directly.
+  if constexpr (bits <= 64) {
+    using Wide = HostUnsignedIntType<2 * bits>;
+    const Wide allOnes{static_cast<Wide>(UnsignedT(~UnsignedT{0}))};
+    const Wide wide{static_cast<Wide>(allOnes * allOnes)};
+    EXPECT_EQ(UnsignedT(wide >> bits), moneSquared.upper.ToUInt<UnsignedT>());
+  }
+}
+
+TYPED_TEST(IntegerValueTypedKind, MultiplySigned) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  IntegerValue theanswer{kind, 42};
+  auto zeroProduct{zero.MultiplySigned(theanswer)};
+  EXPECT_TRUE(zeroProduct.lower.IsZero());
+  EXPECT_TRUE(zeroProduct.upper.IsZero());
+  EXPECT_FALSE(zeroProduct.overflow);
+
+  IntegerValue one{kind, 1};
+  auto identityProduct{one.MultiplySigned(theanswer)};
+  EXPECT_EQ(theanswer, identityProduct.lower);
+  EXPECT_TRUE(identityProduct.upper.IsZero()); // theanswer is positive
+  EXPECT_FALSE(identityProduct.overflow);
+
+  IntegerValue negone{kind, -1};
+  auto negated{negone.MultiplySigned(one)};
+  EXPECT_EQ(negone, negated.lower);
+  EXPECT_EQ(negone, negated.upper); // sign-extended
+  EXPECT_FALSE(negated.overflow);
+
+  // Least*-1 overflows: the true product is one past the representable
+  // range, and wraps back to the bit pattern of Least itself.
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  auto negatedLeast{sminv.MultiplySigned(negone)};
+  EXPECT_EQ(sminv, negatedLeast.lower);
+  EXPECT_TRUE(negatedLeast.upper.IsZero());
+  EXPECT_TRUE(negatedLeast.overflow);
+  EXPECT_TRUE(negatedLeast.SignedMultiplicationOverflowed());
+}
+
+TYPED_TEST(IntegerValueTypedKind, DivideUnsigned) {
+  using UnsignedT = typename TypeParam::UnsignedT;
+  constexpr int kind{TypeParam::kind};
+
+  IntegerValue patternv{kind, 0x0123456789abcdefull};
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  IntegerValue negone{kind, -1};
+  auto byZero{patternv.DivideUnsigned(zero)};
+  EXPECT_TRUE(byZero.divisionByZero);
+  EXPECT_EQ(negone, byZero.quotient);
+  EXPECT_TRUE(byZero.remainder.IsZero());
+
+  IntegerValue theanswer{kind, 42};
+  IntegerValue one{kind, 1};
+  auto byOne{theanswer.DivideUnsigned(one)};
+  EXPECT_FALSE(byOne.divisionByZero);
+  EXPECT_EQ(theanswer, byOne.quotient);
+  EXPECT_TRUE(byOne.remainder.IsZero());
+
+  auto bySelf{theanswer.DivideUnsigned(theanswer)};
+  EXPECT_FALSE(bySelf.divisionByZero);
+  EXPECT_EQ(one, bySelf.quotient);
+  EXPECT_TRUE(bySelf.remainder.IsZero());
+
+  // All-ones is the largest unsigned value.
+  const UnsignedT allOnes{UnsignedT(~UnsignedT{0})};
+  auto moneByAnswer{negone.DivideUnsigned(theanswer)};
+  EXPECT_FALSE(moneByAnswer.divisionByZero);
+  EXPECT_EQ(UnsignedT(allOnes / UnsignedT{42}),
+      moneByAnswer.quotient.ToUInt<UnsignedT>());
+  EXPECT_EQ(UnsignedT(allOnes % UnsignedT{42}),
+      moneByAnswer.remainder.ToUInt<UnsignedT>());
+}
+
+TYPED_TEST(IntegerValueTypedKind, DivideSigned) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+
+  // A nonzero remainder has the sign of the dividend: this is MOD, not MODULO.
+  struct {
+    int64_t x, y, quotient, remainder;
+  } cases[]{
+      {8, 5, 1, 3},
+      {-8, 5, -1, -3},
+      {8, -5, -1, 3},
+      {-8, -5, 1, -3},
+  };
+  for (auto &c : cases) {
+    auto r{IntegerValue(kind, c.x).DivideSigned(IntegerValue{kind, c.y})};
+    EXPECT_FALSE(r.divisionByZero);
+    EXPECT_FALSE(r.overflow);
+    EXPECT_EQ(c.quotient, r.quotient.ToInt64()) << c.x << '/' << c.y;
+    EXPECT_EQ(c.remainder, r.remainder.ToInt64()) << c.x << '/' << c.y;
+  }
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  IntegerValue theanswer{kind, 42};
+  IntegerValue negone{kind, -1};
+  IntegerValue smaxv{kind, std::numeric_limits<SignedT>::max()};
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+
+  // Division by zero saturates in the direction of the dividend's sign.
+  auto positiveByZero{theanswer.DivideSigned(zero)};
+  EXPECT_TRUE(positiveByZero.divisionByZero);
+  EXPECT_EQ(smaxv, positiveByZero.quotient);
+  EXPECT_TRUE(positiveByZero.remainder.IsZero());
+
+  auto negativeByZero{negone.DivideSigned(zero)};
+  EXPECT_TRUE(negativeByZero.divisionByZero);
+  EXPECT_EQ(sminv, negativeByZero.quotient);
+  EXPECT_TRUE(negativeByZero.remainder.IsZero());
+
+  // The most negative number divided by -1 is the sole overflow case.
+  auto leastByMOne{sminv.DivideSigned(negone)};
+  EXPECT_FALSE(leastByMOne.divisionByZero);
+  EXPECT_TRUE(leastByMOne.overflow);
+  EXPECT_EQ(sminv, leastByMOne.quotient);
+  EXPECT_TRUE(leastByMOne.remainder.IsZero());
+}
+
+TYPED_TEST(IntegerValueTypedKind, MODULO) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+  // The result has the sign of the divisor.
+  struct {
+    int64_t x, y, modulo;
+  } cases[]{
+      {8, 5, 3},
+      {-8, 5, 2},
+      {8, -5, -2},
+      {-8, -5, -3},
+  };
+  for (auto &c : cases) {
+    auto r{IntegerValue(kind, c.x).MODULO(IntegerValue{kind, c.y})};
+    EXPECT_FALSE(r.overflow);
+    EXPECT_EQ(c.modulo, r.value.ToInt64()) << c.x << " mod " << c.y;
+  }
+
+  IntegerValue one{kind, 1};
+  IntegerValue theanswer{kind, 42};
+  auto exact{theanswer.MODULO(one)};
+  EXPECT_FALSE(exact.overflow);
+  EXPECT_TRUE(exact.value.IsZero());
+
+  // -1 mod 42: the result takes the sign of the (positive) divisor.
+  IntegerValue negone{kind, -1};
+  auto negByPos{negone.MODULO(theanswer)};
+  EXPECT_FALSE(negByPos.overflow);
+  EXPECT_EQ(41, negByPos.value.ToInt64());
+
+  // Least mod -1 is exactly zero, but MODULO still reports the overflow
+  // that occurs while computing the underlying Least/-1 quotient.
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  auto leastByMOne{sminv.MODULO(negone)};
+  EXPECT_TRUE(leastByMOne.overflow);
+  EXPECT_TRUE(leastByMOne.value.IsZero());
+}
+
+TYPED_TEST(IntegerValueTypedKind, Power) {
+  constexpr int kind{TypeParam::kind};
+  IntegerValue three{kind, 3};
+  IntegerValue two{kind, 2};
+  auto square{three.Power(two)};
+  EXPECT_FALSE(square.overflow);
+  EXPECT_FALSE(square.divisionByZero);
+  EXPECT_FALSE(square.zeroToZero);
+  EXPECT_EQ(9, square.power.ToInt64());
+
+  // x**0 is 1; 0**0 is 1 too, but additionally reports zeroToZero.
+  IntegerValue seven{kind, 7};
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  auto zeroth{seven.Power(zero)};
+  EXPECT_EQ(1, zeroth.power.ToInt64());
+  EXPECT_FALSE(zeroth.zeroToZero);
+  auto zeroToZero{zero.Power(zero)};
+  EXPECT_EQ(1, zeroToZero.power.ToInt64());
+  EXPECT_TRUE(zeroToZero.zeroToZero);
+
+  // 0**-1 divides by zero.
+  IntegerValue minusOne{kind, -1};
+  auto zeroToMinusOne{zero.Power(minusOne)};
+  EXPECT_TRUE(zeroToMinusOne.divisionByZero);
+
+  // Negative exponents truncate towards zero for other bases.
+  auto twoToMinusOne{two.Power(minusOne)};
+  EXPECT_TRUE(twoToMinusOne.power.IsZero());
+  IntegerValue one{kind, 1};
+  IntegerValue minusThree{kind, -3};
+  auto oneToMinusThree{one.Power(minusThree)};
+  EXPECT_EQ(1, oneToMinusThree.power.ToInt64());
+  auto minusOneToMinusThree{minusOne.Power(minusThree)};
+  EXPECT_EQ(-1, minusOneToMinusThree.power.ToInt64());
+  IntegerValue minusTwo{kind, -2};
+  auto minusOneToMinusTwo{minusOne.Power(minusTwo)};
+  EXPECT_EQ(1, minusOneToMinusTwo.power.ToInt64());
+
+  IntegerValue huge{IntegerValue::HUGE(kind)};
+  auto hugeSquared{huge.Power(two)};
+  EXPECT_TRUE(hugeSquared.overflow);
+}
+
+//===----------------------------------------------------------------------===//
+// Raw storage
+//===----------------------------------------------------------------------===//
+
+TYPED_TEST(IntegerValueTypedKind, RawBytesRoundTrip) {
+  using SignedT = typename TypeParam::SignedT;
+  constexpr int kind{TypeParam::kind};
+  ASSERT_EQ(
+      IntegerValue::bytesStored(kind), IntegerValue::Zero(kind).bytesStored());
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  char zeroBuffer[16]{};
+  bool zeroChanged{false};
+  zero.StoreRawBytes(zeroBuffer, zero.bytesStored(), &zeroChanged);
+  EXPECT_FALSE(zeroChanged); // storing a zero never sets the "changed" flag
+  IntegerValue zeroRestored{
+      IntegerValue::FromRawBytes(kind, zeroBuffer, zero.bytesStored())};
+  EXPECT_EQ(kind, zeroRestored.kind());
+  EXPECT_EQ(zero, zeroRestored);
+
+  IntegerValue negone{kind, -1};
+  char moneBuffer[16]{};
+  bool moneChanged{false};
+  negone.StoreRawBytes(moneBuffer, negone.bytesStored(), &moneChanged);
+  EXPECT_TRUE(moneChanged);
+  IntegerValue moneRestored{
+      IntegerValue::FromRawBytes(kind, moneBuffer, negone.bytesStored())};
+  EXPECT_EQ(kind, moneRestored.kind());
+  EXPECT_EQ(negone, moneRestored);
+
+  // Storing the same value again reports no change.
+  moneChanged = false;
+  negone.StoreRawBytes(moneBuffer, negone.bytesStored(), &moneChanged);
+  EXPECT_FALSE(moneChanged);
+  // Overwriting with a different value reports a change.
+  IntegerValue theanswer{kind, 42};
+  bool overwriteChanged{false};
+  theanswer.StoreRawBytes(
+      moneBuffer, theanswer.bytesStored(), &overwriteChanged);
+  EXPECT_TRUE(overwriteChanged);
+  IntegerValue answerRestored{
+      IntegerValue::FromRawBytes(kind, moneBuffer, theanswer.bytesStored())};
+  EXPECT_EQ(theanswer, answerRestored);
+
+  IntegerValue patternv{kind, 0x0123456789abcdefull};
+  char patternBuffer[16]{};
+  bool patternChanged{false};
+  patternv.StoreRawBytes(
+      patternBuffer, patternv.bytesStored(), &patternChanged);
+  EXPECT_TRUE(patternChanged);
+  IntegerValue patternRestored{
+      IntegerValue::FromRawBytes(kind, patternBuffer, patternv.bytesStored())};
+  EXPECT_EQ(kind, patternRestored.kind());
+  EXPECT_EQ(patternv, patternRestored);
+
+  IntegerValue sminv{kind, std::numeric_limits<SignedT>::min()};
+  char sminvBuffer[16]{};
+  bool sminvChanged{false};
+  sminv.StoreRawBytes(sminvBuffer, sminv.bytesStored(), &sminvChanged);
+  EXPECT_TRUE(sminvChanged);
+  IntegerValue sminvRestored{
+      IntegerValue::FromRawBytes(kind, sminvBuffer, sminv.bytesStored())};
+  EXPECT_EQ(kind, sminvRestored.kind());
+  EXPECT_EQ(sminv, sminvRestored);
+}
+
+//===----------------------------------------------------------------------===//
+// Operations between operands of different kinds
+//
+// A dyadic operation converts its argument to the receiver's kind, so these
+// are parameterized over ordered pairs of kinds rather than over single kinds.
+//===----------------------------------------------------------------------===//
+
+class IntegerValueKindPair
+    : public testing::TestWithParam<std::tuple<int, int>> {};
+
+INSTANTIATE_TEST_SUITE_P(AllKindPairs, IntegerValueKindPair,
+    testing::Combine(
+        testing::ValuesIn(std::initializer_list<int> FORTRAN_INTEGER_KINDS),
+        testing::ValuesIn(std::initializer_list<int> FORTRAN_INTEGER_KINDS)),
+    [](const testing::TestParamInfo<std::tuple<int, int>> &info) {
+      return "KIND" + std::to_string(std::get<0>(info.param)) + "AndKIND" +
+          std::to_string(std::get<1>(info.param));
+    });
+
+TEST_P(IntegerValueKindPair, ConvertUnsigned) {
+  const int from{std::get<0>(GetParam())}, to{std::get<1>(GetParam())};
+  const int fromBits{IntegerValue::bits(from)}, toBits{IntegerValue::bits(to)};
+  const int common{std::min(fromBits, toBits)};
+
+  // All ones: zero-extended when widening, truncated (and flagged) otherwise.
+  auto ones{IntegerValue::ConvertUnsigned(IntegerValue{from, -1}, toBits)};
+  EXPECT_EQ(to, ones.value.kind());
+  EXPECT_EQ(toBits < fromBits, ones.overflow);
+  EXPECT_EQ(IntegerValue::MASKR(to, common), ones.value);
+  // A value that fits in either width converts exactly.
+  auto exact{IntegerValue::ConvertUnsigned(IntegerValue{from, 0x34}, toBits)};
+  EXPECT_FALSE(exact.overflow);
+  EXPECT_EQ(IntegerValue(to, 0x34), exact.value);
+  auto zero{IntegerValue::ConvertUnsigned(IntegerValue::Zero(from), toBits)};
+  EXPECT_FALSE(zero.overflow);
+  EXPECT_TRUE(zero.value.IsZero());
+}
+
+TEST_P(IntegerValueKindPair, ConvertSigned) {
+  const int from{std::get<0>(GetParam())}, to{std::get<1>(GetParam())};
+  const int fromBits{IntegerValue::bits(from)}, toBits{IntegerValue::bits(to)};
+  // All ones stays all ones: it sign-extends and truncates to itself.
+  auto ones{IntegerValue::ConvertSigned(IntegerValue{from, -1}, toBits)};
+  EXPECT_EQ(to, ones.value.kind());
+  EXPECT_FALSE(ones.overflow);
+  EXPECT_EQ(IntegerValue(to, -1), ones.value);
+  // Truncation that changes the value is flagged.
+  auto huge{IntegerValue::ConvertSigned(IntegerValue::HUGE(from), toBits)};
+  EXPECT_EQ(toBits < fromBits, huge.overflow);
+  EXPECT_EQ(toBits < fromBits ? IntegerValue(to, -1)
+                              : IntegerValue::MASKR(to, fromBits - 1),
+      huge.value);
+  auto exact{IntegerValue::ConvertSigned(IntegerValue{from, -56}, toBits)};
+  EXPECT_FALSE(exact.overflow);
+  EXPECT_EQ(IntegerValue(to, -56), exact.value);
+}
+
+TEST_P(IntegerValueKindPair, MixedKindOperandsAreCoerced) {
+  const int receiver{std::get<0>(GetParam())};
+  const int other{std::get<1>(GetParam())};
+  IntegerValue x{receiver, 0x5a};
+  IntegerValue allOnes{other, -1};
+  // The result takes the receiver's kind; the argument is converted to it,
+  // preserving its sign.
+  EXPECT_EQ(receiver, x.IOR(allOnes).kind());
+  EXPECT_EQ(IntegerValue(receiver, -1), x.IOR(allOnes));
+  EXPECT_EQ(x, x.IAND(allOnes));
+  EXPECT_EQ(Ordering::Greater, x.CompareSigned(allOnes));
+  EXPECT_EQ(IntegerValue(receiver, 0x5a - 1), x.AddSigned(allOnes).value);
+  // A monostate operand behaves as a zero of the receiver's width.
+  EXPECT_EQ(x, x.IOR(IntegerValue{}));
+}
+
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+TEST(IntegerValue, Dump) { IntegerValue(4, -1).dump(); }
+#endif
+
+// Not an IntegerValue method, but the ordering that its comparisons return;
+// checked here because the legacy non-GTest test does so too.
+TEST(Ordering, Reverse) {
+  EXPECT_EQ(Ordering::Greater, Reverse(Ordering::Less));
+  EXPECT_EQ(Ordering::Less, Reverse(Ordering::Greater));
+  EXPECT_EQ(Ordering::Equal, Reverse(Ordering::Equal));
+}
+
+//===----------------------------------------------------------------------===//
+// Exhaustive tests
+//
+// The tests above check a selection of bit patterns for every kind; these
+// replicate the coverage of the legacy non-GTest test
+// flang/unittests/Evaluate/integer.cpp by checking every value (and, for the
+// dyadic operations, every pair of values) of a narrow kind.
+//===----------------------------------------------------------------------===//
+
+void ExhaustiveUnary(int kind) {
+  const int bits{IntegerValue::bits(kind)};
+  ASSERT_LE(bits, 16); // the reference arithmetic below assumes a narrow kind
+  const uint64_t maxUnsigned{(uint64_t{1} << bits) - 1};
+
+  IntegerValue zero{IntegerValue::Zero(kind)};
+  ASSERT_TRUE(zero.IsZero());
+  ASSERT_EQ(0u, zero.ToUInt64());
+  ASSERT_EQ(0, zero.ToInt64());
+  ASSERT_EQ(int64_t(maxUnsigned >> 1), IntegerValue::HUGE(kind).ToInt64());
+
+  for (uint64_t x{0}; x <= maxUnsigned; ++x) {
+    SCOPED_TRACE(testing::Message() << "kind=" << kind << " x=" << x);
+    IntegerValue a{kind, x};
+    ASSERT_EQ(x, a.ToUInt64());
+    ASSERT_EQ(kind, a.kind());
+    IntegerValue copy{a};
+    ASSERT_EQ(x, copy.ToUInt64());
+    copy = a;
+    ASSERT_EQ(x, copy.ToUInt64());
+    ASSERT_EQ(x == 0, a.IsZero());
+
+    // Decimal and hexadecimal formatting round-trip through Read().
+    std::string udec{a.UnsignedDecimal()};
+    const char *p{udec.c_str()};
+    auto readDecimal{IntegerValue::Read(kind, p, 10, /*isSigned=*/false)};
+    ASSERT_FALSE(readDecimal.overflow);
+    ASSERT_EQ(x, readDecimal.value.ToUInt64());
+    ASSERT_EQ('\0', *p);
+    std::string hex{a.Hexadecimal()};
+    p = hex.c_str();
+    auto readHex{IntegerValue::Read(kind, p, 16, /*isSigned=*/false)};
+    ASSERT_FALSE(readHex.overflow);
+    ASSERT_EQ(x, readHex.value.ToUInt64());
+    ASSERT_EQ('\0', *p);
+
+    ASSERT_EQ(x ^ maxUnsigned, a.NOT().ToUInt64());
+
+    const bool isNegative{(x >> (bits - 1)) != 0};
+    const bool isMostNegative{x == (uint64_t{1} << (bits - 1))};
+    auto negated{a.Negate()};
+    ASSERT_EQ(isMostNegative, negated.overflow);
+    ASSERT_EQ((~x + 1) & maxUnsigned, negated.value.ToUInt64());
+    auto abs{a.ABS()};
+    ASSERT_EQ(isMostNegative, abs.overflow);
+    ASSERT_EQ(isNegative ? (~x + 1) & maxUnsigned : x, abs.value.ToUInt64());
+
+    const int lzbc{a.LEADZ()};
+    ASSERT_GE(lzbc, 0);
+    ASSERT_LE(lzbc, bits);
+    ASSERT_EQ(x == 0, lzbc == bits);
+    ASSERT_LT(x, uint64_t{1} << (bits - lzbc));
+    ASSERT_GE(x + x + !x, uint64_t{1} << (bits - lzbc));
+
+    int popcheck{0};
+    for (int j{0}; j < bits; ++j) {
+      popcheck += (x >> j) & 1;
+    }
+    ASSERT_EQ(popcheck, a.POPCNT());
+    ASSERT_EQ((popcheck & 1) != 0, a.POPPAR());
+    int trailcheck{0};
+    for (; trailcheck < bits; ++trailcheck) {
+      if ((x >> trailcheck) & 1) {
+        break;
+      }
+    }
+    ASSERT_EQ(trailcheck, a.TRAILZ());
+    for (int j{0}; j < bits; ++j) {
+      ASSERT_EQ(((x >> j) & 1) != 0, a.BTEST(j)) << "bit " << j;
+    }
+
+    const int64_t sx{a.ToInt64()};
+    if (isNegative) {
+      ASSERT_TRUE(a.IsNegative());
+      ASSERT_LT(sx, 0);
+      ASSERT_EQ(Ordering::Less, a.CompareToZeroSigned());
+    } else {
+      ASSERT_FALSE(a.IsNegative());
+      ASSERT_GE(sx, 0);
+      ASSERT_EQ(x == 0 ? Ordering::Equal : Ordering::Greater,
+          a.CompareToZeroSigned());
+    }
+    ASSERT_EQ(x, uint64_t(sx) & maxUnsigned);
+
+    for (int count{0}; count <= bits + 1; ++count) {
+      const uint64_t left{(x << count) & maxUnsigned};
+      ASSERT_EQ(left, a.SHIFTL(count).ToUInt64()) << "count=" << count;
+      ASSERT_EQ(left, a.ISHFT(count).ToUInt64()) << "count=" << count;
+      const uint64_t right{x >> count};
+      ASSERT_EQ(right, a.SHIFTR(count).ToUInt64()) << "count=" << count;
+      ASSERT_EQ(right, a.ISHFT(-count).ToUInt64()) << "count=" << count;
+      const uint64_t fill{isNegative ? ~uint64_t{0} : 0};
+      const uint64_t arithmetic{count >= bits
+              ? fill & maxUnsigned
+              : (right | ((fill << (bits - count)) & maxUnsigned))};
+      ASSERT_EQ(arithmetic, a.SHIFTA(count).ToUInt64()) << "count=" << count;
+    }
+  }
+}
+
+TEST(IntegerValue, ExhaustiveUnaryKind1) { ExhaustiveUnary(1); }
+
+TEST(IntegerValue, ExhaustiveUnaryKind2) { ExhaustiveUnary(2); }
+
+TEST(IntegerValue, ExhaustiveDyadicKind1) {
+  constexpr int kind{1};
+  constexpr int bits{8};
+  constexpr uint64_t maxUnsigned{0xff};
+  constexpr int64_t maxPositiveSigned{0x7f};
+  constexpr int64_t mostNegativeSigned{-0x80};
+
+  for (uint64_t x{0}; x <= maxUnsigned; ++x) {
+    IntegerValue a{kind, x};
+    const int64_t sx{a.ToInt64()};
+    for (uint64_t y{0}; y <= maxUnsigned; ++y) {
+      SCOPED_TRACE(testing::Message() << "x=" << x << " y=" << y);
+      IntegerValue b{kind, y};
+      const int64_t sy{b.ToInt64()};
+
+      ASSERT_EQ(x < y ? Ordering::Less
+              : x > y ? Ordering::Greater
+                      : Ordering::Equal,
+          a.CompareUnsigned(b));
+      ASSERT_EQ(x >= y, a.BGE(b));
+      ASSERT_EQ(x > y, a.BGT(b));
+      ASSERT_EQ(x <= y, a.BLE(b));
+      ASSERT_EQ(x < y, a.BLT(b));
+      ASSERT_EQ(sx < sy ? Ordering::Less
+              : sx > sy ? Ordering::Greater
+                        : Ordering::Equal,
+          a.CompareSigned(b));
+      ASSERT_EQ(sx < sy, a < b);
+      ASSERT_EQ(sx == sy, a == b);
+
+      ASSERT_EQ(x & y, a.IAND(b).ToUInt64());
+      ASSERT_EQ(x | y, a.IOR(b).ToUInt64());
+      ASSERT_EQ(x ^ y, a.IEOR(b).ToUInt64());
+      ASSERT_EQ(std::max(sx, sy), a.MAX(b).ToInt64());
+      ASSERT_EQ(std::min(sx, sy), a.MIN(b).ToInt64());
+
+      auto sum{a.AddUnsigned(b)};
+      ASSERT_EQ(x + y, sum.value.ToUInt64() + (uint64_t{sum.carry} << bits));
+      auto ssum{a.AddSigned(b)};
+      ASSERT_EQ(uint64_t(sx + sy) & maxUnsigned, ssum.value.ToUInt64());
+      ASSERT_EQ(sx + sy < mostNegativeSigned || sx + sy > maxPositiveSigned,
+          ssum.overflow);
+      auto diff{a.SubtractSigned(b)};
+      ASSERT_EQ(uint64_t(sx - sy) & maxUnsigned, diff.value.ToUInt64());
+      ASSERT_EQ(sx - sy < mostNegativeSigned || sx - sy > maxPositiveSigned,
+          diff.overflow);
+      auto dim{a.DIM(b)};
+      ASSERT_EQ(
+          sx > sy ? uint64_t(sx - sy) & maxUnsigned : 0, dim.value.ToUInt64());
+      auto sign{a.SIGN(b)};
+      ASSERT_EQ(uint64_t(sy < 0 ? -std::abs(sx) : std::abs(sx)) & maxUnsigned,
+          sign.value.ToUInt64());
+
+      auto product{a.MultiplyUnsigned(b)};
+      ASSERT_EQ(
+          x * y, (product.upper.ToUInt64() << bits) | product.lower.ToUInt64());
+      auto sproduct{a.MultiplySigned(b)};
+      ASSERT_EQ(uint64_t(sx * sy) & maxUnsigned, sproduct.lower.ToUInt64());
+      ASSERT_EQ(
+          uint64_t((sx * sy) >> bits) & maxUnsigned, sproduct.upper.ToUInt64());
+
+      auto quot{a.DivideUnsigned(b)};
+      ASSERT_EQ(y == 0, quot.divisionByZero);
+      if (y == 0) {
+        ASSERT_EQ(maxUnsigned, quot.quotient.ToUInt64());
+        ASSERT_TRUE(quot.remainder.IsZero());
+      } else {
+        ASSERT_EQ(x / y, quot.quotient.ToUInt64());
+        ASSERT_EQ(x % y, quot.remainder.ToUInt64());
+      }
+
+      auto squot{a.DivideSigned(b)};
+      const bool badCase{sx == mostNegativeSigned && sy == -1};
+      ASSERT_EQ(y == 0, squot.divisionByZero);
+      ASSERT_EQ(badCase, squot.overflow);
+      if (y == 0) {
+        ASSERT_EQ(sx >= 0 ? maxPositiveSigned : mostNegativeSigned,
+            squot.quotient.ToInt64());
+        ASSERT_TRUE(squot.remainder.IsZero());
+      } else if (badCase) {
+        ASSERT_EQ(sx, squot.quotient.ToInt64());
+        ASSERT_TRUE(squot.remainder.IsZero());
+      } else {
+        ASSERT_EQ(sx / sy, squot.quotient.ToInt64());
+        ASSERT_EQ(sx % sy, squot.remainder.ToInt64());
+        int64_t modulo{sx % sy};
+        if (modulo != 0 && ((sx < 0) != (sy < 0))) {
+          modulo += sy;
+        }
+        ASSERT_EQ(uint64_t(modulo) & maxUnsigned, a.MODULO(b).value.ToUInt64());
+      }
+    }
+  }
+}
+
+TYPED_TEST(IntegerValueTypedKind, Print) {
+  constexpr int kind{TypeParam::kind};
+  constexpr int pos{IntKindPos<TypeParam>};
+
+  llvm::SmallString<128> buf;
+  llvm::raw_svector_ostream os{buf};
+  IntegerValue abc{kind, 42};
+  abc.print(os);
+
+  const char *results[]{"42_1", "42_2", "42_4", "42_8", "42_16"};
+  EXPECT_EQ(results[pos], os.str());
+}
+
+} // namespace
diff --git a/flang/unittests/Evaluate/LogicalValueTest.cpp b/flang/unittests/Evaluate/LogicalValueTest.cpp
new file mode 100644
index 0000000000000..bb0e9d9575f56
--- /dev/null
+++ b/flang/unittests/Evaluate/LogicalValueTest.cpp
@@ -0,0 +1,324 @@
+//===-- flang/unittests/Evaluate/LogicalValueTest.cpp ---------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "gtest/gtest.h"
+#include "flang/Common/type-kinds.h"
+#include "flang/Evaluate/logical-value.h"
+#include <cstddef>
+#include <iterator>
+#include <utility>
+
+using namespace Fortran::common;
+using namespace Fortran::evaluate;
+using namespace Fortran::evaluate::value;
+
+namespace {
+
+class LogicalValueKind : public testing::TestWithParam<int> {};
+INSTANTIATE_TEST_SUITE_P(LogicalValueKind, LogicalValueKind,
+    testing::ValuesIn(LogicalKinds),
+    [](const testing::TestParamInfo<int> &info) {
+      return "LOGICAL(" + std::to_string(info.param) + ")";
+    });
+
+constexpr int KindPos(int kind) {
+  for (std::size_t i{0}; i < std::size(LogicalKinds); ++i) {
+    if (LogicalKinds[i] == kind) {
+      return static_cast<int>(i);
+    }
+  }
+  return -1;
+}
+
+//===----------------------------------------------------------------------===//
+// Tests
+//===----------------------------------------------------------------------===//
+
+TEST(LogicalValue, DefaultConstructionIsMonostate) {
+  LogicalValue x;
+  EXPECT_TRUE(x.IsMonostate());
+  EXPECT_FALSE(x.IsTrue());
+}
+
+TEST_P(LogicalValueKind, ConstructFromBool) {
+  const int kind{GetParam()};
+
+  LogicalValue truth{kind, true};
+  EXPECT_FALSE(truth.IsMonostate());
+  EXPECT_EQ(kind, truth.kind());
+  EXPECT_TRUE(truth.IsTrue());
+
+  LogicalValue falsehood{kind, false};
+  EXPECT_EQ(kind, falsehood.kind());
+  EXPECT_FALSE(falsehood.IsTrue());
+}
+
+TEST_P(LogicalValueKind, ConstructFromWord) {
+  const int kind{GetParam()};
+
+  LogicalValue zero{kind, IntegerValue{kind, 0}};
+  EXPECT_FALSE(zero.IsTrue());
+
+  LogicalValue one{kind, IntegerValue{kind, 1}};
+  EXPECT_TRUE(one.IsTrue());
+
+  LogicalValue two{kind, IntegerValue{kind, 2}};
+  EXPECT_TRUE(two.IsTrue());
+
+  LogicalValue allOnes{kind, IntegerValue{kind, -1}};
+  EXPECT_TRUE(allOnes.IsTrue());
+}
+
+TEST_P(LogicalValueKind, CopyAndMove) {
+  const int kind{GetParam()};
+
+  LogicalValue x{kind, true};
+  LogicalValue copyConstructed{x};
+  EXPECT_TRUE(copyConstructed.IsTrue());
+
+  LogicalValue copyAssigned;
+  copyAssigned = x;
+  EXPECT_EQ(kind, copyAssigned.kind());
+  EXPECT_TRUE(copyAssigned.IsTrue());
+
+  LogicalValue moveConstructed{std::move(copyConstructed)};
+  EXPECT_TRUE(moveConstructed.IsTrue());
+
+  LogicalValue moveAssigned;
+  moveAssigned = std::move(copyAssigned);
+  EXPECT_TRUE(moveAssigned.IsTrue());
+}
+
+TEST_P(LogicalValueKind, KindCheckingConstructors) {
+  const int kind{GetParam()};
+
+  LogicalValue x{kind, true};
+  EXPECT_EQ(kind, LogicalValue(kind, x).kind());
+  EXPECT_TRUE(LogicalValue(kind, x).IsTrue());
+
+  LogicalValue y{kind, false};
+  LogicalValue moved{kind, std::move(y)};
+  EXPECT_EQ(kind, moved.kind());
+  EXPECT_FALSE(moved.IsTrue());
+}
+
+TEST_P(LogicalValueKind, Zero) {
+  const int kind{GetParam()};
+
+  LogicalValue zero{LogicalValue::Zero(kind)};
+  EXPECT_FALSE(zero.IsMonostate());
+  EXPECT_EQ(kind, zero.kind());
+  EXPECT_FALSE(zero.IsTrue());
+  EXPECT_TRUE(zero.IsCanonical());
+}
+
+TEST(LogicalValue, Bits) {
+  EXPECT_EQ(8, LogicalValue::bits(1));
+  EXPECT_EQ(16, LogicalValue::bits(2));
+  EXPECT_EQ(32, LogicalValue::bits(4));
+  EXPECT_EQ(64, LogicalValue::bits(8));
+  EXPECT_EQ(32, LogicalValue(4, true).bits());
+}
+
+TEST(LogicalValue, BytesStored) {
+  EXPECT_EQ(1u, LogicalValue::bytesStored(1));
+  EXPECT_EQ(2u, LogicalValue::bytesStored(2));
+  EXPECT_EQ(4u, LogicalValue::bytesStored(4));
+  EXPECT_EQ(8u, LogicalValue::bytesStored(8));
+  EXPECT_EQ(4u, LogicalValue(4, true).bytesStored());
+}
+
+TEST_P(LogicalValueKind, Word_) {
+  const int kind{GetParam()};
+
+  // .TRUE. is represented canonically as 1 and .FALSE. as 0.
+  EXPECT_EQ(1, LogicalValue(kind, true).word().ToInt64());
+  EXPECT_EQ(0, LogicalValue(kind, false).word().ToInt64());
+  EXPECT_EQ(kind, LogicalValue(kind, true).word().kind());
+  // A word constructed from a raw pattern is preserved.
+  EXPECT_EQ(2, LogicalValue(kind, IntegerValue{kind, 2}).word().ToInt64());
+}
+
+TEST_P(LogicalValueKind, IsCanonical) {
+  const int kind{GetParam()};
+
+  EXPECT_TRUE(LogicalValue(kind, true).IsCanonical());
+  EXPECT_TRUE(LogicalValue(kind, false).IsCanonical());
+  EXPECT_TRUE(LogicalValue(kind, IntegerValue{kind, 0}).IsCanonical());
+  EXPECT_TRUE(LogicalValue(kind, IntegerValue{kind, 1}).IsCanonical());
+  EXPECT_FALSE(LogicalValue(kind, IntegerValue{kind, 2}).IsCanonical());
+  EXPECT_FALSE(LogicalValue(kind, IntegerValue{kind, -1}).IsCanonical());
+}
+
+TEST_P(LogicalValueKind, IsTrue) {
+  const int kind{GetParam()};
+
+  EXPECT_FALSE(LogicalValue{}.IsTrue());
+  EXPECT_FALSE(LogicalValue(kind, false).IsTrue());
+  EXPECT_TRUE(LogicalValue(kind, true).IsTrue());
+  EXPECT_TRUE(LogicalValue(kind, IntegerValue{kind, 2}).IsTrue());
+}
+
+TEST_P(LogicalValueKind, RelationalOperators) {
+  const int kind{GetParam()};
+  LogicalValue f{kind, false}, t{kind, true};
+
+  EXPECT_TRUE(f < t);
+  EXPECT_FALSE(t < f);
+  EXPECT_FALSE(f < f);
+  EXPECT_FALSE(t < t);
+
+  EXPECT_TRUE(f <= f);
+  EXPECT_TRUE(f <= t);
+  EXPECT_FALSE(t <= f);
+  EXPECT_FALSE(t <= t);
+
+  EXPECT_TRUE(f == f);
+  EXPECT_TRUE(t == t);
+  EXPECT_FALSE(f == t);
+  EXPECT_FALSE(f != f);
+  EXPECT_TRUE(f != t);
+
+  EXPECT_TRUE(t >= t);
+  EXPECT_TRUE(t >= f);
+  EXPECT_FALSE(f >= f);
+  EXPECT_FALSE(f >= t);
+
+  EXPECT_TRUE(t > f);
+  EXPECT_FALSE(f > t);
+  EXPECT_FALSE(t > t);
+  EXPECT_FALSE(f > f);
+
+  EXPECT_TRUE(LogicalValue(kind, IntegerValue{kind, 2}) == t);
+}
+
+TEST_P(LogicalValueKind, NOT) {
+  const int kind{GetParam()};
+
+  EXPECT_TRUE(LogicalValue(kind, false).NOT().IsTrue());
+  EXPECT_FALSE(LogicalValue(kind, true).NOT().IsTrue());
+  EXPECT_EQ(kind, LogicalValue(kind, true).NOT().kind());
+}
+
+TEST_P(LogicalValueKind, AND) {
+  const int kind{GetParam()};
+
+  LogicalValue f{kind, false}, t{kind, true};
+  EXPECT_FALSE(f.AND(f).IsTrue());
+  EXPECT_FALSE(f.AND(t).IsTrue());
+  EXPECT_FALSE(t.AND(f).IsTrue());
+  EXPECT_TRUE(t.AND(t).IsTrue());
+  EXPECT_EQ(kind, t.AND(t).kind());
+}
+
+TEST_P(LogicalValueKind, OR) {
+  const int kind{GetParam()};
+
+  LogicalValue f{kind, false}, t{kind, true};
+  EXPECT_FALSE(f.OR(f).IsTrue());
+  EXPECT_TRUE(f.OR(t).IsTrue());
+  EXPECT_TRUE(t.OR(f).IsTrue());
+  EXPECT_TRUE(t.OR(t).IsTrue());
+  EXPECT_EQ(kind, f.OR(f).kind());
+}
+
+TEST_P(LogicalValueKind, EQV) {
+  const int kind{GetParam()};
+
+  LogicalValue f{kind, false}, t{kind, true};
+  EXPECT_TRUE(f.EQV(f).IsTrue());
+  EXPECT_FALSE(f.EQV(t).IsTrue());
+  EXPECT_FALSE(t.EQV(f).IsTrue());
+  EXPECT_TRUE(t.EQV(t).IsTrue());
+  EXPECT_EQ(kind, f.EQV(f).kind());
+}
+
+TEST_P(LogicalValueKind, NEQV) {
+  const int kind{GetParam()};
+
+  LogicalValue f{kind, false}, t{kind, true};
+  EXPECT_FALSE(f.NEQV(f).IsTrue());
+  EXPECT_TRUE(f.NEQV(t).IsTrue());
+  EXPECT_TRUE(t.NEQV(f).IsTrue());
+  EXPECT_FALSE(t.NEQV(t).IsTrue());
+  EXPECT_EQ(kind, f.NEQV(f).kind());
+}
+
+TEST_P(LogicalValueKind, RawBytesRoundTrip) {
+  const int kind{GetParam()};
+
+  for (bool truth : {false, true}) {
+    SCOPED_TRACE(testing::Message() << "truth=" << truth);
+
+    LogicalValue original{kind, truth};
+    char buffer[8]{};
+    ASSERT_EQ(LogicalValue::bytesStored(kind), original.bytesStored())
+        << "truth=" << truth;
+    bool changed{false};
+    original.StoreRawBytes(buffer, original.bytesStored(), &changed);
+    EXPECT_EQ(truth, changed) << "truth=" << truth;
+    LogicalValue restored{
+        LogicalValue::FromRawBytes(kind, buffer, original.bytesStored())};
+    EXPECT_EQ(kind, restored.kind()) << "truth=" << truth;
+    EXPECT_EQ(truth, restored.IsTrue()) << "truth=" << truth;
+    EXPECT_TRUE(restored.IsCanonical()) << "truth=" << truth;
+  }
+}
+
+TEST_P(LogicalValueKind, Print) {
+  const int kind{GetParam()};
+  const int pos{KindPos(kind)};
+
+  struct Case {
+    LogicalValue value;
+    const char *results[4];
+  };
+  const Case cases[]{
+      {LogicalValue{kind, false},
+          {".false._1", ".false._2", ".false._4", ".false._8"}},
+      {LogicalValue{kind, true},
+          {".true._1", ".true._2", ".true._4", ".true._8"}},
+      {LogicalValue{kind, IntegerValue{kind, 2}},
+          {"transfer(2_1,.false._1)", "transfer(2_2,.false._2)",
+              "transfer(2_4,.false._4)", "transfer(2_8,.false._8)"}},
+  };
+
+  for (const auto &c : cases) {
+    llvm::SmallString<128> buf;
+    llvm::raw_svector_ostream os{buf};
+    c.value.print(os);
+    EXPECT_EQ(c.results[pos], os.str());
+  }
+}
+
+// Replicates the coverage of the legacy non-GTest test
+// flang/unittests/Evaluate/logical.cpp.
+TEST_P(LogicalValueKind, TruthTables) {
+  const int kind{GetParam()};
+
+  EXPECT_EQ(8 * kind, LogicalValue::bits(kind));
+  EXPECT_FALSE(LogicalValue{}.IsTrue());
+  EXPECT_FALSE(LogicalValue(kind, false).IsTrue());
+  EXPECT_TRUE(LogicalValue(kind, true).IsTrue());
+  EXPECT_TRUE(LogicalValue(kind, false).NOT().IsTrue());
+  EXPECT_FALSE(LogicalValue(kind, true).NOT().IsTrue());
+  for (bool x : {false, true}) {
+    for (bool y : {false, true}) {
+      LogicalValue a{kind, x}, b{kind, y};
+      SCOPED_TRACE(
+          testing::Message() << "kind=" << kind << " x=" << x << " y=" << y);
+
+      EXPECT_EQ(x && y, a.AND(b).IsTrue());
+      EXPECT_EQ(x || y, a.OR(b).IsTrue());
+      EXPECT_EQ(x == y, a.EQV(b).IsTrue());
+      EXPECT_EQ(x != y, a.NEQV(b).IsTrue());
+    }
+  }
+}
+
+} // namespace
diff --git a/flang/unittests/Evaluate/RealValueTest.cpp b/flang/unittests/Evaluate/RealValueTest.cpp
new file mode 100644
index 0000000000000..cbf87d39fecd4
--- /dev/null
+++ b/flang/unittests/Evaluate/RealValueTest.cpp
@@ -0,0 +1,1142 @@
+//===-- flang/unittests/Evaluate/RealValueTest.cpp ------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "gtest/gtest.h"
+#include "flang/Common/Fortran-consts.h"
+#include "flang/Common/type-kinds.h"
+#include "flang/Evaluate/integer-value.h"
+#include "flang/Evaluate/real-value.h"
+#include "flang/Evaluate/typekind-traits.h"
+#include "llvm/Support/raw_ostream.h"
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <iterator>
+#include <string>
+
+using namespace Fortran::common;
+using namespace Fortran::evaluate;
+using namespace Fortran::evaluate::value;
+
+namespace {
+
+//===----------------------------------------------------------------------===//
+// Parameterization over the REAL kinds
+//===----------------------------------------------------------------------===//
+
+struct KindName {
+  template <typename TP> static std::string GetName(int) {
+    return "REAL(" + std::to_string(TP::kind) + ")";
+  }
+};
+
+// The subset of REAL kinds with a portable native host arithmetic type
+// (float for REAL(4), double for REAL(8)), used to cross-check against
+// hardware arithmetic.
+using RealHostTypedKinds = testing::Types<TypeKind<TypeCategory::Real, 4>,
+    TypeKind<TypeCategory::Real, 8>>;
+
+template <typename T> class RealValueHostTypedKind : public testing::Test {};
+TYPED_TEST_SUITE(RealValueHostTypedKind, RealHostTypedKinds, KindName);
+
+class RealValueKind : public testing::TestWithParam<int> {};
+INSTANTIATE_TEST_SUITE_P(RealValueKind, RealValueKind,
+    testing::ValuesIn(RealKinds), [](const testing::TestParamInfo<int> &info) {
+      return "REAL(" + std::to_string(info.param) + ")";
+    });
+
+//===----------------------------------------------------------------------===//
+// Helpers
+//===----------------------------------------------------------------------===//
+
+constexpr int KindPos(int kind) {
+  for (std::size_t i{0}; i < std::size(RealKinds); ++i) {
+    if (RealKinds[i] == kind) {
+      return static_cast<int>(i);
+    }
+  }
+  return -1;
+}
+
+testing::AssertionResult RealValuesEqual(const char *lhsExpr,
+    const char *rhsExpr, const RealValue &lhs, const RealValue &rhs) {
+  if (lhs == rhs) {
+    return testing::AssertionSuccess();
+  }
+  return testing::AssertionFailure()
+      << lhsExpr << " (" << lhs.DumpHexadecimal() << ") != " << rhsExpr << " ("
+      << rhs.DumpHexadecimal() << ")";
+}
+
+#define EXPECT_REAL_EQ(lhs, rhs) EXPECT_PRED_FORMAT2(RealValuesEqual, lhs, rhs)
+
+std::string AsFortranString(const RealValue &x, int kind, bool minimal) {
+  std::string s;
+  llvm::raw_string_ostream os{s};
+  x.AsFortran(os, kind, minimal);
+  return s;
+}
+
+/// Takes an integer and distributes its bits across a floating-point value so
+/// that a short sweep still covers signs, zeroes, subnormals, infinities and
+/// NaNs.  The LSB complements the result.  Copied from the legacy real.cpp.
+static std::uint32_t SpreadBits(std::uint32_t n) {
+  static const int shifts[]{
+      -1, 31, 23, 30, 22, 0, 24, 29, 25, 28, 26, 1, 16, 21, 2, -1};
+  std::uint32_t x{0};
+  for (int j{1}; shifts[j] >= 0; ++j) {
+    x |= ((n >> j) & 1) << shifts[j];
+  }
+  x ^= -(n & 1);
+  return x;
+}
+
+static std::uint64_t SpreadBits(std::uint64_t n) {
+  static const int shifts[]{
+      -1, 63, 52, 62, 51, 0, 53, 61, 54, 60, 55, 59, 1, 16, 50, 2, -1};
+  std::uint64_t x{0};
+  for (int j{1}; shifts[j] >= 0; ++j) {
+    x |= ((n >> j) & 1) << shifts[j];
+  }
+  x ^= -(n & 1);
+  return x;
+}
+
+/// Compares a computed RealValue against the result the host produced for the
+/// same operation.  NaN payloads are not part of the contract, so only the
+/// NaN-ness is compared for those.
+template <typename HostT, typename UnsignedT>
+static void ExpectSameAsHost(const RealValue &got, HostT expected) {
+  if (std::isnan(expected)) {
+    EXPECT_TRUE(got.IsNotANumber())
+        << "expected NaN, got " << got.DumpHexadecimal();
+    return;
+  }
+  union {
+    UnsignedT ui;
+    HostT f;
+  } u;
+  u.f = expected;
+  EXPECT_EQ(static_cast<std::uint64_t>(u.ui), got.RawBits().ToUInt64())
+      << "expected " << static_cast<double>(expected) << ", got "
+      << got.DumpHexadecimal();
+}
+
+//===----------------------------------------------------------------------===//
+// Construction, assignment and kind inquiries
+//===----------------------------------------------------------------------===//
+
+TEST(RealValue, DefaultConstructionIsMonostate) {
+  RealValue x;
+  EXPECT_TRUE(x.IsMonostate());
+  EXPECT_TRUE(x.IsZero());
+  EXPECT_FALSE(x.IsNegative());
+  EXPECT_FALSE(x.IsNotANumber());
+  EXPECT_FALSE(x.IsSignalingNaN());
+  EXPECT_FALSE(x.IsInfinite());
+  EXPECT_TRUE(x.IsFinite());
+  EXPECT_TRUE(x.IsNormal());
+  EXPECT_EQ(0, x.Exponent());
+  EXPECT_TRUE(x.RawBits().IsZero());
+}
+
+TEST_P(RealValueKind, ConstructFromWord) {
+  const int kind{GetParam()};
+  // The word is the raw bit pattern, not a numeric value.
+  RealValue zero{kind, IntegerValue::Zero(kind)};
+  EXPECT_EQ(kind, zero.kind());
+  EXPECT_TRUE(zero.IsZero());
+  EXPECT_FALSE(zero.IsNegative());
+  RealValue minusZero{RealValue::NegativeZero(kind)};
+  EXPECT_TRUE(minusZero.IsZero());
+  EXPECT_TRUE(minusZero.IsNegative());
+}
+
+TEST(RealValue, CopyAndMove) {
+  RealValue x{4, 3.0};
+  RealValue copyConstructed{x};
+  EXPECT_REAL_EQ(x, copyConstructed);
+  RealValue copyAssigned;
+  copyAssigned = x;
+  EXPECT_REAL_EQ(x, copyAssigned);
+  RealValue moveConstructed{std::move(copyConstructed)};
+  EXPECT_REAL_EQ(x, moveConstructed);
+  RealValue moveAssigned;
+  moveAssigned = std::move(copyAssigned);
+  EXPECT_REAL_EQ(x, moveAssigned);
+}
+
+TEST(RealValue, KindCheckingConstructors) {
+  RealValue x{4, 3.0};
+  EXPECT_EQ(4, RealValue(4, x).kind());
+  EXPECT_REAL_EQ(x, RealValue(4, x));
+  RealValue y{8, 3.0};
+  RealValue moved{8, std::move(y)};
+  EXPECT_EQ(8, moved.kind());
+}
+
+TEST_P(RealValueKind, Zero) {
+  const int kind{GetParam()};
+  RealValue zero{RealValue::Zero(kind)};
+  EXPECT_FALSE(zero.IsMonostate());
+  EXPECT_EQ(kind, zero.kind());
+  EXPECT_TRUE(zero.IsZero());
+  EXPECT_FALSE(zero.IsNegative());
+  EXPECT_TRUE(zero.RawBits().IsZero());
+  EXPECT_EQ(0, zero.Exponent());
+  EXPECT_EQ(Relation::Equal, zero.Compare(zero));
+}
+
+TEST(RealValue, Bits) {
+  EXPECT_EQ(16, RealValue::bits(2));
+  EXPECT_EQ(16, RealValue::bits(3));
+  EXPECT_EQ(32, RealValue::bits(4));
+  EXPECT_EQ(64, RealValue::bits(8));
+  EXPECT_EQ(128, RealValue::bits(10)); // 80 significant bits, 128 stored
+  EXPECT_EQ(128, RealValue::bits(16));
+  EXPECT_EQ(32, (RealValue{4, 1.0}.bits()));
+}
+
+TEST(RealValue, BytesStored) {
+  EXPECT_EQ(2u, RealValue::bytesStored(2));
+  EXPECT_EQ(2u, RealValue::bytesStored(3));
+  EXPECT_EQ(4u, RealValue::bytesStored(4));
+  EXPECT_EQ(8u, RealValue::bytesStored(8));
+  EXPECT_EQ(16u, RealValue::bytesStored(10));
+  EXPECT_EQ(16u, RealValue::bytesStored(16));
+  EXPECT_EQ(4u, (RealValue{4, 1.0}.bytesStored()));
+}
+
+TEST(RealValue, KindProperties) {
+  struct {
+    int kind, digits, precision, range, maxExponent, minExponent;
+  } expected[]{
+      {2, 11, 3, 4, 16, -13},
+      {3, 8, 2, 37, 128, -125},
+      {4, 24, 6, 37, 128, -125},
+      {8, 53, 15, 307, 1024, -1021},
+      {10, 64, 18, 4931, 16384, -16381},
+      {16, 113, 33, 4931, 16384, -16381},
+  };
+  for (auto &e : expected) {
+    SCOPED_TRACE(testing::Message() << "kind=" << e.kind);
+    EXPECT_EQ(e.digits, RealValue::DIGITS(e.kind));
+    EXPECT_EQ(e.precision, RealValue::PRECISION(e.kind));
+    EXPECT_EQ(e.range, RealValue::RANGE(e.kind));
+    EXPECT_EQ(e.maxExponent, RealValue::MAXEXPONENT(e.kind));
+    EXPECT_EQ(e.minExponent, RealValue::MINEXPONENT(e.kind));
+  }
+}
+
+//===----------------------------------------------------------------------===//
+// Classification predicates
+//===----------------------------------------------------------------------===//
+
+TEST_P(RealValueKind, IsZero) {
+  const int kind{GetParam()};
+  EXPECT_TRUE(RealValue::Zero(kind).IsZero());
+  EXPECT_TRUE(RealValue::NegativeZero(kind).IsZero());
+  EXPECT_FALSE((RealValue{kind, 1.0}.IsZero()));
+  EXPECT_FALSE(RealValue::Infinity(kind).IsZero());
+  EXPECT_FALSE(RealValue::NotANumber(kind).IsZero());
+}
+
+TEST_P(RealValueKind, IsNegative) {
+  const int kind{GetParam()};
+  EXPECT_FALSE(RealValue::Zero(kind).IsNegative());
+  EXPECT_TRUE(RealValue::NegativeZero(kind).IsNegative());
+  EXPECT_FALSE((RealValue{kind, 1.0}.IsNegative()));
+  EXPECT_TRUE((RealValue{kind, -1.0}.IsNegative()));
+  EXPECT_TRUE(RealValue::Infinity(kind, /*negative=*/true).IsNegative());
+  // A NaN is never reported as negative, whatever its sign bit.
+  EXPECT_FALSE(RealValue::NotANumber(kind).IsNegative());
+}
+
+TEST_P(RealValueKind, IsNotANumber) {
+  const int kind{GetParam()};
+  EXPECT_FALSE(RealValue::Zero(kind).IsNotANumber());
+  EXPECT_FALSE(RealValue::Infinity(kind).IsNotANumber());
+  EXPECT_FALSE(RealValue::Infinity(kind, /*negative=*/true).IsNotANumber());
+  EXPECT_TRUE(RealValue::NotANumber(kind).IsNotANumber());
+  EXPECT_TRUE(RealValue::SignalingNaN(kind).IsNotANumber());
+}
+
+TEST_P(RealValueKind, IsSignalingNaN) {
+  const int kind{GetParam()};
+  EXPECT_FALSE(RealValue::Zero(kind).IsSignalingNaN());
+  EXPECT_FALSE(RealValue::Infinity(kind).IsSignalingNaN());
+  EXPECT_TRUE(RealValue::SignalingNaN(kind).IsSignalingNaN());
+  // NotANumber() produces a quiet NaN.
+  EXPECT_FALSE(RealValue::NotANumber(kind).IsSignalingNaN());
+}
+
+TEST_P(RealValueKind, IsInfinite) {
+  const int kind{GetParam()};
+  EXPECT_FALSE(RealValue::Zero(kind).IsInfinite());
+  EXPECT_FALSE(RealValue::HUGE(kind).IsInfinite());
+  EXPECT_TRUE(RealValue::Infinity(kind).IsInfinite());
+  EXPECT_TRUE(RealValue::Infinity(kind, /*negative=*/true).IsInfinite());
+  EXPECT_FALSE(RealValue::NotANumber(kind).IsInfinite());
+}
+
+TEST_P(RealValueKind, IsFinite) {
+  const int kind{GetParam()};
+  EXPECT_TRUE(RealValue::Zero(kind).IsFinite());
+  EXPECT_TRUE(RealValue::HUGE(kind).IsFinite());
+  EXPECT_TRUE(RealValue::TINY(kind).IsFinite());
+  EXPECT_FALSE(RealValue::Infinity(kind).IsFinite());
+  EXPECT_FALSE(RealValue::Infinity(kind, /*negative=*/true).IsFinite());
+  EXPECT_FALSE(RealValue::NotANumber(kind).IsFinite());
+}
+
+TEST_P(RealValueKind, IsNormal) {
+  const int kind{GetParam()};
+  EXPECT_TRUE(RealValue::Zero(kind).IsNormal()); // zero counts as normal here
+  EXPECT_TRUE(RealValue::TINY(kind).IsNormal());
+  EXPECT_TRUE(RealValue::HUGE(kind).IsNormal());
+  EXPECT_FALSE(RealValue::Infinity(kind).IsNormal());
+  EXPECT_FALSE(RealValue::NotANumber(kind).IsNormal());
+  // The smallest subnormal is not normal.
+  RealValue subnormal{kind, IntegerValue{kind, 1}};
+  EXPECT_FALSE(subnormal.IsNormal());
+}
+
+//===----------------------------------------------------------------------===//
+// Sign manipulation
+//===----------------------------------------------------------------------===//
+
+TEST_P(RealValueKind, ABS) {
+  const int kind{GetParam()};
+  EXPECT_REAL_EQ((RealValue{kind, 3.0}), (RealValue{kind, -3.0}.ABS()));
+  EXPECT_REAL_EQ((RealValue{kind, 3.0}), (RealValue{kind, 3.0}.ABS()));
+  EXPECT_TRUE(RealValue::NegativeZero(kind).ABS().RawBits().IsZero());
+  EXPECT_REAL_EQ(
+      RealValue::Infinity(kind), RealValue::Infinity(kind, true).ABS());
+}
+
+TEST_P(RealValueKind, SetSign) {
+  const int kind{GetParam()};
+  EXPECT_REAL_EQ((RealValue{kind, -3.0}), (RealValue{kind, 3.0}.SetSign(true)));
+  EXPECT_REAL_EQ(
+      (RealValue{kind, 3.0}), (RealValue{kind, -3.0}.SetSign(false)));
+  EXPECT_REAL_EQ((RealValue{kind, 3.0}), (RealValue{kind, 3.0}.SetSign(false)));
+  EXPECT_REAL_EQ(
+      RealValue::NegativeZero(kind), RealValue::Zero(kind).SetSign(true));
+}
+
+TEST_P(RealValueKind, SIGN) {
+  const int kind{GetParam()};
+  EXPECT_REAL_EQ((RealValue{kind, -3.0}),
+      (RealValue{kind, 3.0}.SIGN(RealValue{kind, -1.0})));
+  EXPECT_REAL_EQ((RealValue{kind, 3.0}),
+      (RealValue{kind, -3.0}.SIGN(RealValue{kind, 1.0})));
+  // The sign is taken from the sign bit, so -0.0 makes the result negative.
+  EXPECT_REAL_EQ((RealValue{kind, -3.0}),
+      (RealValue{kind, 3.0}.SIGN(RealValue::NegativeZero(kind))));
+}
+
+TEST_P(RealValueKind, Negate) {
+  const int kind{GetParam()};
+  EXPECT_REAL_EQ((RealValue{kind, -3.0}), (RealValue{kind, 3.0}.Negate()));
+  EXPECT_REAL_EQ((RealValue{kind, 3.0}), (RealValue{kind, -3.0}.Negate()));
+  EXPECT_REAL_EQ(RealValue::NegativeZero(kind), RealValue::Zero(kind).Negate());
+  EXPECT_TRUE(RealValue::NegativeZero(kind).Negate().RawBits().IsZero());
+  EXPECT_REAL_EQ(
+      RealValue::Infinity(kind, true), RealValue::Infinity(kind).Negate());
+}
+
+//===----------------------------------------------------------------------===//
+// Comparison
+//===----------------------------------------------------------------------===//
+
+TEST_P(RealValueKind, Compare) {
+  const int kind{GetParam()};
+  RealValue zero{RealValue::Zero(kind)};
+  RealValue minusZero{RealValue::NegativeZero(kind)};
+  RealValue one{kind, 1.0};
+  RealValue two{kind, 2.0};
+  RealValue inf{RealValue::Infinity(kind)};
+  RealValue negInf{RealValue::Infinity(kind, true)};
+  RealValue nan{RealValue::NotANumber(kind)};
+
+  EXPECT_EQ(Relation::Equal, zero.Compare(zero));
+  EXPECT_EQ(Relation::Equal, zero.Compare(minusZero)); // +0 == -0
+  EXPECT_EQ(Relation::Equal, minusZero.Compare(minusZero));
+  EXPECT_EQ(Relation::Less, one.Compare(two));
+  EXPECT_EQ(Relation::Greater, two.Compare(one));
+  EXPECT_EQ(Relation::Less, zero.Compare(inf));
+  EXPECT_EQ(Relation::Less, minusZero.Compare(inf));
+  EXPECT_EQ(Relation::Greater, zero.Compare(negInf));
+  EXPECT_EQ(Relation::Greater, minusZero.Compare(negInf));
+  EXPECT_EQ(Relation::Equal, inf.Compare(inf));
+  EXPECT_EQ(Relation::Equal, negInf.Compare(negInf));
+  EXPECT_EQ(Relation::Greater, inf.Compare(negInf));
+  // Every comparison against a NaN is unordered.
+  EXPECT_EQ(Relation::Unordered, nan.Compare(nan));
+  EXPECT_EQ(Relation::Unordered, zero.Compare(nan));
+  EXPECT_EQ(Relation::Unordered, minusZero.Compare(nan));
+  EXPECT_EQ(Relation::Unordered, nan.Compare(zero));
+  EXPECT_EQ(Relation::Unordered, nan.Compare(inf));
+  EXPECT_EQ(Relation::Unordered, nan.Compare(negInf));
+}
+
+TEST_P(RealValueKind, EqualityOperators) {
+  const int kind{GetParam()};
+  // operator== compares bit patterns, unlike Compare().
+  EXPECT_TRUE((RealValue{kind, 1.0} == RealValue{kind, 1.0}));
+  EXPECT_FALSE((RealValue{kind, 1.0} == RealValue{kind, 2.0}));
+  EXPECT_TRUE((RealValue{kind, 1.0} != RealValue{kind, 2.0}));
+  EXPECT_FALSE(RealValue::Zero(kind) == RealValue::NegativeZero(kind));
+  EXPECT_TRUE(RealValue::NotANumber(kind) == RealValue::NotANumber(kind));
+}
+
+//===----------------------------------------------------------------------===//
+// Arithmetic
+//===----------------------------------------------------------------------===//
+
+TEST_P(RealValueKind, Add) {
+  const int kind{GetParam()};
+  auto sum{RealValue{kind, 1.0}.Add(RealValue{kind, 2.0})};
+  EXPECT_TRUE(sum.flags.empty());
+  EXPECT_REAL_EQ((RealValue{kind, 3.0}), sum.value);
+  // Cancellation yields +0.0.
+  auto cancelled{RealValue{kind, 3.0}.Add(RealValue{kind, -3.0})};
+  EXPECT_TRUE(cancelled.value.IsZero());
+  EXPECT_FALSE(cancelled.value.IsNegative());
+  // Overflow.
+  auto overflowed{RealValue::HUGE(kind).Add(RealValue::HUGE(kind))};
+  EXPECT_TRUE(overflowed.flags.test(RealFlag::Overflow));
+  EXPECT_TRUE(overflowed.value.IsInfinite());
+  // Inf + (-Inf) is invalid.
+  auto invalid{RealValue::Infinity(kind).Add(RealValue::Infinity(kind, true))};
+  EXPECT_TRUE(invalid.flags.test(RealFlag::InvalidArgument));
+  EXPECT_TRUE(invalid.value.IsNotANumber());
+}
+
+TEST_P(RealValueKind, Subtract) {
+  const int kind{GetParam()};
+  auto diff{RealValue{kind, 3.0}.Subtract(RealValue{kind, 5.0})};
+  EXPECT_TRUE(diff.flags.empty());
+  EXPECT_REAL_EQ((RealValue{kind, -2.0}), diff.value);
+  auto invalid{RealValue::Infinity(kind).Subtract(RealValue::Infinity(kind))};
+  EXPECT_TRUE(invalid.flags.test(RealFlag::InvalidArgument));
+  EXPECT_TRUE(invalid.value.IsNotANumber());
+}
+
+TEST_P(RealValueKind, Multiply) {
+  const int kind{GetParam()};
+  auto product{RealValue{kind, 3.0}.Multiply(RealValue{kind, 5.0})};
+  EXPECT_TRUE(product.flags.empty());
+  EXPECT_REAL_EQ((RealValue{kind, 15.0}), product.value);
+  RealValue negProduct{
+      RealValue{kind, -3.0}.Multiply(RealValue{kind, 5.0}).value};
+  EXPECT_REAL_EQ((RealValue{kind, -15.0}), negProduct);
+  auto overflowed{RealValue::HUGE(kind).Multiply(RealValue{kind, 2.0})};
+  EXPECT_TRUE(overflowed.flags.test(RealFlag::Overflow));
+  EXPECT_TRUE(overflowed.value.IsInfinite());
+  auto underflowed{RealValue::TINY(kind).Multiply(RealValue::TINY(kind))};
+  EXPECT_TRUE(underflowed.flags.test(RealFlag::Underflow));
+  EXPECT_TRUE(underflowed.value.IsZero());
+  // 0 * Inf is invalid.
+  auto invalid{RealValue::Zero(kind).Multiply(RealValue::Infinity(kind))};
+  EXPECT_TRUE(invalid.flags.test(RealFlag::InvalidArgument));
+  EXPECT_TRUE(invalid.value.IsNotANumber());
+}
+
+TEST_P(RealValueKind, Divide) {
+  const int kind{GetParam()};
+  auto quotient{RealValue{kind, 15.0}.Divide(RealValue{kind, 5.0})};
+  EXPECT_TRUE(quotient.flags.empty());
+  EXPECT_REAL_EQ((RealValue{kind, 3.0}), quotient.value);
+  // 1/3 is inexact in every binary format.
+  auto inexact{RealValue{kind, 1.0}.Divide(RealValue{kind, 3.0})};
+  EXPECT_TRUE(inexact.flags.test(RealFlag::Inexact));
+  // Division by zero.
+  auto byZero{RealValue{kind, 1.0}.Divide(RealValue::Zero(kind))};
+  EXPECT_TRUE(byZero.flags.test(RealFlag::DivideByZero));
+  EXPECT_TRUE(byZero.value.IsInfinite());
+  EXPECT_FALSE(byZero.value.IsNegative());
+  auto negByZero{RealValue{kind, -1.0}.Divide(RealValue::Zero(kind))};
+  EXPECT_TRUE(negByZero.value.IsInfinite());
+  EXPECT_TRUE(negByZero.value.IsNegative());
+  // 0/0 is invalid.
+  auto invalid{RealValue::Zero(kind).Divide(RealValue::Zero(kind))};
+  EXPECT_TRUE(invalid.flags.test(RealFlag::InvalidArgument));
+  EXPECT_TRUE(invalid.value.IsNotANumber());
+}
+
+TEST_P(RealValueKind, SQRT) {
+  const int kind{GetParam()};
+  auto four{RealValue{kind, 4.0}.SQRT()};
+  EXPECT_TRUE(four.flags.empty());
+  EXPECT_REAL_EQ((RealValue{kind, 2.0}), four.value);
+  EXPECT_TRUE(RealValue::Zero(kind).SQRT().value.IsZero());
+  // SQRT of a negative number is invalid.
+  auto invalid{RealValue{kind, -1.0}.SQRT()};
+  EXPECT_TRUE(invalid.flags.test(RealFlag::InvalidArgument));
+  EXPECT_TRUE(invalid.value.IsNotANumber());
+  EXPECT_TRUE(RealValue::Infinity(kind).SQRT().value.IsInfinite());
+}
+
+TEST_P(RealValueKind, NEAREST) {
+  const int kind{GetParam()};
+  RealValue one{kind, 1.0};
+
+  // The next value above 1.0 is 1.0+EPSILON.
+  auto up{one.NEAREST(true)};
+  EXPECT_REAL_EQ(one.Add(RealValue::EPSILON(kind)).value, up.value);
+
+  auto down{one.NEAREST(false)};
+  EXPECT_EQ(Relation::Less, down.value.Compare(one));
+  // Stepping back up recovers 1.0 exactly.
+  EXPECT_REAL_EQ(one, down.value.NEAREST(true).value);
+
+  // Stepping down from +0.0 gives the smallest negative subnormal.
+  auto belowZero{RealValue::Zero(kind).NEAREST(false)};
+  EXPECT_TRUE(belowZero.value.IsNegative());
+  EXPECT_FALSE(belowZero.value.IsNormal());
+}
+
+TEST_P(RealValueKind, HYPOT) {
+  const int kind{GetParam()};
+
+  auto hypot{RealValue{kind, 3.0}.HYPOT(RealValue{kind, 4.0})};
+  EXPECT_REAL_EQ((RealValue{kind, 5.0}), hypot.value);
+
+  // HYPOT avoids the overflow that squaring HUGE would produce.
+  auto big{RealValue::HUGE(kind).HYPOT(RealValue::HUGE(kind))};
+  EXPECT_FALSE(big.value.IsNotANumber());
+}
+
+TEST_P(RealValueKind, DIM) {
+  const int kind{GetParam()};
+
+  auto positive{RealValue{kind, 7.0}.DIM(RealValue{kind, 5.0})};
+  EXPECT_REAL_EQ((RealValue{kind, 2.0}), positive.value);
+
+  // MAX(x-y, 0) clamps at zero.
+  auto clamped{RealValue{kind, 5.0}.DIM(RealValue{kind, 7.0})};
+  EXPECT_TRUE(clamped.value.IsZero());
+
+  auto invalid{RealValue::NotANumber(kind).DIM(RealValue{kind, 1.0})};
+  EXPECT_TRUE(invalid.flags.test(RealFlag::InvalidArgument));
+}
+
+TEST_P(RealValueKind, MOD) {
+  const int kind{GetParam()};
+
+  // The result has the sign of the dividend.
+  RealValue m1{RealValue{kind, 8.0}.MOD(RealValue{kind, 5.0}).value};
+  EXPECT_REAL_EQ((RealValue{kind, 3.0}), m1);
+
+  RealValue m2{RealValue{kind, -8.0}.MOD(RealValue{kind, 5.0}).value};
+  EXPECT_REAL_EQ((RealValue{kind, -3.0}), m2);
+
+  RealValue m3{RealValue{kind, 8.0}.MOD(RealValue{kind, -5.0}).value};
+  EXPECT_REAL_EQ((RealValue{kind, 3.0}), m3);
+
+  auto byZero{RealValue{kind, 8.0}.MOD(RealValue::Zero(kind))};
+  EXPECT_TRUE(byZero.flags.test(RealFlag::DivideByZero));
+  EXPECT_TRUE(byZero.value.IsNotANumber());
+}
+
+TEST_P(RealValueKind, MODULO) {
+  const int kind{GetParam()};
+
+  // The result has the sign of the divisor.
+  RealValue m1{RealValue{kind, 8.0}.MODULO(RealValue{kind, 5.0}).value};
+  EXPECT_REAL_EQ((RealValue{kind, 3.0}), m1);
+  RealValue m2{RealValue{kind, -8.0}.MODULO(RealValue{kind, 5.0}).value};
+  EXPECT_REAL_EQ((RealValue{kind, 2.0}), m2);
+  RealValue m3{RealValue{kind, 8.0}.MODULO(RealValue{kind, -5.0}).value};
+  EXPECT_REAL_EQ((RealValue{kind, -2.0}), m3);
+  RealValue m4{RealValue{kind, -8.0}.MODULO(RealValue{kind, -5.0}).value};
+  EXPECT_REAL_EQ((RealValue{kind, -3.0}), m4);
+}
+
+TEST_P(RealValueKind, KahanSummation) {
+  const int kind{GetParam()};
+
+  RealValue correction{RealValue::Zero(kind)};
+  auto sum{
+      RealValue{kind, 1.0}.KahanSummation(RealValue{kind, 2.0}, correction)};
+  EXPECT_REAL_EQ((RealValue{kind, 3.0}), sum.value);
+  EXPECT_TRUE(correction.IsZero());
+  // Adding a value too small to be representable in the sum leaves it in the
+  // correction term instead of losing it.
+  RealValue one{kind, 1.0};
+  RealValue small{RealValue::EPSILON(kind).Divide(RealValue{kind, 4.0}).value};
+  correction = RealValue::Zero(kind);
+  auto lossy{one.KahanSummation(small, correction)};
+  EXPECT_REAL_EQ(one, lossy.value);
+  EXPECT_FALSE(correction.IsZero());
+}
+
+//===----------------------------------------------------------------------===//
+// Kind-specific constants and exponent manipulation
+//===----------------------------------------------------------------------===//
+
+TEST_P(RealValueKind, EPSILON) {
+  const int kind{GetParam()};
+
+  RealValue eps{RealValue::EPSILON(kind)};
+  EXPECT_EQ(kind, eps.kind());
+  EXPECT_FALSE(eps.IsNegative());
+  // EPSILON is the spacing of 1.0, i.e. 2**(1-DIGITS).
+  EXPECT_REAL_EQ(eps, RealValue(kind, 1.0).SPACING());
+  // 1+EPSILON is distinguishable from 1, but 1+EPSILON/2 is not.
+  RealValue one{kind, 1.0};
+  EXPECT_EQ(Relation::Greater, one.Add(eps).value.Compare(one));
+  RealValue halfEps{eps.Divide(RealValue{kind, 2.0}).value};
+  EXPECT_EQ(Relation::Equal, one.Add(halfEps).value.Compare(one));
+}
+
+TEST_P(RealValueKind, HUGE) {
+  const int kind{GetParam()};
+
+  RealValue huge{RealValue::HUGE(kind)};
+  EXPECT_EQ(kind, huge.kind());
+  EXPECT_TRUE(huge.IsFinite());
+  EXPECT_FALSE(huge.IsNegative());
+  // The exponent field is one below the reserved all-ones value that
+  // Infinity() uses.
+  EXPECT_EQ(RealValue::Infinity(kind).Exponent() - 1, huge.Exponent());
+  // Stepping up from HUGE overflows to infinity.
+  EXPECT_TRUE(huge.NEAREST(true).value.IsInfinite());
+}
+
+TEST_P(RealValueKind, TINY) {
+  const int kind{GetParam()};
+
+  RealValue tiny{RealValue::TINY(kind)};
+  EXPECT_EQ(kind, tiny.kind());
+  EXPECT_TRUE(tiny.IsNormal());
+  EXPECT_FALSE(tiny.IsZero());
+  EXPECT_EQ(1, tiny.Exponent()); // the smallest normal exponent
+  // Stepping down from TINY leaves the normal range.
+  EXPECT_FALSE(tiny.NEAREST(false).value.IsNormal());
+}
+
+TEST_P(RealValueKind, NotANumber) {
+  const int kind{GetParam()};
+
+  RealValue nan{RealValue::NotANumber(kind)};
+  EXPECT_EQ(kind, nan.kind());
+  EXPECT_TRUE(nan.IsNotANumber());
+  EXPECT_FALSE(nan.IsSignalingNaN());
+  EXPECT_FALSE(nan.IsFinite());
+}
+
+TEST_P(RealValueKind, Exponent) {
+  const int kind{GetParam()};
+
+  // Exponent() is the raw, biased exponent field.  The bias is recovered
+  // from the (unbiased) Fortran MAXEXPONENT and the raw exponent of
+  // Infinity(), which is the maximum representable raw exponent field.
+  const int maxRawExponent{RealValue::Infinity(kind).Exponent()};
+  const int bias{maxRawExponent - RealValue::MAXEXPONENT(kind)};
+  EXPECT_EQ(0, RealValue::Zero(kind).Exponent());
+  EXPECT_EQ(bias, (RealValue{kind, 1.0}.Exponent()));
+  EXPECT_EQ(bias + 1, (RealValue{kind, 2.0}.Exponent()));
+  EXPECT_EQ(maxRawExponent, RealValue::Infinity(kind).Exponent());
+  EXPECT_EQ(maxRawExponent, RealValue::NotANumber(kind).Exponent());
+}
+
+TEST_P(RealValueKind, EXPONENT) {
+  const int kind{GetParam()};
+
+  // The Fortran EXPONENT() intrinsic returns the unbiased exponent, plus one.
+  EXPECT_EQ(1, (RealValue{kind, 1.0}.EXPONENT().ToInt64()));
+  EXPECT_EQ(2, (RealValue{kind, 2.0}.EXPONENT().ToInt64()));
+  EXPECT_EQ(3, (RealValue{kind, 4.0}.EXPONENT().ToInt64()));
+  EXPECT_EQ(0, RealValue::Zero(kind).EXPONENT().ToInt64());
+  EXPECT_EQ(4, (RealValue{kind, 1.0}.EXPONENT().kind())); // INTEGER(4) result
+}
+
+TEST_P(RealValueKind, RRSPACING) {
+  const int kind{GetParam()};
+
+  // RRSPACING(1.0) is 2**(DIGITS-1).
+  RealValue scaled{RealValue{kind, 1.0}
+          .SCALE(IntegerValue{4, RealValue::DIGITS(kind) - 1})
+          .value};
+  EXPECT_REAL_EQ(scaled, (RealValue{kind, 1.0}.RRSPACING()));
+  EXPECT_FALSE((RealValue{kind, -1.0}.RRSPACING().IsNegative()));
+  EXPECT_TRUE(RealValue::Infinity(kind).RRSPACING().IsNotANumber());
+}
+
+TEST_P(RealValueKind, SPACING) {
+  const int kind{GetParam()};
+
+  EXPECT_REAL_EQ(RealValue::EPSILON(kind), (RealValue{kind, 1.0}.SPACING()));
+  // The spacing of a zero or subnormal value is defined to be TINY.
+  EXPECT_REAL_EQ(RealValue::TINY(kind), RealValue::Zero(kind).SPACING());
+  EXPECT_TRUE(RealValue::Infinity(kind).SPACING().IsNotANumber());
+}
+
+TEST_P(RealValueKind, SET_EXPONENT) {
+  const int kind{GetParam()};
+
+  // SET_EXPONENT(X,I) is FRACTION(X)*2**I.
+  EXPECT_REAL_EQ(
+      (RealValue{kind, 4.0}), (RealValue{kind, 1.0}.SET_EXPONENT(3)));
+  EXPECT_REAL_EQ(
+      (RealValue{kind, 1.0}), (RealValue{kind, 8.0}.SET_EXPONENT(1)));
+  EXPECT_TRUE(RealValue::Zero(kind).SET_EXPONENT(3).IsZero());
+  EXPECT_TRUE(RealValue::Infinity(kind).SET_EXPONENT(3).IsNotANumber());
+}
+
+TEST_P(RealValueKind, FRACTION) {
+  const int kind{GetParam()};
+
+  // FRACTION() normalizes into [0.5, 1.0).
+  EXPECT_REAL_EQ((RealValue{kind, 0.5}), (RealValue{kind, 1.0}.FRACTION()));
+  EXPECT_REAL_EQ((RealValue{kind, 0.75}), (RealValue{kind, 3.0}.FRACTION()));
+  EXPECT_TRUE(RealValue::Zero(kind).FRACTION().IsZero());
+}
+
+TEST_P(RealValueKind, SCALE) {
+  const int kind{GetParam()};
+
+  auto scaled{RealValue{kind, 3.0}.SCALE(IntegerValue{4, 4})};
+  EXPECT_TRUE(scaled.flags.empty());
+  EXPECT_REAL_EQ((RealValue{kind, 48.0}), scaled.value);
+  RealValue rescaled{RealValue{kind, 48.0}.SCALE(IntegerValue{4, -4}).value};
+  EXPECT_REAL_EQ((RealValue{kind, 3.0}), rescaled);
+  // Scaling a zero ignores the factor.
+  EXPECT_TRUE(
+      RealValue::Zero(kind).SCALE(IntegerValue{4, 1000}).value.IsZero());
+}
+
+TEST_P(RealValueKind, FlushSubnormalToZero) {
+  const int kind{GetParam()};
+
+  RealValue subnormal{kind, IntegerValue{kind, 1}};
+  ASSERT_FALSE(subnormal.IsZero());
+  EXPECT_TRUE(subnormal.FlushSubnormalToZero().IsZero());
+  // Normal values pass through unchanged.
+  EXPECT_REAL_EQ(
+      (RealValue{kind, 3.0}), (RealValue{kind, 3.0}.FlushSubnormalToZero()));
+  EXPECT_REAL_EQ(
+      RealValue::TINY(kind), RealValue::TINY(kind).FlushSubnormalToZero());
+}
+
+//===----------------------------------------------------------------------===//
+// Conversions
+//===----------------------------------------------------------------------===//
+
+TEST_P(RealValueKind, FromInteger) {
+  const int kind{GetParam()};
+
+  auto exact{RealValue::FromInteger(kind, IntegerValue{8, 3})};
+  EXPECT_TRUE(exact.flags.empty());
+  EXPECT_EQ(kind, exact.value.kind());
+  EXPECT_EQ(Relation::Equal, exact.value.Compare(RealValue{kind, 3.0}));
+  EXPECT_TRUE(
+      RealValue::FromInteger(kind, IntegerValue::Zero(8)).value.IsZero());
+
+  auto negative{RealValue::FromInteger(kind, IntegerValue{8, -3})};
+  EXPECT_TRUE(negative.value.IsNegative());
+
+  // The same bit pattern read as unsigned is a large positive number.
+  auto asUnsigned{
+      RealValue::FromInteger(kind, IntegerValue{8, -1}, /*isUnsigned=*/true)};
+  EXPECT_FALSE(asUnsigned.value.IsNegative());
+  EXPECT_FALSE(asUnsigned.value.IsZero());
+}
+
+TEST_P(RealValueKind, ToWholeNumber) {
+  const int kind{GetParam()};
+
+  // 3.5 is representable in every supported format.
+  RealValue x{kind, 3.5};
+  EXPECT_REAL_EQ((RealValue{kind, 3.0}), x.ToWholeNumber().value);
+  EXPECT_REAL_EQ(
+      (RealValue{kind, 4.0}), x.ToWholeNumber(RoundingMode::TiesToEven).value);
+  EXPECT_REAL_EQ(
+      (RealValue{kind, 4.0}), x.ToWholeNumber(RoundingMode::Up).value);
+  EXPECT_REAL_EQ(
+      (RealValue{kind, 3.0}), x.ToWholeNumber(RoundingMode::Down).value);
+
+  RealValue negative{x.Negate()};
+  EXPECT_REAL_EQ((RealValue{kind, -3.0}), negative.ToWholeNumber().value);
+  EXPECT_REAL_EQ((RealValue{kind, -4.0}),
+      negative.ToWholeNumber(RoundingMode::Down).value);
+  EXPECT_REAL_EQ(
+      (RealValue{kind, -3.0}), negative.ToWholeNumber(RoundingMode::Up).value);
+  // Whole numbers, infinities and NaNs.
+  EXPECT_REAL_EQ(
+      (RealValue{kind, 3.0}), (RealValue{kind, 3.0}.ToWholeNumber().value));
+  EXPECT_TRUE(
+      RealValue::Infinity(kind).ToWholeNumber().flags.test(RealFlag::Overflow));
+  EXPECT_TRUE(RealValue::NotANumber(kind).ToWholeNumber().flags.test(
+      RealFlag::InvalidArgument));
+}
+
+TEST_P(RealValueKind, ToInteger) {
+  const int kind{GetParam()};
+
+  auto exact{RealValue{kind, 42.0}.ToInteger()};
+  EXPECT_TRUE(exact.flags.empty());
+  EXPECT_EQ(42, exact.value.ToInt64());
+  EXPECT_EQ(8, exact.value.kind()); // an INTEGER(8) by default
+  EXPECT_EQ(4,
+      (RealValue{kind, 42.0}.ToInteger(RoundingMode::ToZero, 32).value.kind()));
+  EXPECT_EQ(-42, (RealValue{kind, -42.0}.ToInteger().value.ToInt64()));
+
+  // Rounding modes.
+  RealValue x{kind, 3.5};
+  EXPECT_EQ(3, x.ToInteger(RoundingMode::ToZero).value.ToInt64());
+  EXPECT_EQ(4, x.ToInteger(RoundingMode::TiesToEven).value.ToInt64());
+  EXPECT_EQ(4, x.ToInteger(RoundingMode::Up).value.ToInt64());
+  EXPECT_EQ(3, x.ToInteger(RoundingMode::Down).value.ToInt64());
+
+  // A NaN is invalid and yields HUGE.
+  auto nan{RealValue::NotANumber(kind).ToInteger()};
+  EXPECT_TRUE(nan.flags.test(RealFlag::InvalidArgument));
+  EXPECT_TRUE(nan.value == IntegerValue::HUGE(8));
+  // An infinity overflows.
+  EXPECT_TRUE(
+      RealValue::Infinity(kind).ToInteger().flags.test(RealFlag::Overflow));
+  // So does a value too large for the target integer.
+  EXPECT_TRUE(RealValue::HUGE(kind)
+          .ToInteger(RoundingMode::ToZero, 8)
+          .flags.test(RealFlag::Overflow));
+}
+
+TEST_P(RealValueKind, Convert) {
+  const int kind{GetParam()};
+
+  // Widening to REAL(16) and narrowing back is lossless.
+  auto widened{RealValue::Convert(16, RealValue{kind, 3.0})};
+  EXPECT_EQ(16, widened.value.kind());
+  auto restored{RealValue::Convert(kind, widened.value)};
+  EXPECT_REAL_EQ((RealValue{kind, 3.0}), restored.value);
+  // Converting to the same kind is the identity.
+  EXPECT_REAL_EQ((RealValue{kind, 3.0}),
+      (RealValue::Convert(kind, RealValue{kind, 3.0}).value));
+
+  // A NaN is invalid but stays a NaN.
+  auto nan{RealValue::Convert(kind, RealValue::NotANumber(16))};
+  EXPECT_TRUE(nan.flags.test(RealFlag::InvalidArgument));
+  EXPECT_TRUE(nan.value.IsNotANumber());
+  // Overflow when the source magnitude exceeds the destination's range.
+  if (kind != 16) {
+    auto overflowed{RealValue::Convert(kind, RealValue::HUGE(16))};
+    EXPECT_TRUE(overflowed.flags.test(RealFlag::Overflow));
+    EXPECT_TRUE(overflowed.value.IsInfinite());
+  }
+}
+
+//===----------------------------------------------------------------------===//
+// Raw bits, formatting and parsing
+//===----------------------------------------------------------------------===//
+
+TEST_P(RealValueKind, RawBits) {
+  const int kind{GetParam()};
+
+  EXPECT_TRUE(RealValue::Zero(kind).RawBits().IsZero());
+
+  // REAL(10) stores 128 bits, but only 80 of them are significant.
+  const int significantBits{kind == 10 ? 80 : RealValue::bits(kind)};
+  RealValue allOnes{kind, IntegerValue::MASKR(kind, significantBits)};
+  EXPECT_EQ(significantBits, allOnes.RawBits().POPCNT());
+  EXPECT_EQ(1, RealValue::NegativeZero(kind).RawBits().POPCNT());
+  EXPECT_EQ(0, RealValue::NegativeZero(kind).RawBits().LEADZ());
+
+  // The bit pattern round-trips through the (kind, Word) constructor.
+  RealValue x{kind, 3.0};
+  EXPECT_REAL_EQ(x, (RealValue{kind, x.RawBits()}));
+}
+
+TEST_P(RealValueKind, RawBytesRoundTrip) {
+  const int kind{GetParam()};
+  RealValue original{kind, -3.0};
+  char buffer[16]{};
+  ASSERT_EQ(RealValue::bytesStored(kind), original.bytesStored());
+
+  bool changed1{false};
+  original.StoreRawBytes(buffer, original.bytesStored(), &changed1);
+  EXPECT_TRUE(changed1);
+
+  RealValue restored{
+      RealValue::FromRawBytes(kind, buffer, original.bytesStored())};
+  EXPECT_EQ(kind, restored.kind());
+  EXPECT_REAL_EQ(original, restored);
+
+  bool changed2{false};
+  original.StoreRawBytes(buffer, original.bytesStored(), &changed2);
+  EXPECT_FALSE(changed2);
+}
+
+// Ported from the legacy non-GTest test flang/unittests/Evaluate/real.cpp.
+TEST(RealValue, DumpHexadecimal) {
+  struct {
+    std::uint64_t raw;
+    const char *expected;
+  } table[]{
+      {0x7f876543, "NaN0x7f876543"},
+      {0x7f800000, "Inf"},
+      {0xff800000, "-Inf"},
+      {0x00000000, "0.0"},
+      {0x80000000, "-0.0"},
+      {0x3f800000, "0x1.0p0"},
+      {0xbf800000, "-0x1.0p0"},
+      {0x40000000, "0x1.0p1"},
+      {0x3f000000, "0x1.0p-1"},
+      {0x7f7fffff, "0x1.fffffep127"},
+      {0x00800000, "0x1.0p-126"},
+      {0x00400000, "0x0.8p-126"},
+      {0x00000001, "0x0.000002p-126"},
+  };
+  for (auto &e : table) {
+    EXPECT_EQ(
+        e.expected, (RealValue{4, IntegerValue{4, e.raw}}.DumpHexadecimal()))
+        << "raw=" << e.raw;
+  }
+}
+
+TEST_P(RealValueKind, AsFortran) {
+  const int kind{GetParam()};
+
+  // NaNs and infinities are emitted as parenthesized expressions.
+  std::string nan{AsFortranString(RealValue::NotANumber(kind), kind, false)};
+  EXPECT_EQ("(0._" + std::to_string(kind) + "/0.)", nan);
+  std::string inf{AsFortranString(RealValue::Infinity(kind), kind, false)};
+  EXPECT_EQ("(1._" + std::to_string(kind) + "/0.)", inf);
+
+  std::string negInf{
+      AsFortranString(RealValue::Infinity(kind, true), kind, false)};
+  EXPECT_EQ("(-1._" + std::to_string(kind) + "/0.)", negInf);
+
+  // A finite value reads back as itself.
+  RealValue x{kind, 0.375};
+  std::string decimal{AsFortranString(x, kind, false)};
+  const char *p{decimal.c_str()};
+  if (*p == '(') {
+    ++p;
+  }
+
+  auto readBack{RealValue::Read(kind, p)};
+  EXPECT_REAL_EQ(x, readBack.value);
+  EXPECT_EQ('_', *p) << decimal;
+  // The minimal form also reads back as itself.
+  std::string minimal{AsFortranString(x, kind, true)};
+  p = minimal.c_str();
+  if (*p == '(') {
+    ++p;
+  }
+  EXPECT_REAL_EQ(x, RealValue::Read(kind, p).value);
+}
+
+TEST_P(RealValueKind, Read) {
+  const int kind{GetParam()};
+  const char *text{"1.0rest"};
+  const char *p{text};
+  auto one{RealValue::Read(kind, p)};
+  EXPECT_EQ(kind, one.value.kind());
+  EXPECT_REAL_EQ((RealValue{kind, 1.0}), one.value);
+  EXPECT_STREQ("rest", p);
+
+  const char *negative{"-2.5"};
+  p = negative;
+  auto minusTwoAndAHalf{RealValue::Read(kind, p)};
+  EXPECT_REAL_EQ((RealValue{kind, -2.5}), minusTwoAndAHalf.value);
+
+  // 0.1 is inexact in every binary format.
+  const char *tenth{"0.1"};
+  p = tenth;
+  EXPECT_TRUE(RealValue::Read(kind, p).flags.test(RealFlag::Inexact));
+}
+
+TEST_P(RealValueKind, RoundingModes) {
+  const int kind{GetParam()};
+
+  // 1 + EPSILON/2 is exactly halfway between 1 and the next value up, so each
+  // rounding mode picks a different result.
+  RealValue one{kind, 1.0};
+  RealValue half{RealValue::EPSILON(kind).Divide(RealValue{kind, 2.0}).value};
+  RealValue up{one.Add(RealValue::EPSILON(kind)).value};
+  EXPECT_REAL_EQ(
+      one, one.Add(half, Rounding{RoundingMode::TiesToEven}).value); // to even
+  EXPECT_REAL_EQ(one, one.Add(half, Rounding{RoundingMode::ToZero}).value);
+  EXPECT_REAL_EQ(one, one.Add(half, Rounding{RoundingMode::Down}).value);
+  EXPECT_REAL_EQ(up, one.Add(half, Rounding{RoundingMode::Up}).value);
+  EXPECT_REAL_EQ(
+      up, one.Add(half, Rounding{RoundingMode::TiesAwayFromZero}).value);
+}
+
+TEST_P(RealValueKind, Print) {
+  const int kind{GetParam()};
+  const int pos{KindPos(kind)};
+
+  llvm::SmallString<128> buf;
+  llvm::raw_svector_ostream os{buf};
+  RealValue v{kind, 42.0};
+  v.print(os);
+
+  const char *results[]{
+      "4.2e1_2", "4.2e1_3", "4.2e1_4", "4.2e1_8", "4.2e1_10", "4.2e1_16"};
+  EXPECT_EQ(results[pos], os.str());
+}
+
+//===----------------------------------------------------------------------===//
+// Ported coverage from flang/unittests/Evaluate/real.cpp
+//===----------------------------------------------------------------------===//
+
+// Mirrors basicTests() from the legacy test: converts every power of two that
+// fits in an INTEGER(8) and converts it back.
+TEST_P(RealValueKind, FromIntegerPowersOfTwo) {
+  const int kind{GetParam()};
+  const int bias{
+      RealValue::Infinity(kind).Exponent() - RealValue::MAXEXPONENT(kind)};
+  for (int j{0}; j < 63; ++j) {
+    SCOPED_TRACE(testing::Message() << "kind=" << kind << " 2**" << j);
+    const std::uint64_t x{std::uint64_t{1} << j};
+    IntegerValue ix{8, x};
+    ASSERT_FALSE(ix.IsNegative());
+    ASSERT_EQ(x, ix.ToUInt64());
+
+    auto vr{RealValue::FromInteger(kind, ix)};
+    EXPECT_FALSE(vr.value.IsNegative());
+    EXPECT_FALSE(vr.value.IsNotANumber());
+    EXPECT_FALSE(vr.value.IsZero());
+    auto back{vr.value.ToInteger()};
+    if (j > bias) {
+      EXPECT_TRUE(vr.flags.test(RealFlag::Overflow));
+      EXPECT_TRUE(vr.value.IsInfinite());
+      EXPECT_TRUE(back.flags.test(RealFlag::Overflow));
+      EXPECT_EQ(0x7fffffffffffffffu, back.value.ToUInt64());
+    } else {
+      EXPECT_TRUE(vr.flags.empty());
+      EXPECT_FALSE(vr.value.IsInfinite());
+      EXPECT_TRUE(back.flags.empty());
+      EXPECT_EQ(x, back.value.ToUInt64());
+      // A power of two is a whole number already.
+      EXPECT_EQ(
+          Relation::Equal, vr.value.ToWholeNumber().value.Compare(vr.value));
+      // Emitting and re-reading the value is lossless.
+      std::string decimal{AsFortranString(vr.value, kind, false)};
+      const char *p{decimal.c_str()};
+      auto check{RealValue::Read(kind, p)};
+      EXPECT_EQ(Relation::Equal, vr.value.Compare(check.value)) << decimal;
+      EXPECT_EQ(x, check.value.ToInteger().value.ToUInt64()) << decimal;
+    }
+
+    IntegerValue negIx{ix.Negate().value};
+    ASSERT_TRUE(negIx.IsNegative());
+    auto negVr{RealValue::FromInteger(kind, negIx)};
+    EXPECT_TRUE(negVr.value.IsNegative());
+    EXPECT_FALSE(negVr.value.IsNotANumber());
+    EXPECT_FALSE(negVr.value.IsZero());
+    auto negBack{negVr.value.ToInteger()};
+    if (j > bias) {
+      EXPECT_TRUE(negVr.flags.test(RealFlag::Overflow));
+      EXPECT_TRUE(negVr.value.IsInfinite());
+      EXPECT_TRUE(negBack.flags.test(RealFlag::Overflow));
+      EXPECT_EQ(0x8000000000000000u, negBack.value.ToUInt64());
+    } else {
+      EXPECT_TRUE(negVr.flags.empty());
+      EXPECT_FALSE(negVr.value.IsInfinite());
+      EXPECT_TRUE(negBack.flags.empty());
+      EXPECT_EQ(negIx.ToInt64(), negBack.value.ToInt64());
+    }
+    EXPECT_EQ(Relation::Equal,
+        negVr.value.ToWholeNumber().value.Compare(negVr.value));
+  }
+}
+
+// Mirrors subsetTests() from the real.cpp legacy test, comparing against the
+// host's hardware arithmetic in the default (round-to-nearest) mode.
+TYPED_TEST(RealValueHostTypedKind, CompareUnaryWithHost) {
+  using HostT = typename TypeParam::HostT;
+  using UnsignedT = typename TypeParam::UnsignedT;
+  constexpr int kind{TypeParam::kind};
+
+  union {
+    UnsignedT ui;
+    HostT f;
+  } u;
+  constexpr UnsignedT operands{4096};
+  for (UnsignedT j{0}; j < operands; ++j) {
+    const UnsignedT raw{SpreadBits(j)};
+    u.ui = raw;
+    const HostT f{u.f};
+    RealValue x{kind, IntegerValue{kind, std::uint64_t{raw}}};
+    SCOPED_TRACE(testing::Message()
+        << "kind=" << kind << " raw=0x" << x.RawBits().Hexadecimal());
+
+    ASSERT_EQ(std::uint64_t{raw}, x.RawBits().ToUInt64());
+    EXPECT_EQ(std::isnan(f), x.IsNotANumber());
+    EXPECT_EQ(std::isinf(f), x.IsInfinite());
+    EXPECT_EQ(std::isfinite(f), x.IsFinite());
+    EXPECT_EQ(f == 0, x.IsZero());
+    EXPECT_EQ(std::signbit(f) && !std::isnan(f), x.IsNegative());
+    EXPECT_EQ(
+        std::isfinite(f) && std::fpclassify(f) != FP_SUBNORMAL, x.IsNormal());
+
+    ExpectSameAsHost<HostT, UnsignedT>(x.ToWholeNumber().value, std::trunc(f));
+    ExpectSameAsHost<HostT, UnsignedT>(x.SQRT().value, std::sqrt(f));
+    ExpectSameAsHost<HostT, UnsignedT>(x.ABS(), std::fabs(f));
+    if (!std::isnan(f)) {
+      ExpectSameAsHost<HostT, UnsignedT>(x.Negate(), -f);
+    }
+
+    // Every value is emitted as a Fortran constant that reads back exactly.
+    const std::string kindSuffix{std::to_string(kind)};
+    std::string text{AsFortranString(x, kind, false)};
+    if (std::isnan(f)) {
+      EXPECT_EQ("(0._" + kindSuffix + "/0.)", text);
+    } else if (std::isinf(f)) {
+      EXPECT_EQ(
+          (std::signbit(f) ? "(-1._" : "(1._") + kindSuffix + "/0.)", text);
+    } else {
+      const char *p{text.c_str()};
+      if (*p == '(') {
+        ++p;
+      }
+      auto readBack{RealValue::Read(kind, p)};
+      EXPECT_EQ(std::uint64_t{raw}, readBack.value.RawBits().ToUInt64())
+          << text;
+      EXPECT_EQ('_', *p) << text;
+    }
+  }
+}
+
+TYPED_TEST(RealValueHostTypedKind, CompareDyadicWithHost) {
+  using HostT = typename TypeParam::HostT;
+  using UnsignedT = typename TypeParam::UnsignedT;
+  constexpr int kind{TypeParam::kind};
+
+  union {
+    UnsignedT ui;
+    HostT f;
+  } u;
+  constexpr UnsignedT operands{128};
+  for (UnsignedT j{0}; j < operands; ++j) {
+    const UnsignedT rj{SpreadBits(j)};
+    u.ui = rj;
+    const HostT fj{u.f};
+    RealValue x{kind, IntegerValue{kind, std::uint64_t{rj}}};
+    for (UnsignedT k{0}; k < operands; ++k) {
+      const UnsignedT rk{SpreadBits(k)};
+      u.ui = rk;
+      const HostT fk{u.f};
+      RealValue y{kind, IntegerValue{kind, std::uint64_t{rk}}};
+      SCOPED_TRACE(testing::Message()
+          << "kind=" << kind << " x=0x" << x.RawBits().Hexadecimal() << " y=0x"
+          << y.RawBits().Hexadecimal());
+      ExpectSameAsHost<HostT, UnsignedT>(x.Add(y).value, fj + fk);
+      ExpectSameAsHost<HostT, UnsignedT>(x.Subtract(y).value, fj - fk);
+      ExpectSameAsHost<HostT, UnsignedT>(x.Multiply(y).value, fj * fk);
+      ExpectSameAsHost<HostT, UnsignedT>(x.Divide(y).value, fj / fk);
+    }
+  }
+}
+
+} // namespace
diff --git a/third-party/unittest/googletest/include/gtest/internal/gtest-param-util.h b/third-party/unittest/googletest/include/gtest/internal/gtest-param-util.h
index 6a81c37fa6afc..9d5e3aa0c6c43 100644
--- a/third-party/unittest/googletest/include/gtest/internal/gtest-param-util.h
+++ b/third-party/unittest/googletest/include/gtest/internal/gtest-param-util.h
@@ -659,10 +659,14 @@ class ParameterizedTestSuiteInfo : public ParameterizedTestSuiteInfoBase {
     // Check for empty string
     if (name.empty()) return false;
 
+// LLVM edit to allow parenthesis in test names, which is already allowed for
+// TYPED_TEST
+#if 0
     // Check for invalid characters
     for (std::string::size_type index = 0; index < name.size(); ++index) {
       if (!IsAlNum(name[index]) && name[index] != '_') return false;
     }
+#endif
 
     return true;
   }

>From 7920ffbed0c7c51dede60c163cac9e8b6d2abc86 Mon Sep 17 00:00:00 2001
From: Michael Kruse <llvm-project at meinersbur.de>
Date: Mon, 17 Aug 2026 21:09:03 +0200
Subject: [PATCH 2/2] Fix potential undefined linker symbol

---
 flang/lib/Evaluate/real-value-impl.cpp | 42 ++++++++++----------------
 flang/lib/Evaluate/real-value-impl.h   | 10 ------
 2 files changed, 16 insertions(+), 36 deletions(-)

diff --git a/flang/lib/Evaluate/real-value-impl.cpp b/flang/lib/Evaluate/real-value-impl.cpp
index 80d6654f439a3..182533f61de5e 100644
--- a/flang/lib/Evaluate/real-value-impl.cpp
+++ b/flang/lib/Evaluate/real-value-impl.cpp
@@ -23,7 +23,8 @@ RealValueImpl::RealValueImpl(int kind, const Word &w) {
     if (w.IsMonostate()) {
       storage_ = R{};
     } else {
-      storage_ = R{FixedIntegerFromValue<typename R::Word>(w)};
+      storage_ =
+          R{IntegerValueImpl::CoerceUnsigned<typename R::Word>(w.impl())};
     }
   });
 }
@@ -249,8 +250,11 @@ IntegerValue RealValueImpl::RawBits() const {
     return {};
   }
 
-  return withWord(
-      [](const auto &v) { return IntegerValueFromFixed(v.RawBits()); });
+  return withWord([](const auto &v) {
+    IntegerValue result;
+    result.impl() = IntegerValueImpl::FromWord(v.RawBits());
+    return result;
+  });
 }
 
 Relation RealValueImpl::Compare(const RealValueImpl &y) const {
@@ -452,7 +456,7 @@ ValueWithRealFlags<IntegerValue> RealValueImpl::ToInteger(
       using W = decltype(target);
       auto r{v.template ToInteger<W>(mode)};
       ValueWithRealFlags<IntegerValue> result;
-      result.value = IntegerValueFromFixed(r.value);
+      result.value.impl() = IntegerValueImpl::FromWord(r.value);
       result.flags = r.flags;
       return result;
     }};
@@ -503,7 +507,10 @@ IntegerValue RealValueImpl::EXPONENT() const {
     llvm_unreachable("unsupported operation over uninitialized value");
   }
   return withWord([](const auto &v) -> IntegerValue {
-    return IntegerValueFromFixed(v.template EXPONENT<Integer<32>>());
+    IntegerValue result;
+    result.impl() =
+        IntegerValueImpl::FromWord(v.template EXPONENT<Integer<32>>());
+    return result;
   });
 }
 
@@ -514,7 +521,10 @@ ValueWithRealFlags<RealValueImpl> RealValueImpl::FromInteger(
   }
   return withWordProto(
       kind, [&](auto proto) -> ValueWithRealFlags<RealValueImpl> {
-        auto r{FromIntegerValue<decltype(proto)>(n, isUnsigned, rounding)};
+        using R = std::decay_t<decltype(proto)>;
+        auto r{n.impl().withWord([&](const auto &concrete) {
+          return R::FromInteger(concrete, isUnsigned, rounding);
+        })};
         return {FromWord(r.value), r.flags};
       });
 }
@@ -566,24 +576,4 @@ llvm::raw_ostream &RealValueImpl::AsFortran(
   return o;
 }
 
-template <typename INT>
-IntegerValue RealValueImpl::IntegerValueFromFixed(const INT &n) {
-  IntegerValue result;
-  result.impl() = IntegerValueImpl::FromWord(n);
-  return result;
-}
-
-template <typename INT>
-INT RealValueImpl::FixedIntegerFromValue(const IntegerValue &v) {
-  return IntegerValueImpl::CoerceUnsigned<INT>(v.impl());
-}
-
-template <typename R>
-ValueWithRealFlags<R> RealValueImpl::FromIntegerValue(
-    const IntegerValue &n, bool isUnsigned, Rounding rounding) {
-  return n.impl().withWord([&](const auto &concrete) {
-    return R::FromInteger(concrete, isUnsigned, rounding);
-  });
-}
-
 } // namespace Fortran::evaluate::value
diff --git a/flang/lib/Evaluate/real-value-impl.h b/flang/lib/Evaluate/real-value-impl.h
index 1ab80ad2fab45..15f4dae3ded47 100644
--- a/flang/lib/Evaluate/real-value-impl.h
+++ b/flang/lib/Evaluate/real-value-impl.h
@@ -256,16 +256,6 @@ class RealValueImpl {
   }
 
 private:
-  template <typename INT>
-  static IntegerValue IntegerValueFromFixed(const INT &);
-
-  template <typename INT>
-  static INT FixedIntegerFromValue(const IntegerValue &);
-
-  template <typename R>
-  static ValueWithRealFlags<R> FromIntegerValue(
-      const IntegerValue &v, bool isUnsigned, Rounding rounding);
-
   Storage storage_;
 };
 



More information about the llvm-branch-commits mailing list