[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
Mon Aug 24 04:09:51 PDT 2026


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

>From b909ec105bcdb8367e7bebe9e932495590662245 Mon Sep 17 00:00:00 2001
From: Michael Kruse <llvm-project at meinersbur.de>
Date: Wed, 19 Aug 2026 09:13:35 +0200
Subject: [PATCH 1/3] [Flang] Introduce *Value classes

---
 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        |  579 ++++
 flang/lib/Evaluate/real-value-impl.h          |  273 ++
 flang/lib/Evaluate/real-value.cpp             |  275 ++
 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 | 2346 +++++++++++++++++
 flang/unittests/Evaluate/LogicalValueTest.cpp |  324 +++
 flang/unittests/Evaluate/RealValueTest.cpp    | 1141 ++++++++
 .../include/gtest/internal/gtest-param-util.h |    4 +
 34 files changed, 10367 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/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 fade8cbcc114f..d0cbf52e1a7a7 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 4db851734ebb2..0274850df8033 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..182533f61de5e
--- /dev/null
+++ b/flang/lib/Evaluate/real-value-impl.cpp
@@ -0,0 +1,579 @@
+//===-- 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{IntegerValueImpl::CoerceUnsigned<typename R::Word>(w.impl())};
+    }
+  });
+}
+
+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) {
+    IntegerValue result;
+    result.impl() = IntegerValueImpl::FromWord(v.RawBits());
+    return result;
+  });
+}
+
+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.impl() = IntegerValueImpl::FromWord(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 {
+    IntegerValue result;
+    result.impl() =
+        IntegerValueImpl::FromWord(v.template EXPONENT<Integer<32>>());
+    return result;
+  });
+}
+
+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> {
+        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};
+      });
+}
+
+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;
+}
+
+} // 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..15f4dae3ded47
--- /dev/null
+++ b/flang/lib/Evaluate/real-value-impl.h
@@ -0,0 +1,273 @@
+//===-- 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:
+  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/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..803a3e106a80a
--- /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(char32_t('\x80'), u[0]);
+
+  CharacterValue w{2, std::u16string{u"\u0100"}};
+  EXPECT_EQ(char32_t{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(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(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(), std::size_t(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(CharT{'a'}, buffer[0]);
+  EXPECT_EQ(CharT{'b'}, buffer[1]);
+  EXPECT_EQ(CharT{'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(CharT{'b'}, buffer[1]);
+  EXPECT_EQ(CharT{'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(CharT{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..59b063dfa1686
--- /dev/null
+++ b/flang/unittests/Evaluate/IntegerValueTest.cpp
@@ -0,0 +1,2346 @@
+//===-- 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};
+  const int kindPos{IntKindPos<TypeParam>};
+  EXPECT_EQ((kind == 16) ? 60 : bits - kindPos - 1, patternv.POPCNT());
+
+  IntegerValue invpatternv{kind, ~UnsignedT(0x7FFFFFFF7FFF7F7Full)};
+  EXPECT_EQ((kind == 16) ? 68 : 1 + kindPos, 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(0xefull, 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{UnsignedT(~UnsignedT{0})};
+    const Wide wide{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..69119522b5f86
--- /dev/null
+++ b/flang/unittests/Evaluate/RealValueTest.cpp
@@ -0,0 +1,1141 @@
+//===-- 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(std::uint64_t{u.ui}, got.RawBits().ToUInt64())
+      << "expected " << 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 b9c547a7a7e6e254009552a2bc20fbed169fd2d7 Mon Sep 17 00:00:00 2001
From: Michael Kruse <llvm-project at meinersbur.de>
Date: Mon, 24 Aug 2026 01:24:59 +0200
Subject: [PATCH 2/3] Backport typekind-traits changes Use kind instead of bits
 in conversion functions

---
 flang/include/flang/Evaluate/integer-value.h  |   4 +-
 .../include/flang/Evaluate/typekind-traits.h  | 139 +++++++++++++++---
 flang/lib/Evaluate/integer-value-impl.cpp     |  14 +-
 flang/lib/Evaluate/integer-value-impl.h       |   4 +-
 flang/lib/Evaluate/integer-value.cpp          |   8 +-
 flang/unittests/Evaluate/IntegerValueTest.cpp |  18 ++-
 6 files changed, 146 insertions(+), 41 deletions(-)

diff --git a/flang/include/flang/Evaluate/integer-value.h b/flang/include/flang/Evaluate/integer-value.h
index d8ddeb39f039d..54d71af5215e4 100644
--- a/flang/include/flang/Evaluate/integer-value.h
+++ b/flang/include/flang/Evaluate/integer-value.h
@@ -130,10 +130,10 @@ class IntegerValue {
 
   /// ZExt or Trunc
   static ValueWithOverflow ConvertUnsigned(
-      const IntegerValue &from, int toBits);
+      int toKind, const IntegerValue &from);
 
   /// SExt or Trunc
-  static ValueWithOverflow ConvertSigned(const IntegerValue &from, int toBits);
+  static ValueWithOverflow ConvertSigned(int toKind, const IntegerValue &from);
 
   std::string UnsignedDecimal() const;
 
diff --git a/flang/include/flang/Evaluate/typekind-traits.h b/flang/include/flang/Evaluate/typekind-traits.h
index 184de18666e24..0559d63dbcc31 100644
--- a/flang/include/flang/Evaluate/typekind-traits.h
+++ b/flang/include/flang/Evaluate/typekind-traits.h
@@ -12,7 +12,9 @@
 #include "flang/Common/Fortran-consts.h"
 #include "flang/Evaluate/common.h"
 #include "flang/Evaluate/integer-value.h"
+#include "flang/Evaluate/logical-value.h"
 #include "flang/Evaluate/real-value.h"
+#include "flang/Evaluate/type.h"
 
 namespace Fortran::evaluate::value {
 class CharacterValue;
@@ -22,47 +24,77 @@ class ComplexValue;
 
 namespace Fortran::evaluate {
 
+/// Traits class for Fortran intrinsics types.
+///
+/// In contrast to Type<CAT>, TypeKind<CAT,KIND> also carries the KIND as
+/// template parameter. Used to resolve a Fortran type to an equivalent C/C++
+/// type of the host compiler. Avoid using it anywhere else as template
+/// instantiation for each KIND separately blows up build time.
+///
+/// Common members:
+///  * category    The CAT template argument
+///  * kind        The KIND template argument
+///  * bits        For types where it makes sense, how many bits of information
+///                it holds
+///  * bytesStored How many bytes this type requires in memory; includes
+///                alignment/padding bytes
+///  * HostT       The equivalent C/C++ type in the host compiler; void if there
+///                is no equivalent
+///  * FortranType The equivalent evaluate::Type<CAT>
+///  * GetType()   The equivalent evaluate::DynamicType
 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 common::TypeCategory category{common::TypeCategory::Integer};
   static constexpr int kind{KIND};
   static constexpr int bits{value::IntegerValue::bits(KIND)};
+  static constexpr int bytesStored{value::IntegerValue::bytesStored(kind)};
   using UnsignedT = common::HostUnsignedIntType<bits>;
   using SignedT = common::HostSignedIntType<bits>;
   using HostT = SignedT;
   using Scalar = value::IntegerValue;
+  using FortranType = Fortran::evaluate::Type<common::TypeCategory::Integer>;
+  static constexpr DynamicType GetType() { return DynamicType{category, kind}; }
 };
 
+using IntegerKindTypes = std::tuple<TypeKind<TypeCategory::Integer, 1>,
+    TypeKind<TypeCategory::Integer, 2>, TypeKind<TypeCategory::Integer, 4>,
+    TypeKind<TypeCategory::Integer, 8>, TypeKind<TypeCategory::Integer, 16>>;
+
 template <int KIND> struct TypeKind<common::TypeCategory::Unsigned, KIND> {
+  static constexpr common::TypeCategory category{
+      common::TypeCategory::Unsigned};
   static constexpr int kind{KIND};
   static constexpr int bits{value::IntegerValue::bits(KIND)};
+  static constexpr int bytesStored{value::IntegerValue::bytesStored(kind)};
   using UnsignedT = common::HostUnsignedIntType<bits>;
   using SignedT = common::HostSignedIntType<bits>;
   using HostT = UnsignedT;
   using Scalar = value::IntegerValue;
+  using FortranType = Fortran::evaluate::Type<common::TypeCategory::Unsigned>;
+  static constexpr DynamicType GetType() { return DynamicType{category, kind}; }
 };
 
+using UnsignedKindTypes = std::tuple<TypeKind<TypeCategory::Unsigned, 1>,
+    TypeKind<TypeCategory::Unsigned, 2>, TypeKind<TypeCategory::Unsigned, 4>,
+    TypeKind<TypeCategory::Unsigned, 8>, TypeKind<TypeCategory::Unsigned, 16>>;
+
+template <int KIND> struct TypeKind<common::TypeCategory::Logical, KIND> {
+  static constexpr common::TypeCategory category{common::TypeCategory::Logical};
+  static constexpr int kind{KIND};
+  static constexpr int bits{value::LogicalValue::bits(KIND)};
+  static constexpr int bytesStored{value::IntegerValue::bytesStored(kind)};
+  using UnsignedT = common::HostUnsignedIntType<bits>;
+  using SignedT = common::HostSignedIntType<bits>;
+  using HostT = UnsignedT;
+  using Scalar = value::LogicalValue;
+  using FortranType = Fortran::evaluate::Type<common::TypeCategory::Logical>;
+  static constexpr DynamicType GetType() { return DynamicType{category, kind}; }
+};
+
+using LogicalKindTypes = std::tuple<TypeKind<TypeCategory::Logical, 1>,
+    TypeKind<TypeCategory::Logical, 2>, TypeKind<TypeCategory::Logical, 4>>;
+
 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.
@@ -78,18 +110,83 @@ template <> struct RealHostType<64> {
 } // namespace detail
 
 template <int KIND> struct TypeKind<common::TypeCategory::Real, KIND> {
+  static constexpr common::TypeCategory category{common::TypeCategory::Real};
   static constexpr int kind{KIND};
   static constexpr int bits{value::RealValue::bits(KIND)};
+  static constexpr int bytesStored{value::IntegerValue::bytesStored(kind)};
   using UnsignedT = common::HostUnsignedIntType<bits>;
   using SignedT = common::HostSignedIntType<bits>;
   using HostT = typename detail::RealHostType<bits>::type;
   using Scalar = value::RealValue;
+  using FortranType = Fortran::evaluate::Type<common::TypeCategory::Real>;
+  static constexpr DynamicType GetType() { return DynamicType{category, kind}; }
 };
 
+using RealKindTypes =
+    std::tuple<TypeKind<TypeCategory::Real, 2>, TypeKind<TypeCategory::Real, 3>,
+        TypeKind<TypeCategory::Real, 4>, TypeKind<TypeCategory::Real, 8>,
+        TypeKind<TypeCategory::Real, 10>, TypeKind<TypeCategory::Real, 16>>;
+
 template <int KIND> struct TypeKind<common::TypeCategory::Complex, KIND> {
+  static constexpr common::TypeCategory category{common::TypeCategory::Complex};
   static constexpr int kind{KIND};
+  static constexpr int bytesStored{value::IntegerValue::bytesStored(kind)};
+  using FortranType = Fortran::evaluate::Type<common::TypeCategory::Complex>;
   using Scalar = value::ComplexValue;
+  static constexpr DynamicType GetType() { return DynamicType{category, kind}; }
+  using Part = TypeKind<common::TypeCategory::Real, KIND>;
+};
+
+using ComplexKindTypes = std::tuple<TypeKind<TypeCategory::Complex, 2>,
+    TypeKind<TypeCategory::Complex, 3>, TypeKind<TypeCategory::Complex, 4>,
+    TypeKind<TypeCategory::Complex, 8>, TypeKind<TypeCategory::Complex, 10>,
+    TypeKind<TypeCategory::Complex, 16>>;
+
+template <> struct TypeKind<common::TypeCategory::Character, 1> {
+  static constexpr common::TypeCategory category{
+      common::TypeCategory::Character};
+  static constexpr int kind{1};
+  static constexpr int bytesStored{value::IntegerValue::bytesStored(kind)};
+  using CharT = char;
+  using StringT = std::basic_string<CharT>;
+  using HostT = void;
+  using Scalar = value::CharacterValue;
+  using FortranType = Fortran::evaluate::Type<common::TypeCategory::Character>;
+  static constexpr DynamicType GetType() { return DynamicType{category, kind}; }
 };
 
+template <> struct TypeKind<common::TypeCategory::Character, 2> {
+  static constexpr common::TypeCategory category{
+      common::TypeCategory::Character};
+  static constexpr int kind{2};
+  static constexpr int bytesStored{value::IntegerValue::bytesStored(kind)};
+  using CharT = char16_t;
+  using StringT = std::basic_string<CharT>;
+  using HostT = void;
+  using Scalar = value::CharacterValue;
+  using FortranType = Fortran::evaluate::Type<common::TypeCategory::Character>;
+  static constexpr DynamicType GetType() { return DynamicType{category, kind}; }
+};
+
+template <> struct TypeKind<common::TypeCategory::Character, 4> {
+  static constexpr common::TypeCategory category{
+      common::TypeCategory::Character};
+  static constexpr int kind{4};
+  static constexpr int bytesStored{value::IntegerValue::bytesStored(kind)};
+  using CharT = char32_t;
+  using StringT = std::basic_string<CharT>;
+  using HostT = void;
+  using Scalar = value::CharacterValue;
+  using FortranType = Fortran::evaluate::Type<common::TypeCategory::Character>;
+  static constexpr DynamicType GetType() { return DynamicType{category, kind}; }
+};
+
+using CharacterKindTypes = std::tuple<TypeKind<TypeCategory::Character, 1>,
+    TypeKind<TypeCategory::Character, 2>, TypeKind<TypeCategory::Character, 4>>;
+
+using AllIntrinsicKindTypes =
+    common::CombineTuples<IntegerKindTypes, UnsignedKindTypes, LogicalKindTypes,
+        RealKindTypes, ComplexKindTypes, CharacterKindTypes>;
+
 } // namespace Fortran::evaluate
 #endif // FORTRAN_EVALUATE_TYPEKINDTRAITS_H_
diff --git a/flang/lib/Evaluate/integer-value-impl.cpp b/flang/lib/Evaluate/integer-value-impl.cpp
index bc1a1d1c8cf73..6ebff524d9c3e 100644
--- a/flang/lib/Evaluate/integer-value-impl.cpp
+++ b/flang/lib/Evaluate/integer-value-impl.cpp
@@ -528,13 +528,14 @@ bool IntegerValueImpl::POPPAR() const {
 }
 
 typename IntegerValueImpl::ValueWithOverflow IntegerValueImpl::ConvertSigned(
-    const IntegerValueImpl &from, int toBits) {
+    int toKind, const IntegerValueImpl &from) {
   if (from.IsMonostate()) {
-    return {};
+    // Now we know the kind
+    return {Zero(toKind), false};
   }
   return from.withWord([&](const auto &x) -> ValueWithOverflow {
     using S = std::decay_t<decltype(x)>;
-    return withWordProto(toBits / 8, [&](auto proto) -> ValueWithOverflow {
+    return withWordProto(toKind, [&](auto proto) -> ValueWithOverflow {
       using T = decltype(proto);
       auto r{T::template ConvertSigned<S>(x)};
       return {FromWord(r.value), r.overflow};
@@ -543,13 +544,14 @@ typename IntegerValueImpl::ValueWithOverflow IntegerValueImpl::ConvertSigned(
 }
 
 typename IntegerValueImpl::ValueWithOverflow IntegerValueImpl::ConvertUnsigned(
-    const IntegerValueImpl &from, int toBits) {
+    int toKind, const IntegerValueImpl &from) {
   if (from.IsMonostate()) {
-    return {};
+    // Now we know the kind
+    return {Zero(toKind), false};
   }
   return from.withWord([&](const auto &x) -> ValueWithOverflow {
     using S = std::decay_t<decltype(x)>;
-    return withWordProto(toBits / 8, [&](auto proto) -> ValueWithOverflow {
+    return withWordProto(toKind, [&](auto proto) -> ValueWithOverflow {
       using T = decltype(proto);
       auto r{T::template ConvertUnsigned<S>(x)};
       return {FromWord(r.value), r.overflow};
diff --git a/flang/lib/Evaluate/integer-value-impl.h b/flang/lib/Evaluate/integer-value-impl.h
index 0ad4608619046..47f36f3e1d789 100644
--- a/flang/lib/Evaluate/integer-value-impl.h
+++ b/flang/lib/Evaluate/integer-value-impl.h
@@ -190,9 +190,9 @@ class IntegerValueImpl {
   bool POPPAR() const;
 
   static ValueWithOverflow ConvertSigned(
-      const IntegerValueImpl &from, int toBits);
+      int toKind, const IntegerValueImpl &from);
   static ValueWithOverflow ConvertUnsigned(
-      const IntegerValueImpl &from, int toBits);
+      int toKind, const IntegerValueImpl &from);
 
   static ValueWithOverflow Read(
       int kind, const char *&pp, int base, bool isSigned);
diff --git a/flang/lib/Evaluate/integer-value.cpp b/flang/lib/Evaluate/integer-value.cpp
index 2ec567a0423ac..735864f8bb4f7 100644
--- a/flang/lib/Evaluate/integer-value.cpp
+++ b/flang/lib/Evaluate/integer-value.cpp
@@ -71,14 +71,14 @@ IntegerValue::ValueWithOverflow IntegerValue::Read(
 }
 
 IntegerValue::ValueWithOverflow IntegerValue::ConvertUnsigned(
-    const IntegerValue &from, int toBits) {
-  auto r{IntegerValueImpl::ConvertUnsigned(from.impl(), toBits)};
+    int toKind, const IntegerValue &from) {
+  auto r{IntegerValueImpl::ConvertUnsigned(toKind, from.impl())};
   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)};
+    int toKind, const IntegerValue &from) {
+  auto r{IntegerValueImpl::ConvertSigned(toKind, from.impl())};
   return {FromImpl(std::move(r.value)), r.overflow};
 }
 
diff --git a/flang/unittests/Evaluate/IntegerValueTest.cpp b/flang/unittests/Evaluate/IntegerValueTest.cpp
index 59b063dfa1686..edaa9f5952f97 100644
--- a/flang/unittests/Evaluate/IntegerValueTest.cpp
+++ b/flang/unittests/Evaluate/IntegerValueTest.cpp
@@ -2062,15 +2062,17 @@ TEST_P(IntegerValueKindPair, ConvertUnsigned) {
   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)};
+  auto ones{IntegerValue::ConvertUnsigned(to, IntegerValue{from, -1})};
   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)};
+  auto exact{IntegerValue::ConvertUnsigned(to, IntegerValue{from, 0x34})};
   EXPECT_FALSE(exact.overflow);
   EXPECT_EQ(IntegerValue(to, 0x34), exact.value);
-  auto zero{IntegerValue::ConvertUnsigned(IntegerValue::Zero(from), toBits)};
+
+  auto zero{IntegerValue::ConvertUnsigned(to, IntegerValue::Zero(from))};
   EXPECT_FALSE(zero.overflow);
   EXPECT_TRUE(zero.value.IsZero());
 }
@@ -2078,18 +2080,20 @@ TEST_P(IntegerValueKindPair, ConvertUnsigned) {
 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)};
+  auto ones{IntegerValue::ConvertSigned(to, IntegerValue{from, -1})};
   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)};
+  auto huge{IntegerValue::ConvertSigned(to, IntegerValue::HUGE(from))};
   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)};
+  auto exact{IntegerValue::ConvertSigned(to, IntegerValue{from, -56})};
   EXPECT_FALSE(exact.overflow);
   EXPECT_EQ(IntegerValue(to, -56), exact.value);
 }
@@ -2099,6 +2103,7 @@ TEST_P(IntegerValueKindPair, MixedKindOperandsAreCoerced) {
   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());
@@ -2106,6 +2111,7 @@ TEST_P(IntegerValueKindPair, MixedKindOperandsAreCoerced) {
   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{}));
 }

>From 83ac049911444eeb400e53c07979fbb2f352f881 Mon Sep 17 00:00:00 2001
From: Michael Kruse <llvm-project at meinersbur.de>
Date: Mon, 24 Aug 2026 13:01:40 +0200
Subject: [PATCH 3/3] clang-cl build fix

---
 flang/include/flang/Common/uint128.h | 87 +++++++++++-----------------
 1 file changed, 33 insertions(+), 54 deletions(-)

diff --git a/flang/include/flang/Common/uint128.h b/flang/include/flang/Common/uint128.h
index 955e2999f19c6..13b61310bed48 100644
--- a/flang/include/flang/Common/uint128.h
+++ b/flang/include/flang/Common/uint128.h
@@ -335,36 +335,9 @@ template <> class numeric_limits<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> {
@@ -374,26 +347,6 @@ template <> class numeric_limits<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};
@@ -401,14 +354,40 @@ template <> class numeric_limits<Fortran::common::SignedInt128> {
   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{}; }
 };
 
+#if defined(__SIZEOF_INT128__) && defined(_MSVC_STL_VERSION)
+// clang-cl knows __int128 and will be used for (u)int128_t, but the MSVC STL
+// does not define stl::numeric_limits for it.
+
+template <> class numeric_limits<unsigned __int128> {
+public:
+  using T = unsigned __int128;
+
+  static constexpr bool is_specialized{true};
+  static constexpr bool is_signed{false};
+  static constexpr bool is_integer{true};
+
+  static constexpr T min() { return static_cast<T>(0); }
+  static constexpr T max() { return ~static_cast<T>(0); }
+};
+
+template <> class numeric_limits<__int128> {
+public:
+  using T = __int128;
+
+  static constexpr bool is_specialized{true};
+  static constexpr bool is_signed{true};
+  static constexpr bool is_integer{true};
+
+  static constexpr T min() {
+    return static_cast<T>(static_cast<unsigned __int128>(1) << 127u);
+  }
+  static constexpr T max() {
+    return static_cast<T>(~(static_cast<unsigned __int128>(1) << 127u));
+  }
+};
+#endif
+
 } // namespace std
 #endif



More information about the llvm-branch-commits mailing list