[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