[flang-commits] [flang] [Flang] KIND De-Templatization (PR #206907)

via flang-commits flang-commits at lists.llvm.org
Tue Aug 11 02:50:35 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-flang-openmp

Author: Michael Kruse (Meinersbur)

<details>
<summary>Changes</summary>

Avoid template instantiation based on a type's kind, but pass the kind as runtime value. The cross-product of template instantiations (including nested `visit` calls of methods that are already per-kind) leads to super-linear compile-time bloat.
For more details, see the RFC: https://discourse.llvm.org/t/rfc-the-cost-of-templates-when-building-flang/91197.

By its nature, this patch is large, like the renaming of a central type would be. These kind of refactoring cannot be easily split up. However, it has already been split up into the following PR stack:

 * #<!-- -->212956
 * #<!-- -->206907 (this PR)

Feel free to suggest what else could be *feasibly* split off.


### Implementation notes

 * The conversion is meant to be mostly mechanical, wherever possible. That is, the following rules were applied:

   1. If a function implementation is independent of KIND, just remove the template argument
   2. If another object is passed as argument (or exists as a field of the class) with the same KIND, get KIND from its `.kind()` method, and store it in a variable `const int kind`.
   3. Otherwise, add a `kind` argument as the *first* function argument.
   4. For class template arguments, implement a `kind()` method from another member that has the same `KIND` template parameter. This can be a `std::variant` case.
   5. If there is none, introduce a `int kind_;` member.

   `kind` as the first argument is because as a template parameter, it is written before all function arguments. But mostly I seriously confused myself with optional arguments. Having it as first function argument provides consistency.

 * Most of the time KIND was part of a `Type<CAT,KIND>` template argument. This was reduced to `Type<CAT>` with a `kind_` runtime member.

 * Some classes already had `GetKind()` methods. I canonicalized them to `kind()`

 * Predefined Types such as `SubscriptInteger` now also have a corresponding `SubscriptIntegerKind`.

 * Uses of `TypeCategory::Derived` have to use a kind of 0. In every other case it must be non-zero.

 * Some `kind()` methods, such as `ProcedureRef::kind()` only exist because the compiler requires them to exist; they are never actually called.

 * Propagation of kinds is checked for consistency with `CHECK` and `CHECK_KIND`. Consistency here means that it must have the same kind as the KIND template-parameter would have. For instance, two function arguments that originally have the same KIND template parameter, must pass `CHECK(x.kind() == y.kind())`.

 * There are reoccuring idioms such as `Expr<T>{Constant<T>{Scalar<T>{c}}}` (sometime implicit through conversions). Each of these may need a new `kind` function argument. To avoid it being repeated up to three times on the same lines, I introduced helpers such as `MakeConstantExpr` to pass it just once. Some of them like `MakeExtentExpr` already existed. Most of the time this makes the statements shorter than even before KIND-detemplatiziation.


 * A mechanical translation wasn't possible everywhere. Here are the most notable cases:

   1. `common::SearchTypes` moved to `evaluate::SearchTypes`; despite the name `SearchTypes` was in "common", but the actual types to iterate through were provided by the caller. With this PR the caller can only decide a TypeCategory, the possible kinds are known by `evaluate::SearchTypes` itself. Since it know gained domain knowledge, I moved it out of `common`.

   2. `lower::HashEvaluateExpr` uses the kind in its hash value and considers expressions with different kind to be unequal.

   3. Naively, `match.h` would also require matching the kind of an expression, but it is only called with the same kinds anyway.

   4. `ComplexPartExtractor` is dead code and doesn't even compile. It doesn't cause a build failure only because it was a template and thus the compiler skipped verifying it. With removing the template state, I had to delete this code.


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


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


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

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

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

 * While this PR is NFCI (No Functional Change Intended), it includes some AI-generated regression tests, that it found to not yet be covered during the creation of this PR. It also includes #<!-- -->213573 and #<!-- -->208760. All tests should pass without this PR as well.



### Followup-Work

 * Cleanup: Some purely mechanical changes allow a larger cleanup. For instance, now many `evaluate::SearchTypes` only iterate over a single TypeCategory, i.e. the entire mechanism becomes superfluous. If left them to not make this PR larger than it already is.

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

 * The added `CHECK` and check `CHECK_KIND` are mostly used to verify the that the runtime kind matches the original KIND template parameter. With further changes, this invariant may not need to be upheld anymore.

 * Some `kind` arguments are only needed to `CHECK` or to propagate it correctly. It may be possible to remove them again, such as for `Constant<T>` which could get the kind from the scalar passed to it. In this case, the scalar could be in monostate, such that it is not yet a reliable source for kind.

 * Some type declaration became pointless, such as `Int1` and `Int8`, which now are the same type.

 * `Type<CAT>` could be replaced globally with just `TypeCategory` when used as a template parameter. When instantiated, it acts like `DynamicType`, but with static `TypeCatergory`.

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



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


---

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


114 Files Affected:

- (modified) flang/CMakeLists.txt (+1) 
- (modified) flang/include/flang/Common/template.h (-32) 
- (modified) flang/include/flang/Evaluate/call.h (+21-4) 
- (added) flang/include/flang/Evaluate/character-value.h (+196) 
- (modified) flang/include/flang/Evaluate/characteristics.h (+1-2) 
- (modified) flang/include/flang/Evaluate/common.h (+7) 
- (added) flang/include/flang/Evaluate/complex-value.h (+166) 
- (removed) flang/include/flang/Evaluate/complex.h (-114) 
- (modified) flang/include/flang/Evaluate/constant.h (+92-16) 
- (modified) flang/include/flang/Evaluate/expression.h (+287-123) 
- (modified) flang/include/flang/Evaluate/fold-designator.h (+5-5) 
- (modified) flang/include/flang/Evaluate/fold.h (+19-10) 
- (modified) flang/include/flang/Evaluate/initial-image.h (+29-14) 
- (added) flang/include/flang/Evaluate/integer-value.h (+318) 
- (added) flang/include/flang/Evaluate/logical-value.h (+153) 
- (removed) flang/include/flang/Evaluate/logical.h (-110) 
- (modified) flang/include/flang/Evaluate/match.h (+7-10) 
- (added) flang/include/flang/Evaluate/object-sizes.h (+81) 
- (added) flang/include/flang/Evaluate/real-value.h (+237) 
- (modified) flang/include/flang/Evaluate/rewrite.h (+10-9) 
- (modified) flang/include/flang/Evaluate/shape.h (+21-1) 
- (modified) flang/include/flang/Evaluate/static-data.h (+1) 
- (modified) flang/include/flang/Evaluate/tools.h (+133-101) 
- (modified) flang/include/flang/Evaluate/type.h (+157-104) 
- (modified) flang/include/flang/Evaluate/variable.h (+48-4) 
- (modified) flang/include/flang/Lower/DirectivesCommon.h (+8-11) 
- (modified) flang/include/flang/Lower/Mangler.h (+10-9) 
- (modified) flang/include/flang/Lower/Support/Utils.h (+9-7) 
- (modified) flang/include/flang/Semantics/dump-expr.h (+2-4) 
- (modified) flang/include/flang/Semantics/scope.h (+1-2) 
- (modified) flang/include/flang/Semantics/type.h (+2) 
- (modified) flang/lib/Evaluate/CMakeLists.txt (+13-4) 
- (modified) flang/lib/Evaluate/call.cpp (+2-1) 
- (added) flang/lib/Evaluate/character-value-impl.cpp (+577) 
- (added) flang/lib/Evaluate/character-value-impl.h (+229) 
- (added) flang/lib/Evaluate/character-value.cpp (+215) 
- (modified) flang/lib/Evaluate/character.h (+28-12) 
- (modified) flang/lib/Evaluate/characteristics.cpp (+4-4) 
- (modified) flang/lib/Evaluate/check-expression.cpp (+14-16) 
- (modified) flang/lib/Evaluate/common.cpp (+6) 
- (added) flang/lib/Evaluate/complex-value.cpp (+182) 
- (removed) flang/lib/Evaluate/complex.cpp (-136) 
- (modified) flang/lib/Evaluate/constant.cpp (+48-48) 
- (modified) flang/lib/Evaluate/expression.cpp (+67-40) 
- (modified) flang/lib/Evaluate/fold-character.cpp (+42-41) 
- (modified) flang/lib/Evaluate/fold-complex.cpp (+18-19) 
- (modified) flang/lib/Evaluate/fold-designator.cpp (+15-11) 
- (modified) flang/lib/Evaluate/fold-implementation.h (+350-243) 
- (modified) flang/lib/Evaluate/fold-integer.cpp (+330-280) 
- (modified) flang/lib/Evaluate/fold-logical.cpp (+210-348) 
- (modified) flang/lib/Evaluate/fold-matmul.h (+6-5) 
- (modified) flang/lib/Evaluate/fold-real.cpp (+94-75) 
- (modified) flang/lib/Evaluate/fold-reduction.cpp (+3-2) 
- (modified) flang/lib/Evaluate/fold-reduction.h (+55-44) 
- (modified) flang/lib/Evaluate/fold.cpp (+5-3) 
- (modified) flang/lib/Evaluate/formatting.cpp (+30-41) 
- (modified) flang/lib/Evaluate/host.h (+70-31) 
- (modified) flang/lib/Evaluate/initial-image.cpp (+43-41) 
- (modified) flang/lib/Evaluate/int-power.h (+7-3) 
- (added) flang/lib/Evaluate/integer-value-impl.cpp (+583) 
- (added) flang/lib/Evaluate/integer-value-impl.h (+308) 
- (added) flang/lib/Evaluate/integer-value.cpp (+304) 
- (modified) flang/lib/Evaluate/intrinsics-library.cpp (+11-5) 
- (added) flang/lib/Evaluate/logical-value.cpp (+25) 
- (removed) flang/lib/Evaluate/logical.cpp (-17) 
- (added) flang/lib/Evaluate/real-value-impl.cpp (+538) 
- (added) flang/lib/Evaluate/real-value-impl.h (+266) 
- (added) flang/lib/Evaluate/real-value.cpp (+264) 
- (modified) flang/lib/Evaluate/shape.cpp (+49-45) 
- (modified) flang/lib/Evaluate/static-data.cpp (+6) 
- (modified) flang/lib/Evaluate/target.cpp (+19-19) 
- (modified) flang/lib/Evaluate/tools.cpp (+170-201) 
- (modified) flang/lib/Evaluate/type.cpp (+12-14) 
- (modified) flang/lib/Evaluate/variable.cpp (+19-20) 
- (modified) flang/lib/Lower/Bridge.cpp (+1-2) 
- (modified) flang/lib/Lower/CallInterface.cpp (+3-2) 
- (modified) flang/lib/Lower/ConvertArrayConstructor.cpp (+19-17) 
- (modified) flang/lib/Lower/ConvertConstant.cpp (+97-90) 
- (modified) flang/lib/Lower/ConvertExprToHLFIR.cpp (+108-103) 
- (modified) flang/lib/Lower/ConvertType.cpp (+10-30) 
- (modified) flang/lib/Lower/OpenMP/OpenMP.cpp (+2-2) 
- (modified) flang/lib/Lower/Support/Utils.cpp (+120-111) 
- (modified) flang/lib/Semantics/check-call.cpp (+1-1) 
- (modified) flang/lib/Semantics/check-case.cpp (+22-18) 
- (modified) flang/lib/Semantics/check-coarray.cpp (+1-1) 
- (modified) flang/lib/Semantics/check-data.cpp (+1-1) 
- (modified) flang/lib/Semantics/check-io.h (+5-2) 
- (modified) flang/lib/Semantics/check-omp-atomic.cpp (+15-14) 
- (modified) flang/lib/Semantics/check-omp-structure.cpp (+1-1) 
- (modified) flang/lib/Semantics/data-to-inits.cpp (+2-2) 
- (modified) flang/lib/Semantics/dump-expr.cpp (+2-7) 
- (modified) flang/lib/Semantics/expression.cpp (+88-85) 
- (modified) flang/lib/Semantics/openmp-utils.cpp (+2-2) 
- (modified) flang/lib/Semantics/pointer-assignment.cpp (+2-2) 
- (modified) flang/lib/Semantics/resolve-names-utils.cpp (+3-3) 
- (modified) flang/lib/Semantics/resolve-names.cpp (+22-17) 
- (modified) flang/lib/Semantics/runtime-type-info.cpp (+44-42) 
- (modified) flang/lib/Semantics/scope.cpp (+5-4) 
- (modified) flang/lib/Semantics/semantics.cpp (+2-2) 
- (modified) flang/lib/Semantics/type.cpp (+11-9) 
- (modified) flang/test/Evaluate/fold-ibits.f90 (+29) 
- (added) flang/test/Evaluate/fold-real-storage-size.f90 (+24) 
- (added) flang/test/Evaluate/fold-real10-storage-size.f90 (+42) 
- (added) flang/test/Evaluate/fold-transfer-partial.f90 (+71) 
- (added) flang/test/Lower/constant-literal-kinds.f90 (+63) 
- (modified) flang/tools/CMakeLists.txt (+1) 
- (added) flang/tools/object-size-probe/CMakeLists.txt (+42) 
- (added) flang/tools/object-size-probe/object-size-probe.cpp (+99) 
- (modified) flang/unittests/CMakeLists.txt (+8) 
- (modified) flang/unittests/Evaluate/expression.cpp (+14-9) 
- (modified) flang/unittests/Evaluate/folding.cpp (+18-9) 
- (modified) flang/unittests/Evaluate/intrinsics.cpp (+107-95) 
- (modified) flang/unittests/Evaluate/logical.cpp (+28-29) 
- (modified) flang/unittests/Evaluate/real.cpp (+9-8) 


``````````diff
diff --git a/flang/CMakeLists.txt b/flang/CMakeLists.txt
index 0c5a690f8712f..5a3b7c1d378be 100644
--- a/flang/CMakeLists.txt
+++ b/flang/CMakeLists.txt
@@ -301,6 +301,7 @@ endif()
 set(LLVM_BUILD_TOOLS ON)
 
 include_directories(BEFORE
+  ${FLANG_BINARY_DIR}/include/object-sizes/$<CONFIG>
   ${FLANG_BINARY_DIR}/include
   ${FLANG_SOURCE_DIR}/include)
 
diff --git a/flang/include/flang/Common/template.h b/flang/include/flang/Common/template.h
index 6501994133759..cf7ddd02cbfa5 100644
--- a/flang/include/flang/Common/template.h
+++ b/flang/include/flang/Common/template.h
@@ -289,37 +289,5 @@ std::optional<R> MapOptional(R (*f)(A &&...), std::optional<A> &&...x) {
   return MapOptional(std::function<R(A && ...)>{f}, std::move(x)...);
 }
 
-// Given a VISITOR class of the general form
-//   struct VISITOR {
-//     using Result = ...;
-//     using Types = std::tuple<...>;
-//     template<typename T> Result Test() { ... }
-//   };
-// SearchTypes will traverse the element types in the tuple in order
-// and invoke VISITOR::Test<T>() on each until it returns a value that
-// casts to true.  If no invocation of Test succeeds, SearchTypes will
-// return a default value.
-template <std::size_t J, typename VISITOR>
-common::IfNoLvalue<typename VISITOR::Result, VISITOR> SearchTypesHelper(
-    VISITOR &&visitor, typename VISITOR::Result &&defaultResult) {
-  using Tuple = typename VISITOR::Types;
-  if constexpr (J < std::tuple_size_v<Tuple>) {
-    if (auto result{visitor.template Test<std::tuple_element_t<J, Tuple>>()}) {
-      return result;
-    }
-    return SearchTypesHelper<J + 1, VISITOR>(
-        std::move(visitor), std::move(defaultResult));
-  } else {
-    return std::move(defaultResult);
-  }
-}
-
-template <typename VISITOR>
-common::IfNoLvalue<typename VISITOR::Result, VISITOR> SearchTypes(
-    VISITOR &&visitor,
-    typename VISITOR::Result defaultResult = typename VISITOR::Result{}) {
-  return SearchTypesHelper<0, VISITOR>(
-      std::move(visitor), std::move(defaultResult));
-}
 } // namespace Fortran::common
 #endif // FORTRAN_COMMON_TEMPLATE_H_
diff --git a/flang/include/flang/Evaluate/call.h b/flang/include/flang/Evaluate/call.h
index f04ec6c373c88..5001fbc85920e 100644
--- a/flang/include/flang/Evaluate/call.h
+++ b/flang/include/flang/Evaluate/call.h
@@ -296,6 +296,11 @@ struct SpecificIntrinsic {
 };
 
 struct ProcedureDesignator {
+  static int kind() {
+    llvm_unreachable("This class has no kind");
+    return 0;
+  }
+
   EVALUATE_UNION_CLASS_BOILERPLATE(ProcedureDesignator)
   explicit ProcedureDesignator(SpecificIntrinsic &&i) : u{std::move(i)} {}
   explicit ProcedureDesignator(const Symbol &n) : u{n} {}
@@ -333,6 +338,11 @@ using Chevrons = std::vector<Expr<SomeType>>;
 
 class ProcedureRef {
 public:
+  static int kind() {
+    llvm_unreachable("This class has no kind");
+    return 0;
+  }
+
   CLASS_BOILERPLATE(ProcedureRef)
   ProcedureRef(ProcedureDesignator &&p, ActualArguments &&a,
       bool hasAlternateReturns = false)
@@ -393,15 +403,19 @@ class ProcedureRef {
 
 template <typename A> class FunctionRef : public ProcedureRef {
 public:
+  constexpr int kind() const { return kind_; }
+
   using Result = A;
   CLASS_BOILERPLATE(FunctionRef)
-  explicit FunctionRef(ProcedureRef &&pr) : ProcedureRef{std::move(pr)} {}
-  FunctionRef(ProcedureDesignator &&p, ActualArguments &&a)
-      : ProcedureRef{std::move(p), std::move(a)} {}
+
+  explicit FunctionRef(int kind, ProcedureRef &&pr)
+      : ProcedureRef{std::move(pr)}, kind_{kind} {}
+  FunctionRef(int kind, ProcedureDesignator &&p, ActualArguments &&a)
+      : ProcedureRef{std::move(p), std::move(a)}, kind_{kind} {}
 
   std::optional<DynamicType> GetType() const {
     if constexpr (IsLengthlessIntrinsicType<A>) {
-      return A::GetType();
+      return DynamicType{A::category, kind_};
     } else if (auto type{proc_.GetType()}) {
       // TODO: Non constant explicit length parameters of PDTs result should
       // likely be dropped too. This is not as easy as for characters since some
@@ -413,6 +427,9 @@ template <typename A> class FunctionRef : public ProcedureRef {
       return std::nullopt;
     }
   }
+
+private:
+  int kind_;
 };
 } // namespace Fortran::evaluate
 #endif // FORTRAN_EVALUATE_CALL_H_
diff --git a/flang/include/flang/Evaluate/character-value.h b/flang/include/flang/Evaluate/character-value.h
new file mode 100644
index 0000000000000..aff74cf8a5cb1
--- /dev/null
+++ b/flang/include/flang/Evaluate/character-value.h
@@ -0,0 +1,196 @@
+//===-- 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_CHAR_VALUE_H_
+#define FORTRAN_EVALUATE_CHAR_VALUE_H_
+
+#include "flang/Evaluate/common.h"
+#include "flang/Evaluate/object-sizes.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:
+  /// 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();
+
+  // rule-of-five
+  ~CharacterValue();
+  CharacterValue(const CharacterValue &);
+  CharacterValue(CharacterValue &&);
+  CharacterValue &operator=(const CharacterValue &);
+  CharacterValue &operator=(CharacterValue &&);
+
+  // ctors
+  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);
+
+#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() * kind(); }
+
+  // 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;
+
+  // 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()]; }
+
+  void StoreRawBytes(void *dst, size_t size, bool *changed = nullptr) const;
+
+  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");
+    }
+  }
+
+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
+#endif // FORTRAN_EVALUATE_CHAR_VALUE_H_
diff --git a/flang/include/flang/Evaluate/characteristics.h b/flang/include/flang/Evaluate/characteristics.h
index 1ba0bf693c763..492a128ab10f1 100644
--- a/flang/include/flang/Evaluate/characteristics.h
+++ b/flang/include/flang/Evaluate/characteristics.h
@@ -118,9 +118,8 @@ class TypeAndShape {
   }
 
   // Specialization for character designators
-  template <int KIND>
   static std::optional<TypeAndShape> Characterize(
-      const Designator<Type<TypeCategory::Character, KIND>> &x,
+      const Designator<Type<TypeCategory::Character>> &x,
       FoldingContext &context, bool invariantOnly = true) {
     const auto *symbol{UnwrapWholeSymbolOrComponentDataRef(x)};
     if (symbol && !symbol->owner().IsDerivedType()) { // Whole variable
diff --git a/flang/include/flang/Evaluate/common.h b/flang/include/flang/Evaluate/common.h
index 6adf395442edf..7368a424b46b1 100644
--- a/flang/include/flang/Evaluate/common.h
+++ b/flang/include/flang/Evaluate/common.h
@@ -34,6 +34,10 @@ namespace Fortran::evaluate {
 class IntrinsicProcTable;
 class TargetCharacteristics;
 
+namespace value {
+class CharacterValue;
+}
+
 using common::ConstantSubscript;
 using common::RealFlag;
 using common::RealFlags;
@@ -74,6 +78,9 @@ static constexpr Ordering Compare(
   }
 }
 
+Ordering Compare(
+    const value::CharacterValue &x, const value::CharacterValue &y);
+
 static constexpr Ordering Reverse(Ordering ordering) {
   if (ordering == Ordering::Less) {
     return Ordering::Greater;
diff --git a/flang/include/flang/Evaluate/complex-value.h b/flang/include/flang/Evaluate/complex-value.h
new file mode 100644
index 0000000000000..9fc1b7eeb8fe6
--- /dev/null
+++ b/flang/include/flang/Evaluate/complex-value.h
@@ -0,0 +1,166 @@
+//===-- 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"
+
+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};
+  }
+
+#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
+#endif // FORTRAN_EVALUATE_COMPLEX_VALUE_H_
diff --git a/flang/include/flang/Evaluate/complex.h b/flang/include/flang/Evaluate/complex.h
deleted file mode 100644
index 9781db9a25a64..0000000000000
--- a/flang/include/flang/Evaluate/complex.h
+++ /dev/null
@@ -1,114 +0,0 @@
-//===-- include/flang/Evaluate/complex.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_H_
-#define FORTRAN_EVALUATE_COMPLEX_H_
-
-#include "formatting.h"
-#include "real.h"
-#include <string>
-
-namespace llvm {
-class raw_ostream;
-}
-
-namespace Fortran::evaluate::value {
-
-template <typename REAL_TYPE> class Complex {
-public:
-  using Part = REAL_TYPE;
-  static constexpr int bits{2 * Part::bits};
-
-  constexpr Complex() {} // (+0.0, +0.0)
-  constexpr Complex(const Complex &) = default;
-  constexpr Complex(const Part &r, const Part &i) : re_{r}, im_{i} {}
-  explicit constexpr Complex(const Part &r) : re_{r} {}
-  constexpr Complex &operator=(const Complex &) = default;
-  constexpr Complex &operator=(Complex &&) = default;
-
-  constexpr bool operator==(const Complex &that) const {
-    return re_ == that.re_ && im_ == that.im_;
-  }
-
-  constexpr const Part &REAL() const { return re_; }
-  constexpr const Part &AIMAG() const { return im_; }
-  constexpr Complex CONJG() const { return {re_, im_.Negate()}; }
-  constexpr Complex Negate() const { return {re_.Negate(), im_.N...
[truncated]

``````````

</details>


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


More information about the flang-commits mailing list