Skip to content

Refactor/cxx20 modernization - #141

Merged
ajaypanyala merged 22 commits into
NWChemEx:mainfrom
abagusetty:refactor/cxx20-modernization
Jul 5, 2026
Merged

Refactor/cxx20 modernization#141
ajaypanyala merged 22 commits into
NWChemEx:mainfrom
abagusetty:refactor/cxx20-modernization

Conversation

@abagusetty

Copy link
Copy Markdown
Contributor

Is this pull request associated with an issue(s)?
N/A

Description
Adds C++20 feature support with modern CXX practices

TODOs
N/A

abagusetty and others added 21 commits March 25, 2026 10:31
…types, CMake

- block_span.hpp: Replace raw ptr+vector with std::mdspan (dynamic rank),
  add make_block_span factory, retain BufKind tag, add [[nodiscard]]
- strong_num.hpp: Replace 12 comparison operators with operator<=> (spaceship),
  add StrongNumeric concept, replace enable_if with requires clauses,
  add [[nodiscard]] on value(), remove #define DEBUG_STRONGNUM in favor of
  inline constexpr bool
- errors.hpp: Replace __FILE__/__LINE__ macro patchwork with std::source_location,
  add tamm_expects() inline function, retain macro wrappers for ABI compat,
  UNREACHABLE() now calls std::unreachable() (C++23 compat path)
- boundvec.hpp: Add std::copyable concept constraint, replace manual destructor
  calls with std::destroy_at, replace placement-style assignment with
  std::construct_at, add spaceship operator==/<=>
- types.hpp: Replace __cplusplus<201703L apply shim (now guaranteed C++20),
  add inline constexpr maxrank, harden hash_combine with std::bit_cast,
  add [[nodiscard]] to pure query functions
- CMakeLists.txt: Enforce cxx_std_20, add FetchContent fallback for
  kokkos/mdspan on older stdlibs (GCC<13, Clang<17)
Restores all /** @brief ... @tparam ... @PARAM ... @return */ Doxygen
blocks and inline doc-comments that were dropped during the C++20
modernization commit (18f02de).

- strong_num.hpp: restore @todo blocks, @brief/@tparam/@param/@return
  for checked_cast overloads, class-level Doxygen for StrongNum,
  @brief for value_type/NumType aliases, and /**< */ member doc
- errors.hpp: restore @brief Doxygen for NOT_IMPLEMENTED,
  NOT_ALLOWED, UNREACHABLE, EXPECTS, EXPECTS_STR macros
- boundvec.hpp: restore full @brief/@tparam/@param/@return/@pre
  Doxygen for all methods and the class itself; restore @brief
  for operator== / operator!= / operator<<
- block_span.hpp: restore /// doc-comments on all accessors
  and constructors to match original intent
- types.hpp: restore @param/@return doc-comments on to_ga_eltype
  and from_ga_eltype; restore inline comments on aliases
- lru_cache.hpp: initialize cycle_ to 0 in-class (was UB on first use);
  fix access() to return it->second instead of dereferencing the
  map iterator directly (which yields a pair, not Value)
- perm.hpp: silence signed/unsigned comparison warning in
  perm_map_apply by casting pm to size_t before comparing with
  input_vec.size(); use std::distance for iterator subtraction
  in perm_compute/perm_map_compute instead of raw pointer arithmetic
- boundvec.hpp: fix resize() EXPECTS guard — size_type is unsigned so
  sz >= 0 is a tautology and was never firing; replace with a
  meaningful upper-bound check: EXPECTS(sz <= maxsize)
- types.hpp: make make_label() thread-safe by using std::atomic<Label>
  with memory_order_relaxed for the monotonic counter
block_buffer.hpp:
- Copy ctor: initialize allocated=false before the delete guard to avoid
  reading an uninitialized member (UB on the freshly-constructed object)
- Copy assignment: fix typo re=block_buffer.indexedTensor -> re=block_buffer.re
- Move ctor: steal the allocated flag from the source and reset it to
  false so the moved-from dtor does not double-delete the buffer

block_mult_plan.hpp:
- prep_flat_plan: fix tautological comparison lhs==rhs1 && lhs==rhs1;
  second condition must be lhs==rhs2
- prep_loop_gemm_plan: fix copy-paste bug where both rhs1_pos and
  rhs2_pos searched [rhs1_labels_.begin(), rhs2_labels_.end()); each
  must search its own range
- GemmPlan: initialize num_batch_indices_=0 in-class (was uninitialized,
  causing UB in update_plan/apply on every batched GEMM)
- GemmPlan::update_plan: remove local 'int num_batches_' that shadowed
  the class member; the member is now correctly written so apply() sees
  the computed batch count
- scalar_vec_mult_update/assign: remove spurious N-element loop wrapping
  a flat_update/flat_assign call that already processes all elements;
  loop caused O(N^2) work instead of O(N)
- GeneralMultPlan: cache the three intermediate buffers as members;
  resize only when block dimensions change instead of heap-allocating on
  every apply() call (hot path in CCSD doubles iterations)
types.hpp:
- Change IndexVector from std::vector<Index> to BoundVec<Index, maxrank>
  eliminating one heap allocation per distributed block get/put/add call;
  IndexVectorHash/Equal updated to work with BoundVec
- Add stack-allocated BlockIdVec alias for local use

block_buffer.hpp:
- Replace raw new[]/delete[] + bool allocated ownership with
  std::vector<T> storage_ (owner) + std::span<T> buf_span_ (non-owning view)
- Rule-of-Zero: copy/move/dtor are all compiler-generated and correct;
  eliminates the three ownership bugs fixed in the prior commit permanently
- release_put/add and release() updated to clear storage_ directly

block_span.hpp:
- block_dims() now returns std::span<const size_t> view into a cached
  TensorVec<size_t> extents_ member instead of heap-allocating a new
  std::vector on every call; old vector-returning overload kept as
  block_dims_vec() for callers that truly need ownership
- All internal uses of block_dims() updated to use the span overload

block_mult_plan.hpp:
- GeneralMultPlan: cache permuted block dims (linter_dims_, r1inter_dims_,
  r2inter_dims_) as members; computed once in apply() only when the
  block shape changes (guarded by extent comparison), eliminating
  perm_map_apply vector allocation on every contraction block
- BlockMultPlan::apply_impl: replace per-call plan reconstruction with
  a cached std::variant<FlatBlockMultPlan,GemmPlan,TTGTPlan,GeneralMultPlan>
  member built once in the constructor; apply_impl becomes a trivial
  std::visit dispatch with zero label-vector copies at call time
- All IndexLabelVec constructor parameters changed to
  std::span<const TiledIndexLabel> to avoid copies at call sites

block_assign_plan.hpp:
- Cache the complex-type scratch buffer new_rhs_vec_ as a member;
  resize only on growth (same pattern as GeneralMultPlan buffers)
  eliminating one heap allocation per complex block assignment
block_mult_plan.hpp:

Bug 1 — GeneralMultPlan: intermediate buffers were hardcoded to
  std::vector<double>, causing type-punning / memory corruption for
  float and complex tensor types.  Changed to std::vector<std::byte>
  with byte-count sizing (nelems * sizeof(T)) so the same cached buffer
  works correctly for any element type T.

Bug 2 — GemmPlan::update_plan: bufa_/bufb_/bufc_ were stored via
  '&lhs.buf()' — taking the address of a T* rvalue returned by buf(),
  which is a dangling pointer / UB.  Changed to store the data pointer
  value directly: bufc_ = lhs.buf() (void* = T*, implicit cast).

Bug 3 — BlockMultPlan::apply() public overload (Scalar lscale/rscale)
  had an empty body {}, silently discarding every contraction invoked
  through the Scalar API.  Replaced with a forwarding call to
  apply_impl() after casting Scalar to T1 so all contraction paths
  actually execute.
Stabilize the C++20 modernization branch and fix latent bugs found while
auditing the surrounding code.

cxx20 refactor regressions:
- types.hpp: revert IndexVector to std::vector<Index> (BoundVec<Index,8>
  overflowed for index/tile lists that hold thousands of entries and lacked
  data()/erase()/range-insert/(count,value) API used at ~15 call sites)
- block_span.hpp: remove impossible std::dextents<size_t,dynamic_extent>
  (dynamic-rank mdspan is ill-formed); model blocks as ptr+extents, add
  operator[]/data()/flat_span(); block_dims() returns const vector& to match
  kernel signatures
- block_mult_plan.hpp: drop UB reinterpret_cast<BlockSpan<T1>&>; constrain
  apply() to T1==T2==T3; use Scalar::get<T1>() instead of illegal static_cast
- scalar.hpp: add type-safe Scalar::get<T>() variant extractor
- boundvec.hpp: restore (count,value) ctor, data(), reverse iterators,
  range/count insert_back; constrain iterator ctors with std::input_iterator
- CMakeLists.txt: drop unused kokkos/mdspan FetchContent

Pre-existing bugs:
- blockops_cpu.hpp: ipgen_idx off-by-one OOB read; index_permute_update(lscale)
  lbuf[i] -> lbuf[c]
- tensor_impl.hpp: spin ignore used spin_sizes[1] instead of [2]
- setop.hpp: writes/reads/accumulates(ec) missing return in else branch
- addop.hpp/setop.hpp: remove self-assign in optype ternary
- range.hpp / index_space.hpp / tiled_index_space.hpp: initialize previously
  uninitialized members (Range lo_/hi_/step_, hash_value_)
- index_space_interface.hpp: rename inverted has_duplicate() -> has_no_duplicate()
- tiled_index_space.hpp: collapse redundant map lookups (single find + if-init)
The cxx20 strong_num refactor broke widespread call sites:
- StrongNum value ctor was made explicit, breaking implicit conversions
  relied on everywhere (Proc p = GA_Nodeid(), Offset off = 0,
  Size sz = block_size(...), vector<Proc>::resize(n,1)). Make it implicit.
- operator<=> alone does not synthesize ==/!=; add a defaulted
  operator==(const NumType&) so same-type == and != work again
  (e.g. step_proc_ != Proc{1}).
- errors.hpp: EXPECTS now uses static_cast<bool>(cond) so pointer-like
  conditions with an explicit operator bool (shared_ptr/unique_ptr) still
  work, e.g. EXPECTS(distribution_).
- blockops_cpu.hpp: include tamm/scalar.hpp (uses Scalar directly).
GCC 14.3 (C++20) eagerly instantiates the defaulted special members of the
*IndexSpaceImpl classes during class definition. Those classes hold
std::vector<TiledIndexSpace> members, so the element type must be complete at
that point; with only the forward declaration from types.hpp the build failed
with "arithmetic on a pointer to an incomplete type 'tamm::TiledIndexSpace'".

Include tiled_index_space.hpp here. This header is only included by
index_space.cpp and tiled_index_space.hpp does not include it back, so no
include cycle is introduced.
Remove dead code, collapse duplication, and modernize with C++20; no public
API/ABI change (verified against exachem usage) and no UPC++ sections touched.
Net ~1050 fewer lines plus one correctness fix.

Dead code:
- delete op_cost.hpp (unused OpCostTerm/Expr/Order) and its include/CMake entry
- delete op_executor_analysis.hpp (dead duplicate of op_executor.hpp)

Op layer:
- add internal::validate_index_labels(); collapse 5 identical validate() bodies
  (mult/add/set/scan/map) into one ranges-based helper
- clone() -> std::make_shared<InjectedClassName>(*this) across all 7 op types
- flatten the 4 near-identical *Plan subclasses (mult/add/set) to a shared base
  with direct writes()/accumulates()/reads(); add virtual dtor (fixes deleting
  derived via base shared_ptr); the former local/global split was a scheduling
  no-op with no external callers
- Rule of Zero: drop redundant =default copy-ctors / hand-written specials
  (op types, LabelMap, ProcGroup) to restore implicit moves

Ranges / accessors:
- is_dense/is_sparse/is_dense_labels/has_sparse_labels -> std::ranges none_of/any_of
- std::iota (MPI-only paths) and vector::insert(count,value) for fill loops
- tindices()/Distribution::proc_grid() return const& (+ [[nodiscard]])
- reuse internal::merge_vector for label concatenations

Fixes:
- block_lambda_plan prep_flat_plan compared labels_list[1] on every iteration
  instead of [i], skipping size checks for operands >=2; rewritten with
  std::ranges::equal
- remove stray duplicate #pragma once at EOF of label_translator.hpp
@abagusetty
abagusetty marked this pull request as draft July 5, 2026 16:01
@ajaypanyala
ajaypanyala marked this pull request as ready for review July 5, 2026 16:42
@ajaypanyala
ajaypanyala merged commit 0e8afd4 into NWChemEx:main Jul 5, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants