From a0ba5ab9d0709abbd20788ef414ecc9c04f49019 Mon Sep 17 00:00:00 2001 From: AlexanderMitrofanov Date: Mon, 28 Sep 2026 18:46:03 +0200 Subject: [PATCH 1/8] Replace uBLAS storage matrices with vector-backed mdspan containers --- ChangeLog | 15 ++ README.md | 7 +- configure.ac | 18 ++ doc/Makefile.am | 2 + doc/mdspan-storage-samples.tsv | 161 +++++++++++++ doc/mdspan-storage.md | 172 ++++++++++++++ src/IntaRNA/AccessibilityFromStream.h | 4 +- src/IntaRNA/AccessibilityVrna.cpp | 6 +- src/IntaRNA/AccessibilityVrna.h | 4 +- src/IntaRNA/HelixHandlerIdxOffset.h | 1 - src/IntaRNA/HelixHandlerUnpaired.h | 6 +- src/IntaRNA/InteractionEnergyVrna.h | 4 +- src/IntaRNA/Makefile.am | 1 + src/IntaRNA/Matrix.h | 220 ++++++++++++++++++ src/IntaRNA/NussinovHandler.h | 8 +- src/IntaRNA/PredictionTrackerPairMinE.h | 4 +- src/IntaRNA/PredictionTrackerSpotProbAll.h | 4 +- src/IntaRNA/PredictorMfe2d.h | 4 +- .../PredictorMfe2dHelixBlockHeuristic.h | 1 - src/IntaRNA/PredictorMfe2dHeuristic.h | 4 +- src/IntaRNA/PredictorMfeEns2d.h | 4 +- src/IntaRNA/PredictorMfeEns2dHeuristic.h | 4 +- src/IntaRNA/PredictorMfeEns2dSeedExtension.h | 3 +- src/IntaRNA/SeedHandler.h | 1 - src/IntaRNA/SeedHandlerIdxOffset.h | 1 - src/IntaRNA/SeedHandlerMfe.h | 4 +- tests/AccessibilityBasePair_test.cpp | 1 - tests/AccessibilityVrna_test.cpp | 1 - tests/Makefile.am | 4 + tests/Matrix_test.cpp | 160 +++++++++++++ tests/NussinovHandler_test.cpp | 1 - tests/benchmark/compare-matrix-storage.py | 121 ++++++++++ 32 files changed, 909 insertions(+), 42 deletions(-) create mode 100644 doc/mdspan-storage-samples.tsv create mode 100644 doc/mdspan-storage.md create mode 100644 src/IntaRNA/Matrix.h create mode 100644 tests/Matrix_test.cpp create mode 100644 tests/benchmark/compare-matrix-storage.py diff --git a/ChangeLog b/ChangeLog index 4938b429..e85ac959 100644 --- a/ChangeLog +++ b/ChangeLog @@ -23,6 +23,9 @@ ## Technical changes and Optimizations +- replace uBLAS storage matrices with std::vector and std::mdspan, retaining + compact upper-band and triangular storage; configure checks mdspan support + - reduce temporary allocations and fix cleanup on error paths - remove redundant accessibility checks from predictor, helix and seed handling - C++23 required; build and installation checks with GCC 14 and Apple Clang @@ -51,6 +54,18 @@ ################################################################################ ################################################################################ +260928 + * IntaRNA/Matrix.h and storage aliases: + + replace uBLAS dense, upper-band and upper-triangular storage with owned + std::vector data accessed through C++23 std::mdspan + + preserve logical resize, copy/move ownership and compact storage + * configure.ac, README.md: + + probe native mdspan library support and document the toolchain requirement + * tests/Matrix_test.cpp, tests/benchmark/compare-matrix-storage.py: + + add storage regression tests and reproducible before/after measurements + * doc/mdspan-storage.md: + + record validation and performance evaluation for issue #246 + 260928 Martin Raden * IntaRNA/IndexRangeList: + listIsComplete : whether or not the list is complete (no further ranges can be added) diff --git a/README.md b/README.md index c115f384..55009e0a 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,9 @@ you with an encapsulated IntaRNA installation. If you are going to compile IntaRNA from source, ensure you meet the following dependencies: -- compiler supporting C++23 and OpenMP (GCC 14 or Apple Clang) +- compiler supporting C++23 and OpenMP, with a standard library providing + `std::mdspan` (for example GCC 16/libstdc++ or Clang 18/libc++; configure + checks the actual library support) - [boost C++ library](http://www.boost.org/) version >= 1.50.0 (ensure the following libraries are installed for development (not just runtime libraries!); or install all e.g. in Ubuntu via package `libboost-all-dev`) - libboost_regex @@ -2186,7 +2188,8 @@ processed using doxygen to generate html/pdf versions. When IntaRNA is build while `pkg-config` is present, according pkg-config information is generated and installed too. -IntaRNA's public headers require C++23. External consumers must therefore +IntaRNA's public headers require C++23, including `` support in the +standard library. External consumers must therefore select `-std=c++23` (or a newer standard) in their own build; the installed pkg-config metadata supplies IntaRNA and dependency flags but deliberately does not override the consuming project's language mode. diff --git a/configure.ac b/configure.ac index ea32bd28..f958f06b 100644 --- a/configure.ac +++ b/configure.ac @@ -87,6 +87,24 @@ sink << stream.view(); AC_MSG_ERROR([A C++23 standard library providing std::string::contains, std::string::resize_and_overwrite, and std::stringstream::view is required. Upgrade libstdc++/libc++ or select a newer C++23 toolchain.])]) CXXFLAGS=$intarna_saved_CXXFLAGS +# Require the standard C++23 mdspan API used by the owning storage matrices. +AC_MSG_CHECKING([for C++23 std::mdspan over std::vector]) +AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [[#include +#include +#ifndef __cpp_lib_mdspan +#error C++23 std::mdspan is unavailable +#endif]], + [[std::vector storage(6); +std::mdspan> matrix(storage.data(), 2, 3); +matrix[1, 2] = 42; +std::mdspan> packed(storage.data(), 6); +return packed[5] != 42;]])], + [AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no]) + AC_MSG_ERROR([A C++23 standard library providing , std::mdspan, and std::dextents is required. Select a toolchain with mdspan support (for example GCC 16/libstdc++ or Clang 18/libc++).])]) + # check if python is available AM_PATH_PYTHON([$PYTHON_REQUIRED_VERSION],, [:]) AM_CONDITIONAL([HAVE_PYTHON], [test "$PYTHON" != ":"]) diff --git a/doc/Makefile.am b/doc/Makefile.am index 322e9d93..da0f922d 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -4,6 +4,8 @@ ################################################################ EXTRA_DIST = \ + mdspan-storage.md \ + mdspan-storage-samples.tsv \ conda.txt \ doxygen.cfg \ latex-deps/adjcalc.sty \ diff --git a/doc/mdspan-storage-samples.tsv b/doc/mdspan-storage-samples.tsv new file mode 100644 index 00000000..8380476c --- /dev/null +++ b/doc/mdspan-storage-samples.tsv @@ -0,0 +1,161 @@ +case variant repetition warmup wall_s user_s system_s max_rss_kib sha256 +banded-narrow baseline 0 True 0.748509165016003 0.73 0.0 18008 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +biological-default candidate 0 True 0.053829810000024736 0.04 0.0 17196 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +seed-extension baseline 0 True 0.13006498100003228 0.12 0.0 18528 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +seed-extension candidate 0 True 0.13067681598477066 0.12 0.0 18656 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +exact-ensemble candidate 0 True 0.9819709150178824 0.97 0.0 16384 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +exact-ensemble baseline 0 True 0.9815648299991153 0.97 0.0 16640 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +banded-narrow candidate 0 True 0.7572962760168593 0.75 0.0 18264 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +triangular-base-pair candidate 0 True 2.379414976981934 2.37 0.0 14720 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +helix-block baseline 0 True 0.19673078999039717 0.18 0.0 18820 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +exact-mfe candidate 0 True 0.69025363898254 0.68 0.0 16512 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +banded-default candidate 0 True 1.0723729919991456 1.05 0.01 20364 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +dense-no-accessibility baseline 0 True 4.230289809027454 4.22 0.0 15420 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +seed-bulges candidate 0 True 1.0127681890153326 1.0 0.0 24188 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +biological-default baseline 0 True 0.053902272979030386 0.04 0.0 17068 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +exact-mfe baseline 0 True 0.6841453249799088 0.67 0.0 16384 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +seed-bulges baseline 0 True 1.0127227320044767 1.0 0.0 24316 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +dense-no-accessibility candidate 0 True 3.9657662389799953 3.95 0.0 15420 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +triangular-base-pair baseline 0 True 2.4299706220044754 2.42 0.0 14592 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +banded-default baseline 0 True 1.0790185799996834 1.06 0.0 20360 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +helix-block candidate 0 True 0.1969136840198189 0.18 0.0 18824 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +banded-default baseline 1 False 1.0722512750071473 1.06 0.0 20364 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +exact-mfe baseline 1 False 0.6942282770178281 0.68 0.0 16512 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +seed-extension baseline 1 False 0.13053329699323513 0.12 0.0 18528 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +banded-narrow baseline 1 False 0.7494599880010355 0.74 0.0 18132 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +helix-block baseline 1 False 0.19486815700656734 0.18 0.0 18696 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +exact-mfe candidate 1 False 0.7069615049986169 0.69 0.0 16508 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +dense-no-accessibility candidate 1 False 4.0176698039867915 4.0 0.0 15544 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +biological-default candidate 1 False 0.052929678990039974 0.04 0.0 17068 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +seed-extension candidate 1 False 0.13123885198729113 0.12 0.0 18528 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +seed-bulges baseline 1 False 1.019247093994636 1.0 0.01 24316 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +helix-block candidate 1 False 0.21852754900464788 0.21 0.0 18824 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +biological-default baseline 1 False 0.05390525099937804 0.04 0.0 17324 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +banded-narrow candidate 1 False 0.7592199039936531 0.75 0.0 18264 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +triangular-base-pair baseline 1 False 2.5025068250251934 2.49 0.0 14592 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +dense-no-accessibility baseline 1 False 4.105743416992482 4.09 0.0 15420 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +banded-default candidate 1 False 1.0784328089794144 1.07 0.0 20492 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +exact-ensemble baseline 1 False 0.9777506150130648 0.97 0.0 16636 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +exact-ensemble candidate 1 False 0.9771509199927095 0.96 0.0 16384 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +triangular-base-pair candidate 1 False 2.389686937996885 2.38 0.0 14592 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +seed-bulges candidate 1 False 1.0261880180041771 1.0 0.01 24576 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +seed-extension candidate 2 False 0.12970469900756143 0.12 0.0 18532 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +triangular-base-pair baseline 2 False 2.412077880988363 2.4 0.0 14592 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +biological-default candidate 2 False 0.054381732014007866 0.04 0.0 17328 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +biological-default baseline 2 False 0.05381018598563969 0.04 0.0 17200 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +dense-no-accessibility baseline 2 False 3.9219096369924955 3.9 0.01 15288 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +seed-bulges candidate 2 False 1.0077957839821465 0.99 0.01 24496 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +helix-block candidate 2 False 0.19335647198022343 0.18 0.0 18692 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +exact-mfe candidate 2 False 0.6848772919911426 0.67 0.0 16636 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +banded-default candidate 2 False 1.0675118719809689 1.05 0.0 20492 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +exact-mfe baseline 2 False 0.680244479997782 0.66 0.0 16640 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +seed-extension baseline 2 False 0.13002752800821327 0.11 0.0 18532 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +banded-narrow candidate 2 False 0.747600399015937 0.74 0.0 18264 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +helix-block baseline 2 False 0.19494313400355168 0.18 0.0 18824 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +triangular-base-pair candidate 2 False 2.3863645019882824 2.37 0.0 14720 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +dense-no-accessibility candidate 2 False 4.009611593995942 3.99 0.0 15292 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +seed-bulges baseline 2 False 1.0244588130153716 1.01 0.01 24448 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +exact-ensemble candidate 2 False 0.9934219329734333 0.98 0.0 16384 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +banded-default baseline 2 False 1.080055470985826 1.06 0.0 20492 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +exact-ensemble baseline 2 False 0.9788159620075021 0.97 0.0 16636 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +banded-narrow baseline 2 False 0.746295086981263 0.73 0.0 18004 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +seed-bulges candidate 3 False 1.0149268800159916 0.99 0.01 24448 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +exact-ensemble candidate 3 False 1.0010013820137829 0.99 0.0 16512 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +dense-no-accessibility candidate 3 False 4.0274970380123705 4.01 0.0 15284 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +banded-narrow baseline 3 False 0.7617115470056888 0.75 0.0 18136 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +biological-default baseline 3 False 0.053766514000017196 0.04 0.0 17196 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +dense-no-accessibility baseline 3 False 4.007093457999872 3.99 0.0 15416 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +banded-default candidate 3 False 1.1033082619833294 1.09 0.0 20360 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +helix-block candidate 3 False 0.1982204559899401 0.18 0.0 18692 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +seed-extension candidate 3 False 0.1319897020002827 0.12 0.0 18660 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +seed-extension baseline 3 False 0.13220640399958938 0.12 0.0 18532 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +helix-block baseline 3 False 0.1980427590024192 0.18 0.0 18820 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +banded-narrow candidate 3 False 0.7636790300020948 0.75 0.0 18008 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +banded-default baseline 3 False 1.0845191730186343 1.07 0.0 20364 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +biological-default candidate 3 False 0.05415553899365477 0.04 0.0 17200 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +triangular-base-pair candidate 3 False 2.403063821984688 2.39 0.0 14720 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +exact-ensemble baseline 3 False 0.9855942669964861 0.97 0.0 16640 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +seed-bulges baseline 3 False 1.0205752179899719 1.0 0.01 24448 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +exact-mfe candidate 3 False 0.7091608720074873 0.7 0.0 16768 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +exact-mfe baseline 3 False 0.6855379629996605 0.67 0.0 16640 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +triangular-base-pair baseline 3 False 2.430000252003083 2.42 0.0 14592 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +seed-bulges baseline 4 False 1.0145660449925344 1.0 0.01 24448 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +banded-narrow candidate 4 False 0.750193821004359 0.74 0.0 18132 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +dense-no-accessibility baseline 4 False 3.941041692014551 3.92 0.0 15416 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +exact-ensemble baseline 4 False 0.9844577139883768 0.97 0.0 16640 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +seed-extension candidate 4 False 0.13126711000222713 0.12 0.0 18400 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +dense-no-accessibility candidate 4 False 3.993995585013181 3.98 0.0 15544 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +exact-mfe baseline 4 False 0.7005224649910815 0.69 0.0 16636 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +exact-ensemble candidate 4 False 0.984857142000692 0.97 0.0 16768 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +exact-mfe candidate 4 False 0.6909903499763459 0.68 0.0 16768 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +banded-narrow baseline 4 False 0.7487436160154175 0.74 0.0 18136 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +seed-extension baseline 4 False 0.13062545499997213 0.12 0.0 18532 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +helix-block candidate 4 False 0.193796725012362 0.18 0.0 18948 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +triangular-base-pair candidate 4 False 2.3743815569905564 2.36 0.0 14720 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +triangular-base-pair baseline 4 False 2.43054360098904 2.42 0.0 14464 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +biological-default candidate 4 False 0.056888616993092 0.04 0.0 17196 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +banded-default baseline 4 False 1.0727517240156885 1.06 0.0 20488 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +helix-block baseline 4 False 0.19768703999579884 0.18 0.0 18824 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +banded-default candidate 4 False 1.0709572880004998 1.06 0.0 20616 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +biological-default baseline 4 False 0.053800850990228355 0.04 0.0 17196 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +seed-bulges candidate 4 False 1.017281427019043 1.0 0.01 24320 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +helix-block candidate 5 False 0.19911300501553342 0.18 0.0 18568 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +banded-default candidate 5 False 1.080931734002661 1.06 0.0 20488 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +banded-narrow candidate 5 False 0.7637649679963943 0.75 0.0 17880 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +dense-no-accessibility baseline 5 False 3.9499355830193963 3.94 0.0 15416 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +banded-default baseline 5 False 1.0821002619923092 1.07 0.0 20236 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +triangular-base-pair candidate 5 False 2.380466925998917 2.37 0.0 14720 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +exact-mfe candidate 5 False 0.6919861320056953 0.68 0.0 16512 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +triangular-base-pair baseline 5 False 2.435454866994405 2.42 0.0 14720 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +seed-bulges baseline 5 False 1.0204134389932733 1.0 0.01 24316 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +biological-default baseline 5 False 0.053386224986752495 0.04 0.0 17200 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +banded-narrow baseline 5 False 0.758633858000394 0.74 0.0 18004 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +seed-bulges candidate 5 False 1.0201335650053807 1.0 0.01 24320 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +dense-no-accessibility candidate 5 False 3.9980762990016956 3.98 0.0 15544 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +helix-block baseline 5 False 0.19587320199934766 0.18 0.0 18952 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +seed-extension candidate 5 False 0.12946359199122526 0.12 0.0 18660 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +exact-ensemble baseline 5 False 0.9801981329801492 0.97 0.0 16764 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +seed-extension baseline 5 False 0.12967221898725256 0.12 0.0 18400 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +exact-ensemble candidate 5 False 0.9760011530015618 0.96 0.0 16640 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +exact-mfe baseline 5 False 0.681056217988953 0.67 0.0 16508 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +biological-default candidate 5 False 0.05329809497925453 0.04 0.0 17072 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +dense-no-accessibility candidate 6 False 3.972242271003779 3.96 0.0 15412 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +banded-default baseline 6 False 1.0732750840252265 1.06 0.0 20492 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +banded-narrow baseline 6 False 0.7524369099992327 0.74 0.0 18136 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +seed-bulges candidate 6 False 1.0194505229883362 1.0 0.01 24324 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +biological-default baseline 6 False 0.07208988099591807 0.06 0.0 17200 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +exact-mfe baseline 6 False 0.6927969090174884 0.68 0.0 16512 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +exact-mfe candidate 6 False 0.7001914370048326 0.69 0.0 16508 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +banded-narrow candidate 6 False 0.7616175840084907 0.75 0.0 18008 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +biological-default candidate 6 False 0.054218976001720876 0.04 0.0 16944 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +helix-block candidate 6 False 0.19713925800169818 0.18 0.0 18696 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +seed-extension candidate 6 False 0.13204737298656255 0.12 0.0 18404 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +triangular-base-pair baseline 6 False 2.4657496720028576 2.45 0.0 14592 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +exact-ensemble baseline 6 False 0.994601194979623 0.98 0.0 16508 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +seed-bulges baseline 6 False 1.026366575009888 1.01 0.0 24192 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +seed-extension baseline 6 False 0.13164772000163794 0.12 0.0 18528 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +triangular-base-pair candidate 6 False 2.4138111770153046 2.4 0.0 14464 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +helix-block baseline 6 False 0.19486837802105583 0.18 0.0 18692 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +dense-no-accessibility baseline 6 False 3.987991597998189 3.97 0.0 15668 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +banded-default candidate 6 False 1.0942632410151418 1.08 0.0 20492 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +exact-ensemble candidate 6 False 0.9948785779997706 0.98 0.0 16512 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +exact-ensemble baseline 7 False 0.9979603519896045 0.99 0.0 16508 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +biological-default candidate 7 False 0.05436836200533435 0.04 0.0 17072 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +seed-extension baseline 7 False 0.1316904489940498 0.12 0.0 18660 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +exact-ensemble candidate 7 False 0.9869280020066071 0.97 0.0 16640 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +seed-bulges candidate 7 False 1.0115285690117162 0.99 0.01 24320 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +helix-block candidate 7 False 0.19749678400694393 0.18 0.01 18824 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +banded-narrow baseline 7 False 0.7603128549817484 0.75 0.0 18004 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +seed-extension candidate 7 False 0.13174183800583705 0.12 0.0 18528 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +exact-mfe baseline 7 False 0.683925209013978 0.67 0.0 16768 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +biological-default baseline 7 False 0.05364844799623825 0.04 0.0 17324 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +seed-bulges baseline 7 False 1.0122875639935955 0.99 0.0 24316 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +helix-block baseline 7 False 0.19527189401560463 0.18 0.0 18952 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +triangular-base-pair baseline 7 False 2.429712784010917 2.42 0.0 14464 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +dense-no-accessibility candidate 7 False 3.952714154002024 3.94 0.0 15416 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +dense-no-accessibility baseline 7 False 3.9070906529959757 3.89 0.0 15544 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +banded-default candidate 7 False 1.0620757259894162 1.05 0.0 20616 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +exact-mfe candidate 7 False 0.6853935719991568 0.67 0.0 16640 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +banded-narrow candidate 7 False 0.7484049310151022 0.73 0.0 18004 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +triangular-base-pair candidate 7 False 2.4042081009829417 2.39 0.0 14720 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +banded-default baseline 7 False 1.0850039450160693 1.07 0.0 20360 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 diff --git a/doc/mdspan-storage.md b/doc/mdspan-storage.md new file mode 100644 index 00000000..75f3a0e2 --- /dev/null +++ b/doc/mdspan-storage.md @@ -0,0 +1,172 @@ +# Vector/mdspan storage evaluation (issue #246) + +The migration preserves scientific output and compact storage. In the measured +single-host experiment, performance is effectively neutral: median wall-time +changes range from 1.7% faster to 1.2% slower, with similar peak memory use. +There is no substantial performance gain to justify the stricter toolchain +requirement by itself. Treat this as a storage modernization; retain the +measurements below when deciding whether to adopt the new build requirement. + +## Scope and representation + +All production `boost::numeric::ublas` storage aliases have been replaced by +owning containers in `IntaRNA/Matrix.h`. The dynamic programming recurrences, +traversal order, energy types and floating-point operations are unchanged. +`boost::multi_array` seed/helix recurrence tensors are outside this issue's +uBLAS scope and remain unchanged. + +| Container | Logical shape | Stored elements | mdspan access | +| --- | --- | --- | --- | +| `Matrix` | rows by columns | rows * columns | 2D row-major `[i,j]` | +| `UpperBandedMatrix` | upper band including diagonal | rows * min(columns, upper+1) | 2D row-major `[i,j-i]` | +| `UpperTriangularMatrix` | square upper triangle | n * (n+1) / 2 | 1D packed offset | + +The triangular container uses a one-dimensional view because its packed row +lengths vary; a standard affine 2D layout would require quadratic rectangular +storage or a custom mapping. The band contains row-end padding, as did uBLAS, +and clamps superdiagonals that exceed the number of columns. In particular, +10,000 rows with ten superdiagonals take 110,000 elements, not 100 million. +The accessibility constructors still request their existing extra +superdiagonals for dangling-end probabilities. + +Each vector owns its data. A short-lived mdspan is created at access time, +so there is no view pointer to repair after copying, moving or resizing. +Default resize preserves the overlapping logical cells; preserving resize +initializes new cells to zero/default values. Callers of non-preserving resize +must overwrite cells before reading. Const access outside a triangle/band +returns zero. Mutable access must address a stored cell, checked by assertions +in debug builds. Allocation-size overflow is rejected before allocating. +The intentionally small interface supports the operations used by IntaRNA, +not the full uBLAS expression/iterator interface. Triangular matrices must be +square and banded matrices must have lower bandwidth zero. + +## Build requirements + +Configure compiles actual mutable 2D and const 1D `std::mdspan` accesses over +vector storage. An unsupported standard library fails with an actionable error, +including when its compiler accepts `-std=c++23`. Public-header consumers also +need `` and C++23. + +Native support is available in [GCC 16/libstdc++](https://gcc.gnu.org/onlinedocs/libstdc++/manual/status.html) +and [LLVM 18/libc++](https://releases.llvm.org/18.1.8/projects/libcxx/docs/Status/Cxx23.html). +The local validation below uses GCC; it does not claim a Clang/macOS build. +Boost remains a dependency for other project components and for the independent +reference containers in the storage regression tests. + +## Reproduction + +Build the parent revision `c5823e0` and this change in separate directories +with the same compiler, dependencies and configure flags. For each checkout: + +```sh +bash autotools-init.sh +./configure CXX=/path/to/g++ CC=/path/to/gcc \ + --with-boost=/path/to/deps --with-boost-libdir=/path/to/deps/lib \ + --with-vrna=/path/to/deps \ + CPPFLAGS=-I/path/to/deps/include \ + LDFLAGS='-L/path/to/deps/lib -Wl,-rpath,/path/to/deps/lib' +make -j4 +make -j4 check +``` + +Then, with no build or other benchmark running: + +```sh +python3 tests/benchmark/compare-matrix-storage.py \ + /path/to/parent/src/bin/IntaRNA /path/to/candidate/src/bin/IntaRNA \ + /path/to/new-results-directory --repetitions=7 --cpu=2 +``` + +The script generates seeded synthetic inputs, copies the repository's biological +fhlA/OxyS inputs, randomizes execution order, excludes one warmup per binary and +case, and retains stdout, stderr and each timing sample. It compares stdout +bytes for every execution, failing immediately on a mismatch or process error. +It records binary/input hashes, exact arguments, versions, affinity and controlled +environment. Wall time includes startup, loading and output; peak RSS comes from +GNU time. The test cases cover accessibility bands, dense predictors, seed +bulges, helix blocks, exact MFE, exact ensemble, seed extension and Nussinov +triangular storage. This is a bounded, single-host performance experiment. + +## Validation on 2026-09-28 + +- Parent release: 4,341 assertions in 37 API cases; all 20 CLI golden cases pass. +- Candidate release and debug (`--enable-debug`): 33,264 assertions in 41 API + cases; all 20 CLI golden cases pass in each build. +- Standalone storage tests: 28,923 assertions in four cases, passing with + AddressSanitizer and UndefinedBehaviorSanitizer (`-O0 -g1 + -fsanitize=address,undefined -fno-omit-frame-pointer`). LeakSanitizer was + disabled because the execution sandbox does not support its process tracing; + no claim of leak-sanitizer coverage is made. +- Native GCC 16.2 configure probe passes. A GCC 16.2 configure run with an + intentionally unavailable `` fails at the new probe with the expected + diagnostic, after the other C++23 checks pass. +- `make install` installs `Matrix.h`; a separate C++23 consumer including the + installed accessibility, Nussinov and seed-extension headers compiles and runs. + +The storage cases cover empty and rectangular shapes, logical preservation on +resize, zero-initialization of new cells, const structural zeros, the last +stored superdiagonal, packed triangular boundaries, non-scalar cells, +copy/move/swap independence, non-preserving resize, and allocation-size overflow. +Reference values come from uBLAS. New uBLAS primitive cells must be initialized +explicitly before comparison; its banded preserving resize is also unsuitable +as an oracle for some rectangular/empty transitions, so that reference is +rebuilt explicitly from overlapping cells. + +## Same-host performance results + +Baseline: upstream `c5823e0`. Candidate: the storage migration in this change. + +Both are optimized builds using conda-forge GCC/libstdc++ 16.2.0, Boost 1.85.0 +and ViennaRNA 2.7.2 on Linux x86-64, AMD Ryzen 5 7530U. Configure adds +`-O3 -fno-strict-aliasing`; OpenMP is enabled with `--threads=1`. +Runs are pinned to CPU 2 with `LC_ALL=C`, `OPENBLAS_NUM_THREADS=1`, +`OMP_DYNAMIC=FALSE`. No build or sanitizer runs overlap these timings. + +One warmup plus seven measured executions per binary/case: 160 executions +total, all stdout results byte-identical within and between variants. +The table shows median wall seconds and median peak RSS in MiB. A negative +time change means faster. RSS is a process-level measurement and includes +the executable, shared libraries and ViennaRNA allocations. + +| Workload | uBLAS time (s) | mdspan time (s) | Change | uBLAS / mdspan RSS (MiB) | +| --- | ---: | ---: | ---: | ---: | +| biological-default | 0.0538 | 0.0542 | +0.8% | 16.80 / 16.67 | +| banded-default | 1.0801 | 1.0784 | -0.2% | 19.89 / 20.01 | +| banded-narrow | 0.7524 | 0.7592 | +0.9% | 17.71 / 17.59 | +| dense-no-accessibility | 3.9499 | 3.9981 | +1.2% | 15.05 / 15.05 | +| seed-bulges | 1.0204 | 1.0173 | -0.3% | 23.75 / 23.75 | +| helix-block | 0.1953 | 0.1975 | +1.1% | 18.38 / 18.26 | +| exact-mfe | 0.6855 | 0.6920 | +0.9% | 16.25 / 16.25 | +| exact-ensemble | 0.9845 | 0.9869 | +0.3% | 16.25 / 16.12 | +| seed-extension | 0.1306 | 0.1313 | +0.5% | 18.10 / 18.09 | +| triangular-base-pair | 2.4305 | 2.3897 | -1.7% | 14.25 / 14.38 | + +Measured wall-time ranges (min–max across the seven measured samples): + +| Workload | uBLAS (s) | mdspan (s) | +| --- | ---: | ---: | +| biological-default | 0.0534–0.0721 | 0.0529–0.0569 | +| banded-default | 1.0723–1.0850 | 1.0621–1.1033 | +| banded-narrow | 0.7463–0.7617 | 0.7476–0.7638 | +| dense-no-accessibility | 3.9071–4.1057 | 3.9527–4.0275 | +| seed-bulges | 1.0123–1.0264 | 1.0078–1.0262 | +| helix-block | 0.1949–0.1980 | 0.1934–0.2185 | +| exact-mfe | 0.6802–0.7005 | 0.6849–0.7092 | +| exact-ensemble | 0.9778–0.9980 | 0.9760–1.0010 | +| seed-extension | 0.1297–0.1322 | 0.1295–0.1320 | +| triangular-base-pair | 2.4121–2.5025 | 2.3744–2.4138 | + +All samples (including explicitly marked warmups) and output hashes are in +[mdspan-storage-samples.tsv](mdspan-storage-samples.tsv). Full invocation +arguments and deterministic input generation are in the benchmark script. + +Binary SHA-256 values: + +- baseline: `e82b2031d59d2a5c07620cb7361ca1cb42eb5d0c052752762ee131c35d2ef0bb` +- candidate: `76799b89844581511e1010b72af10bcf2e28c47fe1111c21171733491e66e1c8` + +These measurements describe the selected workloads on one host and one +toolchain. They do not establish a universal speedup. Short biological +runs include substantial startup overhead; differences near the observed +spread should be treated as inconclusive. Clang/macOS and other CPU +architectures still need their own measurements. diff --git a/src/IntaRNA/AccessibilityFromStream.h b/src/IntaRNA/AccessibilityFromStream.h index f71a7353..6de865a7 100644 --- a/src/IntaRNA/AccessibilityFromStream.h +++ b/src/IntaRNA/AccessibilityFromStream.h @@ -6,7 +6,7 @@ #include -#include +#include "IntaRNA/Matrix.h" namespace IntaRNA { @@ -85,7 +85,7 @@ class AccessibilityFromStream: public Accessibility protected: //! type for the ED value matrix (upper triangular matrix banded by maxLength) - typedef boost::numeric::ublas::banded_matrix EdMatrix; + typedef UpperBandedMatrix EdMatrix; //! the ED values for the given sequence EdMatrix edValues; diff --git a/src/IntaRNA/AccessibilityVrna.cpp b/src/IntaRNA/AccessibilityVrna.cpp index 66aa2ea5..ea88794f 100644 --- a/src/IntaRNA/AccessibilityVrna.cpp +++ b/src/IntaRNA/AccessibilityVrna.cpp @@ -96,11 +96,7 @@ AccessibilityVrna::AccessibilityVrna( ); } else { // init ED values for short sequences - for (auto row = edValues.begin1(); row != edValues.end1(); row++) { - for (auto ed = row.begin(); ed != row.end(); ed++) { - *ed = 0; - } - } + edValues.clear(); } } diff --git a/src/IntaRNA/AccessibilityVrna.h b/src/IntaRNA/AccessibilityVrna.h index 51fe5f47..35448e21 100644 --- a/src/IntaRNA/AccessibilityVrna.h +++ b/src/IntaRNA/AccessibilityVrna.h @@ -5,7 +5,7 @@ #include "IntaRNA/Accessibility.h" #include "IntaRNA/VrnaHandler.h" -#include +#include "IntaRNA/Matrix.h" #include @@ -86,7 +86,7 @@ class AccessibilityVrna : public Accessibility { protected: //! type for the ED value matrix (upper triangular matrix banded by maxLength) - typedef boost::numeric::ublas::banded_matrix EdMatrix; + typedef UpperBandedMatrix EdMatrix; //! the ED values for the given sequence EdMatrix edValues; diff --git a/src/IntaRNA/HelixHandlerIdxOffset.h b/src/IntaRNA/HelixHandlerIdxOffset.h index 7abd4a33..bd8dfe32 100644 --- a/src/IntaRNA/HelixHandlerIdxOffset.h +++ b/src/IntaRNA/HelixHandlerIdxOffset.h @@ -7,7 +7,6 @@ #include -#include namespace IntaRNA { diff --git a/src/IntaRNA/HelixHandlerUnpaired.h b/src/IntaRNA/HelixHandlerUnpaired.h index 88f7f1eb..0be76450 100644 --- a/src/IntaRNA/HelixHandlerUnpaired.h +++ b/src/IntaRNA/HelixHandlerUnpaired.h @@ -8,7 +8,7 @@ #include -#include +#include "IntaRNA/Matrix.h" #include @@ -39,8 +39,8 @@ class HelixHandlerUnpaired : public HelixHandler { //! it holds both the energy (first) as well as the length of the helix using //! the length combination of encodeHelixLength() //! The third entry is the bestBP, i.e. the optimal number of bases for this left boundary - typedef boost::numeric::ublas::matrix< std::tuple > HelixMatrix; - typedef boost::numeric::ublas::matrix< std::pair > HelixSeedMatrix; + typedef Matrix< std::tuple > HelixMatrix; + typedef Matrix< std::pair > HelixSeedMatrix; public: diff --git a/src/IntaRNA/InteractionEnergyVrna.h b/src/IntaRNA/InteractionEnergyVrna.h index 2759a5d2..5374f1fb 100644 --- a/src/IntaRNA/InteractionEnergyVrna.h +++ b/src/IntaRNA/InteractionEnergyVrna.h @@ -19,7 +19,7 @@ extern "C" { } #endif -#include +#include "IntaRNA/Matrix.h" #define Evrna_2_E( e ) ( static_cast(e) ) @@ -286,7 +286,7 @@ class InteractionEnergyVrna: public InteractionEnergy { const int bpGC; //! matrix to store ES values (upper triangular matrix) - typedef boost::numeric::ublas::triangular_matrix EsMatrix; + typedef UpperTriangularMatrix EsMatrix; //! the ES values for seq1 if computed (otherwise NULL) EsMatrix * esValues1; diff --git a/src/IntaRNA/Makefile.am b/src/IntaRNA/Makefile.am index 56b7ed87..1c0967de 100644 --- a/src/IntaRNA/Makefile.am +++ b/src/IntaRNA/Makefile.am @@ -26,6 +26,7 @@ libIntaRNA_adir = $(includedir)/IntaRNA # the list of header files that belong to the library (to be installed later) libIntaRNA_a_HEADERS = \ intarna_config.h \ + Matrix.h \ Accessibility.h \ AccessibilityConstraint.h \ AccessibilityDisabled.h \ diff --git a/src/IntaRNA/Matrix.h b/src/IntaRNA/Matrix.h new file mode 100644 index 00000000..34bec953 --- /dev/null +++ b/src/IntaRNA/Matrix.h @@ -0,0 +1,220 @@ +#ifndef INTARNA_MATRIX_H_ +#define INTARNA_MATRIX_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace IntaRNA { + +namespace matrix_detail { +inline std::size_t product(std::size_t rows, std::size_t columns) { + if (columns != 0 && rows > std::numeric_limits::max() / columns) + throw std::length_error("matrix dimensions overflow"); + return rows * columns; +} +inline std::size_t triangle(std::size_t n) { + if (n == std::numeric_limits::max()) + throw std::length_error("matrix dimensions overflow"); + return n % 2 == 0 ? product(n / 2, n + 1) : product(n, (n + 1) / 2); +} +} + +/** Owning row-major matrix. Views are constructed on access, so copies, moves + * and resizes cannot leave a cached mdspan pointing into another allocation. + * resize preserves the overlapping rectangle unless preserve=false is passed. + * Preserving resize value-initializes new cells. With preserve=false, callers + * must overwrite all cells before reading. clear resets cells without changing + * shape. + */ +template +class Matrix { + std::vector values; + std::size_t rows = 0, columns = 0; + using Extents = std::dextents; +public: + using value_type = T; + Matrix() = default; + Matrix(std::size_t rows, std::size_t columns, const T &value = T{}) + : values(matrix_detail::product(rows, columns), value), rows(rows), columns(columns) {} + Matrix(const Matrix &) = default; + Matrix &operator=(const Matrix &) = default; + Matrix(Matrix &&other) noexcept : values(std::move(other.values)), + rows(std::exchange(other.rows, 0)), columns(std::exchange(other.columns, 0)) {} + Matrix &operator=(Matrix &&other) noexcept { + if (this != &other) { + Matrix moved(std::move(other)); + swap(moved); + } + return *this; + } + std::size_t size1() const noexcept { return rows; } + std::size_t size2() const noexcept { return columns; } + std::size_t storageSize() const noexcept { return values.size(); } + T &operator()(std::size_t i, std::size_t j) { + assert(i < rows && j < columns); + return std::mdspan(values.data(), rows, columns)[i, j]; + } + const T &operator()(std::size_t i, std::size_t j) const { + assert(i < rows && j < columns); + return std::mdspan(values.data(), rows, columns)[i, j]; + } + void clear() { std::fill(values.begin(), values.end(), T{}); } + void swap(Matrix &other) noexcept { + values.swap(other.values); + std::swap(rows, other.rows); + std::swap(columns, other.columns); + } + void resize(std::size_t newRows, std::size_t newColumns, bool preserve = true) { + if (rows == newRows && columns == newColumns) return; + if (!preserve) { + values.resize(matrix_detail::product(newRows, newColumns)); + rows = newRows; + columns = newColumns; + return; + } + Matrix next(newRows, newColumns); + for (std::size_t i = 0; i < std::min(rows, newRows); ++i) + for (std::size_t j = 0; j < std::min(columns, newColumns); ++j) + next(i, j) = (*this)(i, j); + swap(next); + } +}; + +/** Square upper-triangular matrix with exactly n*(n+1)/2 stored cells. + * Rows are packed consecutively. A one-dimensional mdspan addresses the + * packed storage because triangular row lengths are not an affine 2D layout. + * Const access below the diagonal returns zero; writes require i <= j. + */ +template +class UpperTriangularMatrix { + std::vector values; + std::size_t n = 0; + using Extents = std::dextents; + std::size_t offset(std::size_t i, std::size_t j) const noexcept { + const auto remaining = n - i; + // Constructor has already checked that every triangular size fits. + const auto tail = remaining % 2 == 0 + ? (remaining / 2) * (remaining + 1) : remaining * ((remaining + 1) / 2); + return values.size() - tail + j - i; + } + static std::size_t count(std::size_t rows, std::size_t columns) { + if (rows != columns) throw std::invalid_argument("upper-triangular matrix must be square"); + return matrix_detail::triangle(rows); + } +public: + using value_type = T; + UpperTriangularMatrix() = default; + UpperTriangularMatrix(std::size_t rows, std::size_t columns) + : values(count(rows, columns)), n(rows) {} + UpperTriangularMatrix(const UpperTriangularMatrix &) = default; + UpperTriangularMatrix &operator=(const UpperTriangularMatrix &) = default; + UpperTriangularMatrix(UpperTriangularMatrix &&other) noexcept + : values(std::move(other.values)), n(std::exchange(other.n, 0)) {} + UpperTriangularMatrix &operator=(UpperTriangularMatrix &&other) noexcept { + if (this != &other) { + UpperTriangularMatrix moved(std::move(other)); + swap(moved); + } + return *this; + } + std::size_t size1() const noexcept { return n; } + std::size_t size2() const noexcept { return n; } + std::size_t storageSize() const noexcept { return values.size(); } + T &operator()(std::size_t i, std::size_t j) { + assert(i <= j && j < n); + return std::mdspan(values.data(), values.size())[offset(i, j)]; + } + const T &operator()(std::size_t i, std::size_t j) const { + assert(i < n && j < n); + static const T zero{}; + return i > j ? zero : std::mdspan(values.data(), values.size())[offset(i, j)]; + } + void clear() { std::fill(values.begin(), values.end(), T{}); } + void swap(UpperTriangularMatrix &other) noexcept { + values.swap(other.values); + std::swap(n, other.n); + } + void resize(std::size_t rows, std::size_t columns, bool preserve = true) { + const auto cells = count(rows, columns); + if (rows == n) return; + if (!preserve) { + values.resize(cells); + n = rows; + return; + } + UpperTriangularMatrix next(rows, columns); + for (std::size_t i = 0; i < std::min(n, rows); ++i) + for (std::size_t j = i; j < std::min(n, rows); ++j) + next(i, j) = (*this)(i, j); + swap(next); + } +}; + +/** Upper band including the diagonal and 'upper' superdiagonals. + * Stores rows * min(columns, upper+1) cells, never a dense square for a + * narrow band. Logical (i,j) maps to mdspan[i,j-i] in the owning Matrix. + * Const access outside the band returns zero; writes must be in the band. + */ +template +class UpperBandedMatrix { + Matrix band; + std::size_t columns = 0; + static std::size_t width(std::size_t columns, std::size_t lower, std::size_t upper) { + if (lower != 0) throw std::invalid_argument("upper-banded matrix requires lower=0"); + return upper >= columns ? columns : upper + 1; + } +public: + using value_type = T; + UpperBandedMatrix() = default; + UpperBandedMatrix(std::size_t rows, std::size_t columns, std::size_t lower, std::size_t upper) + : band(rows, width(columns, lower, upper)), columns(columns) {} + UpperBandedMatrix(const UpperBandedMatrix &) = default; + UpperBandedMatrix &operator=(const UpperBandedMatrix &) = default; + UpperBandedMatrix(UpperBandedMatrix &&other) noexcept + : band(std::move(other.band)), columns(std::exchange(other.columns, 0)) {} + UpperBandedMatrix &operator=(UpperBandedMatrix &&other) noexcept { + if (this != &other) { + UpperBandedMatrix moved(std::move(other)); + swap(moved); + } + return *this; + } + std::size_t size1() const noexcept { return band.size1(); } + std::size_t size2() const noexcept { return columns; } + std::size_t storageSize() const noexcept { return band.storageSize(); } + T &operator()(std::size_t i, std::size_t j) { + assert(i < size1() && j < columns && i <= j && j - i < band.size2()); + return band(i, j - i); + } + const T &operator()(std::size_t i, std::size_t j) const { + assert(i < size1() && j < columns); + static const T zero{}; + return i > j || j - i >= band.size2() ? zero : band(i, j - i); + } + void clear() { band.clear(); } + void swap(UpperBandedMatrix &other) noexcept { + band.swap(other.band); + std::swap(columns, other.columns); + } + void resize(std::size_t rows, std::size_t newColumns, std::size_t lower, + std::size_t upper, bool preserve = true) { + UpperBandedMatrix next(rows, newColumns, lower, upper); + if (preserve) { + for (std::size_t i = 0; i < std::min(size1(), rows); ++i) + for (std::size_t d = 0; d < std::min(band.size2(), next.band.size2()) + && i < std::min(columns, newColumns) + && d < std::min(columns, newColumns) - i; ++d) + next.band(i, d) = band(i, d); + } + swap(next); + } +}; + +} // namespace IntaRNA +#endif diff --git a/src/IntaRNA/NussinovHandler.h b/src/IntaRNA/NussinovHandler.h index 07103df2..9d041764 100644 --- a/src/IntaRNA/NussinovHandler.h +++ b/src/IntaRNA/NussinovHandler.h @@ -8,7 +8,7 @@ #include #include -#include +#include "IntaRNA/Matrix.h" namespace IntaRNA { @@ -16,13 +16,13 @@ class NussinovHandler { public: //! Probability triangular matrix - typedef boost::numeric::ublas::triangular_matrix Z2dMatrix; + typedef UpperTriangularMatrix Z2dMatrix; //! Energy triangular matrix - typedef boost::numeric::ublas::triangular_matrix E2dMatrix; + typedef UpperTriangularMatrix E2dMatrix; //! Index triangular matrix - typedef boost::numeric::ublas::triangular_matrix IdxMatrix; + typedef UpperTriangularMatrix IdxMatrix; /*** * Get the partition function Q between the indices (from, to) diff --git a/src/IntaRNA/PredictionTrackerPairMinE.h b/src/IntaRNA/PredictionTrackerPairMinE.h index 03acca17..06bd9c2f 100644 --- a/src/IntaRNA/PredictionTrackerPairMinE.h +++ b/src/IntaRNA/PredictionTrackerPairMinE.h @@ -8,7 +8,7 @@ #include #include -#include +#include "IntaRNA/Matrix.h" namespace IntaRNA { @@ -107,7 +107,7 @@ class PredictionTrackerPairMinE: public PredictionTracker const std::string sep_string; //! matrix type to hold the mfe energies and boundaries for interaction site starts - typedef boost::numeric::ublas::matrix E2dMatrix; + typedef Matrix E2dMatrix; //! the index-pair-wise minimal energy values E2dMatrix pairMinE; diff --git a/src/IntaRNA/PredictionTrackerSpotProbAll.h b/src/IntaRNA/PredictionTrackerSpotProbAll.h index 47d9938c..906b49ba 100644 --- a/src/IntaRNA/PredictionTrackerSpotProbAll.h +++ b/src/IntaRNA/PredictionTrackerSpotProbAll.h @@ -8,7 +8,7 @@ #include #include -#include +#include "IntaRNA/Matrix.h" namespace IntaRNA { @@ -111,7 +111,7 @@ class PredictionTrackerSpotProbAll: public PredictionTracker Z_type overallZ; //! matrix type to hold the partition function for each index pair - typedef boost::numeric::ublas::matrix Z2dMatrix; + typedef Matrix Z2dMatrix; //! the index-pair-wise minimal energy values Z2dMatrix pairZ; diff --git a/src/IntaRNA/PredictorMfe2d.h b/src/IntaRNA/PredictorMfe2d.h index 4688d74e..2e060f49 100644 --- a/src/IntaRNA/PredictorMfe2d.h +++ b/src/IntaRNA/PredictorMfe2d.h @@ -5,7 +5,7 @@ #include "IntaRNA/PredictorMfe.h" #include "IntaRNA/Interaction.h" -#include +#include "IntaRNA/Matrix.h" namespace IntaRNA { @@ -21,7 +21,7 @@ class PredictorMfe2d: public PredictorMfe { protected: //! matrix type to hold the mfe energies for interaction site starts - typedef boost::numeric::ublas::matrix E2dMatrix; + typedef Matrix E2dMatrix; public: diff --git a/src/IntaRNA/PredictorMfe2dHelixBlockHeuristic.h b/src/IntaRNA/PredictorMfe2dHelixBlockHeuristic.h index 7634a345..2370901c 100644 --- a/src/IntaRNA/PredictorMfe2dHelixBlockHeuristic.h +++ b/src/IntaRNA/PredictorMfe2dHelixBlockHeuristic.h @@ -6,7 +6,6 @@ #include "IntaRNA/Interaction.h" #include "IntaRNA/HelixHandlerIdxOffset.h" -#include namespace IntaRNA { diff --git a/src/IntaRNA/PredictorMfe2dHeuristic.h b/src/IntaRNA/PredictorMfe2dHeuristic.h index 268704aa..857c5f0f 100644 --- a/src/IntaRNA/PredictorMfe2dHeuristic.h +++ b/src/IntaRNA/PredictorMfe2dHeuristic.h @@ -5,7 +5,7 @@ #include "IntaRNA/PredictorMfe.h" #include "IntaRNA/Interaction.h" -#include +#include "IntaRNA/Matrix.h" namespace IntaRNA { @@ -27,7 +27,7 @@ class PredictorMfe2dHeuristic: public PredictorMfe { protected: //! matrix type to hold the mfe energies and boundaries for interaction site starts - typedef boost::numeric::ublas::matrix E2dMatrix; + typedef Matrix E2dMatrix; public: diff --git a/src/IntaRNA/PredictorMfeEns2d.h b/src/IntaRNA/PredictorMfeEns2d.h index df845b5e..57b7f5c3 100644 --- a/src/IntaRNA/PredictorMfeEns2d.h +++ b/src/IntaRNA/PredictorMfeEns2d.h @@ -5,7 +5,7 @@ #include "IntaRNA/PredictorMfeEns.h" #include "IntaRNA/Interaction.h" -#include +#include "IntaRNA/Matrix.h" namespace IntaRNA { @@ -22,7 +22,7 @@ class PredictorMfeEns2d: public PredictorMfeEns { protected: //! matrix type to hold the partition functions for interaction site starts - typedef boost::numeric::ublas::matrix Z2dMatrix; + typedef Matrix Z2dMatrix; public: diff --git a/src/IntaRNA/PredictorMfeEns2dHeuristic.h b/src/IntaRNA/PredictorMfeEns2dHeuristic.h index 7eb03067..cec1c905 100644 --- a/src/IntaRNA/PredictorMfeEns2dHeuristic.h +++ b/src/IntaRNA/PredictorMfeEns2dHeuristic.h @@ -5,7 +5,7 @@ #include "IntaRNA/PredictorMfeEns2d.h" #include "IntaRNA/Interaction.h" -#include +#include "IntaRNA/Matrix.h" namespace IntaRNA { @@ -28,7 +28,7 @@ class PredictorMfeEns2dHeuristic: public PredictorMfeEns2d { protected: //! matrix type to hold the mfe energies and boundaries for interaction site starts - typedef boost::numeric::ublas::matrix Z2dMatrix; + typedef Matrix Z2dMatrix; public: diff --git a/src/IntaRNA/PredictorMfeEns2dSeedExtension.h b/src/IntaRNA/PredictorMfeEns2dSeedExtension.h index f0a0fc93..92107dac 100644 --- a/src/IntaRNA/PredictorMfeEns2dSeedExtension.h +++ b/src/IntaRNA/PredictorMfeEns2dSeedExtension.h @@ -3,6 +3,7 @@ #define INTARNA_PREDICTORMFEENS2DSEEDEXTENSION_H_ #include "IntaRNA/PredictorMfeEns.h" +#include "IntaRNA/Matrix.h" #include "IntaRNA/SeedHandlerIdxOffset.h" namespace IntaRNA { @@ -24,7 +25,7 @@ class PredictorMfeEns2dSeedExtension: public PredictorMfeEns { protected: //! matrix type to hold the partition functions for interaction site starts - typedef boost::numeric::ublas::matrix Z2dMatrix; + typedef Matrix Z2dMatrix; public: diff --git a/src/IntaRNA/SeedHandler.h b/src/IntaRNA/SeedHandler.h index 7682cb45..55253d5c 100644 --- a/src/IntaRNA/SeedHandler.h +++ b/src/IntaRNA/SeedHandler.h @@ -7,7 +7,6 @@ #include -#include namespace IntaRNA { diff --git a/src/IntaRNA/SeedHandlerIdxOffset.h b/src/IntaRNA/SeedHandlerIdxOffset.h index 1f964636..e798a5ff 100644 --- a/src/IntaRNA/SeedHandlerIdxOffset.h +++ b/src/IntaRNA/SeedHandlerIdxOffset.h @@ -7,7 +7,6 @@ #include -#include namespace IntaRNA { diff --git a/src/IntaRNA/SeedHandlerMfe.h b/src/IntaRNA/SeedHandlerMfe.h index 4938bd82..30fdeef8 100644 --- a/src/IntaRNA/SeedHandlerMfe.h +++ b/src/IntaRNA/SeedHandlerMfe.h @@ -8,7 +8,7 @@ #include -#include +#include "IntaRNA/Matrix.h" namespace IntaRNA { @@ -37,7 +37,7 @@ class SeedHandlerMfe : public SeedHandler //! matrix to store the seed information for each seed left side (i1,i2); //! it holds both the energy (first) as well as the length of the seed using //! the length combination using encodeSeedLength() - typedef boost::numeric::ublas::matrix< std::pair > SeedMatrix; + typedef Matrix< std::pair > SeedMatrix; public: diff --git a/tests/AccessibilityBasePair_test.cpp b/tests/AccessibilityBasePair_test.cpp index 0dda2556..fb92a3a3 100644 --- a/tests/AccessibilityBasePair_test.cpp +++ b/tests/AccessibilityBasePair_test.cpp @@ -3,7 +3,6 @@ #undef NDEBUG -#include #include "IntaRNA/AccessibilityBasePair.h" using namespace IntaRNA; diff --git a/tests/AccessibilityVrna_test.cpp b/tests/AccessibilityVrna_test.cpp index e462bffc..d2110eb1 100644 --- a/tests/AccessibilityVrna_test.cpp +++ b/tests/AccessibilityVrna_test.cpp @@ -3,7 +3,6 @@ #undef NDEBUG -#include #include "IntaRNA/AccessibilityDisabled.h" #include "IntaRNA/AccessibilityVrna.h" #include "IntaRNA/InteractionEnergyVrna.h" diff --git a/tests/Makefile.am b/tests/Makefile.am index 3bc74a69..f0cf4c72 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -50,6 +50,7 @@ runApiTests_SOURCES = \ PredictorMfeEnsRegression_test.cpp \ PredictorTinyOracle_test.cpp \ PredictorSeedOracle_test.cpp \ + Matrix_test.cpp \ NussinovHandler_test.cpp \ RnaSequence_test.cpp \ OutputStreamHandlerSortedCsv_test.cpp \ @@ -70,3 +71,6 @@ LIBS= -L$(top_builddir)/src/IntaRNA -lIntaRNA \ runApiTests_CXXFLAGS = -I$(top_builddir)/src \ @AM_CXXFLAGS@ @CXXFLAGS@ \ -DELPP_NO_LOG_TO_FILE + +# Reproducible before/after storage performance comparison (not a test gate). +EXTRA_DIST = benchmark/compare-matrix-storage.py diff --git a/tests/Matrix_test.cpp b/tests/Matrix_test.cpp new file mode 100644 index 00000000..ab9bce12 --- /dev/null +++ b/tests/Matrix_test.cpp @@ -0,0 +1,160 @@ +#include "catch.hpp" +#include "IntaRNA/Matrix.h" + +#include +#include +#include +#include + +using namespace IntaRNA; +namespace ublas = boost::numeric::ublas; + +namespace { +template +void compare(const Actual &actual, const Reference &reference) { + REQUIRE(actual.size1() == reference.size1()); + REQUIRE(actual.size2() == reference.size2()); + for (std::size_t i = 0; i < actual.size1(); ++i) + for (std::size_t j = 0; j < actual.size2(); ++j) + REQUIRE(actual(i, j) == reference(i, j)); +} + +// Build a reference explicitly: uBLAS's preserving band resize itself can +// throw on rectangular/empty shapes (e.g. 1x2 -> 3x3 -> 0x1 in Boost 1.85). +void resizeReferenceBand(ublas::banded_matrix &matrix, std::size_t rows, + std::size_t columns, std::size_t upper) { + ublas::banded_matrix next(rows, columns, 0, upper); + next.clear(); + for (std::size_t i = 0; i < std::min(rows, matrix.size1()); ++i) + for (std::size_t j = i; j < std::min(columns, matrix.size2()) + && j-i <= std::min(upper, matrix.upper()); ++j) + next(i, j) = matrix(i, j); + matrix.swap(next); +} + +template +void checkOwnership(M original) { + original(0, 0) = 7; + M copy(original); + copy(0, 0) = 9; + REQUIRE(original(0, 0) == 7); + M assigned; + assigned = copy; + copy.clear(); + REQUIRE(assigned(0, 0) == 9); + M moved(std::move(assigned)); + REQUIRE(moved(0, 0) == 9); + REQUIRE(assigned.size1() == 0); + REQUIRE(assigned.size2() == 0); + assigned = std::move(moved); + REQUIRE(assigned(0, 0) == 9); + REQUIRE(moved.size1() == 0); + assigned.swap(original); + REQUIRE(assigned(0, 0) == 7); + REQUIRE(original(0, 0) == 9); +} +} + +TEST_CASE("Dense matrix preserves logical cells across reshape", "[Matrix]") { + Matrix actual(3, 5); + ublas::matrix reference(3, 5); + for (std::size_t i = 0; i < 3; ++i) + for (std::size_t j = 0; j < 5; ++j) + actual(i, j) = reference(i, j) = 10 * i + j; + compare(actual, reference); + for (auto shape : {std::pair{5u, 3u}, {2u, 7u}, {2u, 7u}, {0u, 4u}, {4u, 0u}, {1u, 1u}}) { + const auto oldRows = reference.size1(), oldColumns = reference.size2(); + actual.resize(shape.first, shape.second); + reference.resize(shape.first, shape.second); + // uBLAS leaves new primitive cells uninitialized: only retained cells + // have a defined reference value. Match vector's zero initialization. + for (std::size_t i = 0; i < reference.size1(); ++i) + for (std::size_t j = 0; j < reference.size2(); ++j) + if (i >= oldRows || j >= oldColumns) reference(i, j) = 0; + compare(actual, reference); + REQUIRE(actual.storageSize() == shape.first * shape.second); + } + actual.resize(2, 3, false); + actual(1, 2) = 42; + REQUIRE(std::as_const(actual)(1, 2) == 42); + actual.clear(); + REQUIRE(actual.size1() == 2); + REQUIRE(actual(1, 2) == 0); + checkOwnership(Matrix(2, 3)); + + Matrix> objects(2, 2, {42, "seed"}); + auto copy = objects; + objects.resize(3, 1); + REQUIRE(objects(1, 0).second == "seed"); + copy(1, 1).second = "helix"; + REQUIRE(objects(1, 0).second == "seed"); +} + +TEST_CASE("Upper triangular matrix retains packed indexing", "[Matrix]") { + for (std::size_t n = 0; n <= 12; ++n) { + UpperTriangularMatrix actual(n, n); + ublas::triangular_matrix reference(n, n); + REQUIRE(actual.storageSize() == n * (n + 1) / 2); + for (std::size_t i = 0; i < n; ++i) + for (std::size_t j = i; j < n; ++j) + actual(i, j) = reference(i, j) = 100 * i + j + 1; + compare(actual, reference); + for (auto size : {n + 3, n / 2, std::size_t(0)}) { + const auto oldSize = reference.size1(); + actual.resize(size, size); + reference.resize(size, size); + for (std::size_t i = 0; i < size; ++i) + for (std::size_t j = std::max(i, oldSize); j < size; ++j) + reference(i, j) = 0; + compare(actual, reference); + } + actual.resize(n, n, false); + actual.clear(); + if (n) REQUIRE(actual(n-1, n-1) == 0); + } + checkOwnership(UpperTriangularMatrix(3, 3)); + REQUIRE_THROWS_AS(UpperTriangularMatrix(2, 3), std::invalid_argument); +} + +TEST_CASE("Upper band retains zeros and the last superdiagonal", "[Matrix]") { + for (std::size_t rows = 0; rows <= 7; ++rows) { + for (std::size_t columns = 0; columns <= 7; ++columns) { + for (std::size_t upper = 0; upper <= 8; ++upper) { + CAPTURE(rows, columns, upper); + UpperBandedMatrix actual(rows, columns, 0, upper); + ublas::banded_matrix reference(rows, columns, 0, upper); + REQUIRE(actual.storageSize() == rows * std::min(columns, upper + 1)); + for (std::size_t i = 0; i < rows; ++i) + for (std::size_t j = i; j < columns && j-i <= upper; ++j) + actual(i, j) = reference(i, j) = 100 * i + j + 1; + compare(actual, reference); + actual.resize(rows + 2, columns + 1, 0, upper + 1); + resizeReferenceBand(reference, rows + 2, columns + 1, upper + 1); + compare(actual, reference); + actual.resize(rows / 2, columns / 2, 0, upper / 2); + resizeReferenceBand(reference, rows / 2, columns / 2, upper / 2); + compare(actual, reference); + } + } + } + checkOwnership(UpperBandedMatrix(3, 3, 0, 1)); + UpperBandedMatrix narrow(10000, 10000, 0, 10); + REQUIRE(narrow.storageSize() == 110000); + narrow.resize(2, 2, 0, 0, false); + narrow(1, 1) = 42; + REQUIRE(std::as_const(narrow)(1, 1) == 42); + REQUIRE(std::as_const(narrow)(0, 1) == 0); + REQUIRE_THROWS_AS(UpperBandedMatrix(2, 2, 1, 1), std::invalid_argument); +} + +TEST_CASE("Oversized matrix dimensions fail before allocation", "[Matrix]") { + const auto max = std::numeric_limits::max(); + REQUIRE_THROWS_AS(Matrix(max, 2), std::length_error); + REQUIRE_THROWS_AS(UpperTriangularMatrix(max, max), std::length_error); + REQUIRE_THROWS_AS(UpperTriangularMatrix(max / 2, max / 2), std::length_error); + REQUIRE_THROWS_AS(UpperBandedMatrix(max, 3, 0, 2), std::length_error); + Matrix matrix(1, 1, 42); + REQUIRE_THROWS_AS(matrix.resize(max, 2, false), std::length_error); + REQUIRE(matrix.size1() == 1); + REQUIRE(matrix(0, 0) == 42); +} diff --git a/tests/NussinovHandler_test.cpp b/tests/NussinovHandler_test.cpp index af70f566..d2b0c904 100644 --- a/tests/NussinovHandler_test.cpp +++ b/tests/NussinovHandler_test.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include "IntaRNA/NussinovHandler.h" using namespace IntaRNA; diff --git a/tests/benchmark/compare-matrix-storage.py b/tests/benchmark/compare-matrix-storage.py new file mode 100644 index 00000000..2e2df761 --- /dev/null +++ b/tests/benchmark/compare-matrix-storage.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +"""Compare two identically built IntaRNA binaries; save every sample and output. + +Requires Python 3 and Linux /usr/bin/time. No optional Python packages. +Run after builds/tests finish, on an otherwise idle machine. +""" +import argparse +import hashlib +import json +import os +from pathlib import Path +import platform +import random +import statistics +import subprocess +import time + + +def sha(data): + return hashlib.sha256(data).hexdigest() + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("baseline", type=Path) + parser.add_argument("candidate", type=Path) + parser.add_argument("output", type=Path) + parser.add_argument("--repetitions", type=int, default=7) + parser.add_argument("--cpu", type=int) + args = parser.parse_args() + if args.repetitions < 1: + parser.error("repetitions must be positive") + if args.cpu is not None: + os.sched_setaffinity(0, {args.cpu}) + out = args.output.resolve() + out.mkdir(parents=True, exist_ok=False) + binaries = {"baseline": args.baseline.resolve(), "candidate": args.candidate.resolve()} + env = dict(os.environ, LC_ALL="C", OPENBLAS_NUM_THREADS="1", OMP_DYNAMIC="FALSE") + root = Path(__file__).resolve().parents[2] + rng = random.Random(246) + target = "".join(rng.choices("ACGU", k=1000)) + query = "".join(rng.choices("ACGU", k=120)) + inputs = {} + for name, seq in [("t1000", target), ("t300", target[:300]), ("t80", target[:80]), + ("q120", query), ("q80", query[:80])]: + path = out / (name + ".fa") + path.write_text(">" + name + "\n" + seq + "\n") + inputs[name] = path + for name in ("fhlA", "OxyS"): + path = out / (name + ".fa") + path.write_bytes((root / "doc/handson" / (name + ".fasta")).read_bytes()) + inputs[name] = path + common = ["--threads=1", "--outMode=C", "--outCsvCols=id1,id2,start1,end1,start2,end2,E,bpList", + "--default-log-file=/dev/null"] + cases = [] + + def case(name, target_name, query_name, *options): + cases.append({"name": name, "args": ["--target=" + str(inputs[target_name]), + "--query=" + str(inputs[query_name]), *common, *options]}) + + case("biological-default", "fhlA", "OxyS") + case("banded-default", "t1000", "q120") + case("banded-narrow", "t1000", "q120", "--accL=30", "--accW=100") + case("dense-no-accessibility", "t1000", "q120", "--acc=N") + case("seed-bulges", "t300", "q120", "--seedMaxUP=2") + case("helix-block", "t300", "q120", "--model=B") + case("exact-mfe", "t80", "q80", "--mode=M", "--model=S", "--noSeed", "--intLenMax=30") + case("exact-ensemble", "t80", "q80", "--mode=M", "--model=P", "--noSeed", "--intLenMax=30") + case("seed-extension", "t300", "q120", "--mode=H", "--model=P", "--intLenMax=40") + case("triangular-base-pair", "t300", "q120", "--energy=B") + metadata = {"platform": platform.platform(), "affinity": sorted(os.sched_getaffinity(0)), + "repetitions": args.repetitions, "warmups": 1, "seed": 246, + "binaries": {name: {"path": str(path), "sha256": sha(path.read_bytes()), + "version": subprocess.check_output([str(path), "--version"], env=env).decode()} + for name, path in binaries.items()}, + "inputs": {name: sha(path.read_bytes()) for name, path in inputs.items()}, + "cases": cases, "environment": {key: env[key] for key in ("LC_ALL", "OPENBLAS_NUM_THREADS", "OMP_DYNAMIC")}} + (out / "metadata.json").write_text(json.dumps(metadata, indent=2) + "\n") + expected = {} + samples = [] + for repetition in range(args.repetitions + 1): + schedule = [(case, variant) for case in cases for variant in binaries] + rng.shuffle(schedule) + for current, variant in schedule: + stem = out / f"{current['name']}.{variant}.{repetition}" + command = ["/usr/bin/time", "-f", "%U %S %M", "-o", str(stem) + ".time", + str(binaries[variant]), *current["args"]] + started = time.perf_counter() + result = subprocess.run(command, env=env, capture_output=True, timeout=180) + wall = time.perf_counter() - started + Path(str(stem) + ".stdout").write_bytes(result.stdout) + Path(str(stem) + ".stderr").write_bytes(result.stderr) + if result.returncode: + raise RuntimeError(f"{stem.name} failed: {result.stderr.decode()}") + digest = sha(result.stdout) + if digest != expected.setdefault(current["name"], digest): + raise RuntimeError(f"output mismatch: {stem.name}") + user, system, rss = map(float, Path(str(stem) + ".time").read_text().split()) + row = {"case": current["name"], "variant": variant, "repetition": repetition, + "warmup": repetition == 0, "wall_s": wall, "user_s": user, "system_s": system, + "max_rss_kib": int(rss), "sha256": digest} + samples.append(row) + with (out / "samples.jsonl").open("a") as handle: + handle.write(json.dumps(row) + "\n") + print(f"Completed round {repetition}/{args.repetitions}", flush=True) + summary = [] + for current in cases: + row = {"case": current["name"], "output_identical": True} + for variant in binaries: + selected = [x for x in samples if not x["warmup"] and x["case"] == current["name"] and x["variant"] == variant] + row[variant] = {key: statistics.median(x[key] for x in selected) for key in ("wall_s", "user_s", "max_rss_kib")} + row[variant]["wall_min_s"] = min(x["wall_s"] for x in selected) + row[variant]["wall_max_s"] = max(x["wall_s"] for x in selected) + row["wall_change_percent"] = 100 * (row["candidate"]["wall_s"] / row["baseline"]["wall_s"] - 1) + summary.append(row) + (out / "summary.json").write_text(json.dumps(summary, indent=2) + "\n") + print(json.dumps(summary, indent=2)) + + +if __name__ == "__main__": + main() From 009bf5b2ad8e77e3e2807a025555ccdbf6456a61 Mon Sep 17 00:00:00 2001 From: AlexanderMitrofanov Date: Mon, 28 Sep 2026 19:13:29 +0200 Subject: [PATCH 2/8] Use GCC 16 in CI for native mdspan support --- .github/workflows/build.yml | 23 +++++++++++++---------- doc/mdspan-storage.md | 8 +++++++- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 75894565..d471ca86 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,11 +18,11 @@ jobs: fail-fast: false matrix: include: - - name: GCC 14 release + - name: GCC 16 release os: ubuntu-24.04 compiler: gcc configure_debug: --disable-debug - - name: GCC 14 debug + - name: GCC 16 debug os: ubuntu-24.04 compiler: gcc configure_debug: --enable-debug @@ -42,22 +42,25 @@ jobs: environment-file: conda-build-env.yml activate-environment: intarna-build-env - - name: Install and select GCC 14 + - name: Install and select GCC 16 if: matrix.compiler == 'gcc' run: | - sudo apt-get update - sudo apt-get install --yes gcc-14 g++-14 - echo "CC=gcc-14" >> "$GITHUB_ENV" - echo "CXX=g++-14" >> "$GITHUB_ENV" + # GCC 16's libstdc++ provides the required C++23 std::mdspan. + # Use implementation packages so conda activation does not override + # configure's release/debug optimization flags. + conda install --yes --override-channels -c conda-forge -c bioconda \ + "gcc_impl_linux-64=16" "gxx_impl_linux-64=16" + echo "CC=${CONDA_PREFIX}/bin/x86_64-conda-linux-gnu-gcc" >> "$GITHUB_ENV" + echo "CXX=${CONDA_PREFIX}/bin/x86_64-conda-linux-gnu-g++" >> "$GITHUB_ENV" echo "LDFLAGS=-L${CONDA_PREFIX}/lib -Wl,-rpath,${CONDA_PREFIX}/lib" >> "$GITHUB_ENV" - - name: Verify GCC 14 + - name: Verify GCC 16 if: matrix.compiler == 'gcc' run: | "$CC" --version "$CXX" --version - test "$("$CC" -dumpfullversion | cut -d. -f1)" = 14 - test "$("$CXX" -dumpfullversion | cut -d. -f1)" = 14 + test "$("$CC" -dumpfullversion | cut -d. -f1)" = 16 + test "$("$CXX" -dumpfullversion | cut -d. -f1)" = 16 - name: Set up Apple Clang with OpenMP if: matrix.compiler == 'apple-clang' diff --git a/doc/mdspan-storage.md b/doc/mdspan-storage.md index 75f3a0e2..d427e921 100644 --- a/doc/mdspan-storage.md +++ b/doc/mdspan-storage.md @@ -49,7 +49,13 @@ need `` and C++23. Native support is available in [GCC 16/libstdc++](https://gcc.gnu.org/onlinedocs/libstdc++/manual/status.html) and [LLVM 18/libc++](https://releases.llvm.org/18.1.8/projects/libcxx/docs/Status/Cxx23.html). -The local validation below uses GCC; it does not claim a Clang/macOS build. +The local validation below uses GCC. GitHub Actions also passed the Apple Clang +17 release build on macOS 15, including the full test suite, independent public +header compilation, and the installed pkg-config consumer ([CI run](https://github.com/BackofenLab/IntaRNA/actions/runs/36453446578/job/109033658550)). +The Linux CI jobs select GCC 16 from conda-forge: GCC 14's standard library +does not provide mdspan and correctly fails the configure check. Compiler +implementation packages avoid activation hooks overriding configure's release +and debug optimization flags. Boost remains a dependency for other project components and for the independent reference containers in the storage regression tests. From c5847dcfa74c9c7600f66af1fdf9bc00862983ef Mon Sep 17 00:00:00 2001 From: AlexanderMitrofanov Date: Mon, 28 Sep 2026 19:34:10 +0200 Subject: [PATCH 3/8] Fix benchmark timeout cleanup and source archive inputs --- .github/workflows/build.yml | 4 ++ ChangeLog | 4 +- doc/Makefile.am | 2 + doc/mdspan-storage.md | 10 ++++ tests/Makefile.am | 2 +- tests/benchmark/compare-matrix-storage.py | 25 +++++++- .../benchmark/test_compare_matrix_storage.py | 59 +++++++++++++++++++ 7 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 tests/benchmark/test_compare_matrix_storage.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d471ca86..cdbaa6b0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -84,6 +84,10 @@ jobs: "$CXX" --version "$CXX" --version | grep -q "Apple clang" + - name: Test benchmark process cleanup + if: matrix.compiler == 'gcc' + run: python3 tests/benchmark/test_compare_matrix_storage.py + - name: Bootstrap and configure run: | bash autotools-init.sh diff --git a/ChangeLog b/ChangeLog index e85ac959..5f660ed0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -28,7 +28,7 @@ - reduce temporary allocations and fix cleanup on error paths - remove redundant accessibility checks from predictor, helix and seed handling -- C++23 required; build and installation checks with GCC 14 and Apple Clang +- C++23 required; build and installation checks with GCC 16 and Apple Clang - avoid repeated partition-map lookups in exact ensemble prediction - add exhaustive interaction and seed oracles for short-sequence tests - reduced memory use for exact non-seed ensemble prediction without trackers @@ -63,6 +63,8 @@ + probe native mdspan library support and document the toolchain requirement * tests/Matrix_test.cpp, tests/benchmark/compare-matrix-storage.py: + add storage regression tests and reproducible before/after measurements + + terminate the timed process group on timeout, with a regression check + + include the biological benchmark inputs in source distributions * doc/mdspan-storage.md: + record validation and performance evaluation for issue #246 diff --git a/doc/Makefile.am b/doc/Makefile.am index da0f922d..48b958af 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -6,6 +6,8 @@ EXTRA_DIST = \ mdspan-storage.md \ mdspan-storage-samples.tsv \ + handson/fhlA.fasta \ + handson/OxyS.fasta \ conda.txt \ doxygen.cfg \ latex-deps/adjcalc.sty \ diff --git a/doc/mdspan-storage.md b/doc/mdspan-storage.md index d427e921..e3fcadff 100644 --- a/doc/mdspan-storage.md +++ b/doc/mdspan-storage.md @@ -87,6 +87,10 @@ The script generates seeded synthetic inputs, copies the repository's biological fhlA/OxyS inputs, randomizes execution order, excludes one warmup per binary and case, and retains stdout, stderr and each timing sample. It compares stdout bytes for every execution, failing immediately on a mismatch or process error. +Each execution has a 180-second timeout that terminates both GNU time and its +IntaRNA child, retaining any captured stdout and stderr. The biological input +files are included in source distributions so the script also runs from an +extracted release archive. It records binary/input hashes, exact arguments, versions, affinity and controlled environment. Wall time includes startup, loading and output; peak RSS comes from GNU time. The test cases cover accessibility bands, dense predictors, seed @@ -108,6 +112,12 @@ triangular storage. This is a bounded, single-host performance experiment. diagnostic, after the other C++23 checks pass. - `make install` installs `Matrix.h`; a separate C++23 consumer including the installed accessibility, Nussinov and seed-extension headers compiles and runs. +- Benchmark process regression checks pass, including timeout cleanup of GNU + time's child process. The timeout check fails against the original runner. +- The benchmark runs from an extracted `make dist` archive: all ten workloads + complete with matching outputs (one warmup and one measured run per binary, + 40 executions total). This is a packaging smoke check; the performance table + below retains the original seven-repetition measurements. The storage cases cover empty and rectangular shapes, logical preservation on resize, zero-initialization of new cells, const structural zeros, the last diff --git a/tests/Makefile.am b/tests/Makefile.am index f0cf4c72..d890f212 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -73,4 +73,4 @@ runApiTests_CXXFLAGS = -I$(top_builddir)/src \ -DELPP_NO_LOG_TO_FILE # Reproducible before/after storage performance comparison (not a test gate). -EXTRA_DIST = benchmark/compare-matrix-storage.py +EXTRA_DIST = benchmark/compare-matrix-storage.py benchmark/test_compare_matrix_storage.py diff --git a/tests/benchmark/compare-matrix-storage.py b/tests/benchmark/compare-matrix-storage.py index 2e2df761..38f1a3ab 100644 --- a/tests/benchmark/compare-matrix-storage.py +++ b/tests/benchmark/compare-matrix-storage.py @@ -11,6 +11,7 @@ from pathlib import Path import platform import random +import signal import statistics import subprocess import time @@ -20,6 +21,23 @@ def sha(data): return hashlib.sha256(data).hexdigest() +def run_timed(command, env, timeout=180): + """Bound the complete process group, including GNU time's child process.""" + with subprocess.Popen(command, env=env, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, start_new_session=True) as process: + try: + stdout, stderr = process.communicate(timeout=timeout) + except subprocess.TimeoutExpired as error: + try: + os.killpg(process.pid, signal.SIGKILL) + except ProcessLookupError: + pass + stdout, stderr = process.communicate() + raise subprocess.TimeoutExpired(command, timeout, output=stdout, + stderr=stderr) from error + return subprocess.CompletedProcess(command, process.returncode, stdout, stderr) + + def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("baseline", type=Path) @@ -86,7 +104,12 @@ def case(name, target_name, query_name, *options): command = ["/usr/bin/time", "-f", "%U %S %M", "-o", str(stem) + ".time", str(binaries[variant]), *current["args"]] started = time.perf_counter() - result = subprocess.run(command, env=env, capture_output=True, timeout=180) + try: + result = run_timed(command, env=env) + except subprocess.TimeoutExpired as error: + Path(str(stem) + ".stdout").write_bytes(error.output or b"") + Path(str(stem) + ".stderr").write_bytes(error.stderr or b"") + raise RuntimeError(f"{stem.name} timed out after {error.timeout}s") from error wall = time.perf_counter() - started Path(str(stem) + ".stdout").write_bytes(result.stdout) Path(str(stem) + ".stderr").write_bytes(result.stderr) diff --git a/tests/benchmark/test_compare_matrix_storage.py b/tests/benchmark/test_compare_matrix_storage.py new file mode 100644 index 00000000..059f318a --- /dev/null +++ b/tests/benchmark/test_compare_matrix_storage.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +"""Linux regression checks for the benchmark's process-group timeout.""" +import importlib.util +import os +from pathlib import Path +import signal +import subprocess +import sys +import tempfile +import time +import unittest + +sys.dont_write_bytecode = True +spec = importlib.util.spec_from_file_location( + "benchmark", Path(__file__).with_name("compare-matrix-storage.py")) +benchmark = importlib.util.module_from_spec(spec) +spec.loader.exec_module(benchmark) + + +class BenchmarkProcessTests(unittest.TestCase): + def test_capture_exit_status_and_output(self): + result = benchmark.run_timed( + [sys.executable, "-c", "import sys; print('output'); print('error', file=sys.stderr); sys.exit(7)"], + os.environ, timeout=5) + self.assertEqual(result.returncode, 7) + self.assertEqual(result.stdout, b"output\n") + self.assertEqual(result.stderr, b"error\n") + + @unittest.skipUnless(sys.platform.startswith("linux"), "uses Linux /proc and GNU time") + def test_timeout_kills_child_of_time_wrapper(self): + with tempfile.TemporaryDirectory() as tmp: + pid_file = Path(tmp) / "child.pid" + child = ("import os,pathlib,time; pathlib.Path(" + repr(str(pid_file)) + + ").write_text(str(os.getpid())); print('started', flush=True); time.sleep(60)") + started = time.monotonic() + try: + with self.assertRaises(subprocess.TimeoutExpired) as caught: + benchmark.run_timed(["/usr/bin/time", sys.executable, "-c", child], + os.environ, timeout=2) + self.assertLess(time.monotonic() - started, 10) + self.assertIn(b"started", caught.exception.output) + self.assertTrue(pid_file.exists(), "child must start before testing cleanup") + pid = int(pid_file.read_text()) + try: + state = Path(f"/proc/{pid}/stat").read_text().rsplit(")", 1)[1].split()[0] + except FileNotFoundError: + state = "gone" + self.assertIn(state, ("gone", "Z", "X"), "timed-out child is still running") + finally: + # Keep the regression test safe even against a broken runner. + if pid_file.exists(): + try: + os.kill(int(pid_file.read_text()), signal.SIGKILL) + except ProcessLookupError: + pass + + +if __name__ == "__main__": + unittest.main() From 363ab7e933c02ae1a37dd4826968e1930cc8ff35 Mon Sep 17 00:00:00 2001 From: AlexanderMitrofanov Date: Mon, 28 Sep 2026 19:39:10 +0200 Subject: [PATCH 4/8] Clean up benchmark children when interrupted --- ChangeLog | 3 +- doc/mdspan-storage.md | 6 ++- tests/benchmark/compare-matrix-storage.py | 10 ++-- .../benchmark/test_compare_matrix_storage.py | 52 ++++++++++++++++--- 4 files changed, 58 insertions(+), 13 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5f660ed0..d357bf53 100644 --- a/ChangeLog +++ b/ChangeLog @@ -63,7 +63,8 @@ + probe native mdspan library support and document the toolchain requirement * tests/Matrix_test.cpp, tests/benchmark/compare-matrix-storage.py: + add storage regression tests and reproducible before/after measurements - + terminate the timed process group on timeout, with a regression check + + terminate the timed process group on timeout or interruption, with + regression checks + include the biological benchmark inputs in source distributions * doc/mdspan-storage.md: + record validation and performance evaluation for issue #246 diff --git a/doc/mdspan-storage.md b/doc/mdspan-storage.md index e3fcadff..57cf717c 100644 --- a/doc/mdspan-storage.md +++ b/doc/mdspan-storage.md @@ -88,7 +88,8 @@ fhlA/OxyS inputs, randomizes execution order, excludes one warmup per binary and case, and retains stdout, stderr and each timing sample. It compares stdout bytes for every execution, failing immediately on a mismatch or process error. Each execution has a 180-second timeout that terminates both GNU time and its -IntaRNA child, retaining any captured stdout and stderr. The biological input +IntaRNA child, retaining any captured stdout and stderr. Interrupting the +benchmark also terminates the process group. The biological input files are included in source distributions so the script also runs from an extracted release archive. It records binary/input hashes, exact arguments, versions, affinity and controlled @@ -113,7 +114,8 @@ triangular storage. This is a bounded, single-host performance experiment. - `make install` installs `Matrix.h`; a separate C++23 consumer including the installed accessibility, Nussinov and seed-extension headers compiles and runs. - Benchmark process regression checks pass, including timeout cleanup of GNU - time's child process. The timeout check fails against the original runner. + time's child process and cleanup on interruption. Both checks reject runners + without the corresponding cleanup. - The benchmark runs from an extracted `make dist` archive: all ten workloads complete with matching outputs (one warmup and one measured run per binary, 40 executions total). This is a packaging smoke check; the performance table diff --git a/tests/benchmark/compare-matrix-storage.py b/tests/benchmark/compare-matrix-storage.py index 38f1a3ab..d91909b8 100644 --- a/tests/benchmark/compare-matrix-storage.py +++ b/tests/benchmark/compare-matrix-storage.py @@ -27,14 +27,18 @@ def run_timed(command, env, timeout=180): stderr=subprocess.PIPE, start_new_session=True) as process: try: stdout, stderr = process.communicate(timeout=timeout) - except subprocess.TimeoutExpired as error: + except BaseException as error: + # The child is in a separate session, so terminal interrupts must + # also trigger explicit cleanup of the complete process group. try: os.killpg(process.pid, signal.SIGKILL) except ProcessLookupError: pass stdout, stderr = process.communicate() - raise subprocess.TimeoutExpired(command, timeout, output=stdout, - stderr=stderr) from error + if isinstance(error, subprocess.TimeoutExpired): + raise subprocess.TimeoutExpired(command, timeout, output=stdout, + stderr=stderr) from error + raise return subprocess.CompletedProcess(command, process.returncode, stdout, stderr) diff --git a/tests/benchmark/test_compare_matrix_storage.py b/tests/benchmark/test_compare_matrix_storage.py index 059f318a..e64d1055 100644 --- a/tests/benchmark/test_compare_matrix_storage.py +++ b/tests/benchmark/test_compare_matrix_storage.py @@ -9,6 +9,7 @@ import tempfile import time import unittest +from unittest.mock import patch sys.dont_write_bytecode = True spec = importlib.util.spec_from_file_location( @@ -18,6 +19,15 @@ class BenchmarkProcessTests(unittest.TestCase): + def assert_child_stopped(self, pid_file): + self.assertTrue(pid_file.exists(), "child must start before testing cleanup") + pid = int(pid_file.read_text()) + try: + state = Path(f"/proc/{pid}/stat").read_text().rsplit(")", 1)[1].split()[0] + except FileNotFoundError: + state = "gone" + self.assertIn(state, ("gone", "Z", "X"), "child is still running") + def test_capture_exit_status_and_output(self): result = benchmark.run_timed( [sys.executable, "-c", "import sys; print('output'); print('error', file=sys.stderr); sys.exit(7)"], @@ -39,13 +49,7 @@ def test_timeout_kills_child_of_time_wrapper(self): os.environ, timeout=2) self.assertLess(time.monotonic() - started, 10) self.assertIn(b"started", caught.exception.output) - self.assertTrue(pid_file.exists(), "child must start before testing cleanup") - pid = int(pid_file.read_text()) - try: - state = Path(f"/proc/{pid}/stat").read_text().rsplit(")", 1)[1].split()[0] - except FileNotFoundError: - state = "gone" - self.assertIn(state, ("gone", "Z", "X"), "timed-out child is still running") + self.assert_child_stopped(pid_file) finally: # Keep the regression test safe even against a broken runner. if pid_file.exists(): @@ -54,6 +58,40 @@ def test_timeout_kills_child_of_time_wrapper(self): except ProcessLookupError: pass + @unittest.skipUnless(sys.platform.startswith("linux"), "uses Linux /proc and GNU time") + def test_interrupt_kills_child_of_time_wrapper(self): + with tempfile.TemporaryDirectory() as tmp: + pid_file = Path(tmp) / "child.pid" + child = ("import os,pathlib,time; pathlib.Path(" + repr(str(pid_file)) + + ").write_text(str(os.getpid())); time.sleep(60)") + communicate = subprocess.Popen.communicate + interrupted = False + + def interrupt_once(process, *args, **kwargs): + nonlocal interrupted + if not interrupted: + interrupted = True + try: + communicate(process, timeout=2) + except subprocess.TimeoutExpired: + raise KeyboardInterrupt from None + return communicate(process, *args, **kwargs) + + started = time.monotonic() + try: + with patch.object(subprocess.Popen, "communicate", interrupt_once): + with self.assertRaises(KeyboardInterrupt): + benchmark.run_timed(["/usr/bin/time", sys.executable, "-c", child], + os.environ, timeout=5) + self.assertLess(time.monotonic() - started, 10) + self.assert_child_stopped(pid_file) + finally: + if pid_file.exists(): + try: + os.kill(int(pid_file.read_text()), signal.SIGKILL) + except ProcessLookupError: + pass + if __name__ == "__main__": unittest.main() From 769606812bd88026ea7a47fcf58353c52823c2c5 Mon Sep 17 00:00:00 2001 From: AlexanderMitrofanov Date: Tue, 29 Sep 2026 19:41:41 +0200 Subject: [PATCH 5/8] Preserve GCC 14 support with bundled Kokkos mdspan fallback --- .github/workflows/build.yml | 32 +- ChangeLog | 18 +- README.md | 21 +- configure.ac | 42 +- doc/Makefile.am | 1 + doc/mdspan-storage-gcc14-samples.tsv | 161 +++ doc/mdspan-storage.md | 141 ++- src/IntaRNA/Matrix.h | 26 +- src/IntaRNA/intarna_config.h.in | 5 + src/Makefile.am | 38 + src/experimental/LICENSE | 228 ++++ .../__p0009_bits/compressed_pair.hpp | 183 +++ src/experimental/__p0009_bits/config.hpp | 305 +++++ .../__p0009_bits/default_accessor.hpp | 44 + .../__p0009_bits/dynamic_extent.hpp | 23 + src/experimental/__p0009_bits/extents.hpp | 688 +++++++++++ .../__p0009_bits/full_extent_t.hpp | 14 + src/experimental/__p0009_bits/layout_left.hpp | 255 ++++ .../__p0009_bits/layout_right.hpp | 251 ++++ .../__p0009_bits/layout_stride.hpp | 679 +++++++++++ src/experimental/__p0009_bits/macros.hpp | 706 +++++++++++ src/experimental/__p0009_bits/mdspan.hpp | 501 ++++++++ .../__p0009_bits/no_unique_address.hpp | 85 ++ .../__p0009_bits/trait_backports.hpp | 120 ++ src/experimental/__p0009_bits/type_list.hpp | 74 ++ src/experimental/__p0009_bits/utility.hpp | 320 +++++ src/experimental/__p1684_bits/mdarray.hpp | 447 +++++++ src/experimental/__p2389_bits/dims.hpp | 12 + .../__p2630_bits/constant_wrapper.hpp | 144 +++ .../__p2630_bits/integral_constant_like.hpp | 144 +++ .../__p2630_bits/strided_slice.hpp | 54 + src/experimental/__p2630_bits/submdspan.hpp | 31 + .../submdspan_canonicalize_slices.hpp | 384 ++++++ .../__p2630_bits/submdspan_extents.hpp | 280 +++++ .../__p2630_bits/submdspan_mapping.hpp | 632 ++++++++++ .../__p2642_bits/layout_padded.hpp | 1075 +++++++++++++++++ .../__p2642_bits/layout_padded_fwd.hpp | 123 ++ src/experimental/mdarray | 15 + src/experimental/mdspan | 26 + src/mdspan/LICENSE | 228 ++++ src/mdspan/mdarray.hpp | 18 + src/mdspan/mdspan.hpp | 30 + 42 files changed, 8547 insertions(+), 57 deletions(-) create mode 100644 doc/mdspan-storage-gcc14-samples.tsv create mode 100644 src/experimental/LICENSE create mode 100644 src/experimental/__p0009_bits/compressed_pair.hpp create mode 100644 src/experimental/__p0009_bits/config.hpp create mode 100644 src/experimental/__p0009_bits/default_accessor.hpp create mode 100644 src/experimental/__p0009_bits/dynamic_extent.hpp create mode 100644 src/experimental/__p0009_bits/extents.hpp create mode 100644 src/experimental/__p0009_bits/full_extent_t.hpp create mode 100644 src/experimental/__p0009_bits/layout_left.hpp create mode 100644 src/experimental/__p0009_bits/layout_right.hpp create mode 100644 src/experimental/__p0009_bits/layout_stride.hpp create mode 100644 src/experimental/__p0009_bits/macros.hpp create mode 100644 src/experimental/__p0009_bits/mdspan.hpp create mode 100644 src/experimental/__p0009_bits/no_unique_address.hpp create mode 100644 src/experimental/__p0009_bits/trait_backports.hpp create mode 100644 src/experimental/__p0009_bits/type_list.hpp create mode 100644 src/experimental/__p0009_bits/utility.hpp create mode 100644 src/experimental/__p1684_bits/mdarray.hpp create mode 100644 src/experimental/__p2389_bits/dims.hpp create mode 100644 src/experimental/__p2630_bits/constant_wrapper.hpp create mode 100644 src/experimental/__p2630_bits/integral_constant_like.hpp create mode 100644 src/experimental/__p2630_bits/strided_slice.hpp create mode 100644 src/experimental/__p2630_bits/submdspan.hpp create mode 100644 src/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp create mode 100644 src/experimental/__p2630_bits/submdspan_extents.hpp create mode 100644 src/experimental/__p2630_bits/submdspan_mapping.hpp create mode 100644 src/experimental/__p2642_bits/layout_padded.hpp create mode 100644 src/experimental/__p2642_bits/layout_padded_fwd.hpp create mode 100644 src/experimental/mdarray create mode 100644 src/experimental/mdspan create mode 100644 src/mdspan/LICENSE create mode 100644 src/mdspan/mdarray.hpp create mode 100644 src/mdspan/mdspan.hpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cdbaa6b0..bb437f12 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,18 +18,21 @@ jobs: fail-fast: false matrix: include: - - name: GCC 16 release + - name: GCC 14 release os: ubuntu-24.04 compiler: gcc configure_debug: --disable-debug - - name: GCC 16 debug + std_mdspan: 0 + - name: GCC 14 debug os: ubuntu-24.04 compiler: gcc configure_debug: --enable-debug + std_mdspan: 0 - name: Apple Clang release os: macos-15 compiler: apple-clang configure_debug: --disable-debug + std_mdspan: 1 steps: - name: Check out sources @@ -42,25 +45,22 @@ jobs: environment-file: conda-build-env.yml activate-environment: intarna-build-env - - name: Install and select GCC 16 + - name: Install and select GCC 14 if: matrix.compiler == 'gcc' run: | - # GCC 16's libstdc++ provides the required C++23 std::mdspan. - # Use implementation packages so conda activation does not override - # configure's release/debug optimization flags. - conda install --yes --override-channels -c conda-forge -c bioconda \ - "gcc_impl_linux-64=16" "gxx_impl_linux-64=16" - echo "CC=${CONDA_PREFIX}/bin/x86_64-conda-linux-gnu-gcc" >> "$GITHUB_ENV" - echo "CXX=${CONDA_PREFIX}/bin/x86_64-conda-linux-gnu-g++" >> "$GITHUB_ENV" + sudo apt-get update + sudo apt-get install --yes gcc-14 g++-14 + echo "CC=gcc-14" >> "$GITHUB_ENV" + echo "CXX=g++-14" >> "$GITHUB_ENV" echo "LDFLAGS=-L${CONDA_PREFIX}/lib -Wl,-rpath,${CONDA_PREFIX}/lib" >> "$GITHUB_ENV" - - name: Verify GCC 16 + - name: Verify GCC 14 if: matrix.compiler == 'gcc' run: | "$CC" --version "$CXX" --version - test "$("$CC" -dumpfullversion | cut -d. -f1)" = 16 - test "$("$CXX" -dumpfullversion | cut -d. -f1)" = 16 + test "$("$CC" -dumpfullversion | cut -d. -f1)" = 14 + test "$("$CXX" -dumpfullversion | cut -d. -f1)" = 14 - name: Set up Apple Clang with OpenMP if: matrix.compiler == 'apple-clang' @@ -100,6 +100,8 @@ jobs: ${{ matrix.configure_debug }} grep -Eq '^CXX = .* -std=c\+\+23([[:space:]]|$)' Makefile grep -q '^#define INTARNA_MULITHREADING 1' src/config.h + grep -q '^#define INTARNA_USE_STD_MDSPAN ${{ matrix.std_mdspan }}$' src/config.h + grep -q '^#define INTARNA_USE_STD_MDSPAN ${{ matrix.std_mdspan }}$' src/IntaRNA/intarna_config.h - name: Build, install, and run full test suite run: | @@ -122,7 +124,9 @@ jobs: - name: Compile and link an installed pkg-config consumer run: | consumer="$RUNNER_TEMP/intarna-consumer.cpp" - printf '#include \nint main() { IntaRNA::RnaSequence rna("test", "ACGU"); return rna.size() == 4 ? 0 : 1; }\n' > "$consumer" + printf '#include \n#include \nint main() { IntaRNA::RnaSequence rna("test", "ACGU"); IntaRNA::Matrix matrix(2, 3); matrix(1, 2) = 42; return rna.size() == 4 && matrix(1, 2) == 42 ? 0 : 1; }\n' > "$consumer" + test -f "$RUNNER_TEMP/intarna-install/include/IntaRNA/mdspan/LICENSE" + test -f "$RUNNER_TEMP/intarna-install/include/IntaRNA/experimental/LICENSE" export PKG_CONFIG_PATH="$RUNNER_TEMP/intarna-install/lib/pkgconfig" "$CXX" -std=c++23 ${CPPFLAGS:-} ${CXXFLAGS:-} "$consumer" \ ${LDFLAGS:-} $(pkg-config --cflags --libs IntaRNA) \ diff --git a/ChangeLog b/ChangeLog index d357bf53..2ab28678 100644 --- a/ChangeLog +++ b/ChangeLog @@ -24,11 +24,12 @@ ## Technical changes and Optimizations - replace uBLAS storage matrices with std::vector and std::mdspan, retaining - compact upper-band and triangular storage; configure checks mdspan support + compact upper-band and triangular storage; configure selects native mdspan + or bundled Kokkos headers while preserving GCC 14 support - reduce temporary allocations and fix cleanup on error paths - remove redundant accessibility checks from predictor, helix and seed handling -- C++23 required; build and installation checks with GCC 16 and Apple Clang +- C++23 required; build and installation checks with GCC 14 and Apple Clang - avoid repeated partition-map lookups in exact ensemble prediction - add exhaustive interaction and seed oracles for short-sequence tests - reduced memory use for exact non-seed ensemble prediction without trackers @@ -54,6 +55,19 @@ ################################################################################ ################################################################################ +260929 + * configure.ac, IntaRNA/Matrix.h, IntaRNA/intarna_config.h.in: + + prefer native mdspan, falling back to bundled Kokkos with a public + INTARNA_USE_STD_MDSPAN define; allow explicit --with-mdspan selection + * src/mdspan, src/experimental: + + vendor only the Kokkos headers and both copies of its license from + stable revision 8989f70749e28f337e6f7aa210db88659dba6f2f + + distribute and install the headers with IntaRNA for external consumers + * CI, README.md, doc/mdspan-storage.md: + + restore GCC 14 release/debug builds and document automatic fallback + + record a clean GCC 14/Kokkos comparison against uBLAS with all outputs + matching across 160 executions; retain the original native measurements + 260928 * IntaRNA/Matrix.h and storage aliases: + replace uBLAS dense, upper-band and upper-triangular storage with owned diff --git a/README.md b/README.md index 55009e0a..1b35f4d5 100644 --- a/README.md +++ b/README.md @@ -180,9 +180,10 @@ you with an encapsulated IntaRNA installation. If you are going to compile IntaRNA from source, ensure you meet the following dependencies: -- compiler supporting C++23 and OpenMP, with a standard library providing - `std::mdspan` (for example GCC 16/libstdc++ or Clang 18/libc++; configure - checks the actual library support) +- compiler supporting C++23 and OpenMP (GCC 14 or Apple Clang). Configure + prefers native `std::mdspan` when available and otherwise uses the bundled + header-only [Kokkos mdspan implementation](https://github.com/kokkos/mdspan). + No separate mdspan installation or compiler upgrade is required. - [boost C++ library](http://www.boost.org/) version >= 1.50.0 (ensure the following libraries are installed for development (not just runtime libraries!); or install all e.g. in Ubuntu via package `libboost-all-dev`) - libboost_regex @@ -2188,12 +2189,21 @@ processed using doxygen to generate html/pdf versions. When IntaRNA is build while `pkg-config` is present, according pkg-config information is generated and installed too. -IntaRNA's public headers require C++23, including `` support in the -standard library. External consumers must therefore +IntaRNA's public headers require C++23. External consumers must therefore select `-std=c++23` (or a newer standard) in their own build; the installed pkg-config metadata supplies IntaRNA and dependency flags but deliberately does not override the consuming project's language mode. +Configure records the selected mdspan implementation in the installed +`IntaRNA/intarna_config.h`. By default (`--with-mdspan=auto`), native +`std::mdspan` is preferred; the bundled Kokkos implementation is selected if +the standard library does not provide the required API. Use +`--with-mdspan=std` or `--with-mdspan=kokkos` to require a particular backend. +The Kokkos headers and licenses are installed below `include/IntaRNA/mdspan` +and `include/IntaRNA/experimental`, so the public matrix header works without +adding another include path. See [the storage report](doc/mdspan-storage.md) +for the pinned upstream revision, validation, and performance results. + ## Mandatory `Easylogging++` initalization ! Since IntaRNA makes heavy use of the `Easylogging++` library, you have to add (and adapt) @@ -2239,4 +2249,3 @@ flags are used within the IntaRNA configuration: [![no](doc/figures/icon-no.39.png)](https://www.freepik.com/free-vector/icons-collection_1638275.htm) [![up](doc/figures/icon-up.38.png)](https://www.freepik.com/free-vector/colored-arrows_794372.htm) Designed by Freepik - diff --git a/configure.ac b/configure.ac index f958f06b..7d16aafd 100644 --- a/configure.ac +++ b/configure.ac @@ -87,7 +87,17 @@ sink << stream.view(); AC_MSG_ERROR([A C++23 standard library providing std::string::contains, std::string::resize_and_overwrite, and std::stringstream::view is required. Upgrade libstdc++/libc++ or select a newer C++23 toolchain.])]) CXXFLAGS=$intarna_saved_CXXFLAGS -# Require the standard C++23 mdspan API used by the owning storage matrices. +# Prefer the standard implementation, falling back to bundled Kokkos headers. +# The selected backend is also recorded in the installed public configuration. +AC_ARG_WITH([mdspan], + [AS_HELP_STRING([--with-mdspan=auto|std|kokkos], + [select mdspan implementation (def=auto: prefer std, otherwise Kokkos)])], + [intarna_mdspan="$withval"], [intarna_mdspan=auto]) +AS_CASE([$intarna_mdspan], [auto|std|kokkos], [], + [AC_MSG_ERROR([--with-mdspan must be auto, std, or kokkos])]) + +INTARNA_USE_STD_MDSPAN=0 +AS_IF([test x"$intarna_mdspan" != x"kokkos"], [ AC_MSG_CHECKING([for C++23 std::mdspan over std::vector]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM( @@ -101,9 +111,35 @@ std::mdspan> matrix(storage.data(), 2, 3); matrix[1, 2] = 42; std::mdspan> packed(storage.data(), 6); return packed[5] != 42;]])], - [AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([yes]) + INTARNA_USE_STD_MDSPAN=1], [AC_MSG_RESULT([no]) - AC_MSG_ERROR([A C++23 standard library providing , std::mdspan, and std::dextents is required. Select a toolchain with mdspan support (for example GCC 16/libstdc++ or Clang 18/libc++).])]) + AS_IF([test x"$intarna_mdspan" = x"std"], + [AC_MSG_ERROR([--with-mdspan=std requires a C++23 standard library providing std::mdspan. Use --with-mdspan=auto or --with-mdspan=kokkos for the bundled implementation.])])]) +]) + +AS_IF([test "$INTARNA_USE_STD_MDSPAN" = 0], [ + AC_MSG_CHECKING([for bundled Kokkos mdspan over std::vector]) + intarna_saved_CPPFLAGS=$CPPFLAGS + CPPFLAGS="-I$srcdir/src $CPPFLAGS" + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [[#include "mdspan/mdspan.hpp" +#include +namespace intarna_mdspan = MDSPAN_IMPL_STANDARD_NAMESPACE;]], + [[std::vector storage(6); +intarna_mdspan::mdspan> matrix(storage.data(), 2, 3); +matrix[1, 2] = 42; +intarna_mdspan::mdspan> packed(storage.data(), 6); +return packed[5] != 42;]])], + [AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no]) + AC_MSG_ERROR([The bundled Kokkos mdspan headers do not compile with this C++23 toolchain. See config.log and ensure src/mdspan and src/experimental are complete.])]) + CPPFLAGS=$intarna_saved_CPPFLAGS +]) +AC_DEFINE_UNQUOTED([INTARNA_USE_STD_MDSPAN], [$INTARNA_USE_STD_MDSPAN], + [Use standard mdspan (1) or the bundled Kokkos implementation (0)]) +AC_SUBST([INTARNA_USE_STD_MDSPAN]) # check if python is available AM_PATH_PYTHON([$PYTHON_REQUIRED_VERSION],, [:]) diff --git a/doc/Makefile.am b/doc/Makefile.am index 48b958af..854a9e2b 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -6,6 +6,7 @@ EXTRA_DIST = \ mdspan-storage.md \ mdspan-storage-samples.tsv \ + mdspan-storage-gcc14-samples.tsv \ handson/fhlA.fasta \ handson/OxyS.fasta \ conda.txt \ diff --git a/doc/mdspan-storage-gcc14-samples.tsv b/doc/mdspan-storage-gcc14-samples.tsv new file mode 100644 index 00000000..fa992258 --- /dev/null +++ b/doc/mdspan-storage-gcc14-samples.tsv @@ -0,0 +1,161 @@ +case variant repetition warmup wall_s user_s system_s max_rss_kib sha256 +banded-narrow baseline 0 True 1.0175709730247036 1.01 0.0 17620 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +biological-default candidate 0 True 0.06816842698026448 0.05 0.0 16688 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +seed-extension baseline 0 True 0.16348259994992986 0.15 0.0 18144 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +seed-extension candidate 0 True 0.16551056498428807 0.15 0.0 18276 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +exact-ensemble candidate 0 True 1.3399355449946597 1.33 0.0 16124 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +exact-ensemble baseline 0 True 1.4030546749709174 1.39 0.0 16252 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +banded-narrow candidate 0 True 1.091954346979037 1.08 0.0 17880 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +triangular-base-pair candidate 0 True 3.2874780479469337 3.16 0.0 14208 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +helix-block baseline 0 True 0.24749278998933733 0.23 0.0 18564 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +exact-mfe candidate 0 True 0.9364550879690796 0.92 0.0 16000 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +banded-default candidate 0 True 1.5406065779970959 1.4 0.0 20108 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +dense-no-accessibility baseline 0 True 6.261506015027408 5.87 0.0 15160 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +seed-bulges candidate 0 True 1.7076850730227306 1.63 0.01 24060 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +biological-default baseline 0 True 0.07223369099665433 0.06 0.0 16936 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +exact-mfe baseline 0 True 1.0313147650449537 0.98 0.0 16256 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +seed-bulges baseline 0 True 1.6173892429796979 1.45 0.01 24188 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +dense-no-accessibility candidate 0 True 6.744421374984086 6.34 0.01 15036 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +triangular-base-pair baseline 0 True 3.567371099954471 3.38 0.0 14208 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +banded-default baseline 0 True 1.5650854969862849 1.52 0.0 19972 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +helix-block candidate 0 True 0.2884423800278455 0.24 0.01 18312 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +banded-default baseline 1 False 1.6061385410139337 1.52 0.0 20104 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +exact-mfe baseline 1 False 1.0887995199882425 1.04 0.0 16248 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +seed-extension baseline 1 False 0.19031108199851587 0.16 0.0 18268 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +banded-narrow baseline 1 False 1.1896683539962396 1.09 0.0 17616 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +helix-block baseline 1 False 0.2582533569657244 0.23 0.0 18436 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +exact-mfe candidate 1 False 1.0785772500094026 1.0 0.0 16076 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +dense-no-accessibility candidate 1 False 6.373841391992755 5.87 0.0 14908 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +biological-default candidate 1 False 0.07256078702630475 0.05 0.0 16940 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +seed-extension candidate 1 False 0.18530136201297864 0.16 0.0 18272 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +seed-bulges baseline 1 False 1.5445343930041417 1.47 0.01 24060 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +helix-block candidate 1 False 0.27777132799383253 0.25 0.0 18436 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +biological-default baseline 1 False 0.07170115801272914 0.06 0.0 16940 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +banded-narrow candidate 1 False 1.149130971985869 1.12 0.0 17880 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +triangular-base-pair baseline 1 False 3.6899603930069134 3.48 0.0 14336 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +dense-no-accessibility baseline 1 False 5.894565894966945 5.59 0.0 15028 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +banded-default candidate 1 False 1.4979209540179 1.4 0.0 19976 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +exact-ensemble baseline 1 False 1.417933996010106 1.32 0.0 16252 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +exact-ensemble candidate 1 False 1.4350046269828454 1.32 0.0 16128 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +triangular-base-pair candidate 1 False 3.312924658996053 3.14 0.0 14208 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +seed-bulges candidate 1 False 1.4406663040281273 1.36 0.01 23936 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +seed-extension candidate 2 False 0.1772772740223445 0.15 0.0 18144 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +triangular-base-pair baseline 2 False 3.3842190469731577 3.23 0.0 14336 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +biological-default candidate 2 False 0.09364493901375681 0.07 0.0 16944 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +biological-default baseline 2 False 0.0807784220087342 0.06 0.01 16680 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +dense-no-accessibility baseline 2 False 5.697911281022243 5.55 0.01 14904 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +seed-bulges candidate 2 False 1.5099837940069847 1.35 0.01 24064 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +helix-block candidate 2 False 0.2634370890446007 0.22 0.0 18440 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +exact-mfe candidate 2 False 0.9962634869734757 0.96 0.0 16256 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +banded-default candidate 2 False 1.5098769220057875 1.44 0.01 20104 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +exact-mfe baseline 2 False 1.0026157579850405 0.96 0.0 16124 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +seed-extension baseline 2 False 0.17642708600033075 0.15 0.0 18268 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +banded-narrow candidate 2 False 1.0967182379681617 1.04 0.0 17876 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +helix-block baseline 2 False 0.26352469000266865 0.24 0.0 18432 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +triangular-base-pair candidate 2 False 3.2632837949786335 3.17 0.0 14336 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +dense-no-accessibility candidate 2 False 6.152719909965526 5.89 0.0 14908 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +seed-bulges baseline 2 False 1.5165449669584632 1.42 0.01 23804 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +exact-ensemble candidate 2 False 1.4548414719756693 1.35 0.0 16128 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +banded-default baseline 2 False 1.608469870989211 1.5 0.0 20104 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +exact-ensemble baseline 2 False 1.415179308038205 1.32 0.0 16120 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +banded-narrow baseline 2 False 1.1256846460164525 1.03 0.0 17744 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +seed-bulges candidate 3 False 1.489151427987963 1.38 0.0 24260 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +exact-ensemble candidate 3 False 1.4297777220490389 1.33 0.0 16176 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +dense-no-accessibility candidate 3 False 6.052590238978155 5.84 0.01 15032 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +banded-narrow baseline 3 False 1.1298291439888999 1.04 0.0 17876 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +biological-default baseline 3 False 0.07192940404638648 0.05 0.0 16812 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +dense-no-accessibility baseline 3 False 5.863939203962218 5.63 0.0 15288 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +banded-default candidate 3 False 1.510665556997992 1.45 0.0 20104 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +helix-block candidate 3 False 0.2827858399832621 0.25 0.0 18436 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +seed-extension candidate 3 False 0.1910666250041686 0.16 0.0 18276 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +seed-extension baseline 3 False 0.19337377196643502 0.16 0.0 18272 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +helix-block baseline 3 False 0.26803521695546806 0.23 0.0 18308 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +banded-narrow candidate 3 False 1.1367247210000642 1.04 0.0 17624 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +banded-default baseline 3 False 1.5544714279822074 1.44 0.01 20100 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +biological-default candidate 3 False 0.07461705099558458 0.05 0.0 16688 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +triangular-base-pair candidate 3 False 3.3672959720133804 3.22 0.0 14208 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +exact-ensemble baseline 3 False 1.407122325967066 1.34 0.0 15992 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +seed-bulges baseline 3 False 1.483391012006905 1.39 0.01 23928 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +exact-mfe candidate 3 False 1.0196299880044535 0.98 0.0 16128 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +exact-mfe baseline 3 False 1.0317744010244496 0.98 0.0 16252 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +triangular-base-pair baseline 3 False 3.3627750229788944 3.22 0.0 14208 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +seed-bulges baseline 4 False 1.6409796279622242 1.53 0.01 23932 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +banded-narrow candidate 4 False 1.2371531950193457 1.1 0.0 17752 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +dense-no-accessibility baseline 4 False 5.989261720038485 5.63 0.0 15028 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +exact-ensemble baseline 4 False 1.4578646600130014 1.31 0.0 15996 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +seed-extension candidate 4 False 0.1782154850079678 0.15 0.0 18276 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +dense-no-accessibility candidate 4 False 6.379806904005818 5.8 0.0 15036 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +exact-mfe baseline 4 False 1.0296550339553505 0.97 0.0 16124 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +exact-ensemble candidate 4 False 1.406764439016115 1.31 0.0 16128 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +exact-mfe candidate 4 False 1.023378943966236 0.97 0.0 16124 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +banded-narrow baseline 4 False 1.125592545955442 1.04 0.0 17876 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +seed-extension baseline 4 False 0.17410698300227523 0.14 0.0 18144 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +helix-block candidate 4 False 0.2614761400036514 0.22 0.0 18308 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +triangular-base-pair candidate 4 False 3.3469028839608654 3.13 0.0 14208 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +triangular-base-pair baseline 4 False 3.4757101840223186 3.15 0.0 14336 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +biological-default candidate 4 False 0.07227770600002259 0.05 0.0 16684 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +banded-default baseline 4 False 1.4561127950437367 1.4 0.0 20100 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +helix-block baseline 4 False 0.2455635599908419 0.23 0.0 18180 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +banded-default candidate 4 False 1.463774676027242 1.45 0.0 20104 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +biological-default baseline 4 False 0.06684111303184181 0.05 0.0 16556 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +seed-bulges candidate 4 False 1.3990389379905537 1.38 0.01 24068 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +helix-block candidate 5 False 0.24328742804937065 0.23 0.0 18564 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +banded-default candidate 5 False 1.457287246012129 1.44 0.0 20108 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +banded-narrow candidate 5 False 1.0636478069936857 1.05 0.0 17624 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +dense-no-accessibility baseline 5 False 8.042433125025127 8.02 0.01 15032 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +banded-default baseline 5 False 2.2961037189816125 2.28 0.01 20236 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +triangular-base-pair candidate 5 False 5.253148886957206 5.24 0.0 14336 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +exact-mfe candidate 5 False 1.013881027000025 1.0 0.0 16124 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +triangular-base-pair baseline 5 False 4.639752815011889 4.56 0.0 14464 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +seed-bulges baseline 5 False 2.1358250789926387 2.11 0.01 24060 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +biological-default baseline 5 False 0.10530924802878872 0.09 0.0 16812 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +banded-narrow baseline 5 False 1.2632286539883353 1.15 0.01 17744 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +seed-bulges candidate 5 False 1.4641846660524607 1.38 0.01 24060 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +dense-no-accessibility candidate 5 False 5.9376585289719515 5.65 0.0 15164 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +helix-block baseline 5 False 0.2664690560195595 0.23 0.0 18560 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +seed-extension candidate 5 False 0.1621473070117645 0.15 0.0 18144 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +exact-ensemble baseline 5 False 1.4241110099828802 1.33 0.0 16252 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +seed-extension baseline 5 False 0.1670123590156436 0.15 0.0 18396 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +exact-ensemble candidate 5 False 1.403439766028896 1.33 0.0 16256 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +exact-mfe baseline 5 False 1.009884756989777 0.96 0.0 16248 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +biological-default candidate 5 False 0.07437305001076311 0.05 0.0 16816 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +dense-no-accessibility candidate 6 False 6.047762565955054 5.69 0.0 15036 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +banded-default baseline 6 False 1.4979784530005418 1.42 0.0 20100 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +banded-narrow baseline 6 False 1.1325834110029973 1.06 0.01 17748 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +seed-bulges candidate 6 False 1.4732049110461958 1.37 0.0 24064 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +biological-default baseline 6 False 0.07230963802430779 0.05 0.0 17064 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +exact-mfe baseline 6 False 1.1652475110022351 1.07 0.0 16256 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +exact-mfe candidate 6 False 1.0783228949876502 0.98 0.0 16124 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +banded-narrow candidate 6 False 1.1679704209673218 1.05 0.0 17620 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +biological-default candidate 6 False 0.07211326999822631 0.06 0.0 16688 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +helix-block candidate 6 False 0.26655097800539806 0.23 0.0 18436 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +seed-extension candidate 6 False 0.17905117897316813 0.15 0.0 18148 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +triangular-base-pair baseline 6 False 3.526964917022269 3.25 0.0 14464 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +exact-ensemble baseline 6 False 1.5063605409814045 1.36 0.0 16124 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +seed-bulges baseline 6 False 1.5509218230145052 1.37 0.01 23804 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +seed-extension baseline 6 False 0.1738699830020778 0.14 0.0 18396 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +triangular-base-pair candidate 6 False 3.3494180080015212 3.2 0.0 14080 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +helix-block baseline 6 False 0.2776018299628049 0.23 0.01 18440 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +dense-no-accessibility baseline 6 False 5.7911512440186925 5.6 0.0 15028 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +banded-default candidate 6 False 1.5641094439779408 1.45 0.0 20108 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +exact-ensemble candidate 6 False 1.3904496340546757 1.34 0.0 16252 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +exact-ensemble baseline 7 False 1.3864627809962258 1.34 0.0 16124 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +biological-default candidate 7 False 0.07277323195012286 0.05 0.01 16688 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +seed-extension baseline 7 False 0.1788350170245394 0.15 0.0 18400 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +exact-ensemble candidate 7 False 1.4735827209660783 1.41 0.0 15996 8c280585610d6e7d0b69feae5d9b0119ee2d3571f0931612039d3860f41e629e +seed-bulges candidate 7 False 1.5722435250063427 1.5 0.01 23808 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +helix-block candidate 7 False 0.29012252000393346 0.25 0.0 18564 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +banded-narrow baseline 7 False 1.0549209419987164 1.03 0.0 17748 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +seed-extension candidate 7 False 0.17241018504137173 0.14 0.0 18144 4e29dac3b549f78a2f50133b3b5c56f9a841843555ad8be817c5d3d6f92ede7e +exact-mfe baseline 7 False 1.0320514210034162 0.95 0.0 16124 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +biological-default baseline 7 False 0.06618713797070086 0.05 0.0 16936 105141fde35a982cbc48d6150d4e45119905956458aeffe76ec88ca142eb5ed8 +seed-bulges baseline 7 False 1.5031366019975394 1.42 0.01 24060 bd8537347a02eb0f8b3ceb3e00135db2ee8fc46e443aa4343075133cb8174d0e +helix-block baseline 7 False 0.2854920930112712 0.26 0.0 18436 d5b1d130f7b7d2f63a2538e9c11911ba0d4342b8a0e1ed999b59aa46d8a075ce +triangular-base-pair baseline 7 False 3.618951381999068 3.45 0.0 14464 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +dense-no-accessibility candidate 7 False 6.2590762979816645 5.81 0.01 15032 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +dense-no-accessibility baseline 7 False 6.219702025002334 5.62 0.0 15032 5451c638e087cc3fff0d2d35dcdd323c9ccdee1470822a87332c4ac3d856d458 +banded-default candidate 7 False 1.5159529200172983 1.46 0.0 20104 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 +exact-mfe candidate 7 False 1.0511699609924108 0.96 0.0 16252 b9c138c8a0a452800ca6840c8700d34be78bfb5ecb2a19b4b0f10311c2868d1f +banded-narrow candidate 7 False 1.096401096030604 1.0 0.0 17620 5329bce563470bb3905c5b7588ea9b7b22107be1f2abc65e2fca3e509a1d5250 +triangular-base-pair candidate 7 False 3.5332374490099028 3.31 0.0 14464 ee2d3d866c851d9d03f02a25e84ec706f4629ca2c293f9901789b0fdd6026c04 +banded-default baseline 7 False 1.4974612219957635 1.35 0.0 19972 2ab6aafe387def9f392426fdeeed93652895e5d4f93fc9d3e6793ec7368faef9 diff --git a/doc/mdspan-storage.md b/doc/mdspan-storage.md index 57cf717c..83279724 100644 --- a/doc/mdspan-storage.md +++ b/doc/mdspan-storage.md @@ -1,11 +1,14 @@ # Vector/mdspan storage evaluation (issue #246) -The migration preserves scientific output and compact storage. In the measured -single-host experiment, performance is effectively neutral: median wall-time -changes range from 1.7% faster to 1.2% slower, with similar peak memory use. -There is no substantial performance gain to justify the stricter toolchain -requirement by itself. Treat this as a storage modernization; retain the -measurements below when deciding whether to adopt the new build requirement. +The migration preserves scientific output and compact storage while retaining +GCC 14 support through bundled Kokkos headers when native mdspan is unavailable. +In the GCC 14 experiment, median wall-time changes range from 5.0% faster to +4.4% slower across workloads, with similar peak memory use. Performance is +mixed; these measurements do not establish a general speedup. + +The earlier native-mdspan experiment with GCC 16 was effectively neutral +(1.7% faster to 1.2% slower). Both experiments, toolchains, and raw sample sets +are kept separately below. Treat this as a storage modernization. ## Scope and representation @@ -42,20 +45,39 @@ square and banded matrices must have lower bandwidth zero. ## Build requirements -Configure compiles actual mutable 2D and const 1D `std::mdspan` accesses over -vector storage. An unsupported standard library fails with an actionable error, -including when its compiler accepts `-std=c++23`. Public-header consumers also -need `` and C++23. - -Native support is available in [GCC 16/libstdc++](https://gcc.gnu.org/onlinedocs/libstdc++/manual/status.html) -and [LLVM 18/libc++](https://releases.llvm.org/18.1.8/projects/libcxx/docs/Status/Cxx23.html). -The local validation below uses GCC. GitHub Actions also passed the Apple Clang -17 release build on macOS 15, including the full test suite, independent public -header compilation, and the installed pkg-config consumer ([CI run](https://github.com/BackofenLab/IntaRNA/actions/runs/36453446578/job/109033658550)). -The Linux CI jobs select GCC 16 from conda-forge: GCC 14's standard library -does not provide mdspan and correctly fails the configure check. Compiler -implementation packages avoid activation hooks overriding configure's release -and debug optimization flags. +The existing C++23/OpenMP requirement is unchanged: GCC 14 and Apple Clang +remain supported. Configure first compiles actual mutable 2D and const 1D +`std::mdspan` accesses over vector storage. If the native API is unavailable, +it compiles the same accesses using the bundled Kokkos implementation. +`INTARNA_USE_STD_MDSPAN` is set to `1` for native mdspan or `0` for Kokkos in +both the build configuration and the installed `IntaRNA/intarna_config.h`. +`Matrix.h` includes that public configuration and selects the corresponding +header and namespace; Kokkos types are not injected into `std`. + +The default `--with-mdspan=auto` chooses native mdspan when supported. +`--with-mdspan=std` requires the native API and reports an actionable error +if it is unavailable. `--with-mdspan=kokkos` selects the bundled headers even +on a toolchain with native support, allowing reproducible backend comparisons. +Both implementations use the same owning containers and storage tests. + +The vendored headers are copied unchanged from the latest +[Kokkos mdspan stable revision `8989f70749e28f337e6f7aa210db88659dba6f2f`](https://github.com/kokkos/mdspan/tree/8989f70749e28f337e6f7aa210db88659dba6f2f) +(as of 2026-09-29). Only upstream `include/mdspan` and `include/experimental` +headers are stored in `src/mdspan` and `src/experimental`. Both folders contain +an exact copy of the upstream `LICENSE` (Apache-2.0 WITH LLVM-exception). +No upstream build system, tests, benchmarks, or generated artifacts are +imported. Header-only use needs no additional upstream resources. + +Source distributions contain both complete header trees and licenses. +Installation preserves their relative include layout under +`include/IntaRNA/mdspan` and `include/IntaRNA/experimental`, avoiding global +installation into another project's `mdspan` or `experimental` include tree. +Installed consumers use the configured backend and the existing IntaRNA +include path; no additional mdspan package or include flag is needed. + +Linux CI retains its GCC 14 release/debug layout and checks automatic Kokkos +selection. Apple Clang CI checks native selection. Both compile every installed +public header independently and link/run an installed matrix consumer. Boost remains a dependency for other project components and for the independent reference containers in the storage regression tests. @@ -98,7 +120,24 @@ GNU time. The test cases cover accessibility bands, dense predictors, seed bulges, helix blocks, exact MFE, exact ensemble, seed extension and Nussinov triangular storage. This is a bounded, single-host performance experiment. -## Validation on 2026-09-28 +## Backend-selection validation on 2026-09-29 + +- GCC 14.4 selects bundled Kokkos automatically; the release build passes + 33,264 assertions in 41 API cases and all 20 CLI golden cases. +- Native GCC 16.2 and bundled Kokkos/GCC 14.4 each pass the 28,923 storage + assertions with AddressSanitizer and UndefinedBehaviorSanitizer. LeakSanitizer + is disabled because of the sandbox's process-tracing limit. +- Configure checks cover GCC 14 automatic fallback, GCC 16 native selection, + forced Kokkos on GCC 16, and actionable failures for forced native on GCC 14, + an invalid backend option, and a missing bundled header tree. +- An installed GCC 14 consumer includes only installed IntaRNA/dependency + headers, exercises all three matrix types, and links/runs successfully. +- All 66 installed public headers compile independently with GCC 14. +- All 30 imported headers and both license copies match pinned upstream bytes. +- An extracted source archive contains the complete bundled headers, licenses, + and GCC 14 samples; configure selects Kokkos and its matrix consumer runs. + +## Original native-backend validation on 2026-09-28 - Parent release: 4,341 assertions in 37 API cases; all 20 CLI golden cases pass. - Candidate release and debug (`--enable-debug`): 33,264 assertions in 41 API @@ -108,9 +147,9 @@ triangular storage. This is a bounded, single-host performance experiment. -fsanitize=address,undefined -fno-omit-frame-pointer`). LeakSanitizer was disabled because the execution sandbox does not support its process tracing; no claim of leak-sanitizer coverage is made. -- Native GCC 16.2 configure probe passes. A GCC 16.2 configure run with an - intentionally unavailable `` fails at the new probe with the expected - diagnostic, after the other C++23 checks pass. +- The original native-only configure probe passed on GCC 16.2 and rejected + a simulated missing ``. The current automatic mode instead selects + Kokkos when native support is unavailable, as checked above. - `make install` installs `Matrix.h`; a separate C++23 consumer including the installed accessibility, Nussinov and seed-extension headers compiles and runs. - Benchmark process regression checks pass, including timeout cleanup of GNU @@ -130,7 +169,7 @@ explicitly before comparison; its banded preserving resize is also unsuitable as an oracle for some rectangular/empty transitions, so that reference is rebuilt explicitly from overlapping cells. -## Same-host performance results +## Original native-backend performance results (GCC 16) Baseline: upstream `c5823e0`. Candidate: the storage migration in this change. @@ -188,3 +227,55 @@ toolchain. They do not establish a universal speedup. Short biological runs include substantial startup overhead; differences near the observed spread should be treated as inconclusive. Clang/macOS and other CPU architectures still need their own measurements. + +## Bundled Kokkos performance results (GCC 14, 2026-09-29) + +The parent `c5823e0` and current candidate were rebuilt cleanly with the same +GCC 14.4.0 compiler and libstdc++ headers, Boost 1.85.0, ViennaRNA 2.7.2, +`-O3 -fno-strict-aliasing`, and OpenMP. Both link to the same dependency-prefix +libstdc++ 16.2 runtime; mdspan availability is determined by the GCC 14 headers. +The candidate automatically selects the pinned Kokkos backend. The host, CPU 2 +affinity, workload arguments, random seed, environment controls, one warmup and +seven measured runs are as described above. No builds, header compilation or +sanitizer checks overlap these timings. + +All 160 executions produce matching stdout for their respective workloads. +Median wall-time changes range from -5.0% to +4.4%. +These observations apply to this host and workload set; they do not establish +a universal performance improvement. + +| Workload | uBLAS time (s) | Kokkos time (s) | Change | uBLAS / Kokkos RSS (MiB) | +| --- | ---: | ---: | ---: | ---: | +| biological-default | 0.0719 | 0.0728 | +1.2% | 16.42 / 16.30 | +| banded-default | 1.5545 | 1.5099 | -2.9% | 19.63 / 19.63 | +| banded-narrow | 1.1298 | 1.1367 | +0.6% | 17.33 / 17.21 | +| dense-no-accessibility | 5.8946 | 6.1527 | +4.4% | 14.68 / 14.68 | +| seed-bulges | 1.5445 | 1.4732 | -4.6% | 23.37 / 23.50 | +| helix-block | 0.2665 | 0.2666 | +0.0% | 18.00 / 18.00 | +| exact-mfe | 1.0318 | 1.0234 | -0.8% | 15.87 / 15.75 | +| exact-ensemble | 1.4179 | 1.4298 | +0.8% | 15.75 / 15.75 | +| seed-extension | 0.1764 | 0.1782 | +1.0% | 17.84 / 17.72 | +| triangular-base-pair | 3.5270 | 3.3494 | -5.0% | 14.00 / 13.88 | + +Measured wall-time ranges across the seven samples: + +| Workload | uBLAS (s) | Kokkos (s) | +| --- | ---: | ---: | +| biological-default | 0.0662–0.1053 | 0.0721–0.0936 | +| banded-default | 1.4561–2.2961 | 1.4573–1.5641 | +| banded-narrow | 1.0549–1.2632 | 1.0636–1.2372 | +| dense-no-accessibility | 5.6979–8.0424 | 5.9377–6.3798 | +| seed-bulges | 1.4834–2.1358 | 1.3990–1.5722 | +| helix-block | 0.2456–0.2855 | 0.2433–0.2901 | +| exact-mfe | 1.0026–1.1652 | 0.9963–1.0786 | +| exact-ensemble | 1.3865–1.5064 | 1.3904–1.4736 | +| seed-extension | 0.1670–0.1934 | 0.1621–0.1911 | +| triangular-base-pair | 3.3628–4.6398 | 3.2633–5.2531 | + +All samples, warmups, and output hashes are in +[mdspan-storage-gcc14-samples.tsv](mdspan-storage-gcc14-samples.tsv). + +Binary SHA-256 values: + +- baseline: `f2d3293f7598980f4e539e925e1cac4bba35b08af688735d5d67250733329571` +- candidate: `b4842a322fa67d61990a3e4a623e19e3c5a049f2bf4d4db3213615c18b900b17` diff --git a/src/IntaRNA/Matrix.h b/src/IntaRNA/Matrix.h index 34bec953..375a6d8b 100644 --- a/src/IntaRNA/Matrix.h +++ b/src/IntaRNA/Matrix.h @@ -5,14 +5,26 @@ #include #include #include -#include #include #include #include +#include "IntaRNA/intarna_config.h" +#if INTARNA_USE_STD_MDSPAN +#include +#else +#include "mdspan/mdspan.hpp" +#endif + namespace IntaRNA { namespace matrix_detail { +#if INTARNA_USE_STD_MDSPAN +namespace md = std; +#else +namespace md = MDSPAN_IMPL_STANDARD_NAMESPACE; +#endif + inline std::size_t product(std::size_t rows, std::size_t columns) { if (columns != 0 && rows > std::numeric_limits::max() / columns) throw std::length_error("matrix dimensions overflow"); @@ -36,7 +48,7 @@ template class Matrix { std::vector values; std::size_t rows = 0, columns = 0; - using Extents = std::dextents; + using Extents = matrix_detail::md::dextents; public: using value_type = T; Matrix() = default; @@ -58,11 +70,11 @@ class Matrix { std::size_t storageSize() const noexcept { return values.size(); } T &operator()(std::size_t i, std::size_t j) { assert(i < rows && j < columns); - return std::mdspan(values.data(), rows, columns)[i, j]; + return matrix_detail::md::mdspan(values.data(), rows, columns)[i, j]; } const T &operator()(std::size_t i, std::size_t j) const { assert(i < rows && j < columns); - return std::mdspan(values.data(), rows, columns)[i, j]; + return matrix_detail::md::mdspan(values.data(), rows, columns)[i, j]; } void clear() { std::fill(values.begin(), values.end(), T{}); } void swap(Matrix &other) noexcept { @@ -95,7 +107,7 @@ template class UpperTriangularMatrix { std::vector values; std::size_t n = 0; - using Extents = std::dextents; + using Extents = matrix_detail::md::dextents; std::size_t offset(std::size_t i, std::size_t j) const noexcept { const auto remaining = n - i; // Constructor has already checked that every triangular size fits. @@ -128,12 +140,12 @@ class UpperTriangularMatrix { std::size_t storageSize() const noexcept { return values.size(); } T &operator()(std::size_t i, std::size_t j) { assert(i <= j && j < n); - return std::mdspan(values.data(), values.size())[offset(i, j)]; + return matrix_detail::md::mdspan(values.data(), values.size())[offset(i, j)]; } const T &operator()(std::size_t i, std::size_t j) const { assert(i < n && j < n); static const T zero{}; - return i > j ? zero : std::mdspan(values.data(), values.size())[offset(i, j)]; + return i > j ? zero : matrix_detail::md::mdspan(values.data(), values.size())[offset(i, j)]; } void clear() { std::fill(values.begin(), values.end(), T{}); } void swap(UpperTriangularMatrix &other) noexcept { diff --git a/src/IntaRNA/intarna_config.h.in b/src/IntaRNA/intarna_config.h.in index dd1b42ce..4c7c3cef 100644 --- a/src/IntaRNA/intarna_config.h.in +++ b/src/IntaRNA/intarna_config.h.in @@ -53,4 +53,9 @@ #define INTARNA_MULITHREADING @INTARNA_MULITHREADING@ #endif +/* mdspan backend selected by configure: standard (1), bundled Kokkos (0). */ +#ifndef INTARNA_USE_STD_MDSPAN +#define INTARNA_USE_STD_MDSPAN @INTARNA_USE_STD_MDSPAN@ +#endif + #endif // INTARNA_CONFIG_H diff --git a/src/Makefile.am b/src/Makefile.am index 997dfe6c..89be198f 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -26,3 +26,41 @@ libeasylogging_a_SOURCES = easylogging++.cc ############################################################################### + +# Bundled Kokkos mdspan headers. Keep the upstream directory layout private to +# IntaRNA's installed headers, with the license beside each header tree. +kokkosdir = $(includedir)/IntaRNA +nobase_kokkos_HEADERS = \ + experimental/__p0009_bits/compressed_pair.hpp \ + experimental/__p0009_bits/config.hpp \ + experimental/__p0009_bits/default_accessor.hpp \ + experimental/__p0009_bits/dynamic_extent.hpp \ + experimental/__p0009_bits/extents.hpp \ + experimental/__p0009_bits/full_extent_t.hpp \ + experimental/__p0009_bits/layout_left.hpp \ + experimental/__p0009_bits/layout_right.hpp \ + experimental/__p0009_bits/layout_stride.hpp \ + experimental/__p0009_bits/macros.hpp \ + experimental/__p0009_bits/mdspan.hpp \ + experimental/__p0009_bits/no_unique_address.hpp \ + experimental/__p0009_bits/trait_backports.hpp \ + experimental/__p0009_bits/type_list.hpp \ + experimental/__p0009_bits/utility.hpp \ + experimental/__p1684_bits/mdarray.hpp \ + experimental/__p2389_bits/dims.hpp \ + experimental/__p2630_bits/constant_wrapper.hpp \ + experimental/__p2630_bits/integral_constant_like.hpp \ + experimental/__p2630_bits/strided_slice.hpp \ + experimental/__p2630_bits/submdspan.hpp \ + experimental/__p2630_bits/submdspan_canonicalize_slices.hpp \ + experimental/__p2630_bits/submdspan_extents.hpp \ + experimental/__p2630_bits/submdspan_mapping.hpp \ + experimental/__p2642_bits/layout_padded.hpp \ + experimental/__p2642_bits/layout_padded_fwd.hpp \ + experimental/mdarray \ + experimental/mdspan \ + mdspan/mdarray.hpp \ + mdspan/mdspan.hpp + +nobase_kokkos_DATA = mdspan/LICENSE experimental/LICENSE +EXTRA_DIST += $(nobase_kokkos_DATA) diff --git a/src/experimental/LICENSE b/src/experimental/LICENSE new file mode 100644 index 00000000..7200d2f2 --- /dev/null +++ b/src/experimental/LICENSE @@ -0,0 +1,228 @@ + ============================================================================== + Kokkos is under the Apache License v2.0 with LLVM Exceptions: + ============================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS Apache 2.0 + + ---- LLVM Exceptions to the Apache 2.0 License ---- + + As an exception, if, as a result of your compiling your source code, portions + of this Software are embedded into an Object form of such source code, you + may redistribute such embedded portions in such Object form without complying + with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + + In addition, if you combine or link compiled forms of this Software with + software that is licensed under the GPLv2 ("Combined Software") and if a + court of competent jurisdiction determines that the patent provision (Section + 3), the indemnity provision (Section 9) or other Section of the License + conflicts with the conditions of the GPLv2, you may retroactively and + prospectively choose to deem waived or otherwise exclude such Section(s) of + the License, but only in their entirety and only with respect to the Combined + Software. + + ============================================================================== + Software from third parties included in Kokkos: + ============================================================================== + + Kokkos contains third party software which is under different license + terms. All such code will be identified clearly using at least one of two + mechanisms: + 1) It will be in a separate directory tree with its own `LICENSE.txt` or + `LICENSE` file at the top containing the specific license and restrictions + which apply to that software, or + 2) It will contain specific license and restriction terms at the top of every + file. + + + THIS SOFTWARE IS PROVIDED BY NTESS "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NTESS OR THE + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Questions? Contact: + Christian R. Trott (crtrott@sandia.gov) and + Damien T. Lebrun-Grandie (lebrungrandt@ornl.gov) + + ************************************************************************ diff --git a/src/experimental/__p0009_bits/compressed_pair.hpp b/src/experimental/__p0009_bits/compressed_pair.hpp new file mode 100644 index 00000000..9b666ca3 --- /dev/null +++ b/src/experimental/__p0009_bits/compressed_pair.hpp @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include "macros.hpp" +#include "trait_backports.hpp" + +#if !defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) +# include "no_unique_address.hpp" +#endif + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace detail { + +// For no unique address emulation, this is the case taken when neither are empty. +// For real `[[no_unique_address]]`, this case is always taken. +template struct impl_compressed_pair { + MDSPAN_IMPL_NO_UNIQUE_ADDRESS T1 m_t1_val{}; + MDSPAN_IMPL_NO_UNIQUE_ADDRESS T2 m_t2_val{}; + MDSPAN_FORCE_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 T1 &first() noexcept { return m_t1_val; } + MDSPAN_FORCE_INLINE_FUNCTION constexpr T1 const &first() const noexcept { + return m_t1_val; + } + MDSPAN_FORCE_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 T2 &second() noexcept { return m_t2_val; } + MDSPAN_FORCE_INLINE_FUNCTION constexpr T2 const &second() const noexcept { + return m_t2_val; + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr impl_compressed_pair() = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr impl_compressed_pair(impl_compressed_pair const &) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr impl_compressed_pair(impl_compressed_pair &&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + MDSPAN_IMPL_CONSTEXPR_14_DEFAULTED impl_compressed_pair & + operator=(impl_compressed_pair const &) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + MDSPAN_IMPL_CONSTEXPR_14_DEFAULTED impl_compressed_pair & + operator=(impl_compressed_pair &&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + ~impl_compressed_pair() = default; + template + MDSPAN_INLINE_FUNCTION constexpr impl_compressed_pair(T1Like &&t1, T2Like &&t2) + : m_t1_val((T1Like &&) t1), m_t2_val((T2Like &&) t2) {} +}; + +#if !defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + +// First empty. +template +struct impl_compressed_pair< + T1, T2, + std::enable_if_t> + : private T1 { + T2 m_t2_val{}; + MDSPAN_FORCE_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 T1 &first() noexcept { + return *static_cast(this); + } + MDSPAN_FORCE_INLINE_FUNCTION constexpr T1 const &first() const noexcept { + return *static_cast(this); + } + MDSPAN_FORCE_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 T2 &second() noexcept { return m_t2_val; } + MDSPAN_FORCE_INLINE_FUNCTION constexpr T2 const &second() const noexcept { + return m_t2_val; + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr impl_compressed_pair() = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr impl_compressed_pair(impl_compressed_pair const &) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr impl_compressed_pair(impl_compressed_pair &&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + MDSPAN_IMPL_CONSTEXPR_14_DEFAULTED impl_compressed_pair & + operator=(impl_compressed_pair const &) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + MDSPAN_IMPL_CONSTEXPR_14_DEFAULTED impl_compressed_pair & + operator=(impl_compressed_pair &&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + ~impl_compressed_pair() = default; + template + MDSPAN_INLINE_FUNCTION constexpr impl_compressed_pair(T1Like &&t1, T2Like &&t2) + : T1((T1Like &&) t1), m_t2_val((T2Like &&) t2) {} +}; + +// Second empty. +template +struct impl_compressed_pair< + T1, T2, + std::enable_if_t> + : private T2 { + T1 m_t1_val{}; + MDSPAN_FORCE_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 T1 &first() noexcept { return m_t1_val; } + MDSPAN_FORCE_INLINE_FUNCTION constexpr T1 const &first() const noexcept { + return m_t1_val; + } + MDSPAN_FORCE_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 T2 &second() noexcept { + return *static_cast(this); + } + MDSPAN_FORCE_INLINE_FUNCTION constexpr T2 const &second() const noexcept { + return *static_cast(this); + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr impl_compressed_pair() = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr impl_compressed_pair(impl_compressed_pair const &) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr impl_compressed_pair(impl_compressed_pair &&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + MDSPAN_IMPL_CONSTEXPR_14_DEFAULTED impl_compressed_pair & + operator=(impl_compressed_pair const &) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + MDSPAN_IMPL_CONSTEXPR_14_DEFAULTED impl_compressed_pair & + operator=(impl_compressed_pair &&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + ~impl_compressed_pair() = default; + + template + MDSPAN_INLINE_FUNCTION constexpr impl_compressed_pair(T1Like &&t1, T2Like &&t2) + : T2((T2Like &&) t2), m_t1_val((T1Like &&) t1) {} +}; + +// Both empty. +template +struct impl_compressed_pair< + T1, T2, + std::enable_if_t> + // We need to use the no_unique_address_emulation wrapper here to avoid + // base class ambiguities. +#ifdef MDSPAN_IMPL_COMPILER_MSVC +// MSVC doesn't allow you to access public static member functions of a type +// when you *happen* to privately inherit from that type. + : protected no_unique_address_emulation, + protected no_unique_address_emulation +#else + : private no_unique_address_emulation, + private no_unique_address_emulation +#endif +{ + using first_base_t = no_unique_address_emulation; + using second_base_t = no_unique_address_emulation; + + MDSPAN_FORCE_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 T1 &first() noexcept { + return this->first_base_t::ref(); + } + MDSPAN_FORCE_INLINE_FUNCTION constexpr T1 const &first() const noexcept { + return this->first_base_t::ref(); + } + MDSPAN_FORCE_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 T2 &second() noexcept { + return this->second_base_t::ref(); + } + MDSPAN_FORCE_INLINE_FUNCTION constexpr T2 const &second() const noexcept { + return this->second_base_t::ref(); + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr impl_compressed_pair() = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr impl_compressed_pair(impl_compressed_pair const &) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr impl_compressed_pair(impl_compressed_pair &&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + MDSPAN_IMPL_CONSTEXPR_14_DEFAULTED impl_compressed_pair & + operator=(impl_compressed_pair const &) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + MDSPAN_IMPL_CONSTEXPR_14_DEFAULTED impl_compressed_pair & + operator=(impl_compressed_pair &&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + ~impl_compressed_pair() = default; + template + MDSPAN_INLINE_FUNCTION constexpr impl_compressed_pair(T1Like &&t1, T2Like &&t2) noexcept + : first_base_t(T1((T1Like &&) t1)), + second_base_t(T2((T2Like &&) t2)) + { } +}; + +#endif // !defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + +} // end namespace detail +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE diff --git a/src/experimental/__p0009_bits/config.hpp b/src/experimental/__p0009_bits/config.hpp new file mode 100644 index 00000000..b61a5247 --- /dev/null +++ b/src/experimental/__p0009_bits/config.hpp @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#ifndef MDSPAN_IMPL_HAS_INCLUDE +# ifndef __has_include +# define MDSPAN_IMPL_HAS_INCLUDE(x) 0 +# else +# define MDSPAN_IMPL_HAS_INCLUDE(x) __has_include(x) +# endif +#endif + +#if MDSPAN_IMPL_HAS_INCLUDE() +# include +#else +# include +# include +#endif + +#ifdef _MSVC_LANG +#define MDSPAN_IMPL_CPLUSPLUS _MSVC_LANG +#else +#define MDSPAN_IMPL_CPLUSPLUS __cplusplus +#endif + +#define MDSPAN_CXX_STD_14 201402L +#define MDSPAN_CXX_STD_17 201703L +#define MDSPAN_CXX_STD_20 202002L +// Note GCC has not updated this in version 13 +#ifdef __clang__ +#define MDSPAN_CXX_STD_23 202302L +#else +#define MDSPAN_CXX_STD_23 202100L +#endif +#define MDSPAN_CXX_STD_26 202603L + +#define MDSPAN_HAS_CXX_14 (MDSPAN_IMPL_CPLUSPLUS >= MDSPAN_CXX_STD_14) +#define MDSPAN_HAS_CXX_17 (MDSPAN_IMPL_CPLUSPLUS >= MDSPAN_CXX_STD_17) +#define MDSPAN_HAS_CXX_20 (MDSPAN_IMPL_CPLUSPLUS >= MDSPAN_CXX_STD_20) +#define MDSPAN_HAS_CXX_23 (MDSPAN_IMPL_CPLUSPLUS >= MDSPAN_CXX_STD_23) +#define MDSPAN_HAS_CXX_26 (MDSPAN_IMPL_CPLUSPLUS >= MDSPAN_CXX_STD_26) + +static_assert(MDSPAN_IMPL_CPLUSPLUS >= MDSPAN_CXX_STD_14, "mdspan requires C++14 or later."); + +#ifndef MDSPAN_IMPL_COMPILER_CLANG +# if defined(__clang__) +# define MDSPAN_IMPL_COMPILER_CLANG __clang__ +# endif +#endif + +#if !defined(MDSPAN_IMPL_COMPILER_MSVC) && !defined(MDSPAN_IMPL_COMPILER_MSVC_CLANG) +# if defined(_MSC_VER) +# if !defined(MDSPAN_IMPL_COMPILER_CLANG) +# define MDSPAN_IMPL_COMPILER_MSVC _MSC_VER +# else +# define MDSPAN_IMPL_COMPILER_MSVC_CLANG _MSC_VER +# endif +# endif +#endif + +#ifndef MDSPAN_IMPL_COMPILER_INTEL +# ifdef __INTEL_COMPILER +# define MDSPAN_IMPL_COMPILER_INTEL __INTEL_COMPILER +# endif +#endif + +#ifndef MDSPAN_IMPL_COMPILER_APPLECLANG +# ifdef __apple_build_version__ +# define MDSPAN_IMPL_COMPILER_APPLECLANG __apple_build_version__ +# endif +#endif + +#ifndef MDSPAN_IMPL_HAS_CUDA +# if defined(__CUDACC__) +# define MDSPAN_IMPL_HAS_CUDA __CUDACC__ +# endif +#endif + +#ifndef MDSPAN_IMPL_HAS_HIP +# if defined(__HIPCC__) +# define MDSPAN_IMPL_HAS_HIP __HIPCC__ +# endif +#endif + +#ifndef MDSPAN_IMPL_HAS_SYCL +# if defined(SYCL_LANGUAGE_VERSION) +# define MDSPAN_IMPL_HAS_SYCL SYCL_LANGUAGE_VERSION +# endif +#endif + +#ifndef MDSPAN_IMPL_HAS_OPENACC +# if defined(_OPENACC) +# define MDSPAN_IMPL_HAS_OPENACC _OPENACC +# endif +#endif + +#ifndef MDSPAN_IMPL_HAS_CPP_ATTRIBUTE +# ifndef __has_cpp_attribute +# define MDSPAN_IMPL_HAS_CPP_ATTRIBUTE(x) 0 +# else +# define MDSPAN_IMPL_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +# endif +#endif + +#ifndef MDSPAN_IMPL_PRESERVE_STANDARD_LAYOUT +// Preserve standard layout by default, but we're not removing the old version +// that turns this off until we're sure this doesn't have an unreasonable cost +// to the compiler or optimizer. +# define MDSPAN_IMPL_PRESERVE_STANDARD_LAYOUT 1 +#endif + +#if !defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) +# if ((MDSPAN_IMPL_HAS_CPP_ATTRIBUTE(no_unique_address) >= 201803L) && \ + (!defined(__NVCC__) || ((__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__ * 10 < 1290) && MDSPAN_HAS_CXX_20)) && \ + (!defined(MDSPAN_IMPL_COMPILER_MSVC) || MDSPAN_HAS_CXX_20)) +# define MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS 1 +# define MDSPAN_IMPL_NO_UNIQUE_ADDRESS [[no_unique_address]] +# else +# define MDSPAN_IMPL_NO_UNIQUE_ADDRESS +# endif +#endif + +// NVCC older than 11.6 chokes on the no-unique-address-emulation +// so just pretend to use it (to avoid the full blown EBO workaround +// which NVCC also doesn't like ...), and leave the macro empty +#ifndef MDSPAN_IMPL_NO_UNIQUE_ADDRESS +# if defined(__NVCC__) +# define MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS 1 +# define MDSPAN_IMPL_USE_FAKE_ATTRIBUTE_NO_UNIQUE_ADDRESS +# endif +# define MDSPAN_IMPL_NO_UNIQUE_ADDRESS +#endif + +// AMDs HIP compiler seems to have issues with concepts +// it pretends concepts exist, but doesn't ship +#ifndef __HIPCC__ +#ifndef MDSPAN_IMPL_USE_CONCEPTS +# if defined(__cpp_concepts) && __cpp_concepts >= 201507L +# define MDSPAN_IMPL_USE_CONCEPTS 1 +# endif +#endif +#endif + +#ifndef MDSPAN_IMPL_USE_FOLD_EXPRESSIONS +# if (defined(__cpp_fold_expressions) && __cpp_fold_expressions >= 201603L) \ + || (!defined(__cpp_fold_expressions) && MDSPAN_HAS_CXX_17) +# define MDSPAN_IMPL_USE_FOLD_EXPRESSIONS 1 +# endif +#endif + +#ifndef MDSPAN_IMPL_USE_INLINE_VARIABLES +# if defined(__cpp_inline_variables) && __cpp_inline_variables >= 201606L \ + || (!defined(__cpp_inline_variables) && MDSPAN_HAS_CXX_17) +# define MDSPAN_IMPL_USE_INLINE_VARIABLES 1 +# endif +#endif + +#ifndef MDSPAN_IMPL_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS +# if (!(defined(__cpp_lib_type_trait_variable_templates) && __cpp_lib_type_trait_variable_templates >= 201510L) \ + || !MDSPAN_HAS_CXX_17) +# if !(defined(MDSPAN_IMPL_COMPILER_APPLECLANG) && MDSPAN_HAS_CXX_17) +# define MDSPAN_IMPL_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS 1 +# endif +# endif +#endif + +#ifndef MDSPAN_IMPL_USE_VARIABLE_TEMPLATES +# if (defined(__cpp_variable_templates) && __cpp_variable_templates >= 201304 && MDSPAN_HAS_CXX_17) \ + || (!defined(__cpp_variable_templates) && MDSPAN_HAS_CXX_17) +# define MDSPAN_IMPL_USE_VARIABLE_TEMPLATES 1 +# endif +#endif // MDSPAN_IMPL_USE_VARIABLE_TEMPLATES + +#ifndef MDSPAN_IMPL_USE_CONSTEXPR_14 +# if (defined(__cpp_constexpr) && __cpp_constexpr >= 201304) \ + || (!defined(__cpp_constexpr) && MDSPAN_HAS_CXX_14) \ + && (!(defined(__INTEL_COMPILER) && __INTEL_COMPILER <= 1700)) +# define MDSPAN_IMPL_USE_CONSTEXPR_14 1 +# endif +#endif + +#ifndef MDSPAN_IMPL_USE_IF_CONSTEXPR_17 +# if (defined(__cpp_if_constexpr) && __cpp_if_constexpr >= 201606) \ + || (!defined(__cpp_constexpr) && MDSPAN_HAS_CXX_17) +# define MDSPAN_IMPL_USE_IF_CONSTEXPR_17 1 +# endif +#endif + +#ifndef MDSPAN_IMPL_USE_INTEGER_SEQUENCE_14 +# if defined(MDSPAN_IMPL_COMPILER_MSVC) +# if (defined(__cpp_lib_integer_sequence) && __cpp_lib_integer_sequence >= 201304) +# define MDSPAN_IMPL_USE_INTEGER_SEQUENCE_14 1 +# endif +# endif +#endif +#ifndef MDSPAN_IMPL_USE_INTEGER_SEQUENCE_14 +# if (defined(__cpp_lib_integer_sequence) && __cpp_lib_integer_sequence >= 201304) \ + || (!defined(__cpp_lib_integer_sequence) && MDSPAN_HAS_CXX_14) \ + /* as far as I can tell, libc++ seems to think this is a C++11 feature... */ \ + || (defined(__GLIBCXX__) && __GLIBCXX__ > 20150422 && __GNUC__ < 5 && !defined(__INTEL_CXX11_MODE__)) + // several compilers lie about integer_sequence working properly unless the C++14 standard is used +# define MDSPAN_IMPL_USE_INTEGER_SEQUENCE_14 1 +# elif defined(MDSPAN_IMPL_COMPILER_APPLECLANG) && MDSPAN_HAS_CXX_14 + // appleclang seems to be missing the __cpp_lib_... macros, but doesn't seem to lie about C++14 making + // integer_sequence work +# define MDSPAN_IMPL_USE_INTEGER_SEQUENCE_14 1 +# endif +#endif + +#ifndef MDSPAN_IMPL_USE_RETURN_TYPE_DEDUCTION +# if (defined(__cpp_return_type_deduction) && __cpp_return_type_deduction >= 201304) \ + || (!defined(__cpp_return_type_deduction) && MDSPAN_HAS_CXX_14) +# define MDSPAN_IMPL_USE_RETURN_TYPE_DEDUCTION 1 +# endif +#endif + +#ifndef MDSPAN_IMPL_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION +# if (!defined(__NVCC__) || (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__ * 10 >= 1170)) && \ + ((defined(__cpp_deduction_guides) && __cpp_deduction_guides >= 201703) || \ + (!defined(__cpp_deduction_guides) && MDSPAN_HAS_CXX_17)) +# define MDSPAN_IMPL_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION 1 +# endif +#endif + +#ifndef MDSPAN_IMPL_USE_STANDARD_TRAIT_ALIASES +# if (defined(__cpp_lib_transformation_trait_aliases) && __cpp_lib_transformation_trait_aliases >= 201304) \ + || (!defined(__cpp_lib_transformation_trait_aliases) && MDSPAN_HAS_CXX_14) +# define MDSPAN_IMPL_USE_STANDARD_TRAIT_ALIASES 1 +# elif defined(MDSPAN_IMPL_COMPILER_APPLECLANG) && MDSPAN_HAS_CXX_14 + // appleclang seems to be missing the __cpp_lib_... macros, but doesn't seem to lie about C++14 +# define MDSPAN_IMPL_USE_STANDARD_TRAIT_ALIASES 1 +# endif +#endif + +#ifndef MDSPAN_IMPL_DEFAULTED_CONSTRUCTORS_INHERITANCE_WORKAROUND +# ifdef __GNUC__ +# if __GNUC__ < 9 +# define MDSPAN_IMPL_DEFAULTED_CONSTRUCTORS_INHERITANCE_WORKAROUND 1 +# endif +# endif +#endif + +#ifndef MDSPAN_CONDITIONAL_EXPLICIT +# if MDSPAN_HAS_CXX_20 +# define MDSPAN_CONDITIONAL_EXPLICIT(COND) explicit(COND) +# else +# define MDSPAN_CONDITIONAL_EXPLICIT(COND) +# endif +#endif + +#ifndef MDSPAN_USE_BRACKET_OPERATOR +# if defined(__cpp_multidimensional_subscript) +// The following if/else is necessary to workaround a clang issue +// relative to using a parameter pack inside a bracket operator in C++2b/C++23 mode +# if defined(MDSPAN_IMPL_COMPILER_CLANG) && \ + ((__clang_major__ < 17) || \ + (__clang_major__ == 17 && __clang_minor__ == 0 && \ + __clang_patchlevel__ == 0)) +# define MDSPAN_USE_BRACKET_OPERATOR 0 +# else +# define MDSPAN_USE_BRACKET_OPERATOR 1 +# endif +# else +# define MDSPAN_USE_BRACKET_OPERATOR 0 +# endif +#endif + +#ifndef MDSPAN_USE_PAREN_OPERATOR +# if !MDSPAN_USE_BRACKET_OPERATOR +# define MDSPAN_USE_PAREN_OPERATOR 1 +# else +# define MDSPAN_USE_PAREN_OPERATOR 0 +# endif +#endif + +#if MDSPAN_USE_BRACKET_OPERATOR +# define MDSPAN_IMPL_OP(mds,...) mds[__VA_ARGS__] +// Corentins demo compiler for subscript chokes on empty [] call, +// though I believe the proposal supports it? +#ifdef MDSPAN_NO_EMPTY_BRACKET_OPERATOR +# define MDSPAN_IMPL_OP0(mds) mds.accessor().access(mds.data_handle(),0) +#else +# define MDSPAN_IMPL_OP0(mds) mds[] +#endif +# define MDSPAN_IMPL_OP1(mds, a) mds[a] +# define MDSPAN_IMPL_OP2(mds, a, b) mds[a,b] +# define MDSPAN_IMPL_OP3(mds, a, b, c) mds[a,b,c] +# define MDSPAN_IMPL_OP4(mds, a, b, c, d) mds[a,b,c,d] +# define MDSPAN_IMPL_OP5(mds, a, b, c, d, e) mds[a,b,c,d,e] +# define MDSPAN_IMPL_OP6(mds, a, b, c, d, e, f) mds[a,b,c,d,e,f] +#else +# define MDSPAN_IMPL_OP(mds,...) mds(__VA_ARGS__) +# define MDSPAN_IMPL_OP0(mds) mds() +# define MDSPAN_IMPL_OP1(mds, a) mds(a) +# define MDSPAN_IMPL_OP2(mds, a, b) mds(a,b) +# define MDSPAN_IMPL_OP3(mds, a, b, c) mds(a,b,c) +# define MDSPAN_IMPL_OP4(mds, a, b, c, d) mds(a,b,c,d) +# define MDSPAN_IMPL_OP5(mds, a, b, c, d, e) mds(a,b,c,d,e) +# define MDSPAN_IMPL_OP6(mds, a, b, c, d, e, f) mds(a,b,c,d,e,f) +#endif + +#if ! defined(MDSPAN_IMPL_ENABLE_P3663) +# define MDSPAN_IMPL_ENABLE_P3663 1 +#endif diff --git a/src/experimental/__p0009_bits/default_accessor.hpp b/src/experimental/__p0009_bits/default_accessor.hpp new file mode 100644 index 00000000..99e0891e --- /dev/null +++ b/src/experimental/__p0009_bits/default_accessor.hpp @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include "macros.hpp" + +#include // size_t + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +template +struct default_accessor { + + using offset_policy = default_accessor; + using element_type = ElementType; + using reference = ElementType&; + using data_handle_type = ElementType*; + + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr default_accessor() noexcept = default; + + MDSPAN_TEMPLATE_REQUIRES( + class OtherElementType, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_convertible, OtherElementType(*)[], element_type(*)[]) + ) + ) + MDSPAN_INLINE_FUNCTION + constexpr default_accessor(default_accessor) noexcept {} + + MDSPAN_INLINE_FUNCTION + constexpr data_handle_type + offset(data_handle_type p, size_t i) const noexcept { + return p + i; + } + + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference access(data_handle_type p, size_t i) const noexcept { + return p[i]; + } + +}; + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE diff --git a/src/experimental/__p0009_bits/dynamic_extent.hpp b/src/experimental/__p0009_bits/dynamic_extent.hpp new file mode 100644 index 00000000..847fed18 --- /dev/null +++ b/src/experimental/__p0009_bits/dynamic_extent.hpp @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include "macros.hpp" + +#if defined(__cpp_lib_span) +#include +#endif + +#include // size_t +#include // numeric_limits + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +#if defined(__cpp_lib_span) +using std::dynamic_extent; +#else +MDSPAN_IMPL_INLINE_VARIABLE constexpr auto dynamic_extent = std::numeric_limits::max(); +#endif +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +//============================================================================================================== diff --git a/src/experimental/__p0009_bits/extents.hpp b/src/experimental/__p0009_bits/extents.hpp new file mode 100644 index 00000000..3770aa7e --- /dev/null +++ b/src/experimental/__p0009_bits/extents.hpp @@ -0,0 +1,688 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once +#include "dynamic_extent.hpp" +#include "utility.hpp" + +#ifdef __cpp_lib_span +#include +#endif +#include +#include + +#include +#include + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace detail { + +// Function used to check compatibility of extents in converting constructor +// can't be a private member function for some reason. +template +MDSPAN_INLINE_FUNCTION +constexpr std::integral_constant impl_check_compatible_extents( + std::integral_constant, + std::integer_sequence, + std::integer_sequence) noexcept { + return {}; +} + +// This helper prevents ICE's on MSVC. +template +struct impl_compare_extent_compatible : std::integral_constant +{}; + +template +MDSPAN_INLINE_FUNCTION +constexpr std::integral_constant< + bool, MDSPAN_IMPL_FOLD_AND(impl_compare_extent_compatible::value)> +impl_check_compatible_extents( + std::integral_constant, + std::integer_sequence, + std::integer_sequence) noexcept { + return {}; +} + +template +MDSPAN_INLINE_FUNCTION +constexpr bool are_valid_indices() { + return + MDSPAN_IMPL_FOLD_AND(std::is_convertible::value) && + MDSPAN_IMPL_FOLD_AND(std::is_nothrow_constructible::value); +} + +// ------------------------------------------------------------------ +// ------------ static_array ---------------------------------------- +// ------------------------------------------------------------------ + +// array like class which provides an array of static values with get +// function and operator []. + +// Implementation of Static Array with recursive implementation of get. +template struct static_array_impl; + +template +struct static_array_impl { + MDSPAN_INLINE_FUNCTION + constexpr static T get(size_t r) { + if (r == R) + return FirstExt; + else + return static_array_impl::get(r); + } +}; + +// End the recursion +template +struct static_array_impl { + MDSPAN_INLINE_FUNCTION + constexpr static T get(size_t) { return FirstExt; } +}; + +// Don't start recursion if size 0 +template struct static_array_impl<0, T> { + MDSPAN_INLINE_FUNCTION + constexpr static T get(size_t) { return T(); } +}; + +// Static array, provides get(), get(r) and operator[r] +template struct static_array: + public static_array_impl<0, T, Values...> { + +public: + using value_type = T; + + MDSPAN_INLINE_FUNCTION + constexpr static size_t size() { return sizeof...(Values); } +}; + + +// ------------------------------------------------------------------ +// ------------ index_sequence_scan --------------------------------- +// ------------------------------------------------------------------ + +// index_sequence_scan takes compile time values and provides get(r) +// and get() which return the sum of the first r-1 values. + +// Recursive implementation for get +template struct index_sequence_scan_impl; + +template +struct index_sequence_scan_impl { + MDSPAN_INLINE_FUNCTION + constexpr static size_t get(size_t r) { + if (r > R) + return FirstVal + index_sequence_scan_impl::get(r); + else + return 0; + } +}; + +template +struct index_sequence_scan_impl { +#if defined(__NVCC__) || defined(__NVCOMPILER) || \ + defined(MDSPAN_IMPL_COMPILER_INTEL) + // NVCC warns about pointless comparison with 0 for R==0 and r being const + // evaluatable and also 0. + MDSPAN_INLINE_FUNCTION + constexpr static size_t get(size_t r) { + return static_cast(R) > static_cast(r) ? FirstVal : 0; + } +#else + MDSPAN_INLINE_FUNCTION + constexpr static size_t get(size_t r) { return R > r ? FirstVal : 0; } +#endif +}; +template <> struct index_sequence_scan_impl<0> { + MDSPAN_INLINE_FUNCTION + constexpr static size_t get(size_t) { return 0; } +}; + +// ------------------------------------------------------------------ +// ------------ possibly_empty_array ------------------------------- +// ------------------------------------------------------------------ + +// array like class which provides get function and operator [], and +// has a specialization for the size 0 case. +// This is needed to make the maybe_static_array be truly empty, for +// all static values. + +template struct possibly_empty_array { + T vals[N]{}; + MDSPAN_INLINE_FUNCTION + constexpr T &operator[](size_t r) { return vals[r]; } + MDSPAN_INLINE_FUNCTION + constexpr const T &operator[](size_t r) const { return vals[r]; } +}; + +template struct possibly_empty_array { + MDSPAN_INLINE_FUNCTION + constexpr T operator[](size_t) { return T(); } + MDSPAN_INLINE_FUNCTION + constexpr const T operator[](size_t) const { return T(); } +}; + +// ------------------------------------------------------------------ +// ------------ maybe_static_array ---------------------------------- +// ------------------------------------------------------------------ + +// array like class which has a mix of static and runtime values but +// only stores the runtime values. +// The type of the static and the runtime values can be different. +// The position of a dynamic value is indicated through a tag value. +template +struct maybe_static_array { + + static_assert(std::is_convertible::value, "maybe_static_array: TStatic must be convertible to TDynamic"); + static_assert(std::is_convertible::value, "maybe_static_array: TDynamic must be convertible to TStatic"); + +private: + // Static values member + using static_vals_t = static_array; + constexpr static size_t m_size = sizeof...(Values); + constexpr static size_t m_size_dynamic = + MDSPAN_IMPL_FOLD_PLUS_RIGHT((Values == dyn_tag), 0); + + // Dynamic values member + MDSPAN_IMPL_NO_UNIQUE_ADDRESS possibly_empty_array + m_dyn_vals; + + // static mapping of indices to the position in the dynamic values array + using dyn_map_t = index_sequence_scan_impl<0, static_cast(Values == dyn_tag)...>; +public: + + // two types for static and dynamic values + using value_type = TDynamic; + using static_value_type = TStatic; + // tag value indicating dynamic value + constexpr static static_value_type tag_value = dyn_tag; + + constexpr maybe_static_array() = default; + + // constructor for all static values + // TODO: add precondition check? + MDSPAN_TEMPLATE_REQUIRES(class... Vals, + /* requires */ ((m_size_dynamic == 0) && + (sizeof...(Vals) > 0))) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(Vals...) : m_dyn_vals{} {} + + // constructors from dynamic values only + MDSPAN_TEMPLATE_REQUIRES(class... DynVals, + /* requires */ (sizeof...(DynVals) == + m_size_dynamic && + m_size_dynamic > 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(DynVals... vals) + : m_dyn_vals{static_cast(vals)...} {} + + + MDSPAN_TEMPLATE_REQUIRES(class T, size_t N, + /* requires */ (N == m_size_dynamic && N > 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(const std::array &vals) { + for (size_t r = 0; r < N; r++) + m_dyn_vals[r] = static_cast(vals[r]); + } + + MDSPAN_TEMPLATE_REQUIRES(class T, size_t N, + /* requires */ (N == m_size_dynamic && N == 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(const std::array &) : m_dyn_vals{} {} + +#ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES(class T, size_t N, + /* requires */ (N == m_size_dynamic && N > 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(const std::span &vals) { + for (size_t r = 0; r < N; r++) + m_dyn_vals[r] = static_cast(vals[r]); + } + + MDSPAN_TEMPLATE_REQUIRES(class T, size_t N, + /* requires */ (N == m_size_dynamic && N == 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(const std::span &) : m_dyn_vals{} {} +#endif + + // constructors from all values + MDSPAN_TEMPLATE_REQUIRES(class... DynVals, + /* requires */ (sizeof...(DynVals) != + m_size_dynamic && + m_size_dynamic > 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(DynVals... vals) + : m_dyn_vals{} { + static_assert((sizeof...(DynVals) == m_size), "Invalid number of values."); + TDynamic values[m_size]{static_cast(vals)...}; + for (size_t r = 0; r < m_size; r++) { + TStatic static_val = static_vals_t::get(r); + if (static_val == dyn_tag) { + m_dyn_vals[dyn_map_t::get(r)] = values[r]; + } +// Precondition check +#ifdef MDSPAN_DEBUG + else { + assert(values[r] == static_cast(static_val)); + } +#endif + } + } + + MDSPAN_TEMPLATE_REQUIRES( + class T, size_t N, + /* requires */ (N != m_size_dynamic && m_size_dynamic > 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(const std::array &vals) { + static_assert((N == m_size), "Invalid number of values."); +// Precondition check +#ifdef MDSPAN_DEBUG + assert(N == m_size); +#endif + for (size_t r = 0; r < m_size; r++) { + TStatic static_val = static_vals_t::get(r); + if (static_val == dyn_tag) { + m_dyn_vals[dyn_map_t::get(r)] = static_cast(vals[r]); + } +// Precondition check +#ifdef MDSPAN_DEBUG + else { + assert(static_cast(vals[r]) == + static_cast(static_val)); + } +#endif + } + } + +#ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES( + class T, size_t N, + /* requires */ (N != m_size_dynamic && m_size_dynamic > 0)) + MDSPAN_INLINE_FUNCTION + constexpr maybe_static_array(const std::span &vals) { + static_assert((N == m_size) || (m_size == dynamic_extent)); +#ifdef MDSPAN_DEBUG + assert(N == m_size); +#endif + for (size_t r = 0; r < m_size; r++) { + TStatic static_val = static_vals_t::get(r); + if (static_val == dyn_tag) { + m_dyn_vals[dyn_map_t::get(r)] = static_cast(vals[r]); + } +#ifdef MDSPAN_DEBUG + else { + assert(static_cast(vals[r]) == + static_cast(static_val)); + } +#endif + } + } +#endif + + // access functions + MDSPAN_INLINE_FUNCTION + constexpr static TStatic static_value(size_t r) { return static_vals_t::get(r); } + + MDSPAN_INLINE_FUNCTION + constexpr TDynamic value(size_t r) const { + TStatic static_val = static_vals_t::get(r); + + // FIXME: workaround for nvhpc OpenACC compiler bug + TStatic dyn_tag_copy = dyn_tag; + return static_val == dyn_tag_copy ? m_dyn_vals[dyn_map_t::get(r)] + : static_cast(static_val); + } + MDSPAN_INLINE_FUNCTION + constexpr TDynamic operator[](size_t r) const { return value(r); } + + + // observers + MDSPAN_INLINE_FUNCTION + constexpr static size_t size() { return m_size; } + MDSPAN_INLINE_FUNCTION + constexpr static size_t size_dynamic() { return m_size_dynamic; } +}; + +} // namespace detail +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +// ------------------------------------------------------------------ +// ------------ extents --------------------------------------------- +// ------------------------------------------------------------------ + +// Class to describe the extents of a multi dimensional array. +// Used by mdspan, mdarray and layout mappings. +// See ISO C++ standard [mdspan.extents] + +template class extents { +public: + // typedefs for integral types used + using index_type = IndexType; + using size_type = std::make_unsigned_t; + using rank_type = size_t; + + static_assert(std::is_integral::value && !std::is_same::value, + MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents::index_type must be a signed or unsigned integer type"); +private: + constexpr static rank_type m_rank = sizeof...(Extents); + constexpr static rank_type m_rank_dynamic = + MDSPAN_IMPL_FOLD_PLUS_RIGHT((Extents == dynamic_extent), /* + ... + */ 0); + + // internal storage type using maybe_static_array + using vals_t = + detail::maybe_static_array; + MDSPAN_IMPL_NO_UNIQUE_ADDRESS vals_t m_vals; + +public: + // [mdspan.extents.obs], observers of multidimensional index space + MDSPAN_INLINE_FUNCTION + constexpr static rank_type rank() noexcept { return m_rank; } + MDSPAN_INLINE_FUNCTION + constexpr static rank_type rank_dynamic() noexcept { return m_rank_dynamic; } + + MDSPAN_INLINE_FUNCTION + constexpr index_type extent(rank_type r) const noexcept { return m_vals.value(r); } + MDSPAN_INLINE_FUNCTION + constexpr static size_t static_extent(rank_type r) noexcept { + return vals_t::static_value(r); + } + + // [mdspan.extents.cons], constructors + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr extents() noexcept = default; + + // Construction from just dynamic or all values. + // Precondition check is deferred to maybe_static_array constructor + MDSPAN_TEMPLATE_REQUIRES( + class... OtherIndexTypes, + /* requires */ ( + MDSPAN_IMPL_FOLD_AND(MDSPAN_IMPL_TRAIT(std::is_convertible, OtherIndexTypes, + index_type) /* && ... */) && + MDSPAN_IMPL_FOLD_AND(MDSPAN_IMPL_TRAIT(std::is_nothrow_constructible, index_type, + OtherIndexTypes) /* && ... */) && + (sizeof...(OtherIndexTypes) == m_rank || + sizeof...(OtherIndexTypes) == m_rank_dynamic))) + MDSPAN_INLINE_FUNCTION + constexpr explicit extents(OtherIndexTypes... dynvals) noexcept + : m_vals(static_cast(dynvals)...) { +#if MDSPAN_HAS_CXX_17 + MDSPAN_IMPL_PRECONDITION( + detail::all_values_are_nonnegative_and_representable(dynvals...)); +#endif + } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherIndexType, size_t N, + /* requires */ + ( + MDSPAN_IMPL_TRAIT(std::is_convertible, const OtherIndexType&, index_type) && + MDSPAN_IMPL_TRAIT(std::is_nothrow_constructible, index_type, + const OtherIndexType&) && + (N == m_rank || N == m_rank_dynamic))) + MDSPAN_INLINE_FUNCTION + MDSPAN_CONDITIONAL_EXPLICIT(N != m_rank_dynamic) + constexpr extents(const std::array &exts) noexcept + : m_vals(std::move(exts)) { +#if MDSPAN_HAS_CXX_17 + MDSPAN_IMPL_PRECONDITION( + detail::range_is_nonnegative_and_representable( + std::begin(exts), std::end(exts))); +#endif + } + +#ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES( + class OtherIndexType, size_t N, + /* requires */ + (MDSPAN_IMPL_TRAIT(std::is_convertible, const OtherIndexType&, index_type) && + MDSPAN_IMPL_TRAIT(std::is_nothrow_constructible, index_type, const OtherIndexType&) && + (N == m_rank || N == m_rank_dynamic))) + MDSPAN_INLINE_FUNCTION + MDSPAN_CONDITIONAL_EXPLICIT(N != m_rank_dynamic) + constexpr extents(const std::span &exts) noexcept + : m_vals(std::move(exts)) { + MDSPAN_IMPL_PRECONDITION( + detail::range_is_nonnegative_and_representable( + std::begin(exts), std::end(exts))); + } +#endif + +private: + // Function to construct extents storage from other extents. + // With C++ 17 the first two variants could be collapsed using if constexpr + // in which case you don't need all the requires clauses. + // in C++ 14 mode that doesn't work due to infinite recursion + MDSPAN_TEMPLATE_REQUIRES( + size_t DynCount, size_t R, class OtherExtents, class... DynamicValues, + /* requires */ ((R < m_rank) && (static_extent(R) == dynamic_extent))) + MDSPAN_INLINE_FUNCTION + constexpr + vals_t impl_construct_vals_from_extents(std::integral_constant, + std::integral_constant, + const OtherExtents &exts, + DynamicValues... dynamic_values) noexcept { + return impl_construct_vals_from_extents( + std::integral_constant(), + std::integral_constant(), exts, dynamic_values..., + exts.extent(R)); + } + + MDSPAN_TEMPLATE_REQUIRES( + size_t DynCount, size_t R, class OtherExtents, class... DynamicValues, + /* requires */ ((R < m_rank) && (static_extent(R) != dynamic_extent))) + MDSPAN_INLINE_FUNCTION + constexpr + vals_t impl_construct_vals_from_extents(std::integral_constant, + std::integral_constant, + const OtherExtents &exts, + DynamicValues... dynamic_values) noexcept { + return impl_construct_vals_from_extents( + std::integral_constant(), + std::integral_constant(), exts, dynamic_values...); + } + + MDSPAN_TEMPLATE_REQUIRES( + size_t DynCount, size_t R, class OtherExtents, class... DynamicValues, + /* requires */ ((R == m_rank) && (DynCount == m_rank_dynamic))) + MDSPAN_INLINE_FUNCTION + constexpr + vals_t impl_construct_vals_from_extents(std::integral_constant, + std::integral_constant, + const OtherExtents &, + DynamicValues... dynamic_values) noexcept { + return vals_t{static_cast(dynamic_values)...}; + } + +public: + + // Converting constructor from other extents specializations + MDSPAN_TEMPLATE_REQUIRES( + class OtherIndexType, size_t... OtherExtents, + /* requires */ + ( + /* multi-stage check to protect from invalid pack expansion when sizes + don't match? */ + decltype(detail::impl_check_compatible_extents( + // using: sizeof...(Extents) == sizeof...(OtherExtents) as the second argument fails with MSVC+NVCC with some obscure expansion error + // MSVC: 19.38.33133 NVCC: 12.0 + std::integral_constant::rank() == extents::rank()>{}, + std::integer_sequence{}, + std::integer_sequence{}))::value + ) + ) + MDSPAN_INLINE_FUNCTION + MDSPAN_CONDITIONAL_EXPLICIT((((Extents != dynamic_extent) && + (OtherExtents == dynamic_extent)) || + ...) || + (std::numeric_limits::max() < + std::numeric_limits::max())) + constexpr extents( + const extents &other) noexcept + : m_vals(impl_construct_vals_from_extents( + std::integral_constant(), + std::integral_constant(), other)) { +#if MDSPAN_HAS_CXX_17 + MDSPAN_IMPL_PRECONDITION( + detail::extent_is_representable(other)); +#endif + } + + // Comparison operator + template + MDSPAN_INLINE_FUNCTION friend constexpr bool + operator==(const extents &lhs, + const extents &rhs) noexcept { + return + rank() == extents::rank() && + detail::rankwise_equal(detail::with_rank{}, rhs, lhs, detail::extent); + } + +#if !(MDSPAN_HAS_CXX_20) + template + MDSPAN_INLINE_FUNCTION friend constexpr bool + operator!=(extents const &lhs, + extents const &rhs) noexcept { + return !(lhs == rhs); + } +#endif +}; + +// Recursive helper classes to implement dextents alias for extents +namespace detail { + +template > +struct impl_make_dextents; + +template +struct impl_make_dextents< + IndexType, Rank, ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents> +{ + using type = typename impl_make_dextents< + IndexType, Rank - 1, + ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents>::type; +}; + +template +struct impl_make_dextents< + IndexType, 0, ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents> +{ + using type = ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents; +}; + +} // end namespace detail + +// [mdspan.extents.dextents], alias template +template +using dextents = typename detail::impl_make_dextents::type; + +// Deduction guide for extents +#if defined(MDSPAN_IMPL_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION) +template +extents(IndexTypes...) + -> extents; +#endif + +// Helper type traits for identifying a class as extents. +namespace detail { + +template struct impl_is_extents : ::std::false_type {}; + +template +struct impl_is_extents<::MDSPAN_IMPL_STANDARD_NAMESPACE::extents> + : ::std::true_type {}; + +template +#if MDSPAN_HAS_CXX_17 +inline +#else +static +#endif +constexpr bool impl_is_extents_v = impl_is_extents::value; + +template +MDSPAN_INLINE_FUNCTION +constexpr void +check_lower_bound(InputIndexType user_index, + ExtentsIndexType /* current_extent */, + std::true_type /* is_signed */) +{ + (void) user_index; // prevent unused variable warning +#ifdef MDSPAN_DEBUG + assert(static_cast(user_index) >= 0); +#endif +} + +template +MDSPAN_INLINE_FUNCTION +constexpr void +check_lower_bound(InputIndexType /* user_index */, + ExtentsIndexType /* current_extent */, + std::false_type /* is_signed */) +{} + +template +MDSPAN_INLINE_FUNCTION +constexpr void +check_upper_bound(InputIndexType user_index, + ExtentsIndexType current_extent) +{ + (void) user_index; // prevent unused variable warnings + (void) current_extent; +#ifdef MDSPAN_DEBUG + assert(static_cast(user_index) < current_extent); +#endif +} + +// Returning true to use AND fold instead of comma +// CPP14 mode doesn't like the use of void expressions +// with the way the MDSPAN_IMPL_FOLD_AND is set up +template +MDSPAN_INLINE_FUNCTION +constexpr bool +check_one_index(InputIndex user_index, + ExtentsIndexType current_extent) +{ + check_lower_bound(user_index, current_extent, + std::integral_constant::value>{}); + check_upper_bound(user_index, current_extent); + return true; +} + +template +MDSPAN_INLINE_FUNCTION +constexpr void +check_all_indices_helper(std::index_sequence, + const extents& exts, + Indices... indices) +{ + // Suppress warning about statement has no effect + (void) MDSPAN_IMPL_FOLD_AND( + (check_one_index(indices, exts.extent(RankIndices))) + ); +} + +template +MDSPAN_INLINE_FUNCTION +constexpr void +check_all_indices(const extents& exts, + Indices... indices) +{ + check_all_indices_helper(std::make_index_sequence(), + exts, indices...); +} + +} // namespace detail +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE diff --git a/src/experimental/__p0009_bits/full_extent_t.hpp b/src/experimental/__p0009_bits/full_extent_t.hpp new file mode 100644 index 00000000..ba270e83 --- /dev/null +++ b/src/experimental/__p0009_bits/full_extent_t.hpp @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include "macros.hpp" + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +struct full_extent_t { explicit full_extent_t() = default; }; + +MDSPAN_IMPL_INLINE_VARIABLE constexpr auto full_extent = full_extent_t{ }; + +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE diff --git a/src/experimental/__p0009_bits/layout_left.hpp b/src/experimental/__p0009_bits/layout_left.hpp new file mode 100644 index 00000000..4b767353 --- /dev/null +++ b/src/experimental/__p0009_bits/layout_left.hpp @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include "macros.hpp" +#include "trait_backports.hpp" +#include "extents.hpp" +#include "layout_stride.hpp" +#include "utility.hpp" +#if MDSPAN_HAS_CXX_17 +#include "../__p2642_bits/layout_padded_fwd.hpp" +#endif +#include + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +//============================================================================== + +template +class layout_left::mapping { + public: + using extents_type = Extents; + using index_type = typename extents_type::index_type; + using size_type = typename extents_type::size_type; + using rank_type = typename extents_type::rank_type; + using layout_type = layout_left; + private: + + static_assert(detail::impl_is_extents_v, + MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::layout_left::mapping must be instantiated with a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); + + template + friend class mapping; + + // i0+(i1 + E(1)*(i2 + E(2)*i3)) + template + struct rank_count {}; + + template + MDSPAN_IMPL_HOST_DEVICE + constexpr index_type compute_offset( + rank_count, const I& i, Indices... idx) const { + return compute_offset(rank_count(), idx...) * + m_extents.extent(r) + i; + } + + template + MDSPAN_IMPL_HOST_DEVICE + constexpr index_type compute_offset( + rank_count, const I& i) const { + return i; + } + + MDSPAN_IMPL_HOST_DEVICE + constexpr index_type compute_offset(rank_count<0,0>) const { return 0; } + + public: + + //-------------------------------------------------------------------------------- + + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping() noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(mapping const&) noexcept = default; + + MDSPAN_IMPL_HOST_DEVICE + constexpr mapping(extents_type const& exts) noexcept + :m_extents(exts) + { } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_constructible, extents_type, OtherExtents) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible::value)) // needs two () due to comma + MDSPAN_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 + mapping(mapping const& other) noexcept // NOLINT(google-explicit-constructor) + :m_extents(other.extents()) + { + /* + * TODO: check precondition + * other.required_span_size() is a representable value of type index_type + */ + } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_constructible, extents_type, OtherExtents) && + (extents_type::rank() <= 1) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible::value)) // needs two () due to comma + MDSPAN_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 + mapping(layout_right::mapping const& other) noexcept // NOLINT(google-explicit-constructor) + :m_extents(other.extents()) + { + /* + * TODO: check precondition + * other.required_span_size() is a representable value of type index_type + */ + } + +#if MDSPAN_HAS_CXX_17 + /** + * Converting constructor from `layout_left_padded::mapping`. + * + * This overload participates in overload resolution only if Mapping is a + * layout_left_padded mapping and extents_type is constructible from + * Mapping::extents_type. + * + * \note There is currently a difference from p2642r2, where this function + * is specified as taking `layout_left_padded< padding_value >::mapping< + * Extents>`. However, this makes `padding_value` non-deducible. + */ + MDSPAN_TEMPLATE_REQUIRES( + class Mapping, + /* requires */ (detail::is_layout_left_padded_mapping::value + &&std::is_constructible_v< + extents_type, typename Mapping::extents_type>)) + MDSPAN_CONDITIONAL_EXPLICIT( + (!std::is_convertible_v)) + MDSPAN_INLINE_FUNCTION constexpr mapping(const Mapping &other) noexcept + : m_extents(other.extents()) { + detail::check_padded_layout_converting_constructor_mandates( + detail::with_rank{}); + detail::check_padded_layout_converting_constructor_preconditions< + extents_type>(detail::with_rank{}, other); + } +#endif + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_constructible, extents_type, OtherExtents) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0)) + MDSPAN_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 + mapping(layout_stride::mapping const& other) noexcept // NOLINT(google-explicit-constructor) + :m_extents(other.extents()) + { + /* + * TODO: check precondition + * other.required_span_size() is a representable value of type index_type + */ + detail::validate_strides(detail::with_rank{}, layout_left{}, m_extents, other); + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED MDSPAN_IMPL_CONSTEXPR_14_DEFAULTED mapping& operator=(mapping const&) noexcept = default; + + MDSPAN_INLINE_FUNCTION + constexpr const extents_type& extents() const noexcept { + return m_extents; + } + + MDSPAN_INLINE_FUNCTION + constexpr index_type required_span_size() const noexcept { + index_type value = 1; + for(rank_type r=0; r()) + ) + ) + MDSPAN_IMPL_HOST_DEVICE + constexpr index_type operator()(Indices... idxs) const noexcept { +#if ! defined(NDEBUG) + detail::check_all_indices(this->extents(), idxs...); +#endif // ! NDEBUG + return compute_offset(rank_count<0, extents_type::rank()>(), static_cast(idxs)...); + } + + + + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return true; } + + MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_exhaustive() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { return true; } + + MDSPAN_INLINE_FUNCTION + constexpr index_type stride(rank_type i) const noexcept +#if MDSPAN_HAS_CXX_20 + requires ( Extents::rank() > 0 ) +#endif + { + index_type value = 1; + for(rank_type r=0; r const& rhs) noexcept { + return lhs.extents() == rhs.extents(); + } + + // In C++ 20 the not equal exists if equal is found +#if !(MDSPAN_HAS_CXX_20) + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( Extents::rank() == OtherExtents::rank()) + ) + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator!=(mapping const& lhs, mapping const& rhs) noexcept { + return lhs.extents() != rhs.extents(); + } +#endif + + // Not really public, but currently needed to implement fully constexpr useable submdspan: + template + MDSPAN_INLINE_FUNCTION + constexpr index_type impl_get_stride(MDSPAN_IMPL_STANDARD_NAMESPACE::extents,std::integer_sequence) const { + return MDSPAN_IMPL_FOLD_TIMES_RIGHT((Idx():1),1); + } + template + MDSPAN_INLINE_FUNCTION + constexpr index_type impl_stide() const noexcept { + return impl_get_stride(m_extents, std::make_index_sequence()); + } + +private: + MDSPAN_IMPL_NO_UNIQUE_ADDRESS extents_type m_extents{}; + + // [mdspan.submdspan.mapping], submdspan mapping specialization + template + MDSPAN_INLINE_FUNCTION + constexpr auto submdspan_mapping_impl( + SliceSpecifiers... slices) const; + + template + MDSPAN_INLINE_FUNCTION + friend constexpr auto submdspan_mapping( + const mapping& src, SliceSpecifiers... slices) { + return src.submdspan_mapping_impl(slices...); + } +}; + + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE diff --git a/src/experimental/__p0009_bits/layout_right.hpp b/src/experimental/__p0009_bits/layout_right.hpp new file mode 100644 index 00000000..4ef1c225 --- /dev/null +++ b/src/experimental/__p0009_bits/layout_right.hpp @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include "macros.hpp" +#include "trait_backports.hpp" +#include "extents.hpp" +#include "layout_stride.hpp" +#include "utility.hpp" +#if MDSPAN_HAS_CXX_17 +#include "../__p2642_bits/layout_padded_fwd.hpp" +#endif + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +//============================================================================== +template +class layout_right::mapping { + public: + using extents_type = Extents; + using index_type = typename extents_type::index_type; + using size_type = typename extents_type::size_type; + using rank_type = typename extents_type::rank_type; + using layout_type = layout_right; + private: + + static_assert(detail::impl_is_extents_v, + MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::layout_right::mapping must be instantiated with a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); + + template + friend class mapping; + + // i0+(i1 + E(1)*(i2 + E(2)*i3)) + template + struct rank_count {}; + + template + MDSPAN_IMPL_HOST_DEVICE + constexpr index_type compute_offset( + index_type offset, rank_count, const I& i, Indices... idx) const { + return compute_offset(offset * m_extents.extent(r) + i,rank_count(), idx...); + } + + template + MDSPAN_IMPL_HOST_DEVICE + constexpr index_type compute_offset( + rank_count<0,extents_type::rank()>, const I& i, Indices... idx) const { + return compute_offset(i,rank_count<1,extents_type::rank()>(),idx...); + } + + MDSPAN_IMPL_HOST_DEVICE + constexpr index_type compute_offset(size_t offset, rank_count) const { + return static_cast(offset); + } + + MDSPAN_IMPL_HOST_DEVICE + constexpr index_type compute_offset(rank_count<0,0>) const { return 0; } + + public: + + //-------------------------------------------------------------------------------- + + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping() noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(mapping const&) noexcept = default; + + MDSPAN_IMPL_HOST_DEVICE + constexpr mapping(extents_type const& exts) noexcept + :m_extents(exts) + { } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_constructible, extents_type, OtherExtents) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible::value)) // needs two () due to comma + MDSPAN_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 + mapping(mapping const& other) noexcept // NOLINT(google-explicit-constructor) + :m_extents(other.extents()) + { + /* + * TODO: check precondition + * other.required_span_size() is a representable value of type index_type + */ + } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_constructible, extents_type, OtherExtents) && + (extents_type::rank() <= 1) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible::value)) // needs two () due to comma + MDSPAN_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 + mapping(layout_left::mapping const& other) noexcept // NOLINT(google-explicit-constructor) + :m_extents(other.extents()) + { + /* + * TODO: check precondition + * other.required_span_size() is a representable value of type index_type + */ + } + + /** + * Converting constructor from `layout_right_padded::mapping`. + * + * This overload participates in overload resolution only if Mapping is a layout_right_padded mapping and + * extents_type is constructible from Mapping::extents_type. + * + * \note There is currently a difference from p2642r2, where this function is specified as taking + * `layout_right_padded< padding_value >::mapping< Extents>`. However, this makes `padding_value` non-deducible. + */ +#if MDSPAN_HAS_CXX_17 + MDSPAN_TEMPLATE_REQUIRES( + class Mapping, + /* requires */ (detail::is_layout_right_padded_mapping::value + &&std::is_constructible_v< + extents_type, typename Mapping::extents_type>)) + MDSPAN_CONDITIONAL_EXPLICIT( + (!std::is_convertible_v)) + MDSPAN_INLINE_FUNCTION constexpr mapping(const Mapping &other) noexcept + : m_extents(other.extents()) { + detail::check_padded_layout_converting_constructor_mandates( + detail::with_rank{}); + detail::check_padded_layout_converting_constructor_preconditions< + extents_type>(detail::with_rank{}, other); + } +#endif + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_constructible, extents_type, OtherExtents) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0)) + MDSPAN_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 + mapping(layout_stride::mapping const& other) noexcept // NOLINT(google-explicit-constructor) + :m_extents(other.extents()) + { + /* + * TODO: check precondition + * other.required_span_size() is a representable value of type index_type + */ + detail::validate_strides(detail::with_rank{}, layout_right{}, m_extents, other); + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED MDSPAN_IMPL_CONSTEXPR_14_DEFAULTED mapping& operator=(mapping const&) noexcept = default; + + MDSPAN_INLINE_FUNCTION + constexpr const extents_type& extents() const noexcept { + return m_extents; + } + + MDSPAN_INLINE_FUNCTION + constexpr index_type required_span_size() const noexcept { + index_type value = 1; + for(rank_type r=0; r != extents_type::rank(); ++r) value*=m_extents.extent(r); + return value; + } + + //-------------------------------------------------------------------------------- + + MDSPAN_TEMPLATE_REQUIRES( + class ... Indices, + /* requires */ ( + (sizeof...(Indices) == extents_type::rank()) && + (detail::are_valid_indices()) + ) + ) + MDSPAN_IMPL_HOST_DEVICE + constexpr index_type operator()(Indices... idxs) const noexcept { +#if ! defined(NDEBUG) + detail::check_all_indices(this->extents(), idxs...); +#endif // ! NDEBUG + return compute_offset(rank_count<0, extents_type::rank()>(), static_cast(idxs)...); + } + + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_exhaustive() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { return true; } + + MDSPAN_INLINE_FUNCTION + constexpr index_type stride(rank_type i) const noexcept +#if MDSPAN_HAS_CXX_20 + requires ( Extents::rank() > 0 ) +#endif + { + index_type value = 1; + for(rank_type r=extents_type::rank()-1; r>i; r--) value*=m_extents.extent(r); + return value; + } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( Extents::rank() == OtherExtents::rank()) + ) + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator==(mapping const& lhs, mapping const& rhs) noexcept { + return lhs.extents() == rhs.extents(); + } + + // In C++ 20 the not equal exists if equal is found +#if !(MDSPAN_HAS_CXX_20) + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ (Extents::rank() == OtherExtents::rank()) + ) + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator!=(mapping const& lhs, mapping const& rhs) noexcept { + return lhs.extents() != rhs.extents(); + } +#endif + + // Not really public, but currently needed to implement fully constexpr useable submdspan: + template + MDSPAN_INLINE_FUNCTION + constexpr index_type impl_get_stride(MDSPAN_IMPL_STANDARD_NAMESPACE::extents,std::integer_sequence) const { + return MDSPAN_IMPL_FOLD_TIMES_RIGHT((Idx>N? m_extents.template extent():1),1); + } + template + MDSPAN_INLINE_FUNCTION + constexpr index_type impl_stide() const noexcept { + return impl_get_stride(m_extents, std::make_index_sequence()); + } + +private: + MDSPAN_IMPL_NO_UNIQUE_ADDRESS extents_type m_extents{}; + + // [mdspan.submdspan.mapping], submdspan mapping specialization + template + MDSPAN_INLINE_FUNCTION + constexpr auto submdspan_mapping_impl( + SliceSpecifiers... slices) const; + + template + MDSPAN_INLINE_FUNCTION + friend constexpr auto submdspan_mapping( + const mapping& src, SliceSpecifiers... slices) { + return src.submdspan_mapping_impl(slices...); + } +}; + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE diff --git a/src/experimental/__p0009_bits/layout_stride.hpp b/src/experimental/__p0009_bits/layout_stride.hpp new file mode 100644 index 00000000..37fbb30d --- /dev/null +++ b/src/experimental/__p0009_bits/layout_stride.hpp @@ -0,0 +1,679 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include "macros.hpp" +#include "extents.hpp" +#include "trait_backports.hpp" +#include "compressed_pair.hpp" +#include "utility.hpp" + +#if MDSPAN_HAS_CXX_17 +#include "../__p2642_bits/layout_padded_fwd.hpp" +#endif + +#if !defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) +# include "no_unique_address.hpp" +#endif + +#include +#include +#include + +#ifdef __cpp_lib_span +#include +#endif +#if defined(MDSPAN_IMPL_USE_CONCEPTS) && MDSPAN_HAS_CXX_20 && defined(__cpp_lib_concepts) +# include +#endif + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +struct layout_left { + template + class mapping; +}; +struct layout_right { + template + class mapping; +}; + +namespace detail { +#if MDSPAN_HAS_CXX_17 + using std::void_t; +#else + template using void_t = void; +#endif + // FIXME GCC <= 12: workaround gcc-12 bug that shows up in Kokkos; compilation fails when Mapping doesn't have + // extents_type. Normally this should just be a substitution failure, but causes an error with GCC <= 12 + // FIXME MSVC: I guess MSVC has a similar issue when it hits Layout::template mapping + template + struct is_mapping_of_impl : std::false_type {}; + + // FIXME GCC <= 12: We can't just do a conjunction of the two conditions, because the affected GCC versions seem to not + // short-circuit when resolving the substitution of Mapping + template + struct is_mapping_of_impl, void_t< typename Layout::template mapping >> + : std::is_same, Mapping> + {}; + + template + constexpr bool is_mapping_of = + is_mapping_of_impl::value; + +#if defined(MDSPAN_IMPL_USE_CONCEPTS) && MDSPAN_HAS_CXX_20 +# if !defined(__cpp_lib_concepts) + namespace internal { + namespace detail { + template + concept same_as = std::is_same_v; + } // namespace detail + template + concept same_as = detail::same_as && detail::same_as; + } // namespace internal +# endif + + template + concept layout_mapping_alike = requires { + requires impl_is_extents::value; +#if defined(__cpp_lib_concepts) + { M::is_always_strided() } -> std::same_as; + { M::is_always_exhaustive() } -> std::same_as; + { M::is_always_unique() } -> std::same_as; +#else + { M::is_always_strided() } -> internal::same_as; + { M::is_always_exhaustive() } -> internal::same_as; + { M::is_always_unique() } -> internal::same_as; +#endif + std::bool_constant::value; + std::bool_constant::value; + std::bool_constant::value; + }; +#endif + +} // namespace detail + +struct layout_stride { + template + class mapping +#if !defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + : private detail::no_unique_address_emulation< + detail::impl_compressed_pair< + Extents, + detail::possibly_empty_array + > + > +#endif + { + public: + using extents_type = Extents; + using index_type = typename extents_type::index_type; + using size_type = typename extents_type::size_type; + using rank_type = typename extents_type::rank_type; + using layout_type = layout_stride; + + // This could be a `requires`, but I think it's better and clearer as a `static_assert`. + static_assert(detail::impl_is_extents_v, + MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::layout_stride::mapping must be instantiated with a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); + + + private: + + //---------------------------------------------------------------------------- + + using strides_storage_t = detail::possibly_empty_array; + using member_pair_t = detail::impl_compressed_pair; + +#if defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + MDSPAN_IMPL_NO_UNIQUE_ADDRESS member_pair_t m_members; +#else + using base_t = detail::no_unique_address_emulation; +#endif + + MDSPAN_FORCE_INLINE_FUNCTION constexpr strides_storage_t const& + strides_storage() const noexcept { +#if defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + return m_members.second(); +#else + return this->base_t::ref().second(); +#endif + } + MDSPAN_FORCE_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 strides_storage_t& + strides_storage() noexcept { +#if defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + return m_members.second(); +#else + return this->base_t::ref().second(); +#endif + } + + template + MDSPAN_IMPL_HOST_DEVICE + constexpr index_type get_size(::MDSPAN_IMPL_STANDARD_NAMESPACE::extents,std::integer_sequence) const { + return MDSPAN_IMPL_FOLD_TIMES_RIGHT( static_cast(extents().extent(Idx)), 1 ); + } + + //---------------------------------------------------------------------------- + + template + friend class mapping; + + //---------------------------------------------------------------------------- + + // Workaround for non-deducibility of the index sequence template parameter if it's given at the top level + template + struct deduction_workaround; + + template + struct deduction_workaround> + { + template + MDSPAN_INLINE_FUNCTION + static constexpr bool _eq_impl(mapping const& self, mapping const& other) noexcept { + using common_t = std::common_type_t; + return MDSPAN_IMPL_FOLD_AND((static_cast(self.stride(Idxs)) == static_cast(other.stride(Idxs))) /* && ... */) + && MDSPAN_IMPL_FOLD_AND((static_cast(self.extents().extent(Idxs)) == static_cast(other.extents().extent(Idxs))) /* || ... */); + } + template + MDSPAN_INLINE_FUNCTION + static constexpr bool _not_eq_impl(mapping const& self, mapping const& other) noexcept { + using common_t = std::common_type_t; + return MDSPAN_IMPL_FOLD_OR((static_cast(self.stride(Idxs)) != static_cast(other.stride(Idxs))) /* || ... */) + || MDSPAN_IMPL_FOLD_OR((static_cast(self.extents().extent(Idxs)) != static_cast(other.extents().extent(Idxs))) /* || ... */); + } + + template + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr size_t _call_op_impl(mapping const& self, Integral... idxs) noexcept { + return MDSPAN_IMPL_FOLD_PLUS_RIGHT((idxs * self.stride(Idxs)), /* + ... + */ 0); + } + + MDSPAN_INLINE_FUNCTION + static constexpr size_t _req_span_size_impl(mapping const& self) noexcept { + // assumes no negative strides; not sure if I'm allowed to assume that or not + return deduction_workaround_impl::_call_op_impl(self, (self.extents().template extent() - 1)...) + 1; + } + + template + MDSPAN_INLINE_FUNCTION + static constexpr const strides_storage_t fill_strides(const OtherMapping& map) { + return strides_storage_t{static_cast(map.stride(Idxs))...}; + } + + MDSPAN_INLINE_FUNCTION + static constexpr const strides_storage_t& fill_strides(const strides_storage_t& s) { + return s; + } + + template + static constexpr const strides_storage_t fill_strides(const std::array& s) { + return strides_storage_t{static_cast(s[Idxs])...}; + } + + MDSPAN_TEMPLATE_REQUIRES( + class IntegralType, + (MDSPAN_IMPL_TRAIT(std::is_convertible, IntegralType, typename extents_type::index_type)) + ) + MDSPAN_INLINE_FUNCTION + // Need to avoid zero length c-array + static constexpr const strides_storage_t fill_strides(mdspan_non_standard_tag, const IntegralType (&s)[extents_type::rank()>0?extents_type::rank():1]) { + return strides_storage_t{static_cast(s[Idxs])...}; + } + +#ifdef __cpp_lib_span + template + static constexpr const strides_storage_t fill_strides(const std::span& s) { + return strides_storage_t{static_cast(s[Idxs])...}; + } +#endif + + MDSPAN_INLINE_FUNCTION + static constexpr std::array return_strides(const strides_storage_t& s) { + return std::array{s[Idxs]...}; + } + + template + MDSPAN_INLINE_FUNCTION + static constexpr size_t return_zero() { return 0; } + + template + MDSPAN_INLINE_FUNCTION + static constexpr typename Mapping::index_type + offset(const Mapping& m) { return m(return_zero()...); } + }; + + // Can't use defaulted parameter in the deduction_workaround template because of a bug in MSVC warning C4348. + using deduction_workaround_impl = deduction_workaround>; + + MDSPAN_FUNCTION + static constexpr strides_storage_t strides_storage(detail::with_rank<0>) { + return {}; + } + + template + MDSPAN_FUNCTION + static constexpr strides_storage_t strides_storage(detail::with_rank) { + strides_storage_t s{}; + + extents_type e; + index_type stride = 1; + for(int r = static_cast(extents_type::rank() - 1); r >= 0; r--) { + s[r] = stride; + stride *= e.extent(r); + } + + return s; + } + + //---------------------------------------------------------------------------- + +#if defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + MDSPAN_INLINE_FUNCTION constexpr explicit + mapping(member_pair_t&& m) : m_members(::std::move(m)) {} +#else + MDSPAN_INLINE_FUNCTION constexpr explicit + mapping(base_t&& __b) : base_t(::std::move(__b)) {} +#endif + + public: + + //-------------------------------------------------------------------------------- + + MDSPAN_INLINE_FUNCTION constexpr mapping() noexcept +#if defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + : m_members{ +#else + : base_t(base_t{member_pair_t( +#endif + extents_type(), + strides_storage_t(strides_storage(detail::with_rank{})) +#if defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + } +#else + )}) +#endif + {} + + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(mapping const&) noexcept = default; + + MDSPAN_TEMPLATE_REQUIRES( + class IntegralTypes, + /* requires */ ( + // MSVC 19.32 does not like using index_type here, requires the typename Extents::index_type + // error C2641: cannot deduce template arguments for 'MDSPAN_IMPL_STANDARD_NAMESPACE::layout_stride::mapping' + MDSPAN_IMPL_TRAIT(std::is_convertible, const std::remove_const_t&, typename Extents::index_type) && + MDSPAN_IMPL_TRAIT(std::is_nothrow_constructible, typename Extents::index_type, const std::remove_const_t&) + ) + ) + constexpr + mapping( + extents_type const& e, + std::array const& s + ) noexcept +#if defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + : m_members{ +#else + : base_t(base_t{member_pair_t( +#endif + e, strides_storage_t(deduction_workaround_impl::fill_strides(s)) +#if defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + } +#else + )}) +#endif + { + /* + * TODO: check preconditions + * - s[i] > 0 is true for all i in the range [0, rank_ ). + * - REQUIRED-SPAN-SIZE(e, s) is a representable value of type index_type ([basic.fundamental]). + * - If rank_ is greater than 0, then there exists a permutation P of the integers in the + * range [0, rank_), such that s[ pi ] >= s[ pi − 1 ] * e.extent( pi − 1 ) is true for + * all i in the range [1, rank_ ), where pi is the ith element of P. + */ + } + + MDSPAN_TEMPLATE_REQUIRES( + class IntegralTypes, + /* requires */ ( + // MSVC 19.32 does not like using index_type here, requires the typename Extents::index_type + // error C2641: cannot deduce template arguments for 'MDSPAN_IMPL_STANDARD_NAMESPACE::layout_stride::mapping' + MDSPAN_IMPL_TRAIT(std::is_convertible, const std::remove_const_t&, typename Extents::index_type) && + MDSPAN_IMPL_TRAIT(std::is_nothrow_constructible, typename Extents::index_type, const std::remove_const_t&) + ) + ) + MDSPAN_INLINE_FUNCTION + constexpr + mapping( + mdspan_non_standard_tag, + extents_type const& e, + // Need to avoid zero-length c-array + const IntegralTypes (&s)[extents_type::rank()>0?extents_type::rank():1] + ) noexcept +#if defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + : m_members{ +#else + : base_t(base_t{member_pair_t( +#endif + e, strides_storage_t(deduction_workaround_impl::fill_strides(mdspan_non_standard, s)) +#if defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + } +#else + )}) +#endif + { + /* + * TODO: check preconditions + * - s[i] > 0 is true for all i in the range [0, rank_ ). + * - REQUIRED-SPAN-SIZE(e, s) is a representable value of type index_type ([basic.fundamental]). + * - If rank_ is greater than 0, then there exists a permutation P of the integers in the + * range [0, rank_), such that s[ pi ] >= s[ pi − 1 ] * e.extent( pi − 1 ) is true for + * all i in the range [1, rank_ ), where pi is the ith element of P. + */ + } + +#ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES( + class IntegralTypes, + /* requires */ ( + // MSVC 19.32 does not like using index_type here, requires the typename Extents::index_type + // error C2641: cannot deduce template arguments for 'MDSPAN_IMPL_STANDARD_NAMESPACE::layout_stride::mapping' + MDSPAN_IMPL_TRAIT(std::is_convertible, const std::remove_const_t&, typename Extents::index_type) && + MDSPAN_IMPL_TRAIT(std::is_nothrow_constructible, typename Extents::index_type, const std::remove_const_t&) + ) + ) + constexpr + mapping( + extents_type const& e, + std::span const& s + ) noexcept +#if defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + : m_members{ +#else + : base_t(base_t{member_pair_t( +#endif + e, strides_storage_t(deduction_workaround_impl::fill_strides(s)) +#if defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + } +#else + )}) +#endif + { + /* + * TODO: check preconditions + * - s[i] > 0 is true for all i in the range [0, rank_ ). + * - REQUIRED-SPAN-SIZE(e, s) is a representable value of type index_type ([basic.fundamental]). + * - If rank_ is greater than 0, then there exists a permutation P of the integers in the + * range [0, rank_), such that s[ pi ] >= s[ pi − 1 ] * e.extent( pi − 1 ) is true for + * all i in the range [1, rank_ ), where pi is the ith element of P. + */ + } +#endif // __cpp_lib_span + +#if !(defined(MDSPAN_IMPL_USE_CONCEPTS) && MDSPAN_HAS_CXX_20) + MDSPAN_TEMPLATE_REQUIRES( + class StridedLayoutMapping, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_constructible, extents_type, typename StridedLayoutMapping::extents_type) && + detail::is_mapping_of && + StridedLayoutMapping::is_always_unique() && + StridedLayoutMapping::is_always_strided() + ) + ) +#else + template + requires( + detail::layout_mapping_alike && + MDSPAN_IMPL_TRAIT(std::is_constructible, extents_type, typename StridedLayoutMapping::extents_type) && + StridedLayoutMapping::is_always_unique() && + StridedLayoutMapping::is_always_strided() + ) +#endif + MDSPAN_CONDITIONAL_EXPLICIT( + !(std::is_convertible::value && + (detail::is_mapping_of || + detail::is_mapping_of || + detail::is_layout_left_padded_mapping::value || // Don't need to guard for C++14 as this isn't compiled in < C++20 + detail::is_layout_right_padded_mapping::value || + detail::is_mapping_of)) + ) // needs two () due to comma + MDSPAN_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 + mapping(StridedLayoutMapping const& other) noexcept // NOLINT(google-explicit-constructor) +#if defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + : m_members{ +#else + : base_t(base_t{member_pair_t( +#endif + other.extents(), strides_storage_t(deduction_workaround_impl::fill_strides(other)) +#if defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + } +#else + )}) +#endif + { + /* + * TODO: check preconditions + * - other.stride(i) > 0 is true for all i in the range [0, rank_ ). + * - other.required_span_size() is a representable value of type index_type ([basic.fundamental]). + * - OFFSET(other) == 0 + */ + } + + //-------------------------------------------------------------------------------- + + MDSPAN_INLINE_FUNCTION_DEFAULTED MDSPAN_IMPL_CONSTEXPR_14_DEFAULTED + mapping& operator=(mapping const&) noexcept = default; + + MDSPAN_INLINE_FUNCTION constexpr const extents_type& extents() const noexcept { +#if defined(MDSPAN_IMPL_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) + return m_members.first(); +#else + return this->base_t::ref().first(); +#endif + } + + MDSPAN_INLINE_FUNCTION + constexpr std::array< index_type, extents_type::rank() > strides() const noexcept { + return deduction_workaround_impl::return_strides(strides_storage()); + } + + MDSPAN_INLINE_FUNCTION + constexpr index_type required_span_size() const noexcept { + index_type span_size = 1; + // using int here to avoid warning about pointless comparison to 0 + for(int r = 0; r < static_cast(extents_type::rank()); r++) { + // Return early if any of the extents are zero + if(extents().extent(r)==0) return 0; + span_size += ( static_cast(extents().extent(r) - 1 ) * strides_storage()[r]); + } + return span_size; + } + + + MDSPAN_TEMPLATE_REQUIRES( + class... Indices, + /* requires */ ( + sizeof...(Indices) == Extents::rank() && + (detail::are_valid_indices()) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr index_type operator()(Indices... idxs) const noexcept { +#if ! defined(NDEBUG) + detail::check_all_indices(this->extents(), idxs...); +#endif // ! NDEBUG + return static_cast(deduction_workaround_impl::_call_op_impl(*this, static_cast(idxs)...)); + } + + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return true; } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { + return false; + } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return true; } + + MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { return true; } + + private: + MDSPAN_INLINE_FUNCTION + constexpr bool exhaustive_for_nonzero_span_size() const + { + return required_span_size() == get_size(extents(), std::make_index_sequence()); + } + + MDSPAN_INLINE_FUNCTION + constexpr bool is_exhaustive_impl(detail::with_rank<0>) const + { + return true; + } + MDSPAN_INLINE_FUNCTION + constexpr bool is_exhaustive_impl(detail::with_rank<1>) const + { + if (required_span_size() != static_cast(0)) { + return exhaustive_for_nonzero_span_size(); + } + return stride(0) == 1; + } + template + MDSPAN_INLINE_FUNCTION + constexpr bool is_exhaustive_impl(detail::with_rank) const + { + if (required_span_size() != static_cast(0)) { + return exhaustive_for_nonzero_span_size(); + } + + rank_type r_largest = 0; + for (rank_type r = 1; r < extents_type::rank(); r++) { + if (stride(r) > stride(r_largest)) { + r_largest = r; + } + } + for (rank_type r = 0; r < extents_type::rank(); r++) { + if (extents().extent(r) == 0 && r != r_largest) { + return false; + } + } + return true; + } + + public: + MDSPAN_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 bool is_exhaustive() const noexcept { + return is_exhaustive_impl(detail::with_rank{}); + } + MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { return true; } + + + MDSPAN_INLINE_FUNCTION + constexpr index_type stride(rank_type r) const noexcept { + return strides_storage()[r]; + } + +#if !(defined(MDSPAN_IMPL_USE_CONCEPTS) && MDSPAN_HAS_CXX_20) + MDSPAN_TEMPLATE_REQUIRES( + class StridedLayoutMapping, + /* requires */ ( + detail::is_mapping_of && + (extents_type::rank() == StridedLayoutMapping::extents_type::rank()) && + StridedLayoutMapping::is_always_strided() + ) + ) +#else + template + requires( + detail::layout_mapping_alike && + (extents_type::rank() == StridedLayoutMapping::extents_type::rank()) && + StridedLayoutMapping::is_always_strided() + ) +#endif + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator==(const mapping& x, const StridedLayoutMapping& y) noexcept { + return (x.extents() == y.extents()) && + (deduction_workaround_impl::offset(y) == static_cast(0)) && + detail::rankwise_equal(detail::with_rank{}, x, y, detail::stride); + } + + // This one is not technically part of the proposal. Just here to make implementation a bit more optimal hopefully + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + (extents_type::rank() == OtherExtents::rank()) + ) + ) + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator==(mapping const& lhs, mapping const& rhs) noexcept { + return deduction_workaround_impl::_eq_impl(lhs, rhs); + } + +#if !MDSPAN_HAS_CXX_20 + MDSPAN_TEMPLATE_REQUIRES( + class StridedLayoutMapping, + /* requires */ ( + detail::is_mapping_of && + (extents_type::rank() == StridedLayoutMapping::extents_type::rank()) && + StridedLayoutMapping::is_always_strided() + ) + ) + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator!=(const mapping& x, const StridedLayoutMapping& y) noexcept { + return !(x == y); + } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ ( + (extents_type::rank() == OtherExtents::rank()) + ) + ) + MDSPAN_INLINE_FUNCTION + friend constexpr bool operator!=(mapping const& lhs, mapping const& rhs) noexcept { + return deduction_workaround_impl::_not_eq_impl(lhs, rhs); + } +#endif + + // [mdspan.submdspan.mapping], submdspan mapping specialization + template + MDSPAN_INLINE_FUNCTION + constexpr auto submdspan_mapping_impl( + SliceSpecifiers... slices) const; + + template + MDSPAN_INLINE_FUNCTION + friend constexpr auto submdspan_mapping( + const mapping& src, SliceSpecifiers... slices) { + return src.submdspan_mapping_impl(slices...); + } + }; +}; + +namespace detail { + +template +MDSPAN_INLINE_FUNCTION +constexpr void validate_strides(with_rank<0>, Layout, const Extents&, const Mapping&) +{} + +template +MDSPAN_INLINE_FUNCTION +constexpr void validate_strides(with_rank, Layout, const Extents& ext, const Mapping& other) +{ + static_assert(std::is_same::value && + (std::is_same::value || + std::is_same::value) + , "This function is only intended to validate construction of " + "a layout_left or layout_right mapping from a layout_stride mapping."); + + constexpr auto is_left = std::is_same::value; + + typename Extents::index_type expected_stride = 1; + + for (std::size_t r = 0; r < N; r++) { + const std::size_t s = is_left ? r : N - 1 - r; + + MDSPAN_IMPL_PRECONDITION(common_integral_compare(expected_stride, other.stride(s)) + && "invalid strides for layout_{left,right}"); + + expected_stride *= ext.extent(s); + } +} + +} // namespace detail +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE diff --git a/src/experimental/__p0009_bits/macros.hpp b/src/experimental/__p0009_bits/macros.hpp new file mode 100644 index 00000000..9955e41c --- /dev/null +++ b/src/experimental/__p0009_bits/macros.hpp @@ -0,0 +1,706 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include "config.hpp" + +#include +#include +#include // std::is_void +#if defined(MDSPAN_IMPL_HAS_SYCL) +#include // sycl::ext::oneapi::experimental::printf +#endif +#if defined(MDSPAN_IMPL_HAS_CUDA) || defined(MDSPAN_IMPL_HAS_HIP) || defined(MDSPAN_IMPL_HAS_SYCL) || defined(MDSPAN_IMPL_HAS_OPENACC) +#include "assert.h" +#endif + +#ifndef MDSPAN_IMPL_HOST_DEVICE +# if defined(MDSPAN_IMPL_HAS_CUDA) || defined(MDSPAN_IMPL_HAS_HIP) +# define MDSPAN_IMPL_HOST_DEVICE __host__ __device__ +# else +# define MDSPAN_IMPL_HOST_DEVICE +# endif +#endif + +#ifndef MDSPAN_FORCE_INLINE_FUNCTION +# ifdef MDSPAN_IMPL_COMPILER_MSVC // Microsoft compilers +# define MDSPAN_FORCE_INLINE_FUNCTION __forceinline MDSPAN_IMPL_HOST_DEVICE +# else +# define MDSPAN_FORCE_INLINE_FUNCTION __attribute__((always_inline)) MDSPAN_IMPL_HOST_DEVICE +# endif +#endif + +#ifndef MDSPAN_INLINE_FUNCTION +# define MDSPAN_INLINE_FUNCTION inline MDSPAN_IMPL_HOST_DEVICE +#endif + +#ifndef MDSPAN_FUNCTION +# define MDSPAN_FUNCTION MDSPAN_IMPL_HOST_DEVICE +#endif + +#ifdef MDSPAN_IMPL_HAS_HIP +# define MDSPAN_DEDUCTION_GUIDE MDSPAN_IMPL_HOST_DEVICE +#else +# define MDSPAN_DEDUCTION_GUIDE +#endif + +// In CUDA defaulted functions do not need host device markup +#ifndef MDSPAN_INLINE_FUNCTION_DEFAULTED +# define MDSPAN_INLINE_FUNCTION_DEFAULTED +#endif + +//============================================================================== +// {{{1 + +#define MDSPAN_PP_COUNT(...) \ + MDSPAN_IMPL_PP_INTERNAL_EXPAND_ARGS( \ + MDSPAN_IMPL_PP_INTERNAL_ARGS_AUGMENTER(__VA_ARGS__) \ + ) + +#define MDSPAN_IMPL_PP_INTERNAL_ARGS_AUGMENTER(...) unused, __VA_ARGS__ +#define MDSPAN_IMPL_PP_INTERNAL_EXPAND(x) x +#define MDSPAN_IMPL_PP_INTERNAL_EXPAND_ARGS(...) \ + MDSPAN_IMPL_PP_INTERNAL_EXPAND( \ + MDSPAN_IMPL_PP_INTERNAL_COUNT( \ + __VA_ARGS__, 69, 68, 67, 66, 65, 64, 63, 62, 61, \ + 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, \ + 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, \ + 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, \ + 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, \ + 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 \ + ) \ + ) +# define MDSPAN_IMPL_PP_INTERNAL_COUNT( \ + _1_, _2_, _3_, _4_, _5_, _6_, _7_, _8_, _9_, \ + _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, \ + _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, \ + _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, \ + _60, _61, _62, _63, _64, _65, _66, _67, _68, _69, \ + _70, count, ...) count \ + /**/ + +#define MDSPAN_PP_STRINGIFY_IMPL(x) #x +#define MDSPAN_PP_STRINGIFY(x) MDSPAN_PP_STRINGIFY_IMPL(x) + +#define MDSPAN_PP_CAT_IMPL(x, y) x ## y +#define MDSPAN_PP_CAT(x, y) MDSPAN_PP_CAT_IMPL(x, y) + +#define MDSPAN_PP_EVAL(X, ...) X(__VA_ARGS__) + +#define MDSPAN_PP_REMOVE_PARENS_IMPL(...) __VA_ARGS__ +#define MDSPAN_PP_REMOVE_PARENS(...) MDSPAN_PP_REMOVE_PARENS_IMPL __VA_ARGS__ + +#define MDSPAN_IMPL_STANDARD_NAMESPACE_STRING MDSPAN_PP_STRINGIFY(MDSPAN_IMPL_STANDARD_NAMESPACE) +#define MDSPAN_IMPL_PROPOSED_NAMESPACE_STRING MDSPAN_PP_STRINGIFY(MDSPAN_IMPL_STANDARD_NAMESPACE) "::" MDSPAN_PP_STRINGIFY(MDSPAN_IMPL_PROPOSED_NAMESPACE) + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace detail { + +#if defined(MDSPAN_IMPL_HAS_CUDA) || defined(MDSPAN_IMPL_HAS_HIP) +MDSPAN_FUNCTION inline void default_precondition_violation_handler(const char* cond, const char* file, unsigned line) +{ + ::printf("%s:%u: precondition failure: `%s`\n", file, line, cond); + assert(0); +} +#elif defined(MDSPAN_IMPL_HAS_SYCL) +MDSPAN_FUNCTION inline void default_precondition_violation_handler(const char* cond, const char* file, unsigned line) +{ +#ifdef __INTEL_LLVM_COMPILER + sycl::ext::oneapi::experimental::printf("%s:%u: precondition failure: `%s`\n", file, line, cond); +#else + (void) cond; + (void) file; + (void) line; +#endif + assert(0); +} +#elif defined(MDSPAN_IMPL_HAS_OPENACC) +MDSPAN_FUNCTION inline void default_precondition_violation_handler(const char* /* cond */, const char* /* file */, unsigned /* line */) +{ + assert(false); +} +#else +MDSPAN_FUNCTION inline void default_precondition_violation_handler(const char* cond, const char* file, unsigned line) +{ + std::fprintf(stderr, "%s:%u: precondition failure: `%s`\n", file, line, cond); + std::abort(); +} +#endif + +} // namespace detail +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +#ifndef MDSPAN_IMPL_PRECONDITION_VIOLATION_HANDLER +#define MDSPAN_IMPL_PRECONDITION_VIOLATION_HANDLER(cond, file, line) \ + MDSPAN_IMPL_STANDARD_NAMESPACE::detail::default_precondition_violation_handler(cond, file, line) +#endif + +#ifndef MDSPAN_IMPL_CHECK_PRECONDITION + #ifdef NDEBUG + #define MDSPAN_IMPL_CHECK_PRECONDITION 0 + #else + #define MDSPAN_IMPL_CHECK_PRECONDITION 1 + #endif +#endif + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace detail { + +template +MDSPAN_FUNCTION constexpr void precondition(const char* cond, const char* file, unsigned line) +{ + if (!check) { return; } + // in case the macro doesn't use the arguments for custom macros + (void) cond; + (void) file; + (void) line; + MDSPAN_IMPL_PRECONDITION_VIOLATION_HANDLER(cond, file, line); +} + +} // namespace detail +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +#define MDSPAN_IMPL_PRECONDITION(...) \ + do { \ + if (!(__VA_ARGS__)) { \ + MDSPAN_IMPL_STANDARD_NAMESPACE::detail::precondition(#__VA_ARGS__, __FILE__, __LINE__); \ + } \ + } while (0) + +// end Preprocessor helpers }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +// These compatibility macros don't help with partial ordering, but they should do the trick +// for what we need to do with concepts in mdspan +#ifdef MDSPAN_IMPL_USE_CONCEPTS +# define MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) > requires REQ +# define MDSPAN_FUNCTION_REQUIRES(PAREN_PREQUALS, FNAME, PAREN_PARAMS, QUALS, REQ) \ + MDSPAN_PP_REMOVE_PARENS(PAREN_PREQUALS) FNAME PAREN_PARAMS QUALS requires REQ \ + /**/ +#else +# define MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) , typename ::std::enable_if<(REQ), int>::type = 0> +# define MDSPAN_FUNCTION_REQUIRES(PAREN_PREQUALS, FNAME, PAREN_PARAMS, QUALS, REQ) \ + MDSPAN_TEMPLATE_REQUIRES( \ + class function_requires_ignored=void, \ + (std::is_void::value && REQ) \ + ) MDSPAN_PP_REMOVE_PARENS(PAREN_PREQUALS) FNAME PAREN_PARAMS QUALS \ + /**/ +#endif + +#if defined(MDSPAN_IMPL_COMPILER_MSVC) && (!defined(_MSVC_TRADITIONAL) || _MSVC_TRADITIONAL) +# define MDSPAN_TEMPLATE_REQUIRES(...) \ + MDSPAN_PP_CAT( \ + MDSPAN_PP_CAT(MDSPAN_TEMPLATE_REQUIRES_, MDSPAN_PP_COUNT(__VA_ARGS__))\ + (__VA_ARGS__), \ + ) \ + /**/ +#else +# define MDSPAN_TEMPLATE_REQUIRES(...) \ + MDSPAN_PP_EVAL( \ + MDSPAN_PP_CAT(MDSPAN_TEMPLATE_REQUIRES_, MDSPAN_PP_COUNT(__VA_ARGS__)), \ + __VA_ARGS__ \ + ) \ + /**/ +#endif + +#define MDSPAN_TEMPLATE_REQUIRES_2(TP1, REQ) \ + template end Concept emulation }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +#ifdef MDSPAN_IMPL_USE_INLINE_VARIABLES +# define MDSPAN_IMPL_INLINE_VARIABLE inline +#else +# define MDSPAN_IMPL_INLINE_VARIABLE +#endif + +// end inline variables }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +#if MDSPAN_IMPL_USE_RETURN_TYPE_DEDUCTION +# define MDSPAN_IMPL_DEDUCE_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \ + auto MDSPAN_PP_REMOVE_PARENS(SIGNATURE) { return MDSPAN_PP_REMOVE_PARENS(BODY); } +# define MDSPAN_IMPL_DEDUCE_DECLTYPE_AUTO_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \ + decltype(auto) MDSPAN_PP_REMOVE_PARENS(SIGNATURE) { return MDSPAN_PP_REMOVE_PARENS(BODY); } +#else +# define MDSPAN_IMPL_DEDUCE_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \ + auto MDSPAN_PP_REMOVE_PARENS(SIGNATURE) \ + -> std::remove_cv_t> \ + { return MDSPAN_PP_REMOVE_PARENS(BODY); } +# define MDSPAN_IMPL_DEDUCE_DECLTYPE_AUTO_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \ + auto MDSPAN_PP_REMOVE_PARENS(SIGNATURE) \ + -> decltype(BODY) \ + { return MDSPAN_PP_REMOVE_PARENS(BODY); } + +#endif + +// end Return type deduction }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +struct enable_fold_comma { }; + +#ifdef MDSPAN_IMPL_USE_FOLD_EXPRESSIONS +# define MDSPAN_IMPL_FOLD_AND(...) ((__VA_ARGS__) && ...) +# define MDSPAN_IMPL_FOLD_AND_TEMPLATE(...) ((__VA_ARGS__) && ...) +# define MDSPAN_IMPL_FOLD_OR(...) ((__VA_ARGS__) || ...) +# define MDSPAN_IMPL_FOLD_ASSIGN_LEFT(INIT, ...) (INIT = ... = (__VA_ARGS__)) +# define MDSPAN_IMPL_FOLD_ASSIGN_RIGHT(PACK, ...) (PACK = ... = (__VA_ARGS__)) +# define MDSPAN_IMPL_FOLD_TIMES_RIGHT(PACK, ...) (PACK * ... * (__VA_ARGS__)) +# define MDSPAN_IMPL_FOLD_PLUS_RIGHT(PACK, ...) (PACK + ... + (__VA_ARGS__)) +# define MDSPAN_IMPL_FOLD_COMMA(...) ((__VA_ARGS__), ...) +#else + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +namespace fold_compatibility_impl { + +// We could probably be more clever here, but at the (small) risk of losing some compiler understanding. For the +// few operations we need, it's not worth generalizing over the operation + +#if MDSPAN_IMPL_USE_RETURN_TYPE_DEDUCTION + +MDSPAN_FORCE_INLINE_FUNCTION +constexpr decltype(auto) fold_right_and_impl() { + return true; +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr decltype(auto) fold_right_and_impl(Arg&& arg, Args&&... args) { + return ((Arg&&)arg) && fold_compatibility_impl::fold_right_and_impl((Args&&)args...); +} + +MDSPAN_FORCE_INLINE_FUNCTION +constexpr decltype(auto) fold_right_or_impl() { + return false; +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto fold_right_or_impl(Arg&& arg, Args&&... args) { + return ((Arg&&)arg) || fold_compatibility_impl::fold_right_or_impl((Args&&)args...); +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto fold_left_assign_impl(Arg1&& arg1) { + return (Arg1&&)arg1; +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto fold_left_assign_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) { + return fold_compatibility_impl::fold_left_assign_impl((((Arg1&&)arg1) = ((Arg2&&)arg2)), (Args&&)args...); +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto fold_right_assign_impl(Arg1&& arg1) { + return (Arg1&&)arg1; +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto fold_right_assign_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) { + return ((Arg1&&)arg1) = fold_compatibility_impl::fold_right_assign_impl((Arg2&&)arg2, (Args&&)args...); +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto fold_right_plus_impl(Arg1&& arg1) { + return (Arg1&&)arg1; +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto fold_right_plus_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) { + return ((Arg1&&)arg1) + fold_compatibility_impl::fold_right_plus_impl((Arg2&&)arg2, (Args&&)args...); +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto fold_right_times_impl(Arg1&& arg1) { + return (Arg1&&)arg1; +} + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr auto fold_right_times_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) { + return ((Arg1&&)arg1) * fold_compatibility_impl::fold_right_times_impl((Arg2&&)arg2, (Args&&)args...); +} + +#else + +//------------------------------------------------------------------------------ +// {{{2 + +template +struct fold_right_and_impl_; +template <> +struct fold_right_and_impl_<> { + using rv = bool; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr rv + impl() noexcept { + return true; + } +}; +template +struct fold_right_and_impl_ { + using next_t = fold_right_and_impl_; + using rv = decltype(std::declval() && std::declval()); + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr rv + impl(Arg&& arg, Args&&... args) noexcept { + return ((Arg&&)arg) && next_t::impl((Args&&)args...); + } +}; + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr typename fold_right_and_impl_::rv +fold_right_and_impl(Args&&... args) { + return fold_right_and_impl_::impl((Args&&)args...); +} + +// end right and }}}2 +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// {{{2 + +template +struct fold_right_or_impl_; +template <> +struct fold_right_or_impl_<> { + using rv = bool; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr rv + impl() noexcept { + return false; + } +}; +template +struct fold_right_or_impl_ { + using next_t = fold_right_or_impl_; + using rv = decltype(std::declval() || std::declval()); + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr rv + impl(Arg&& arg, Args&&... args) noexcept { + return ((Arg&&)arg) || next_t::impl((Args&&)args...); + } +}; + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr typename fold_right_or_impl_::rv +fold_right_or_impl(Args&&... args) { + return fold_right_or_impl_::impl((Args&&)args...); +} + +// end right or }}}2 +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// {{{2 + +template +struct fold_right_plus_impl_; +template +struct fold_right_plus_impl_ { + using rv = Arg&&; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr rv + impl(Arg&& arg) noexcept { + return (Arg&&)arg; + } +}; +template +struct fold_right_plus_impl_ { + using next_t = fold_right_plus_impl_; + using rv = decltype(std::declval() + std::declval()); + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr rv + impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept { + return ((Arg1&&)arg) + next_t::impl((Arg2&&)arg2, (Args&&)args...); + } +}; + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr typename fold_right_plus_impl_::rv +fold_right_plus_impl(Args&&... args) { + return fold_right_plus_impl_::impl((Args&&)args...); +} + +// end right plus }}}2 +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// {{{2 + +template +struct fold_right_times_impl_; +template +struct fold_right_times_impl_ { + using rv = Arg&&; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr rv + impl(Arg&& arg) noexcept { + return (Arg&&)arg; + } +}; +template +struct fold_right_times_impl_ { + using next_t = fold_right_times_impl_; + using rv = decltype(std::declval() * std::declval()); + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr rv + impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept { + return ((Arg1&&)arg) * next_t::impl((Arg2&&)arg2, (Args&&)args...); + } +}; + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr typename fold_right_times_impl_::rv +fold_right_times_impl(Args&&... args) { + return fold_right_times_impl_::impl((Args&&)args...); +} + +// end right times }}}2 +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// {{{2 + +template +struct fold_right_assign_impl_; +template +struct fold_right_assign_impl_ { + using rv = Arg&&; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr rv + impl(Arg&& arg) noexcept { + return (Arg&&)arg; + } +}; +template +struct fold_right_assign_impl_ { + using next_t = fold_right_assign_impl_; + using rv = decltype(std::declval() = std::declval()); + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr rv + impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept { + return ((Arg1&&)arg) = next_t::impl((Arg2&&)arg2, (Args&&)args...); + } +}; + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr typename fold_right_assign_impl_::rv +fold_right_assign_impl(Args&&... args) { + return fold_right_assign_impl_::impl((Args&&)args...); +} + +// end right assign }}}2 +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// {{{2 + +template +struct fold_left_assign_impl_; +template +struct fold_left_assign_impl_ { + using rv = Arg&&; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr rv + impl(Arg&& arg) noexcept { + return (Arg&&)arg; + } +}; +template +struct fold_left_assign_impl_ { + using assign_result_t = decltype(std::declval() = std::declval()); + using next_t = fold_left_assign_impl_; + using rv = typename next_t::rv; + MDSPAN_FORCE_INLINE_FUNCTION + static constexpr rv + impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept { + return next_t::impl(((Arg1&&)arg) = (Arg2&&)arg2, (Args&&)args...); + } +}; + +template +MDSPAN_FORCE_INLINE_FUNCTION +constexpr typename fold_left_assign_impl_::rv +fold_left_assign_impl(Args&&... args) { + return fold_left_assign_impl_::impl((Args&&)args...); +} + +// end left assign }}}2 +//------------------------------------------------------------------------------ + +#endif + + +template +constexpr enable_fold_comma fold_comma_impl(Args&&...) noexcept { return { }; } + +template +struct fold_bools; + +} // fold_compatibility_impl + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +# define MDSPAN_IMPL_FOLD_AND(...) MDSPAN_IMPL_STANDARD_NAMESPACE::fold_compatibility_impl::fold_right_and_impl((__VA_ARGS__)...) +# define MDSPAN_IMPL_FOLD_OR(...) MDSPAN_IMPL_STANDARD_NAMESPACE::fold_compatibility_impl::fold_right_or_impl((__VA_ARGS__)...) +# define MDSPAN_IMPL_FOLD_ASSIGN_LEFT(INIT, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::fold_compatibility_impl::fold_left_assign_impl(INIT, (__VA_ARGS__)...) +# define MDSPAN_IMPL_FOLD_ASSIGN_RIGHT(PACK, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::fold_compatibility_impl::fold_right_assign_impl((PACK)..., __VA_ARGS__) +# define MDSPAN_IMPL_FOLD_TIMES_RIGHT(PACK, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::fold_compatibility_impl::fold_right_times_impl((PACK)..., __VA_ARGS__) +# define MDSPAN_IMPL_FOLD_PLUS_RIGHT(PACK, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::fold_compatibility_impl::fold_right_plus_impl((PACK)..., __VA_ARGS__) +# define MDSPAN_IMPL_FOLD_COMMA(...) MDSPAN_IMPL_STANDARD_NAMESPACE::fold_compatibility_impl::fold_comma_impl((__VA_ARGS__)...) + +# define MDSPAN_IMPL_FOLD_AND_TEMPLATE(...) \ + MDSPAN_IMPL_TRAIT(std::is_same, fold_compatibility_impl::fold_bools<(__VA_ARGS__)..., true>, fold_compatibility_impl::fold_bools) + +#endif + +// end fold expressions }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +#if MDSPAN_IMPL_USE_VARIABLE_TEMPLATES +# define MDSPAN_IMPL_TRAIT(TRAIT, ...) TRAIT##_v<__VA_ARGS__> +#else +# define MDSPAN_IMPL_TRAIT(TRAIT, ...) TRAIT<__VA_ARGS__>::value +#endif + +// end Variable template compatibility }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +#if MDSPAN_IMPL_USE_CONSTEXPR_14 +# define MDSPAN_IMPL_CONSTEXPR_14 constexpr +// Workaround for a bug (I think?) in EDG frontends +# ifdef __EDG__ +# define MDSPAN_IMPL_CONSTEXPR_14_DEFAULTED +# else +# define MDSPAN_IMPL_CONSTEXPR_14_DEFAULTED constexpr +# endif +#else +# define MDSPAN_IMPL_CONSTEXPR_14 +# define MDSPAN_IMPL_CONSTEXPR_14_DEFAULTED +#endif + +// end Pre-C++14 constexpr }}}1 +//============================================================================== + +#if MDSPAN_IMPL_USE_IF_CONSTEXPR_17 +# define MDSPAN_IMPL_IF_CONSTEXPR_17 constexpr +#else +# define MDSPAN_IMPL_IF_CONSTEXPR_17 +#endif diff --git a/src/experimental/__p0009_bits/mdspan.hpp b/src/experimental/__p0009_bits/mdspan.hpp new file mode 100644 index 00000000..d8b92dfc --- /dev/null +++ b/src/experimental/__p0009_bits/mdspan.hpp @@ -0,0 +1,501 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include "default_accessor.hpp" +#include "layout_right.hpp" +#include "macros.hpp" +#include "extents.hpp" +#include "trait_backports.hpp" +#include "compressed_pair.hpp" + +#include +#include +#include + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +template < + class ElementType, + class Extents, + class LayoutPolicy = layout_right, + class AccessorPolicy = default_accessor +> +class mdspan +{ +private: + static_assert(detail::impl_is_extents_v, + MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::mdspan's Extents template parameter must be a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); + static_assert(std::is_same::value, + MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::mdspan's ElementType template parameter must be the same as its AccessorPolicy::element_type."); + + // Workaround for non-deducibility of the index sequence template parameter if it's given at the top level + template + struct deduction_workaround; + + template + struct deduction_workaround> + { + MDSPAN_FORCE_INLINE_FUNCTION static constexpr + size_t size(mdspan const& self) noexcept { + return MDSPAN_IMPL_FOLD_TIMES_RIGHT((self.mapping_ref().extents().extent(Idxs)), /* * ... * */ size_t(1)); + } + MDSPAN_FORCE_INLINE_FUNCTION static constexpr + bool empty(mdspan const& self) noexcept { + return (self.rank()>0) && MDSPAN_IMPL_FOLD_OR((self.mapping_ref().extents().extent(Idxs)==index_type(0))); + } + template + MDSPAN_FORCE_INLINE_FUNCTION static constexpr + ReferenceType callop(mdspan const& self, const std::array& indices) noexcept { + return self.accessor_ref().access(self.ptr_ref(), self.mapping_ref()(indices[Idxs]...)); + } +#ifdef __cpp_lib_span + template + MDSPAN_FORCE_INLINE_FUNCTION static constexpr + ReferenceType callop(mdspan const& self, const std::span& indices) noexcept { + return self.accessor_ref().access(self.ptr_ref(), self.mapping_ref()(indices[Idxs]...)); + } +#endif + }; + +public: + + //-------------------------------------------------------------------------------- + // Domain and codomain types + + using extents_type = Extents; + using layout_type = LayoutPolicy; + using accessor_type = AccessorPolicy; + using mapping_type = typename layout_type::template mapping; + using element_type = ElementType; + using value_type = std::remove_cv_t; + using index_type = typename extents_type::index_type; + using size_type = typename extents_type::size_type; + using rank_type = typename extents_type::rank_type; + using data_handle_type = typename accessor_type::data_handle_type; + using reference = typename accessor_type::reference; + + MDSPAN_INLINE_FUNCTION static constexpr rank_type rank() noexcept { return extents_type::rank(); } + MDSPAN_INLINE_FUNCTION static constexpr rank_type rank_dynamic() noexcept { return extents_type::rank_dynamic(); } + MDSPAN_INLINE_FUNCTION static constexpr size_t static_extent(size_t r) noexcept { return extents_type::static_extent(r); } + MDSPAN_INLINE_FUNCTION constexpr index_type extent(size_t r) const noexcept { return mapping_ref().extents().extent(r); } + +private: + + // Can't use defaulted parameter in the deduction_workaround template because of a bug in MSVC warning C4348. + using deduction_workaround_impl = deduction_workaround>; + + using map_acc_pair_t = detail::impl_compressed_pair; + +public: + + //-------------------------------------------------------------------------------- + // [mdspan.basic.cons], mdspan constructors, assignment, and destructor + +#if !MDSPAN_HAS_CXX_20 + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan() = default; +#else + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan() + requires( + // nvhpc has a bug where using just rank_dynamic() here doesn't work ... + (extents_type::rank_dynamic() > 0) && + MDSPAN_IMPL_TRAIT(std::is_default_constructible, data_handle_type) && + MDSPAN_IMPL_TRAIT(std::is_default_constructible, mapping_type) && + MDSPAN_IMPL_TRAIT(std::is_default_constructible, accessor_type) + ) = default; +#endif + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan(const mdspan&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan(mdspan&&) = default; + + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + ((sizeof...(SizeTypes) == rank()) || (sizeof...(SizeTypes) == rank_dynamic())) && + (detail::are_valid_indices()) && + MDSPAN_IMPL_TRAIT(std::is_constructible, mapping_type, extents_type) && + MDSPAN_IMPL_TRAIT(std::is_default_constructible, accessor_type) + ) + ) + MDSPAN_INLINE_FUNCTION + explicit constexpr mdspan(data_handle_type p, SizeTypes... dynamic_extents) + // TODO @proposal-bug shouldn't I be allowed to do `move(p)` here? + : m_members(std::move(p), map_acc_pair_t(mapping_type(extents_type(static_cast(std::move(dynamic_extents))...)), accessor_type())) + { } + + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, size_t N, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_convertible, const SizeType&, index_type) && + MDSPAN_IMPL_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) && + ((N == rank()) || (N == rank_dynamic())) && + MDSPAN_IMPL_TRAIT(std::is_constructible, mapping_type, extents_type) && + MDSPAN_IMPL_TRAIT(std::is_default_constructible, accessor_type) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT(N != rank_dynamic()) + MDSPAN_INLINE_FUNCTION + constexpr mdspan(data_handle_type p, const std::array& dynamic_extents) + : m_members(std::move(p), map_acc_pair_t(mapping_type(extents_type(dynamic_extents)), accessor_type())) + { } + +#ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, size_t N, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_convertible, const SizeType&, index_type) && + MDSPAN_IMPL_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) && + ((N == rank()) || (N == rank_dynamic())) && + MDSPAN_IMPL_TRAIT(std::is_constructible, mapping_type, extents_type) && + MDSPAN_IMPL_TRAIT(std::is_default_constructible, accessor_type) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT(N != rank_dynamic()) + MDSPAN_INLINE_FUNCTION + constexpr mdspan(data_handle_type p, std::span dynamic_extents) + : m_members(std::move(p), map_acc_pair_t(mapping_type(extents_type(as_const(dynamic_extents))), accessor_type())) + { } +#endif + + MDSPAN_FUNCTION_REQUIRES( + (MDSPAN_INLINE_FUNCTION constexpr), + mdspan, (data_handle_type p, const extents_type& exts), , + /* requires */ (MDSPAN_IMPL_TRAIT(std::is_default_constructible, accessor_type) && + MDSPAN_IMPL_TRAIT(std::is_constructible, mapping_type, const extents_type&)) + ) : m_members(std::move(p), map_acc_pair_t(mapping_type(exts), accessor_type())) + { } + + MDSPAN_FUNCTION_REQUIRES( + (MDSPAN_INLINE_FUNCTION constexpr), + mdspan, (data_handle_type p, const mapping_type& m), , + /* requires */ (MDSPAN_IMPL_TRAIT(std::is_default_constructible, accessor_type)) + ) : m_members(std::move(p), map_acc_pair_t(m, accessor_type())) + { } + + MDSPAN_INLINE_FUNCTION + constexpr mdspan(data_handle_type p, const mapping_type& m, const accessor_type& a) + : m_members(std::move(p), map_acc_pair_t(m, a)) + { } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherElementType, class OtherExtents, class OtherLayoutPolicy, class OtherAccessor, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_constructible, mapping_type, const typename OtherLayoutPolicy::template mapping&) && + MDSPAN_IMPL_TRAIT(std::is_constructible, accessor_type, const OtherAccessor&) + ) + ) + MDSPAN_CONDITIONAL_EXPLICIT( + !MDSPAN_IMPL_TRAIT(std::is_convertible, const typename OtherLayoutPolicy::template mapping&, mapping_type) || + !MDSPAN_IMPL_TRAIT(std::is_convertible, const OtherAccessor&, accessor_type) + ) + MDSPAN_INLINE_FUNCTION + constexpr mdspan(const mdspan& other) + : m_members(other.ptr_ref(), map_acc_pair_t(other.mapping_ref(), other.accessor_ref())) + { + static_assert(MDSPAN_IMPL_TRAIT(std::is_constructible, data_handle_type, typename OtherAccessor::data_handle_type),"Incompatible data_handle_type for mdspan construction"); + static_assert(MDSPAN_IMPL_TRAIT(std::is_constructible, extents_type, OtherExtents),"Incompatible extents for mdspan construction"); + /* + * TODO: Check precondition + * For each rank index r of extents_type, static_extent(r) == dynamic_extent || static_extent(r) == other.extent(r) is true. + */ + } + + /* Might need this on NVIDIA? + MDSPAN_INLINE_FUNCTION_DEFAULTED + ~mdspan() = default; + */ + + MDSPAN_INLINE_FUNCTION_DEFAULTED MDSPAN_IMPL_CONSTEXPR_14_DEFAULTED mdspan& operator=(const mdspan&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED MDSPAN_IMPL_CONSTEXPR_14_DEFAULTED mdspan& operator=(mdspan&&) = default; + + + //-------------------------------------------------------------------------------- + // [mdspan.basic.mapping], mdspan mapping domain multidimensional index to access codomain element + + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + extents_type::rank() == sizeof...(SizeTypes) && + (detail::are_valid_indices()) + ) + ) + constexpr reference at(SizeTypes... indices) const + { + size_t r = 0; + for (const auto& index : {indices...}) { + if (is_index_oor(index, mapping_ref().extents().extent(r))) { + throw std::out_of_range( + "mdspan::at(...," + std::to_string(index) + ",...) out-of-range at rank index " + std::to_string(r) + + " for mdspan with extent {...," + std::to_string(mapping_ref().extents().extent(r)) + ",...}"); + } + ++r; + } + return accessor_ref().access(ptr_ref(), mapping_ref()(static_cast(std::move(indices))...)); + } + + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_convertible, const SizeType&, index_type) && + MDSPAN_IMPL_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) + ) + ) + constexpr reference at(const std::array& indices) const + { + for (size_t r = 0; r < indices.size(); ++r) { + if (is_index_oor(indices[r], mapping_ref().extents().extent(r))) { + throw std::out_of_range( + "mdspan::at({...," + std::to_string(indices[r]) + ",...}) out-of-range at rank index " + std::to_string(r) + + " for mdspan with extent {...," + std::to_string(mapping_ref().extents().extent(r)) + ",...}"); + } + } + return deduction_workaround_impl::template callop(*this, indices); + } + + #ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_convertible, const SizeType&, index_type) && + MDSPAN_IMPL_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) + ) + ) + constexpr reference at(std::span indices) const + { + for (size_t r = 0; r < indices.size(); ++r) { + if (is_index_oor(indices[r], mapping_ref().extents().extent(r))) { + throw std::out_of_range( + "mdspan::at({...," + std::to_string(indices[r]) + ",...}) out-of-range at rank index " + std::to_string(r) + + " for mdspan with extent {...," + std::to_string(mapping_ref().extents().extent(r)) + ",...}"); + } + } + return deduction_workaround_impl::template callop(*this, indices); + } + #endif // __cpp_lib_span + + #if MDSPAN_USE_BRACKET_OPERATOR + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + extents_type::rank() == sizeof...(SizeTypes) && + (detail::are_valid_indices()) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator[](SizeTypes... indices) const + { + return accessor_ref().access(ptr_ref(), mapping_ref()(static_cast(std::move(indices))...)); + } + #endif + + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_convertible, const SizeType&, index_type) && + MDSPAN_IMPL_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator[](const std::array& indices) const + { + return deduction_workaround_impl::template callop(*this, indices); + } + + #ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_convertible, const SizeType&, index_type) && + MDSPAN_IMPL_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator[](std::span indices) const + { + return deduction_workaround_impl::template callop(*this, indices); + } + #endif // __cpp_lib_span + + #if !MDSPAN_USE_BRACKET_OPERATOR + MDSPAN_TEMPLATE_REQUIRES( + class Index, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_convertible, Index, index_type) && + MDSPAN_IMPL_TRAIT(std::is_nothrow_constructible, index_type, Index) && + extents_type::rank() == 1 + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator[](Index idx) const + { + return accessor_ref().access(ptr_ref(), mapping_ref()(static_cast(std::move(idx)))); + } + #endif + + #if MDSPAN_USE_PAREN_OPERATOR + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + extents_type::rank() == sizeof...(SizeTypes) && + (detail::are_valid_indices()) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator()(SizeTypes... indices) const + { + return accessor_ref().access(ptr_ref(), mapping_ref()(static_cast(std::move(indices))...)); + } + + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_convertible, const SizeType&, index_type) && + MDSPAN_IMPL_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator()(const std::array& indices) const + { + return deduction_workaround_impl::template callop(*this, indices); + } + + #ifdef __cpp_lib_span + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_convertible, const SizeType&, index_type) && + MDSPAN_IMPL_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator()(std::span indices) const + { + return deduction_workaround_impl::template callop(*this, indices); + } + #endif // __cpp_lib_span + #endif // MDSPAN_USE_PAREN_OPERATOR + + MDSPAN_INLINE_FUNCTION constexpr size_type size() const noexcept { + return static_cast(deduction_workaround_impl::size(*this)); + } + + MDSPAN_INLINE_FUNCTION constexpr bool empty() const noexcept { + return deduction_workaround_impl::empty(*this); + } + + MDSPAN_INLINE_FUNCTION + friend constexpr void swap(mdspan& x, mdspan& y) noexcept { + // can't call the std::swap inside on HIP + #if !defined(MDSPAN_IMPL_HAS_HIP) && !defined(MDSPAN_IMPL_HAS_CUDA) + using std::swap; + swap(x.ptr_ref(), y.ptr_ref()); + swap(x.mapping_ref(), y.mapping_ref()); + swap(x.accessor_ref(), y.accessor_ref()); + #else + mdspan tmp = y; + y = x; + x = tmp; + #endif + } + + //-------------------------------------------------------------------------------- + // [mdspan.basic.domobs], mdspan observers of the domain multidimensional index space + + + MDSPAN_INLINE_FUNCTION constexpr const extents_type& extents() const noexcept { return mapping_ref().extents(); } + MDSPAN_INLINE_FUNCTION constexpr const data_handle_type& data_handle() const noexcept { return ptr_ref(); } + MDSPAN_INLINE_FUNCTION constexpr const mapping_type& mapping() const noexcept { return mapping_ref(); } + MDSPAN_INLINE_FUNCTION constexpr const accessor_type& accessor() const noexcept { return accessor_ref(); } + + //-------------------------------------------------------------------------------- + // [mdspan.basic.obs], mdspan observers of the mapping + + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() { return mapping_type::is_always_unique(); } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() { return mapping_type::is_always_exhaustive(); } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() { return mapping_type::is_always_strided(); } + + MDSPAN_INLINE_FUNCTION constexpr bool is_unique() const { return mapping_ref().is_unique(); } + MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const { return mapping_ref().is_exhaustive(); } + MDSPAN_INLINE_FUNCTION constexpr bool is_strided() const { return mapping_ref().is_strided(); } + MDSPAN_INLINE_FUNCTION constexpr index_type stride(size_t r) const { return mapping_ref().stride(r); } + +private: + + detail::impl_compressed_pair m_members{}; + + MDSPAN_FORCE_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 data_handle_type& ptr_ref() noexcept { return m_members.first(); } + MDSPAN_FORCE_INLINE_FUNCTION constexpr data_handle_type const& ptr_ref() const noexcept { return m_members.first(); } + MDSPAN_FORCE_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 mapping_type& mapping_ref() noexcept { return m_members.second().first(); } + MDSPAN_FORCE_INLINE_FUNCTION constexpr mapping_type const& mapping_ref() const noexcept { return m_members.second().first(); } + MDSPAN_FORCE_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 accessor_type& accessor_ref() noexcept { return m_members.second().second(); } + MDSPAN_FORCE_INLINE_FUNCTION constexpr accessor_type const& accessor_ref() const noexcept { return m_members.second().second(); } + + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_convertible, const SizeType&, index_type) && + MDSPAN_IMPL_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION constexpr bool is_index_oor(SizeType index, index_type extent) const noexcept { + // Check for negative indices + if MDSPAN_IMPL_IF_CONSTEXPR_17 (MDSPAN_IMPL_TRAIT(std::is_signed, SizeType)) { + if(index < 0) { + return true; + } + } + return static_cast(index) >= extent; + } + + template + friend class mdspan; + +}; + +#if defined(MDSPAN_IMPL_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION) +MDSPAN_TEMPLATE_REQUIRES( + class ElementType, class... SizeTypes, + /* requires */ MDSPAN_IMPL_FOLD_AND(MDSPAN_IMPL_TRAIT(std::is_convertible, SizeTypes, size_t) /* && ... */) && + (sizeof...(SizeTypes) > 0) +) +MDSPAN_DEDUCTION_GUIDE explicit mdspan(ElementType*, SizeTypes...) + -> mdspan>; + +MDSPAN_TEMPLATE_REQUIRES( + class Pointer, + (MDSPAN_IMPL_TRAIT(std::is_pointer, std::remove_reference_t)) +) +MDSPAN_DEDUCTION_GUIDE mdspan(Pointer&&) -> mdspan>, extents>; + +MDSPAN_TEMPLATE_REQUIRES( + class CArray, + (MDSPAN_IMPL_TRAIT(std::is_array, CArray) && (std::rank_v == 1)) +) +MDSPAN_DEDUCTION_GUIDE mdspan(CArray&) -> mdspan, extents>>; + +template +MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, const ::std::array&) + -> mdspan>; + +#ifdef __cpp_lib_span +template +MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, ::std::span) + -> mdspan>; +#endif + +// This one is necessary because all the constructors take `data_handle_type`s, not +// `ElementType*`s, and `data_handle_type` is taken from `accessor_type::data_handle_type`, which +// seems to throw off automatic deduction guides. +template +MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, const extents&) + -> mdspan>; + +template +MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, const MappingType&) + -> mdspan; + +template +MDSPAN_DEDUCTION_GUIDE mdspan(const typename AccessorType::data_handle_type, const MappingType&, const AccessorType&) + -> mdspan; +#endif + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE diff --git a/src/experimental/__p0009_bits/no_unique_address.hpp b/src/experimental/__p0009_bits/no_unique_address.hpp new file mode 100644 index 00000000..1a4af56f --- /dev/null +++ b/src/experimental/__p0009_bits/no_unique_address.hpp @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include "macros.hpp" +#include "trait_backports.hpp" + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace detail { + +//============================================================================== + +template +struct no_unique_address_emulation { + using stored_type = T; + T m_v; + MDSPAN_FORCE_INLINE_FUNCTION constexpr T const &ref() const noexcept { + return m_v; + } + MDSPAN_FORCE_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 T &ref() noexcept { + return m_v; + } +}; + +// Empty case +// This doesn't work if T is final, of course, but we're not using anything +// like that currently. That kind of thing could be added pretty easily though +template +struct no_unique_address_emulation< + T, Disambiguator, + std::enable_if_t> : +#ifdef MDSPAN_IMPL_COMPILER_MSVC + // MSVC doesn't allow you to access public static member functions of a type + // when you *happen* to privately inherit from that type. + protected +#else + // But we still want this to be private if possible so that we don't accidentally + // access members of T directly rather than calling ref() first, which wouldn't + // work if T happens to be stateful and thus we're using the unspecialized definition + // of no_unique_address_emulation above. + private +#endif + T { + using stored_type = T; + MDSPAN_FORCE_INLINE_FUNCTION constexpr T const &ref() const noexcept { + return *static_cast(this); + } + MDSPAN_FORCE_INLINE_FUNCTION MDSPAN_IMPL_CONSTEXPR_14 T &ref() noexcept { + return *static_cast(this); + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr no_unique_address_emulation() noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr no_unique_address_emulation( + no_unique_address_emulation const &) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr no_unique_address_emulation( + no_unique_address_emulation &&) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + MDSPAN_IMPL_CONSTEXPR_14_DEFAULTED no_unique_address_emulation & + operator=(no_unique_address_emulation const &) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + MDSPAN_IMPL_CONSTEXPR_14_DEFAULTED no_unique_address_emulation & + operator=(no_unique_address_emulation &&) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + ~no_unique_address_emulation() noexcept = default; + + // Explicitly make this not a reference so that the copy or move + // constructor still gets called. + MDSPAN_INLINE_FUNCTION + explicit constexpr no_unique_address_emulation(T const& v) noexcept : T(v) {} + MDSPAN_INLINE_FUNCTION + explicit constexpr no_unique_address_emulation(T&& v) noexcept : T(::std::move(v)) {} +}; + +//============================================================================== + +} // end namespace detail +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE diff --git a/src/experimental/__p0009_bits/trait_backports.hpp b/src/experimental/__p0009_bits/trait_backports.hpp new file mode 100644 index 00000000..96b24c3e --- /dev/null +++ b/src/experimental/__p0009_bits/trait_backports.hpp @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#ifndef MDSPAN_INCLUDE_EXPERIMENTAL_BITS_TRAIT_BACKPORTS_HPP_ +#define MDSPAN_INCLUDE_EXPERIMENTAL_BITS_TRAIT_BACKPORTS_HPP_ + +#include "macros.hpp" +#include "config.hpp" + +#include +#include // integer_sequence + +//============================================================================== +// {{{1 + +#ifdef MDSPAN_IMPL_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS + +#if MDSPAN_IMPL_USE_VARIABLE_TEMPLATES +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +#define MDSPAN_IMPL_BACKPORT_TRAIT(TRAIT) \ + template MDSPAN_IMPL_INLINE_VARIABLE constexpr auto TRAIT##_v = TRAIT::value; + +MDSPAN_IMPL_BACKPORT_TRAIT(is_assignable) +MDSPAN_IMPL_BACKPORT_TRAIT(is_constructible) +MDSPAN_IMPL_BACKPORT_TRAIT(is_convertible) +MDSPAN_IMPL_BACKPORT_TRAIT(is_default_constructible) +MDSPAN_IMPL_BACKPORT_TRAIT(is_trivially_destructible) +MDSPAN_IMPL_BACKPORT_TRAIT(is_same) +MDSPAN_IMPL_BACKPORT_TRAIT(is_empty) +MDSPAN_IMPL_BACKPORT_TRAIT(is_void) + +#undef MDSPAN_IMPL_BACKPORT_TRAIT + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +#endif // MDSPAN_IMPL_USE_VARIABLE_TEMPLATES + +#endif // MDSPAN_IMPL_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS + +// end Variable template trait backports (e.g., is_void_v) }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +#if !defined(MDSPAN_IMPL_USE_INTEGER_SEQUENCE_14) || !MDSPAN_IMPL_USE_INTEGER_SEQUENCE_14 + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +template +struct integer_sequence { + static constexpr size_t size() noexcept { return sizeof...(Vals); } + using value_type = T; +}; + +template +using index_sequence = std::integer_sequence; + +namespace __detail { + +template +struct __make_int_seq_impl; + +template +struct __make_int_seq_impl> +{ + using type = integer_sequence; +}; + +template +struct __make_int_seq_impl< + T, N, I, integer_sequence +> : __make_int_seq_impl> +{ }; + +} // end namespace __detail + +template +using make_integer_sequence = typename __detail::__make_int_seq_impl>::type; + +template +using make_index_sequence = typename __detail::__make_int_seq_impl>::type; + +template +using index_sequence_for = make_index_sequence; + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +#endif + +// end integer sequence (ugh...) }}}1 +//============================================================================== + +//============================================================================== +// {{{1 + +#if !defined(MDSPAN_IMPL_USE_STANDARD_TRAIT_ALIASES) || !MDSPAN_IMPL_USE_STANDARD_TRAIT_ALIASES + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +#define MDSPAN_IMPL_BACKPORT_TRAIT_ALIAS(TRAIT) \ + template using TRAIT##_t = typename TRAIT::type; + +MDSPAN_IMPL_BACKPORT_TRAIT_ALIAS(remove_cv) +MDSPAN_IMPL_BACKPORT_TRAIT_ALIAS(remove_reference) + +template +using enable_if_t = typename enable_if<_B, T>::type; + +#undef MDSPAN_IMPL_BACKPORT_TRAIT_ALIAS + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +#endif + +// end standard trait aliases }}}1 +//============================================================================== + +#endif //MDSPAN_INCLUDE_EXPERIMENTAL_BITS_TRAIT_BACKPORTS_HPP_ diff --git a/src/experimental/__p0009_bits/type_list.hpp b/src/experimental/__p0009_bits/type_list.hpp new file mode 100644 index 00000000..3314e0e2 --- /dev/null +++ b/src/experimental/__p0009_bits/type_list.hpp @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#include "macros.hpp" + +#include "trait_backports.hpp" // make_index_sequence + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +//============================================================================== + +namespace detail { + +template struct type_list { static constexpr auto size = sizeof...(Ts); }; + +// Implementation of type_list at() that's heavily optimized for small typelists +template struct type_at; +template > struct type_at_large_impl; + +template +struct type_at_entry { }; + +template +struct type_at_assign_op_ignore_rest { + template + type_at_assign_op_ignore_rest operator=(T&&); + using type = Result; +}; + +struct type_at_assign_op_impl { + template + type_at_assign_op_impl operator=(type_at_entry&&); + template + type_at_assign_op_ignore_rest operator=(type_at_entry&&); +}; + +template +struct type_at_large_impl, std::integer_sequence> + : decltype( + MDSPAN_IMPL_FOLD_ASSIGN_LEFT(type_at_assign_op_impl{}, /* = ... = */ type_at_entry{}) + ) +{ }; + +template +struct type_at> + : type_at_large_impl> +{ }; + +template +struct type_at<0, type_list> { + using type = T0; +}; + +template +struct type_at<1, type_list> { + using type = T1; +}; + +template +struct type_at<2, type_list> { + using type = T2; +}; + +template +struct type_at<3, type_list> { + using type = T3; +}; + + +} // namespace detail + +//============================================================================== + +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE diff --git a/src/experimental/__p0009_bits/utility.hpp b/src/experimental/__p0009_bits/utility.hpp new file mode 100644 index 00000000..2cc6aefd --- /dev/null +++ b/src/experimental/__p0009_bits/utility.hpp @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include +#include +#include +#include +#if defined(MDSPAN_IMPL_HAS_CUDA) && defined(__NVCC__) && (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__ * 10 >= 1260) +#include +#else +#include +#endif +#include "macros.hpp" + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace detail { + +// Backport of std::remove_cvref / std::remove_cvref_t (C++20) +#if (__cplusplus >= 202002L) + using std::remove_cvref_t; +#else + template + using remove_cvref_t = std::remove_cv_t>; +#endif // __cplusplus >= 202002L + +// type alias used for rank-based tag dispatch +// +// this is used to enable alternatives to constexpr if when building for C++14 +// +template +using with_rank = std::integral_constant; + +template +MDSPAN_INLINE_FUNCTION +constexpr bool common_integral_compare(I1 x, I2 y) +{ + static_assert(std::is_integral::value && + std::is_integral::value, ""); + + using I = std::common_type_t; + return static_cast(x) == static_cast(y); +} + +template +MDSPAN_INLINE_FUNCTION +constexpr bool rankwise_equal(with_rank<0>, const T1&, const T2&, F) +{ + return true; +} + +template +MDSPAN_INLINE_FUNCTION +constexpr bool rankwise_equal(with_rank, const T1& x, const T2& y, F func) +{ + bool match = true; + + for (std::size_t r = 0; r < N; r++) { + match = match && common_integral_compare(func(x, r), func(y, r)); + } + + return match; +} + +#if MDSPAN_HAS_CXX_17 +inline +#endif +constexpr struct extent_functor +{ + template + MDSPAN_INLINE_FUNCTION + constexpr auto operator()(const T& x, I i) const + { + return x.extent(i); + } +} extent; + +#if MDSPAN_HAS_CXX_17 +inline +#endif +constexpr struct stride_functor +{ + template + MDSPAN_INLINE_FUNCTION + constexpr auto operator()(const T& x, I i) const + { + return x.stride(i); + } +} stride; + +// same as std::integral_constant but with __host__ __device__ annotations on +// the implicit conversion function and the call operator +template +struct integral_constant { + using value_type = T; + using type = integral_constant; + + static constexpr T value = v; + + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr integral_constant() = default; + + // These interop functions work, because other than the value_type operator + // everything of std::integral_constant works on device (defaulted functions) + MDSPAN_FUNCTION + constexpr integral_constant(std::integral_constant) {} + + MDSPAN_FUNCTION constexpr operator std::integral_constant() const noexcept { + return std::integral_constant{}; + } + + MDSPAN_FUNCTION constexpr operator value_type() const noexcept { + return value; + } + + MDSPAN_FUNCTION constexpr value_type operator()() const noexcept { + return value; + } +}; + +// The tuple implementation only comes in play when using capabilities +// such as submdspan which require C++17 anyway +#if MDSPAN_HAS_CXX_17 +template +struct tuple_member { + using type = T; + static constexpr size_t idx = Idx; + T val; + MDSPAN_FUNCTION constexpr T& get() { return val; } + MDSPAN_FUNCTION constexpr const T& get() const { return val; } +}; + +// A helper class which will be used via a fold expression to +// select the type with the correct Idx in a pack of tuple_member +template +struct tuple_idx_matcher { + using type = tuple_member; + template + MDSPAN_FUNCTION + constexpr auto operator | ([[maybe_unused]] Other v) const { + if constexpr (Idx == SearchIdx) { return *this; } + else { return v; } + } +}; + +template +struct tuple_impl; + +template +struct tuple_impl, Elements...>: public tuple_member ... { + + MDSPAN_FUNCTION + constexpr tuple_impl(Elements ... vals):tuple_member{vals}... {} + + template + MDSPAN_FUNCTION + constexpr auto& get() { + using base_t = decltype((tuple_idx_matcher() | ...) ); + return base_t::type::get(); + } + template + MDSPAN_FUNCTION + constexpr const auto& get() const { + using base_t = decltype((tuple_idx_matcher() | ...) ); + return base_t::type::get(); + } +}; + +// A simple tuple-like class for representing slices internally and is compatible with device code +// This doesn't support type access since we don't need it +// This is not meant as an external API +template +struct tuple: public tuple_impl()), Elements...> { + MDSPAN_FUNCTION + constexpr tuple(Elements ... vals):tuple_impl()), Elements ...>(vals ...) {} +}; + +template +MDSPAN_FUNCTION +constexpr auto& get(tuple& vals) { return vals.template get(); } + +template +MDSPAN_FUNCTION +constexpr const auto& get(const tuple& vals) { return vals.template get(); } + +template +tuple(Elements ...) -> tuple; +#endif + +#if MDSPAN_HAS_CXX_17 +// std::in_range and friends, tagged for device execution +// Backport from https://en.cppreference.com/w/cpp/utility/intcmp +// and https://en.cppreference.com/w/cpp/utility/in_range +template +MDSPAN_INLINE_FUNCTION constexpr bool cmp_less(T t, U u) noexcept { + if constexpr (std::is_signed_v == std::is_signed_v) + return t < u; + else if constexpr (std::is_signed_v) + return t < 0 || std::make_unsigned_t(t) < u; + else + return u >= 0 && t < std::make_unsigned_t(u); +} + +template +MDSPAN_INLINE_FUNCTION constexpr bool cmp_less_equal(T t, U u) noexcept { + return !cmp_less(u, t); +} + +template +MDSPAN_INLINE_FUNCTION constexpr bool cmp_greater_equal(T t, U u) noexcept { + return !cmp_less(t, u); +} + +template +MDSPAN_INLINE_FUNCTION constexpr bool in_range(T t) noexcept { + static_assert(std::is_integral_v && std::is_integral_v); + +#if defined(MDSPAN_IMPL_HAS_CUDA) && defined(__NVCC__) && (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__ * 10 >= 1260) + using cuda::std::numeric_limits; +#else + using std::numeric_limits; +#endif + return cmp_greater_equal(t, numeric_limits::min()) && + cmp_less_equal(t, numeric_limits::max()); +} + +template +MDSPAN_INLINE_FUNCTION constexpr bool is_nonnegative_and_representable(T t) noexcept { + // T might not be integral and thus invalid to pass to in_range + // Only check this if we can actually call in_range + if constexpr (std::is_integral_v) + { + if constexpr (std::is_signed_v) { + if (t < 0) + return false; + } + + return in_range(t); + } else + { + if constexpr (std::is_signed_v) { + if (static_cast(t) < 0) + return false; + } + + return true; + } +} + +template +MDSPAN_INLINE_FUNCTION constexpr bool +all_values_are_representable(Values... values) noexcept { + return ( in_range( values ) && ... ); +} + +template +MDSPAN_INLINE_FUNCTION constexpr bool +all_values_are_nonnegative_and_representable(Values... values) noexcept { + return ( is_nonnegative_and_representable( values ) && ... ); +} + +template +MDSPAN_INLINE_FUNCTION constexpr bool + range_is_nonnegative_and_representable(ContiguousIterator begin, ContiguousIterator end) noexcept { + for ( auto it = begin; it < end; ++it ) + { + if ( !is_nonnegative_and_representable( *it ) ) + return false; + } + + return true; +} + +template +MDSPAN_INLINE_FUNCTION constexpr bool +extent_is_representable(const Extents &exts) noexcept { + for ( std::size_t r = 0; r < Extents::rank(); ++r ) + { + if ( !is_nonnegative_and_representable( exts.extent(r) ) ) + return false; + } + + return true; +} + +template +MDSPAN_INLINE_FUNCTION constexpr bool +check_mul_result_is_nonnegative_and_representable(T a, T b) { +// FIXME_SYCL The code below compiles to old_llvm.umul.with.overflow.i64 +// which isn't defined in device code +#ifdef __SYCL_DEVICE_ONLY__ + (void) a; + (void) b; + return true; +#else + if (b == 0 || a == 0) + return true; + + if constexpr (std::is_signed_v) { + if ( a < 0 || b < 0 ) return false; + } +#if defined(MDSPAN_IMPL_HAS_CUDA) && defined(__NVCC__) && (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__ * 10 >= 1260) + using cuda::std::numeric_limits; +#else + using std::numeric_limits; +#endif + return a <= numeric_limits::max() / b; +#endif +} +#endif +} // namespace detail + +#if MDSPAN_HAS_CXX_17 +inline +#endif +constexpr struct mdspan_non_standard_tag { +} mdspan_non_standard; + +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE diff --git a/src/experimental/__p1684_bits/mdarray.hpp b/src/experimental/__p1684_bits/mdarray.hpp new file mode 100644 index 00000000..7d2a55f1 --- /dev/null +++ b/src/experimental/__p1684_bits/mdarray.hpp @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include "../../mdspan/mdspan.hpp" +#include +#include + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace MDSPAN_IMPL_PROPOSED_NAMESPACE { + +namespace { + template + struct size_of_extents; + + template + struct size_of_extents> { + constexpr static size_t value() { + size_t size = 1; + for(size_t r=0; r::rank(); r++) + size *= extents::static_extent(r); + return size; + } + }; +} + +namespace { + template + struct container_is_array : std::false_type { + template + static constexpr C construct(const M& m) { return C(m.required_span_size()); } + }; + template + struct container_is_array> : std::true_type { + template + static constexpr std::array construct(const M&) { return std::array(); } + }; +} + +template < + class ElementType, + class Extents, + class LayoutPolicy = layout_right, + class Container = std::vector +> +class mdarray { +private: + static_assert(::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::impl_is_extents_v, + MDSPAN_IMPL_PROPOSED_NAMESPACE_STRING "::mdspan's Extents template parameter must be a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); + +public: + + //-------------------------------------------------------------------------------- + // Domain and codomain types + + using extents_type = Extents; + using layout_type = LayoutPolicy; + using container_type = Container; + using mapping_type = typename layout_type::template mapping; + using element_type = ElementType; + using mdspan_type = mdspan; + using const_mdspan_type = mdspan; + using value_type = std::remove_cv_t; + using index_type = typename Extents::index_type; + using size_type = typename Extents::size_type; + using rank_type = typename Extents::rank_type; + using pointer = typename container_type::pointer; + using reference = typename container_type::reference; + using const_pointer = typename container_type::const_pointer; + using const_reference = typename container_type::const_reference; + +public: + + //-------------------------------------------------------------------------------- + // [mdspan.basic.cons], mdspan constructors, assignment, and destructor + +#if !(MDSPAN_HAS_CXX_20) + MDSPAN_FUNCTION_REQUIRES( + (MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr), + mdarray, (), , + /* requires */ (extents_type::rank_dynamic()!=0)) {} +#else + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdarray() requires(extents_type::rank_dynamic()!=0) = default; +#endif + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdarray(const mdarray&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdarray(mdarray&&) = default; + + // Constructors for container types constructible from a size + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + (::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::are_valid_indices()) && + MDSPAN_IMPL_TRAIT( std::is_constructible, extents_type, SizeTypes...) && + MDSPAN_IMPL_TRAIT( std::is_constructible, mapping_type, extents_type) && + (MDSPAN_IMPL_TRAIT( std::is_constructible, container_type, size_t) || + container_is_array::value) && + (extents_type::rank()>0 || extents_type::rank_dynamic()==0) + ) + ) + MDSPAN_INLINE_FUNCTION + explicit constexpr mdarray(SizeTypes... dynamic_extents) + : map_(extents_type(dynamic_extents...)), ctr_(container_is_array::construct(map_)) + { } + + MDSPAN_FUNCTION_REQUIRES( + (MDSPAN_INLINE_FUNCTION constexpr), + mdarray, (const extents_type& exts), , + /* requires */ ((MDSPAN_IMPL_TRAIT( std::is_constructible, container_type, size_t) || + container_is_array::value) && + MDSPAN_IMPL_TRAIT( std::is_constructible, mapping_type, extents_type)) + ) : map_(exts), ctr_(container_is_array::construct(map_)) + { } + + MDSPAN_FUNCTION_REQUIRES( + (MDSPAN_INLINE_FUNCTION constexpr), + mdarray, (const mapping_type& m), , + /* requires */ (MDSPAN_IMPL_TRAIT( std::is_constructible, container_type, size_t) || + container_is_array::value) + ) : map_(m), ctr_(container_is_array::construct(map_)) + { } + + MDSPAN_FUNCTION_REQUIRES( + (MDSPAN_INLINE_FUNCTION constexpr), + mdarray, (const extents_type& exts, const container_type& ctr), , + /* requires */ (MDSPAN_IMPL_TRAIT( std::is_constructible, mapping_type, extents_type)) + ) : map_(exts), ctr_(ctr) + { assert(ctr.size() >= static_cast(map_.required_span_size())); } + + constexpr mdarray(const mapping_type& m, const container_type& ctr) + : map_(m), ctr_(ctr) + { assert(ctr.size() >= static_cast(map_.required_span_size())); } + + MDSPAN_FUNCTION_REQUIRES( + (MDSPAN_INLINE_FUNCTION constexpr), + mdarray, (const extents_type& exts, container_type&& ctr), , + /* requires */ (MDSPAN_IMPL_TRAIT( std::is_constructible, mapping_type, extents_type)) + ) : map_(exts), ctr_(std::move(ctr)) + { assert(ctr_.size() >= static_cast(map_.required_span_size())); } + + constexpr mdarray(const mapping_type& m, container_type&& ctr) + : map_(m), ctr_(std::move(ctr)) + { assert(ctr_.size() >= static_cast(map_.required_span_size())); } + + + MDSPAN_TEMPLATE_REQUIRES( + class OtherElementType, class OtherExtents, class OtherLayoutPolicy, class OtherContainer, + /* requires */ ( + MDSPAN_IMPL_TRAIT( std::is_constructible, mapping_type, typename OtherLayoutPolicy::template mapping) && + MDSPAN_IMPL_TRAIT( std::is_constructible, container_type, OtherContainer) + ) + ) + MDSPAN_INLINE_FUNCTION + constexpr mdarray(const mdarray& other) + : map_(other.mapping()), ctr_(other.container()) + { + static_assert( std::is_constructible::value, ""); + } + + // Constructors for container types constructible from a size and allocator + MDSPAN_TEMPLATE_REQUIRES( + class Alloc, + /* requires */ (MDSPAN_IMPL_TRAIT( std::is_constructible, container_type, size_t, Alloc) && + MDSPAN_IMPL_TRAIT( std::is_constructible, mapping_type, extents_type)) + ) + MDSPAN_INLINE_FUNCTION + constexpr mdarray(const extents_type& exts, const Alloc& a) + : map_(exts), ctr_(map_.required_span_size(), a) + { } + + MDSPAN_TEMPLATE_REQUIRES( + class Alloc, + /* requires */ (MDSPAN_IMPL_TRAIT( std::is_constructible, container_type, size_t, Alloc)) + ) + MDSPAN_INLINE_FUNCTION + constexpr mdarray(const mapping_type& map, const Alloc& a) + : map_(map), ctr_(map_.required_span_size(), a) + { } + + // Constructors for container types constructible from a container and allocator + MDSPAN_TEMPLATE_REQUIRES( + class Alloc, + /* requires */ (MDSPAN_IMPL_TRAIT( std::is_constructible, container_type, container_type, Alloc) && + MDSPAN_IMPL_TRAIT( std::is_constructible, mapping_type, extents_type)) + ) + MDSPAN_INLINE_FUNCTION + constexpr mdarray(const extents_type& exts, const container_type& ctr, const Alloc& a) + : map_(exts), ctr_(ctr, a) + { assert(ctr_.size() >= static_cast(map_.required_span_size())); } + + MDSPAN_TEMPLATE_REQUIRES( + class Alloc, + /* requires */ (MDSPAN_IMPL_TRAIT( std::is_constructible, container_type, size_t, Alloc)) + ) + MDSPAN_INLINE_FUNCTION + constexpr mdarray(const mapping_type& map, const container_type& ctr, const Alloc& a) + : map_(map), ctr_(ctr, a) + { assert(ctr_.size() >= static_cast(map_.required_span_size())); } + + MDSPAN_TEMPLATE_REQUIRES( + class Alloc, + /* requires */ (MDSPAN_IMPL_TRAIT( std::is_constructible, container_type, container_type, Alloc) && + MDSPAN_IMPL_TRAIT( std::is_constructible, mapping_type, extents_type)) + ) + MDSPAN_INLINE_FUNCTION + constexpr mdarray(const extents_type& exts, container_type&& ctr, const Alloc& a) + : map_(exts), ctr_(std::move(ctr), a) + { assert(ctr_.size() >= static_cast(map_.required_span_size())); } + + MDSPAN_TEMPLATE_REQUIRES( + class Alloc, + /* requires */ (MDSPAN_IMPL_TRAIT( std::is_constructible, container_type, size_t, Alloc)) + ) + MDSPAN_INLINE_FUNCTION + constexpr mdarray(const mapping_type& map, container_type&& ctr, const Alloc& a) + : map_(map), ctr_(std::move(ctr), a) + { assert(ctr_.size() >= map_.required_span_size()); } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherElementType, class OtherExtents, class OtherLayoutPolicy, class OtherContainer, class Alloc, + /* requires */ ( + MDSPAN_IMPL_TRAIT( std::is_constructible, mapping_type, typename OtherLayoutPolicy::template mapping) && + MDSPAN_IMPL_TRAIT( std::is_constructible, container_type, OtherContainer, Alloc) + ) + ) + MDSPAN_INLINE_FUNCTION + constexpr mdarray(const mdarray& other, const Alloc& a) + : map_(other.mapping()), ctr_(other.container(), a) + { + static_assert( std::is_constructible::value, ""); + } + + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdarray& operator= (const mdarray&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdarray& operator= (mdarray&&) = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED + ~mdarray() = default; + + //-------------------------------------------------------------------------------- + // [mdspan.basic.mapping], mdspan mapping domain multidimensional index to access codomain element + + #if MDSPAN_USE_BRACKET_OPERATOR + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + MDSPAN_IMPL_FOLD_AND(MDSPAN_IMPL_TRAIT( std::is_convertible, SizeTypes, index_type) /* && ... */) && + extents_type::rank() == sizeof...(SizeTypes) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr const_reference operator[](SizeTypes... indices) const noexcept + { + return ctr_[map_(static_cast(std::move(indices))...)]; + } + + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + MDSPAN_IMPL_FOLD_AND(MDSPAN_IMPL_TRAIT( std::is_convertible, SizeTypes, index_type) /* && ... */) && + extents_type::rank() == sizeof...(SizeTypes) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator[](SizeTypes... indices) noexcept + { + return ctr_[map_(static_cast(std::move(indices))...)]; + } + #endif + +#if 0 + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, size_t N, + /* requires */ ( + MDSPAN_IMPL_TRAIT( std::is_convertible, SizeType, index_type) && + N == extents_type::rank() + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr const_reference operator[](const std::array& indices) const noexcept + { + return impl::template callop(*this, indices); + } + + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, size_t N, + /* requires */ ( + MDSPAN_IMPL_TRAIT( std::is_convertible, SizeType, index_type) && + N == extents_type::rank() + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator[](const std::array& indices) noexcept + { + return impl::template callop(*this, indices); + } +#endif + + + #if MDSPAN_USE_PAREN_OPERATOR + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + (::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::are_valid_indices()) && + extents_type::rank() == sizeof...(SizeTypes) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr const_reference operator()(SizeTypes... indices) const noexcept + { + return ctr_[map_(static_cast(std::move(indices))...)]; + } + MDSPAN_TEMPLATE_REQUIRES( + class... SizeTypes, + /* requires */ ( + (::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::are_valid_indices()) && + extents_type::rank() == sizeof...(SizeTypes) + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator()(SizeTypes... indices) noexcept + { + return ctr_[map_(static_cast(std::move(indices))...)]; + } + +#if 0 + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, size_t N, + /* requires */ ( + MDSPAN_IMPL_TRAIT( std::is_convertible, SizeType, index_type) && + N == extents_type::rank() + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr const_reference operator()(const std::array& indices) const noexcept + { + return impl::template callop(*this, indices); + } + + MDSPAN_TEMPLATE_REQUIRES( + class SizeType, size_t N, + /* requires */ ( + MDSPAN_IMPL_TRAIT( std::is_convertible, SizeType, index_type) && + N == extents_type::rank() + ) + ) + MDSPAN_FORCE_INLINE_FUNCTION + constexpr reference operator()(const std::array& indices) noexcept + { + return impl::template callop(*this, indices); + } +#endif + #endif + + MDSPAN_INLINE_FUNCTION constexpr pointer data() noexcept { return ctr_.data(); } + MDSPAN_INLINE_FUNCTION constexpr const_pointer data() const noexcept { return ctr_.data(); } + MDSPAN_INLINE_FUNCTION constexpr container_type& container() noexcept { return ctr_; } + MDSPAN_INLINE_FUNCTION constexpr const container_type& container() const noexcept { return ctr_; } + + //-------------------------------------------------------------------------------- + // [mdspan.basic.domobs], mdspan observers of the domain multidimensional index space + + MDSPAN_INLINE_FUNCTION static constexpr rank_type rank() noexcept { return extents_type::rank(); } + MDSPAN_INLINE_FUNCTION static constexpr rank_type rank_dynamic() noexcept { return extents_type::rank_dynamic(); } + MDSPAN_INLINE_FUNCTION static constexpr size_t static_extent(size_t r) noexcept { return extents_type::static_extent(r); } + + MDSPAN_INLINE_FUNCTION constexpr const extents_type& extents() const noexcept { return map_.extents(); } + MDSPAN_INLINE_FUNCTION constexpr index_type extent(size_t r) const noexcept { return map_.extents().extent(r); } + MDSPAN_INLINE_FUNCTION constexpr index_type size() const noexcept { +// return impl::size(*this); + return ctr_.size(); + } + + + //-------------------------------------------------------------------------------- + // [mdspan.basic.obs], mdspan observers of the mapping + + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return mapping_type::is_always_unique(); } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { return mapping_type::is_always_exhaustive(); } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return mapping_type::is_always_strided(); } + + MDSPAN_INLINE_FUNCTION constexpr const mapping_type& mapping() const noexcept { return map_; } + MDSPAN_INLINE_FUNCTION constexpr bool is_unique() const noexcept { return map_.is_unique(); } + MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const noexcept { return map_.is_exhaustive(); } + MDSPAN_INLINE_FUNCTION constexpr bool is_strided() const noexcept { return map_.is_strided(); } + MDSPAN_INLINE_FUNCTION constexpr index_type stride(size_t r) const { return map_.stride(r); } + + // Converstion to mdspan + MDSPAN_TEMPLATE_REQUIRES( + class OtherElementType, class OtherExtents, + class OtherLayoutType, class OtherAccessorType, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_assignable, + mdspan, + mdspan_type) + ) + ) + constexpr operator mdspan () { + return mdspan_type(data(), map_); + } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherElementType, class OtherExtents, + class OtherLayoutType, class OtherAccessorType, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_assignable, + mdspan, + const_mdspan_type) + ) + ) + constexpr operator mdspan () const { + return const_mdspan_type(data(), map_); + } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherAccessorType = default_accessor, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_assignable, mdspan_type, + mdspan) + ) + ) + constexpr mdspan + to_mdspan(const OtherAccessorType& a = default_accessor()) { + return mdspan(data(), map_, a); + } + + MDSPAN_TEMPLATE_REQUIRES( + class OtherAccessorType = default_accessor, + /* requires */ ( + MDSPAN_IMPL_TRAIT(std::is_assignable, const_mdspan_type, + mdspan) + ) + ) + constexpr mdspan + to_mdspan(const OtherAccessorType& a = default_accessor()) const { + return mdspan(data(), map_, a); + } + +private: + mapping_type map_; + container_type ctr_; + + template + friend class mdarray; +}; + + +} // end namespace MDSPAN_IMPL_PROPOSED_NAMESPACE +} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE diff --git a/src/experimental/__p2389_bits/dims.hpp b/src/experimental/__p2389_bits/dims.hpp new file mode 100644 index 00000000..fbac7703 --- /dev/null +++ b/src/experimental/__p2389_bits/dims.hpp @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +template< ::std::size_t Rank, class IndexType = std::size_t> +using dims = + :: MDSPAN_IMPL_STANDARD_NAMESPACE :: dextents; + +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE diff --git a/src/experimental/__p2630_bits/constant_wrapper.hpp b/src/experimental/__p2630_bits/constant_wrapper.hpp new file mode 100644 index 00000000..ae686285 --- /dev/null +++ b/src/experimental/__p2630_bits/constant_wrapper.hpp @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include "../__p0009_bits/utility.hpp" +#include + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace detail { + +// ============================================================ +// constant_wrapper, cw, increment, is_constant_wrapper +// ============================================================ + +#if defined(__cpp_lib_constant_wrapper) + +using std::constant_wrapper; +using std::cw; + +template +constexpr bool is_constant_wrapper = false; + +template +constexpr bool is_constant_wrapper = is_constant_wrapper; + +template +constexpr bool is_constant_wrapper> = true; + +#else // back-port: constant_wrapper = detail::integral_constant + +template +using constant_wrapper = integral_constant; + +template + constexpr auto cw = constant_wrapper{}; + +template +constexpr bool is_constant_wrapper = false; + +template +constexpr bool is_constant_wrapper = is_constant_wrapper; + +// integral_constant is the underlying type of the back-port constant_wrapper +// (alias templates can't be used in partial specialization patterns) +template +constexpr bool is_constant_wrapper> = true; + +#endif // __cpp_lib_constant_wrapper + +// ============================================================ +// increment function for constant wrapper +// ============================================================ + +template +MDSPAN_INLINE_FUNCTION +constexpr auto +increment([[maybe_unused]] constant_wrapper x) { + using value_type = typename decltype(x)::value_type; + return cw< value_type(Value) + value_type(1) >; +} + + +// ============================================================ +// Generic divide / multiply (scalar fall-through) +// ============================================================ + +template +MDSPAN_INLINE_FUNCTION +constexpr auto divide(const T0 &v0, const T1 &v1) { + return IndexT(v0) / IndexT(v1); +} + +template +MDSPAN_INLINE_FUNCTION +constexpr auto multiply(const T0 &v0, const T1 &v1) { + return IndexT(v0) * IndexT(v1); +} + +// ============================================================ +// Compile-time-preserving overloads for std::integral_constant +// (used when strided_slice template parameters are std::integral_constant) +// ============================================================ + +template +MDSPAN_INLINE_FUNCTION +constexpr auto divide(const std::integral_constant &, + const std::integral_constant &) { + // Short-circuit division by zero + // (used for strided_slice with zero extent/stride) + return integral_constant(); +} + +template +MDSPAN_INLINE_FUNCTION +constexpr auto multiply(const std::integral_constant &, + const std::integral_constant &) { + return integral_constant(); +} + +// ============================================================ +// Compile-time-preserving overloads for constant_wrapper +// ============================================================ + +#if defined(__cpp_lib_constant_wrapper) + +// std::constant_wrapper takes a single NTTP +template +MDSPAN_INLINE_FUNCTION +constexpr auto divide(const constant_wrapper &, + const constant_wrapper &) { + constexpr IndexT result = + IndexT(v0) == IndexT(0) ? IndexT(0) : IndexT(v0) / IndexT(v1); + return cw; +} + +template +MDSPAN_INLINE_FUNCTION +constexpr auto multiply(const constant_wrapper &, + const constant_wrapper &) { + constexpr IndexT result = IndexT(v0) * IndexT(v1); + return cw; +} + +#else // back-port: constant_wrapper = integral_constant + +template +MDSPAN_INLINE_FUNCTION +constexpr auto divide(const constant_wrapper &, + const constant_wrapper &) { + return integral_constant(); +} + +template +MDSPAN_INLINE_FUNCTION +constexpr auto multiply(const constant_wrapper &, + const constant_wrapper &) { + return integral_constant(); +} + +#endif // __cpp_lib_constant_wrapper + +} // namespace detail +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE diff --git a/src/experimental/__p2630_bits/integral_constant_like.hpp b/src/experimental/__p2630_bits/integral_constant_like.hpp new file mode 100644 index 00000000..5acdb4b7 --- /dev/null +++ b/src/experimental/__p2630_bits/integral_constant_like.hpp @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include "../__p0009_bits/utility.hpp" +#include +#if defined(__cpp_lib_concepts) +# include +#endif // __cpp_lib_concepts + +// ============================================================ +// equality_comparable back-port (used only by integral_constant_like) +// ============================================================ + +#if defined(__cpp_lib_concepts) + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + namespace detail { + template + struct is_equality_comparable : std::bool_constant> {}; + + template + struct is_equality_comparable_with : std::bool_constant> {}; + } // namespace detail +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +#else + +#include + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace detail { + + template + struct is_equality_comparable : std::false_type {}; + + template + struct is_equality_comparable< + T, + std::void_t< + decltype(std::declval() == std::declval()), + decltype(std::declval() != std::declval()) + > + > : std::bool_constant< + std::is_convertible_v< + decltype(std::declval() == std::declval()), + bool + > && + std::is_convertible_v< + decltype(std::declval() != std::declval()), + bool + > + > {}; + + template + struct is_equality_comparable_with : std::false_type {}; + + template + struct is_equality_comparable_with< + T, U, + std::void_t< + decltype(std::declval() == std::declval()), + decltype(std::declval() != std::declval()), + decltype(std::declval() == std::declval()), + decltype(std::declval() != std::declval()) + > + > : std::bool_constant< + is_equality_comparable::value && + is_equality_comparable::value && + std::is_convertible_v< + decltype(std::declval() == std::declval()), + bool + > && + std::is_convertible_v< + decltype(std::declval() != std::declval()), + bool + > && + std::is_convertible_v< + decltype(std::declval() == std::declval()), + bool + > && + std::is_convertible_v< + decltype(std::declval() != std::declval()), + bool + > + > {}; + +} // namespace detail +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +#endif // defined(__cpp_lib_concepts) + +// ============================================================ +// integral_constant_like concept / trait +// ============================================================ + +#if defined(__cpp_lib_concepts) + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + namespace detail { + + template + concept integral_constant_like = + std::is_integral_v> && + !std::is_same_v> && + std::convertible_to && + std::equality_comparable_with && + std::bool_constant::value && + std::bool_constant(T()) == T::value>::value; + + template + constexpr bool is_integral_constant_like_v = integral_constant_like; + + } // namespace detail +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +#else + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + namespace detail { + + template + struct is_integral_constant_like_impl : std::false_type {}; + + template + struct is_integral_constant_like_impl> : + std::bool_constant< + std::is_integral_v> && + ! std::is_same_v> && + std::is_convertible_v && + is_equality_comparable_with::value && + std::bool_constant::value && + std::bool_constant(T()) == T::value>::value + > + {}; + + template + constexpr bool is_integral_constant_like_v = is_integral_constant_like_impl::value; + + } // namespace detail +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +#endif // __cpp_lib_concepts diff --git a/src/experimental/__p2630_bits/strided_slice.hpp b/src/experimental/__p2630_bits/strided_slice.hpp new file mode 100644 index 00000000..1abd490b --- /dev/null +++ b/src/experimental/__p2630_bits/strided_slice.hpp @@ -0,0 +1,54 @@ + +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include "../__p0009_bits/config.hpp" +#include "constant_wrapper.hpp" +#include "integral_constant_like.hpp" + +#include + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +namespace detail { + template + struct is_signed_or_unsigned_integral_constant_like : std::false_type {}; + + template + struct is_signed_or_unsigned_integral_constant_like< + T, std::enable_if_t> + > : std::bool_constant< + std::is_integral_v> && + ! std::is_same_v> + > + {}; + + template + constexpr bool is_signed_or_unsigned_integral_constant_like_v = + is_signed_or_unsigned_integral_constant_like::value; + + template + constexpr bool mdspan_is_index_like_v = + (std::is_integral_v && ! std::is_same_v) || + is_signed_or_unsigned_integral_constant_like_v; +} // namespace detail + +// Slice Specifier allowing for strides and compile time extent +template +struct strided_slice { + using offset_type = OffsetType; + using extent_type = ExtentType; + using stride_type = StrideType; + + MDSPAN_IMPL_NO_UNIQUE_ADDRESS OffsetType offset{}; + MDSPAN_IMPL_NO_UNIQUE_ADDRESS ExtentType extent{}; + MDSPAN_IMPL_NO_UNIQUE_ADDRESS StrideType stride{}; + + static_assert(detail::mdspan_is_index_like_v); + static_assert(detail::mdspan_is_index_like_v); + static_assert(detail::mdspan_is_index_like_v); +}; + +} // MDSPAN_IMPL_STANDARD_NAMESPACE diff --git a/src/experimental/__p2630_bits/submdspan.hpp b/src/experimental/__p2630_bits/submdspan.hpp new file mode 100644 index 00000000..4e050cc6 --- /dev/null +++ b/src/experimental/__p2630_bits/submdspan.hpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include "submdspan_extents.hpp" +#include "submdspan_canonicalize_slices.hpp" +#include "submdspan_mapping.hpp" + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +template +MDSPAN_INLINE_FUNCTION +constexpr auto +submdspan(const mdspan &src, + SliceSpecifiers... slices) { + detail::check_submdspan_slice_mandates(std::make_index_sequence(), slices...); + + const auto sub_submdspan_mapping_result = submdspan_mapping(src.mapping(), + detail::canonical_slice(slices)...); + // NVCC has a problem with the deduction so lets figure out the type + using sub_mapping_t = std::remove_cv_t; + using sub_extents_t = typename sub_mapping_t::extents_type; + using sub_layout_t = typename sub_mapping_t::layout_type; + using sub_accessor_t = typename AccessorPolicy::offset_policy; + return mdspan( + src.accessor().offset(src.data_handle(), sub_submdspan_mapping_result.offset), + sub_submdspan_mapping_result.mapping, + sub_accessor_t(src.accessor())); +} +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE diff --git a/src/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp b/src/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp new file mode 100644 index 00000000..f14cd2c5 --- /dev/null +++ b/src/experimental/__p2630_bits/submdspan_canonicalize_slices.hpp @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include "submdspan_extents.hpp" +#include + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +#if MDSPAN_HAS_CXX_17 + +namespace detail { + +// ============================================================ +// de_ice: extract the value of an integral-constant-like type +// ============================================================ + +MDSPAN_TEMPLATE_REQUIRES( + class T, + /* requires */ (std::is_integral_v>) +) +MDSPAN_INLINE_FUNCTION +constexpr T de_ice(T val) { + return val; +} + +MDSPAN_TEMPLATE_REQUIRES( + class T, + /* requires */ (is_integral_constant_like_v>) +) +MDSPAN_INLINE_FUNCTION +constexpr decltype(T::value) de_ice([[maybe_unused]] T) { + return T::value; +} + +// ============================================================ +// index_cast: cast to IndexType, preserving integral-constant nature +// ============================================================ + +MDSPAN_TEMPLATE_REQUIRES( + class IndexType, + class OtherIndexType, + /* requires */ ( + std::is_signed_v> || + std::is_unsigned_v> + ) +) +MDSPAN_INLINE_FUNCTION +constexpr auto index_cast(OtherIndexType&& i) noexcept { + return i; +} + +MDSPAN_TEMPLATE_REQUIRES( + class IndexType, + class OtherIndexType, + /* requires */ ( + ! std::is_signed_v> && + ! std::is_unsigned_v> + ) +) +MDSPAN_INLINE_FUNCTION +constexpr auto index_cast(OtherIndexType&& i) noexcept { + return static_cast(std::forward(i)); +} + +// ============================================================ +// canonical_index: canonicalize a value to IndexType, +// preserving integral-constant nature when possible +// ============================================================ + +MDSPAN_TEMPLATE_REQUIRES( + class IndexType, + class S, + /* requires */ (std::is_convertible_v) +) +MDSPAN_INLINE_FUNCTION +constexpr auto canonical_index([[maybe_unused]] S s) { + // TODO: might move to public semi/public only to get error earlier, and + // don't duplicate check + // TODO: add mandate for integral-constant-like representable as IndexType + // TODO: add precondition check that index-cast is representable as IndexType + static_assert(std::is_signed_v || std::is_unsigned_v); + if constexpr (is_integral_constant_like_v) { + return cw(index_cast(S::value))>; + } + else { + return static_cast(index_cast(std::move(s))); + } +} + +// ============================================================ +// subtract_ice: subtract two values, preserving integral-constant +// nature when both inputs are integral-constant-like +// ============================================================ + +template +MDSPAN_INLINE_FUNCTION +constexpr auto subtract_ice([[maybe_unused]] X x, [[maybe_unused]] Y y) { + if constexpr ( + is_integral_constant_like_v> && + is_integral_constant_like_v>) + { + return cw(Y::value) - canonical_index(X::value))>; + } + else { + return canonical_index(y) - canonical_index(x); + } +} + +// ============================================================ +// check_static_bounds: compile-time bounds check for a slice +// +// Returns false if the slice is statically out of bounds. +// +// This function is called only in static_assert contexts. +// ============================================================ + +template +constexpr bool check_static_bounds() +{ + if constexpr (std::is_convertible_v) { + return true; + } + else if constexpr (std::is_convertible_v) { + if constexpr (is_integral_constant_like_v) { + if constexpr (de_ice(S_k{}) < 0) { + return false; + } + else if constexpr ( + Exts_k != dynamic_extent && + Exts_k <= static_cast(de_ice(S_k{}))) + { + return false; + } + else { return true; } + } else { + return true; + } + } + else if constexpr (is_strided_slice::value) { + using offset_type = typename S_k::offset_type; + + if constexpr (is_integral_constant_like_v) { + if constexpr (de_ice(offset_type{}) < 0) { + return false; + } + else if constexpr ( + Exts_k != dynamic_extent && + Exts_k < static_cast(de_ice(offset_type{}))) + { + return false; + } + else if constexpr (is_integral_constant_like_v) { + using extent_type = typename S_k::extent_type; + + if constexpr (de_ice(offset_type{}) + de_ice(extent_type{}) < 0) { + return false; + } + else if constexpr ( + Exts_k != dynamic_extent && + Exts_k < + static_cast(de_ice(offset_type{}) + de_ice(extent_type{}))) + { + return false; + } + else if constexpr ( + Exts_k != dynamic_extent && + 0 <= de_ice(offset_type{}) && + de_ice(offset_type{}) <= + de_ice(offset_type{}) + de_ice(extent_type{}) && + static_cast( + de_ice(offset_type{}) + de_ice(extent_type{})) <= Exts_k) + { + return true; + } + else { + return true; + } + } + else { + return true; + } + } + else { + return true; + } + } else { + // General pair-like case: attempt to get the first and second elements. + // If S_k cannot be structured-bound into two elements, this is ill-formed, + // which implements the Mandates clause. + // Doing this via these lambdas since we can do the declval only in a + // non-evaluated context + auto get_first = [] (S_k s_k) { + auto [s_k0, _x] = s_k; + return s_k0; + }; + auto get_second = [] (S_k s_k) { + auto [_x, s_k1] = s_k; + return s_k1; + }; + using S_k0 = decltype(get_first(std::declval())); + using S_k1 = decltype(get_second(std::declval())); + + if constexpr (is_integral_constant_like_v) { + if constexpr (de_ice(S_k0{}) < 0) { + return false; + } + else if constexpr ( + Exts_k != dynamic_extent && + Exts_k < static_cast(de_ice(S_k0{}))) + { + return false; + } + else if constexpr (is_integral_constant_like_v) { + if constexpr (de_ice(S_k1{}) < de_ice(S_k0{})) { + return false; + } + else if constexpr ( + Exts_k != dynamic_extent && + Exts_k < static_cast(de_ice(S_k1{}))) + { + return false; + } + else if constexpr ( + Exts_k != dynamic_extent && + 0 <= de_ice(S_k0{}) && + de_ice(S_k0{}) <= de_ice(S_k1{}) && + static_cast(de_ice(S_k1{})) <= Exts_k) + { + return true; + } + else { + return true; + } + } + else { + return true; + } + } + else { + return true; + } + } +} + +// ============================================================ +// check_submdspan_slice_mandate: mandate check for the k-th slice +// +// Contains only static_asserts; no actual computation. +// Separated from canonical_slice so that +// mandate checking and canonicalization are distinct concerns. +// ============================================================ + +template +MDSPAN_INLINE_FUNCTION +constexpr bool check_submdspan_slice_mandate( + [[maybe_unused]] const Slice&) +{ + static_assert(check_static_bounds()); + return true; +} + +template +MDSPAN_INLINE_FUNCTION +constexpr bool check_submdspan_slice_mandates( + const std::index_sequence& , + [[maybe_unused]] const Slices& ... slices) +{ + return (check_submdspan_slice_mandate(slices) && ... && true); +} + +// ============================================================ +// canonical_slice: canonicalize a single slice +// +// This function performs ONLY the conversion to canonical form. +// Mandate checking (static_asserts) is NOT done here; it is +// done separately by check_submdspan_slice_mandates. +// +// Templated only on IndexType (the extents index type) and Slice. +// Neither k nor the extents are needed for the actual conversion. +// ============================================================ + +template +MDSPAN_INLINE_FUNCTION +constexpr auto canonical_slice([[maybe_unused]] Slice s) +{ + if constexpr (std::is_convertible_v) { + return full_extent; // canonical full-extent slice + } + else if constexpr (std::is_convertible_v) { + return canonical_index(std::move(s)); // canonical integer index + } + else if constexpr (is_strided_slice::value) { + // Canonicalize each component of the strided_slice + auto offset = canonical_index(s.offset); + auto extent = canonical_index(s.extent); + auto stride = canonical_index(s.stride); + return strided_slice{ + /* .offset = */ offset, + /* .extent = */ extent, + /* .stride = */ stride + }; + } else { + // General pair-like case: structured binding into [first, last) + auto [s_k0, s_k1] = std::move(s); + using S_k0 = decltype(s_k0); + using S_k1 = decltype(s_k1); + static_assert(std::is_convertible_v); + static_assert(std::is_convertible_v); + + auto offset = canonical_index(s_k0); + auto extent = subtract_ice(s_k0, s_k1); + auto stride = cw; + return strided_slice{ + /* .offset = */ offset, + /* .extent = */ extent, + /* .stride = */ stride + }; + } +} + +// ============================================================ +// canonical_slices_impl: implementation helper +// +// First performs mandate checks (static_asserts), then +// returns a detail::tuple of canonical slices. +// Using detail::tuple instead of std::tuple ensures device +// code compatibility (e.g., CUDA). +// ============================================================ + +MDSPAN_TEMPLATE_REQUIRES( + size_t... Inds, + class Extents, + class... Slices, + /* requires */ (sizeof...(Slices) == Extents::rank()) +) +MDSPAN_INLINE_FUNCTION +constexpr auto canonical_slices_impl( + std::index_sequence, + const Extents&, + Slices... slices) +{ + // Mandate checks (static_asserts only, no computation). + // Separated from canonicalization for clarity. + (void)(check_submdspan_slice_mandate(slices) && ... && true); + + // Actual canonicalization: returns detail::tuple for device compatibility. + return detail::tuple{ + canonical_slice(slices)... + }; +} + +} // namespace detail + +// ============================================================ +// canonicalize_slices: public API +// +// Given an extents object and a pack of slice specifiers, +// returns a detail::tuple of canonical slice specifiers. +// Each canonical slice is one of: +// - full_extent_t (for full-extent slices) +// - IndexType (for integer index slices) +// - strided_slice<...> (for range and strided-range slices) +// ============================================================ + +MDSPAN_TEMPLATE_REQUIRES( + class IndexType, + size_t... Extents, + class... Slices, + /* requires */ (sizeof...(Slices) == sizeof...(Extents)) +) +MDSPAN_INLINE_FUNCTION +constexpr auto canonical_slices( + const extents& exts, + Slices... slices) +{ + return detail::canonical_slices_impl( + std::make_index_sequence(), exts, slices...); +} + +#endif // MDSPAN_HAS_CXX_17 + +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE diff --git a/src/experimental/__p2630_bits/submdspan_extents.hpp b/src/experimental/__p2630_bits/submdspan_extents.hpp new file mode 100644 index 00000000..429877e7 --- /dev/null +++ b/src/experimental/__p2630_bits/submdspan_extents.hpp @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include + +#include "constant_wrapper.hpp" +#include "strided_slice.hpp" +#include "../__p0009_bits/utility.hpp" + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace detail { + +// Mapping from submapping ranks to srcmapping ranks +// InvMapRank is an index_sequence, which we build recursively +// to contain the mapped indices. +// end of recursion specialization containing the final index_sequence +template +MDSPAN_INLINE_FUNCTION +constexpr auto inv_map_rank(std::integral_constant, std::index_sequence) { + return std::index_sequence(); +} + +// specialization reducing rank by one (i.e., integral slice specifier) +template +MDSPAN_INLINE_FUNCTION +constexpr auto inv_map_rank(std::integral_constant, std::index_sequence, Slice, + SliceSpecifiers... slices) { + using next_idx_seq_t = std::conditional_t, + std::index_sequence, + std::index_sequence>; + + return inv_map_rank(std::integral_constant(), next_idx_seq_t(), + slices...); +} + +// Helper for identifying strided_slice +template struct is_strided_slice : std::false_type {}; + +template +struct is_strided_slice< + strided_slice> : std::true_type {}; + +// first_of(slice): getting begin of slice specifier range +MDSPAN_TEMPLATE_REQUIRES( + class Integral, + /* requires */(std::is_convertible_v) +) +MDSPAN_INLINE_FUNCTION +constexpr Integral first_of(const Integral &i) { + return i; +} + +template +MDSPAN_INLINE_FUNCTION +constexpr auto first_of(const constant_wrapper&) { + return constant_wrapper(); +} + +MDSPAN_INLINE_FUNCTION +constexpr integral_constant +first_of(const ::MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent_t &) { + return integral_constant(); +} + +template +MDSPAN_INLINE_FUNCTION +constexpr OffsetType +first_of(const strided_slice &r) { + return r.offset; +} + +// last_of(slice): getting end of slice specifier range +// We need however not just the slice but also the extents +// of the original view and which rank from the extents. +// This is needed in the case of slice being full_extent_t. +MDSPAN_TEMPLATE_REQUIRES( + size_t k, class Extents, class Integral, + /* requires */(std::is_convertible_v) +) +MDSPAN_INLINE_FUNCTION +constexpr Integral + last_of(std::integral_constant, const Extents &, const Integral &i) { + return i; +} + +// Suppress spurious warning with NVCC about no return statement. +// This is a known issue in NVCC and NVC++ +// Depending on the CUDA and GCC version we need both the builtin +// and the diagnostic push. I tried really hard to find something shorter +// but no luck ... +#if defined __NVCC__ + #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ + #pragma nv_diagnostic push + #pragma nv_diag_suppress = implicit_return_from_non_void_function + #else + #ifdef __CUDA_ARCH__ + #pragma diagnostic push + #pragma diag_suppress implicit_return_from_non_void_function + #endif + #endif +#elif defined __NVCOMPILER + #pragma diagnostic push + #pragma diag_suppress = implicit_return_from_non_void_function +#endif +template +MDSPAN_INLINE_FUNCTION +constexpr auto last_of(std::integral_constant, const Extents &ext, + ::MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent_t) { + if constexpr (Extents::static_extent(k) == dynamic_extent) { + return ext.extent(k); + } else { + return constant_wrapper(); + } +#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__) + // Even with CUDA_ARCH protection this thing warns about calling host function + __builtin_unreachable(); +#endif +} +#if defined __NVCC__ + #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ + #pragma nv_diagnostic pop + #else + #ifdef __CUDA_ARCH__ + #pragma diagnostic pop + #endif + #endif +#elif defined __NVCOMPILER + #pragma diagnostic pop +#endif + +template +MDSPAN_INLINE_FUNCTION +constexpr OffsetType +last_of(std::integral_constant, const Extents &, + const strided_slice &r) { + return r.extent; +} + +// get stride of slices +template +MDSPAN_INLINE_FUNCTION +constexpr auto stride_of(const T &) { + return integral_constant(); +} + +template +MDSPAN_INLINE_FUNCTION +constexpr auto +stride_of(const strided_slice &r) { + return r.stride; +} + +// compute new static extent from range, preserving static knowledge +template struct StaticExtentFromRange { + constexpr static size_t value = dynamic_extent; +}; + +template +struct StaticExtentFromRange, + std::integral_constant> { + constexpr static size_t value = val1 - val0; +}; + +template +struct StaticExtentFromRange, + integral_constant> { + constexpr static size_t value = val1 - val0; +}; + +// compute new static extent from strided_slice, preserving static +// knowledge +template struct StaticExtentFromStridedRange { + constexpr static size_t value = dynamic_extent; +}; + +template +struct StaticExtentFromStridedRange, + std::integral_constant> { + constexpr static size_t value = val0 > 0 ? 1 + (val0 - 1) / val1 : 0; +}; + +template +struct StaticExtentFromStridedRange, + integral_constant> { + constexpr static size_t value = val0 > 0 ? 1 + (val0 - 1) / val1 : 0; +}; + +// creates new extents through recursive calls to next_extent member function +// next_extent has different overloads for different types of stride specifiers +template +struct extents_constructor { + MDSPAN_TEMPLATE_REQUIRES( + class Slice, class... SlicesAndExtents, + /* requires */(!std::is_convertible_v && + !is_strided_slice::value) + ) + MDSPAN_INLINE_FUNCTION + constexpr static auto next_extent(const Extents &ext, const Slice &sl, + SlicesAndExtents... slices_and_extents) { + constexpr size_t new_static_extent = StaticExtentFromRange< + decltype(first_of(std::declval())), + decltype(last_of(std::integral_constant(), + std::declval(), + std::declval()))>::value; + + using next_t = + extents_constructor; + using index_t = typename Extents::index_type; + return next_t::next_extent( + ext, slices_and_extents..., + index_t(last_of(std::integral_constant(), ext, + sl)) - + index_t(first_of(sl))); + } + + MDSPAN_TEMPLATE_REQUIRES( + class Slice, class... SlicesAndExtents, + /* requires */ (std::is_convertible_v) + ) + MDSPAN_INLINE_FUNCTION + constexpr static auto next_extent(const Extents &ext, const Slice &, + SlicesAndExtents... slices_and_extents) { + using next_t = extents_constructor; + return next_t::next_extent(ext, slices_and_extents...); + } + + template + MDSPAN_INLINE_FUNCTION + constexpr static auto + next_extent(const Extents &ext, + const strided_slice &r, + SlicesAndExtents... slices_and_extents) { + using index_t = typename Extents::index_type; + using new_static_extent_t = + StaticExtentFromStridedRange; + if constexpr (new_static_extent_t::value == dynamic_extent) { + using next_t = + extents_constructor; + return next_t::next_extent( + ext, slices_and_extents..., + r.extent > 0 ? 1 + divide(r.extent - 1, r.stride) : 0); + } else { + constexpr size_t new_static_extent = new_static_extent_t::value; + using next_t = + extents_constructor; + return next_t::next_extent( + ext, slices_and_extents..., index_t(divide(ExtentType(), StrideType()))); + } + } +}; + +template +struct extents_constructor<0, Extents, NewStaticExtents...> { + + template + MDSPAN_INLINE_FUNCTION + constexpr static auto next_extent(const Extents &, NewExtents... new_exts) { + return extents( + new_exts...); + } +}; + +} // namespace detail + +// submdspan_extents creates new extents given src extents and submdspan slice +// specifiers +template +MDSPAN_INLINE_FUNCTION +constexpr auto submdspan_extents(const extents &src_exts, + SliceSpecifiers... slices) { + + using ext_t = extents; + return detail::extents_constructor::next_extent( + src_exts, slices...); +} +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE diff --git a/src/experimental/__p2630_bits/submdspan_mapping.hpp b/src/experimental/__p2630_bits/submdspan_mapping.hpp new file mode 100644 index 00000000..9d286a3b --- /dev/null +++ b/src/experimental/__p2630_bits/submdspan_mapping.hpp @@ -0,0 +1,632 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include +#include +#include // index_sequence +#include "../__p0009_bits/utility.hpp" + +// Suppress spurious warning with NVCC about no return statement. +// This is a known issue in NVCC and NVC++ +// Depending on the CUDA and GCC version we need both the builtin +// and the diagnostic push. I tried really hard to find something shorter +// but no luck ... +#if defined __NVCC__ +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +#pragma nv_diagnostic push +#pragma nv_diag_suppress = implicit_return_from_non_void_function +#else +#ifdef __CUDA_ARCH__ +#pragma diagnostic push +#pragma diag_suppress implicit_return_from_non_void_function +#endif +#endif +#elif defined __NVCOMPILER +#pragma diagnostic push +#pragma diag_suppress = implicit_return_from_non_void_function +#endif + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +//****************************************** +// Return type of submdspan_mapping overloads +//****************************************** +template struct submdspan_mapping_result { + MDSPAN_IMPL_NO_UNIQUE_ADDRESS LayoutMapping mapping{}; + size_t offset; +}; + +namespace detail { + +// We use const Slice& and not Slice&& because the various +// submdspan_mapping_impl overloads use their slices arguments +// multiple times. This makes perfect forwarding not useful, but we +// still don't want to pass those (possibly of size 64 x 3 bits) +// objects by value. +template +MDSPAN_INLINE_FUNCTION constexpr bool +one_slice_out_of_bounds(const IndexType &ext, const Slice &slice) { + using common_t = + std::common_type_t; + return static_cast(detail::first_of(slice)) == + static_cast(ext); +} + +template +MDSPAN_INLINE_FUNCTION constexpr bool +any_slice_out_of_bounds_helper(std::index_sequence, + const extents &exts, + const Slices &... slices) { + return MDSPAN_IMPL_FOLD_OR( + (one_slice_out_of_bounds(exts.extent(RankIndices), slices))); +} + +template +MDSPAN_INLINE_FUNCTION constexpr bool +any_slice_out_of_bounds(const extents &exts, + const Slices &... slices) { + return any_slice_out_of_bounds_helper( + std::make_index_sequence(), exts, slices...); +} + +// constructs sub strides +template +struct sub_strides +{ + T values[N > 0 ? N : 1]; +}; + +template +MDSPAN_INLINE_FUNCTION constexpr auto construct_sub_strides( + const SrcMapping &src_mapping, std::index_sequence, + const MDSPAN_IMPL_STANDARD_NAMESPACE::detail::tuple &slices_stride_factor) { + using index_type = typename SrcMapping::index_type; + return sub_strides{{ + (static_cast(src_mapping.stride(InvMapIdxs)) * + static_cast(get(slices_stride_factor)))...}}; +} + +template +constexpr bool is_range_slice_v = false; + +template +constexpr bool is_range_slice_v = true; + +template +constexpr bool is_range_slice_v< + strided_slice< + OffsetType, + ExtentType, + constant_wrapper>, + IndexType + > = (constant_wrapper::value == IndexType(1)); + +template +struct is_index_slice { + constexpr static bool value = std::is_convertible_v; +}; + +template +constexpr bool is_index_slice_v = is_index_slice::value; + +} // namespace detail + +//********************************** +// layout_left submdspan_mapping +//********************************* +namespace detail { + +// Figure out whether to preserve layout_left +template +struct deduce_layout_left_submapping; + +template +struct deduce_layout_left_submapping< + IndexType, SubRank, std::index_sequence, SliceSpecifiers...> { + + using count_range = index_sequence_scan_impl< + 0u, (is_index_slice_v ? 0u : 1u)...>; + + constexpr static int gap_len = + (((Idx > 0 && count_range::get(Idx) == 1 && + is_index_slice_v) + ? 1 + : 0) + + ... + 0); + + MDSPAN_INLINE_FUNCTION + constexpr static bool layout_left_value() { + // Use layout_left for rank 0 + if constexpr (SubRank == 0) { + return true; + // Use layout_left for rank 1 result if leftmost slice specifier is range like + } else if constexpr (SubRank == 1) { + return ((Idx > 0 || is_range_slice_v)&&...); + } else { + // Preserve if leftmost SubRank-1 slices are full_extent_t and + // the slice at idx Subrank - 1 is a range and + // for idx > SubRank the slice is an index + return ((((Idx < SubRank - 1) && std::is_same_v) || + ((Idx == SubRank - 1) && is_range_slice_v) || + ((Idx > SubRank - 1) && is_index_slice_v)) && ...); + } +#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__) + __builtin_unreachable(); +#endif + } + + MDSPAN_INLINE_FUNCTION + constexpr static bool layout_left_padded_value() { + // Technically could also keep layout_left_padded for SubRank==0 + // and SubRank==1 with leftmost slice specifier being a contiguous range + // but we intercept these cases separately + + // In all other cases: + // leftmost slice must be range + // then there can be a gap with index slices + // then SubRank - 2 full_extent slices + // then another range slice + // then more index slices + // e.g. R I I I F F F R I I for obtaining a rank-5 from a rank-10 + return ((((Idx == 0) && is_range_slice_v) || + ((Idx > 0 && Idx <= gap_len) && is_index_slice_v) || + ((Idx > gap_len && Idx < gap_len + SubRank - 1) && std::is_same_v) || + ((Idx == gap_len + SubRank - 1) && is_range_slice_v) || + ((Idx > gap_len + SubRank - 1) && is_index_slice_v)) && ... ); + } +}; + +// We are reusing the same thing for layout_left and layout_left_padded +// For layout_left as source StaticStride is static_extent(0) +template +MDSPAN_INLINE_FUNCTION constexpr size_t +compute_s_static_layout_left(std::index_sequence) { + // Neither StaticStride nor any of the provided extents can be zero. + // StaticStride can never be zero, the static_extents we are looking at are associated with + // integral slice specifiers - which wouldn't be valid for zero extent + size_t val = ((Idx>0 && Idx<=NumGaps ? (Extents::static_extent(Idx) == dynamic_extent?0:Extents::static_extent(Idx)) : 1) * ... * (StaticStride == dynamic_extent?0:StaticStride)); + return val == 0?dynamic_extent:val; + } + +} // namespace detail + +// Actual submdspan mapping call +template +template +MDSPAN_INLINE_FUNCTION constexpr auto +layout_left::mapping::submdspan_mapping_impl( + SliceSpecifiers... slices) const { + + // Implements mandate check + detail::check_submdspan_slice_mandates(std::make_index_sequence(), slices...); + + // compute sub extents + using src_ext_t = Extents; + auto dst_ext = submdspan_extents(extents(), slices...); + using dst_ext_t = decltype(dst_ext); + + // figure out sub layout type + using deduce_layout = detail::deduce_layout_left_submapping< + typename dst_ext_t::index_type, dst_ext_t::rank(), + std::make_index_sequence, + SliceSpecifiers...>; + + // Figure out if any slice's lower bound equals the corresponding extent. + // If so, bypass evaluating the layout mapping. This fixes LWG Issue 4060. + const bool out_of_bounds = + detail::any_slice_out_of_bounds(this->extents(), slices...); + auto offset = static_cast( + out_of_bounds ? this->required_span_size() + : this->operator()(detail::first_of(slices)...)); + + if constexpr (deduce_layout::layout_left_value()) { + // layout_left case + using dst_mapping_t = typename layout_left::template mapping; + return submdspan_mapping_result{dst_mapping_t(dst_ext), + offset}; + } else if constexpr (deduce_layout::layout_left_padded_value()) { + constexpr size_t S_static = MDSPAN_IMPL_STANDARD_NAMESPACE::detail::compute_s_static_layout_left(std::make_index_sequence()); + using dst_mapping_t = typename layout_left_padded::template mapping; + return submdspan_mapping_result{ + dst_mapping_t(dst_ext, stride(1 + deduce_layout::gap_len)), offset}; + } else { + // layout_stride case + using dst_mapping_t = typename layout_stride::mapping; + auto inv_map = detail::inv_map_rank(std::integral_constant(), + std::index_sequence<>(), slices...); + return submdspan_mapping_result { + dst_mapping_t(mdspan_non_standard, dst_ext, + detail::construct_sub_strides( + *this, inv_map, +// HIP needs deduction guides to have markups so we need to be explicit +// NVCC 11.0 has a bug with deduction guide here, tested that 11.2 does not have +// the issue but Clang-CUDA also doesn't accept the use of deduction guide so +// disable it for CUDA altogether +#if defined(MDSPAN_IMPL_HAS_HIP) || defined(MDSPAN_IMPL_HAS_CUDA) + detail::tuple{ + detail::stride_of(slices)...}).values), +#else + detail::tuple{detail::stride_of(slices)...}).values), +#endif + offset + }; + } +#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__) + __builtin_unreachable(); +#endif +} + +template +template +template +MDSPAN_INLINE_FUNCTION constexpr auto +layout_left_padded::mapping::submdspan_mapping_impl( + SliceSpecifiers... slices) const { + + // Implements mandate check + detail::check_submdspan_slice_mandates(std::make_index_sequence(), slices...); + + // compute sub extents + using src_ext_t = Extents; + auto dst_ext = submdspan_extents(extents(), slices...); + using dst_ext_t = decltype(dst_ext); + + if constexpr (Extents::rank() == 0) { // rank-0 case + using dst_mapping_t = typename layout_left_padded::template mapping; + return submdspan_mapping_result{*this, 0}; + } else { + const bool out_of_bounds = + MDSPAN_IMPL_STANDARD_NAMESPACE::detail::any_slice_out_of_bounds(this->extents(), slices...); + auto offset = static_cast( + out_of_bounds ? this->required_span_size() + : this->operator()(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::first_of(slices)...)); + if constexpr (dst_ext_t::rank() == 0) { // result rank-0 + // The following for some reasons leads to compiler error later, while not using a typedef works: + // Compilers: CUDA 11.2 with GCC 9.1 + // + // using dst_mapping_t = typename layout_left::template mapping; + // return submdspan_mapping_result{dst_mapping_t{dst_ext}, offset}; + // + // Error: submdspan_mapping.hpp:299:23: error: 'dst_mapping_t' does not name a type + // 299 | using dst_mapping_t = typename layout_left::template mapping; + // The same error is given (about dst_mapping_t not naming type) when a different name is used in 299: + // using dst_mapping_t2 = typename layout_left::template mapping; + + return submdspan_mapping_result> + {typename layout_left::template mapping{dst_ext}, offset}; + } else { // general case + // Figure out if any slice's lower bound equals the corresponding extent. + // If so, bypass evaluating the layout mapping. This fixes LWG Issue 4060. + // figure out sub layout type + using deduce_layout = MDSPAN_IMPL_STANDARD_NAMESPACE::detail::deduce_layout_left_submapping< + typename dst_ext_t::index_type, dst_ext_t::rank(), + decltype(std::make_index_sequence()), + SliceSpecifiers...>; + + if constexpr (deduce_layout::layout_left_value() && dst_ext_t::rank() == 1) { // getting rank-1 from leftmost + using dst_mapping_t = typename layout_left::template mapping; + return submdspan_mapping_result{dst_mapping_t{dst_ext}, offset}; + } else if constexpr (deduce_layout::layout_left_padded_value()) { // can keep layout_left_padded + constexpr size_t S_static = MDSPAN_IMPL_STANDARD_NAMESPACE::detail::compute_s_static_layout_left(std::make_index_sequence()); + using dst_mapping_t = typename layout_left_padded::template mapping; + return submdspan_mapping_result{ + dst_mapping_t(dst_ext, stride(1 + deduce_layout::gap_len)), offset}; + } else { // layout_stride + auto inv_map = MDSPAN_IMPL_STANDARD_NAMESPACE::detail::inv_map_rank(std::integral_constant(), + std::index_sequence<>(), slices...); + using dst_mapping_t = typename layout_stride::template mapping; + return submdspan_mapping_result { + dst_mapping_t(mdspan_non_standard, dst_ext, + MDSPAN_IMPL_STANDARD_NAMESPACE::detail::construct_sub_strides( + *this, inv_map, +// HIP needs deduction guides to have markups so we need to be explicit +// NVCC 11.0 has a bug with deduction guide here, tested that 11.2 does not have +// the issue but Clang-CUDA also doesn't accept the use of deduction guide so +// disable it for CUDA alltogether +#if defined(MDSPAN_IMPL_HAS_HIP) || defined(MDSPAN_IMPL_HAS_CUDA) + MDSPAN_IMPL_STANDARD_NAMESPACE::detail::tuple{ + MDSPAN_IMPL_STANDARD_NAMESPACE::detail::stride_of(slices)...}).values), +#else + MDSPAN_IMPL_STANDARD_NAMESPACE::detail::tuple{MDSPAN_IMPL_STANDARD_NAMESPACE::detail::stride_of(slices)...}).values), +#endif + offset + }; + } + } + } + + +#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__) + __builtin_unreachable(); +#endif +} + +//********************************** +// layout_right submdspan_mapping +//********************************* +namespace detail { + +// Figure out whether to preserve layout_right +template +struct deduce_layout_right_submapping; + +template +struct deduce_layout_right_submapping< + IndexType, SubRank, std::index_sequence, SliceSpecifiers...> { + + static constexpr size_t Rank = sizeof...(Idx); + using count_range = index_sequence_scan_impl< + 0u, (std::is_convertible_v ? 0u : 1u)...>; + //__static_partial_sums...>; + constexpr static int gap_len = + (((Idx < Rank - 1 && count_range::get(Idx) == SubRank - 1 && + std::is_convertible_v) + ? 1 + : 0) + + ... + 0); + + MDSPAN_INLINE_FUNCTION + constexpr static bool layout_right_value() { + // Use layout_right for rank 0 + if constexpr (SubRank == 0) { + return true; + // Use layout_right for rank 1 result if rightmost slice specifier is range like + } else if constexpr (SubRank == 1) { + return ((Idx < Rank - 1 || is_range_slice_v)&&...); + } else { + // Preserve if rightmost SubRank-1 slices are full_extent_t and + // the slice at idx Rank-Subrank is a range and + // for idx < Rank - SubRank the slice is an index + return ((((Idx >= Rank - SubRank) && std::is_same_v) || + ((Idx == Rank - SubRank) && is_range_slice_v) || + ((Idx < Rank - SubRank) && is_index_slice_v)) && ...); + } +#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__) + __builtin_unreachable(); +#endif + } + + MDSPAN_INLINE_FUNCTION + constexpr static bool layout_right_padded_value() { + // Technically could also keep layout_right_padded for SubRank==0 + // and SubRank==1 with rightmost slice specifier being a contiguous range + // but we intercept these cases separately + + // In all other cases: + // rightmost slice must be range + // then there can be a gap with index slices + // then SubRank - 2 full_extent slices + // then another range slice + // then more index slices + // e.g. I I R F F F I I I R for obtaining a rank-5 from a rank-10 + return ((((Idx == Rank - 1) && is_range_slice_v) || + ((Idx >= Rank - gap_len - 1 && Idx < Rank - 1) && is_index_slice_v) || + ((Idx > Rank - gap_len - SubRank && Idx < Rank - gap_len - 1) && std::is_same_v) || + ((Idx == Rank - gap_len - SubRank) && is_range_slice_v) || + ((Idx < Rank - gap_len - SubRank) && is_index_slice_v)) && ... ); + } +}; + +// We are reusing the same thing for layout_right and layout_right_padded +// For layout_right as source StaticStride is static_extent(Rank-1) +template +MDSPAN_INLINE_FUNCTION constexpr size_t +compute_s_static_layout_right (std::index_sequence) { + // Neither StaticStride nor any of the provided extents can be zero. + // StaticStride can never be zero, the static_extents we are looking at are associated with + // integral slice specifiers - which wouldn't be valid for zero extent + size_t val = ((Idx >= Extents::rank() - 1 - NumGaps && Idx < Extents::rank() - 1 ? (Extents::static_extent(Idx) == dynamic_extent?0:Extents::static_extent(Idx)) : 1) * ... * (StaticStride == dynamic_extent?0:StaticStride)); + return val == 0?dynamic_extent:val; + } + +} // namespace detail + +// Actual submdspan mapping call +template +template +MDSPAN_INLINE_FUNCTION constexpr auto +layout_right::mapping::submdspan_mapping_impl( + SliceSpecifiers... slices) const { + + // Implements mandate check + detail::check_submdspan_slice_mandates(std::make_index_sequence(), slices...); + + // compute sub extents + using src_ext_t = Extents; + auto dst_ext = submdspan_extents(extents(), slices...); + using dst_ext_t = decltype(dst_ext); + + // figure out sub layout type + using deduce_layout = detail::deduce_layout_right_submapping< + typename dst_ext_t::index_type, dst_ext_t::rank(), + std::make_index_sequence, + SliceSpecifiers...>; + + // Figure out if any slice's lower bound equals the corresponding extent. + // If so, bypass evaluating the layout mapping. This fixes LWG Issue 4060. + const bool out_of_bounds = + detail::any_slice_out_of_bounds(this->extents(), slices...); + auto offset = static_cast( + out_of_bounds ? this->required_span_size() + : this->operator()(detail::first_of(slices)...)); + + if constexpr (deduce_layout::layout_right_value()) { + // layout_right case + using dst_mapping_t = typename layout_right::mapping; + return submdspan_mapping_result{dst_mapping_t(dst_ext), + offset}; + } else if constexpr (deduce_layout::layout_right_padded_value()) { + constexpr size_t S_static = MDSPAN_IMPL_STANDARD_NAMESPACE::detail::compute_s_static_layout_left(std::make_index_sequence()); + using dst_mapping_t = typename layout_right_padded::template mapping; + return submdspan_mapping_result{ + dst_mapping_t(dst_ext, + stride(src_ext_t::rank() - 2 - deduce_layout::gap_len)), + offset}; + } else { + // layout_stride case + using dst_mapping_t = typename layout_stride::mapping; + auto inv_map = detail::inv_map_rank(std::integral_constant(), + std::index_sequence<>(), slices...); + return submdspan_mapping_result { + dst_mapping_t(mdspan_non_standard, dst_ext, + detail::construct_sub_strides( + *this, inv_map, +// HIP needs deduction guides to have markups so we need to be explicit +// NVCC 11.0 has a bug with deduction guide here, tested that 11.2 does not have +// the issue but Clang-CUDA also doesn't accept the use of deduction guide so +// disable it for CUDA altogether +#if defined(MDSPAN_IMPL_HAS_HIP) || defined(MDSPAN_IMPL_HAS_CUDA) + MDSPAN_IMPL_STANDARD_NAMESPACE::detail::tuple{ + detail::stride_of(slices)...}).values), +#else + MDSPAN_IMPL_STANDARD_NAMESPACE::detail::tuple{detail::stride_of(slices)...}).values), +#endif + offset + }; + } +#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__) + __builtin_unreachable(); +#endif +} + +template +template +template +MDSPAN_INLINE_FUNCTION constexpr auto +layout_right_padded::mapping::submdspan_mapping_impl( + SliceSpecifiers... slices) const { + + // Implements mandate check + detail::check_submdspan_slice_mandates(std::make_index_sequence(), slices...); + + // compute sub extents + using src_ext_t = Extents; + auto dst_ext = submdspan_extents(extents(), slices...); + using dst_ext_t = decltype(dst_ext); + + if constexpr (Extents::rank() == 0) { // rank-0 case + using dst_mapping_t = typename layout_right_padded::template mapping; + return submdspan_mapping_result{*this, 0}; + } else { + // Figure out if any slice's lower bound equals the corresponding extent. + // If so, bypass evaluating the layout mapping. This fixes LWG Issue 4060. + // figure out sub layout type + const bool out_of_bounds = + MDSPAN_IMPL_STANDARD_NAMESPACE::detail::any_slice_out_of_bounds(this->extents(), slices...); + auto offset = static_cast( + out_of_bounds ? this->required_span_size() + : this->operator()(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::first_of(slices)...)); + if constexpr (dst_ext_t::rank() == 0) { // result rank-0 + // Same issue as in layout_left_padded: see comment there + // using dst_mapping_t = typename layout_right::template mapping; + // return submdspan_mapping_result{dst_mapping_t{dst_ext}, offset}; + return submdspan_mapping_result> + {typename layout_right::template mapping{dst_ext}, offset}; + } else { // general case + using deduce_layout = MDSPAN_IMPL_STANDARD_NAMESPACE::detail::deduce_layout_right_submapping< + typename dst_ext_t::index_type, dst_ext_t::rank(), + decltype(std::make_index_sequence()), + SliceSpecifiers...>; + + if constexpr (deduce_layout::layout_right_value() && dst_ext_t::rank() == 1) { // getting rank-1 from rightmost + using dst_mapping_t = typename layout_right::template mapping; + return submdspan_mapping_result{dst_mapping_t{dst_ext}, offset}; + } else if constexpr (deduce_layout::layout_right_padded_value()) { // can keep layout_right_padded + constexpr size_t S_static = MDSPAN_IMPL_STANDARD_NAMESPACE::detail::compute_s_static_layout_right(std::make_index_sequence()); + using dst_mapping_t = typename layout_right_padded::template mapping; + return submdspan_mapping_result{ + dst_mapping_t(dst_ext, stride(Extents::rank() - 2 - deduce_layout::gap_len)), offset}; + } else { // layout_stride + auto inv_map = MDSPAN_IMPL_STANDARD_NAMESPACE::detail::inv_map_rank(std::integral_constant(), + std::index_sequence<>(), slices...); + using dst_mapping_t = typename layout_stride::template mapping; + return submdspan_mapping_result { + dst_mapping_t(mdspan_non_standard, dst_ext, + MDSPAN_IMPL_STANDARD_NAMESPACE::detail::construct_sub_strides( + *this, inv_map, +// HIP needs deduction guides to have markups so we need to be explicit +// NVCC 11.0 has a bug with deduction guide here, tested that 11.2 does not have +// the issue but Clang-CUDA also doesn't accept the use of deduction guide so +// disable it for CUDA alltogether +#if defined(MDSPAN_IMPL_HAS_HIP) || defined(MDSPAN_IMPL_HAS_CUDA) + MDSPAN_IMPL_STANDARD_NAMESPACE::detail::tuple{ + MDSPAN_IMPL_STANDARD_NAMESPACE::detail::stride_of(slices)...}).values), +#else + MDSPAN_IMPL_STANDARD_NAMESPACE::detail::tuple{MDSPAN_IMPL_STANDARD_NAMESPACE::detail::stride_of(slices)...}).values), +#endif + offset + }; + } + } + } + + +#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__) + __builtin_unreachable(); +#endif +} + +//********************************** +// layout_stride submdspan_mapping +//********************************* +template +template +MDSPAN_INLINE_FUNCTION constexpr auto +layout_stride::mapping::submdspan_mapping_impl( + SliceSpecifiers... slices) const { + + // Implements mandate check + detail::check_submdspan_slice_mandates(std::make_index_sequence(), slices...); + + auto dst_ext = submdspan_extents(extents(), slices...); + using dst_ext_t = decltype(dst_ext); + auto inv_map = detail::inv_map_rank(std::integral_constant(), + std::index_sequence<>(), slices...); + using dst_mapping_t = typename layout_stride::template mapping; + + // Figure out if any slice's lower bound equals the corresponding extent. + // If so, bypass evaluating the layout mapping. This fixes LWG Issue 4060. + const bool out_of_bounds = + detail::any_slice_out_of_bounds(this->extents(), slices...); + auto offset = static_cast( + out_of_bounds ? this->required_span_size() + : this->operator()(detail::first_of(slices)...)); + + return submdspan_mapping_result { + dst_mapping_t(mdspan_non_standard, dst_ext, + detail::construct_sub_strides( + *this, inv_map, +// HIP needs deduction guides to have markups so we need to be explicit +// NVCC 11.0 has a bug with deduction guide here, tested that 11.2 does not have +// the issue but Clang-CUDA also doesn't accept the use of deduction guide so +// disable it for CUDA alltogether +#if defined(MDSPAN_IMPL_HAS_HIP) || defined(MDSPAN_IMPL_HAS_CUDA) + MDSPAN_IMPL_STANDARD_NAMESPACE::detail::tuple( + detail::stride_of(slices)...)).values), +#else + MDSPAN_IMPL_STANDARD_NAMESPACE::detail::tuple(detail::stride_of(slices)...)).values), +#endif + offset + }; +} + +} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE + +#if defined __NVCC__ +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +#pragma nv_diagnostic pop +#else +#ifdef __CUDA_ARCH__ +#pragma diagnostic pop +#endif +#endif +#elif defined __NVCOMPILER +#pragma diagnostic pop +#endif diff --git a/src/experimental/__p2642_bits/layout_padded.hpp b/src/experimental/__p2642_bits/layout_padded.hpp new file mode 100644 index 00000000..0ced8053 --- /dev/null +++ b/src/experimental/__p2642_bits/layout_padded.hpp @@ -0,0 +1,1075 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include +#include "layout_padded_fwd.hpp" +#include "../__p0009_bits/dynamic_extent.hpp" +#include "../__p0009_bits/extents.hpp" +#include "../__p0009_bits/mdspan.hpp" +#include "../__p0009_bits/layout_left.hpp" +#include "../__p0009_bits/layout_right.hpp" +#include "../__p0009_bits/layout_stride.hpp" +#include "../__p0009_bits/utility.hpp" + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { +namespace detail { +template +MDSPAN_INLINE_FUNCTION +constexpr T +find_next_multiple(T alignment, U offset) +{ + if ( alignment == T(0) ) { + return T(0); + } else { + return ( ( offset + alignment - 1 ) / alignment) * alignment; + } +} + +template +MDSPAN_INLINE_FUNCTION constexpr size_t get_actual_static_padding_value() { + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::in_range; + constexpr auto rank = ExtentsType::rank(); + + if constexpr (rank <= typename ExtentsType::rank_type(1)) { + return 0; + } else if constexpr (PaddingValue != dynamic_extent && + ExtentsType::static_extent(ExtentToPadIdx) != + dynamic_extent) { + static_assert( + (PaddingValue != 0) || + (ExtentsType::static_extent(ExtentToPadIdx) == 0), + "padding stride can be 0 only if " + "extents_type::static_extent(extent-to-pad) is 0 or dynamic_extent"); + constexpr auto ret = find_next_multiple( + PaddingValue, ExtentsType::static_extent(ExtentToPadIdx)); + + using index_type = typename ExtentsType::index_type; + static_assert(in_range(ret), + "The least multiple of padding_value and first-static-extent " + "must be representable by index_type"); + + return ret; + } else { + return dynamic_extent; + } + // Missing return statement warning from NVCC and ICC +#if (defined(__NVCC__) || defined(__INTEL_COMPILER)) && !defined(__NVCOMPILER) + return 0; +#endif +} + +template +struct static_array_type_for_padded_extent { + static constexpr size_t padding_value = PaddingValue; + using index_type = typename Extents::index_type; + using extents_type = Extents; + using type = ::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::maybe_static_array< + index_type, size_t, dynamic_extent, + ::MDSPAN_IMPL_STANDARD_NAMESPACE::detail:: + get_actual_static_padding_value()>; +}; + +template +struct static_array_type_for_padded_extent< + PaddingValue, Extents, ExtentToPadIdx, Rank, std::enable_if_t> { + using index_type = typename Extents::index_type; + using extents_type = Extents; + using type = ::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::maybe_static_array< + index_type, size_t, dynamic_extent, 0>; +}; + +template +struct padded_extent { + static constexpr size_t padding_value = PaddingValue; + using index_type = typename Extents::index_type; + using extents_type = Extents; + using static_array_type = typename static_array_type_for_padded_extent< + padding_value, Extents, ExtentToPadIdx, Extents::rank()>::type; + + MDSPAN_INLINE_FUNCTION + static constexpr auto static_value() { + return static_array_type::static_value(0); + } + + MDSPAN_INLINE_FUNCTION + static constexpr static_array_type init_padding(const Extents &exts) { + if constexpr ((Extents::rank() > 1) && (padding_value == dynamic_extent)) { + return {exts.extent(ExtentToPadIdx)}; + } else { + return init_padding(exts, padding_value); + } + // Missing return statement warning from NVCC and ICC +#if (defined(__NVCC__) || defined(__INTEL_COMPILER)) && !defined(__NVCOMPILER) + return {}; +#endif + } + + MDSPAN_INLINE_FUNCTION static constexpr static_array_type + init_padding([[maybe_unused]] const Extents &exts, + [[maybe_unused]] size_t pv) { + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::in_range; + if constexpr (Extents::rank() > 1) { + auto strd = find_next_multiple(pv, exts.extent(ExtentToPadIdx)); + MDSPAN_IMPL_PRECONDITION(in_range(strd)); + return {strd}; + } else { + return {}; + } + // Missing return statement warning from NVCC and ICC +#if (defined(__NVCC__) || defined(__INTEL_COMPILER)) && !defined(__NVCOMPILER) + return {}; +#endif + } + + template + MDSPAN_INLINE_FUNCTION static constexpr static_array_type + init_padding([[maybe_unused]] const Mapping &other_mapping, + std::integral_constant) { + if constexpr (Extents::rank() > 1) { + return {other_mapping.stride(PaddingStrideIdx)}; + } else { + return {}; + } + // Missing return statement warning from NVCC and ICC +#if (defined(__NVCC__) || defined(__INTEL_COMPILER)) && !defined(__NVCOMPILER) + return {}; +#endif + } +}; + +template +MDSPAN_INLINE_FUNCTION constexpr bool +check_static_extents_representability() { + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::check_mul_result_is_nonnegative_and_representable; + // We cannot check statically for sure if the extents are representable + // if we have dynamic values -- this can only be checked by a precondition + // We can check if the product of only the static extents is representable though... + using index_type = typename Extents::index_type; + + // get rid of NVCC warning "pointless comparison of unsigned integer with zero" + if constexpr ( Extents::rank() > 0 ) { + auto prod = index_type(1); + for (size_t i = 0; i < Extents::rank(); ++i) { + if (Extents::static_extent(i) == dynamic_extent) + continue; + if (!check_mul_result_is_nonnegative_and_representable( + prod, static_cast(Extents::static_extent(i)))) + return false; + prod *= Extents::static_extent(i); + } + } + + return true; +} + +template +MDSPAN_INLINE_FUNCTION constexpr bool +check_extents_representability(const Extents &exts) { + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::check_mul_result_is_nonnegative_and_representable; + using index_type = typename Extents::index_type; + + // get rid of NVCC warning "pointless comparison of unsigned integer with zero" + if constexpr ( Extents::rank() > 0 ) { + auto prod = index_type(1); + for (size_t i = 0; i < Extents::rank(); ++i) { + if (!check_mul_result_is_nonnegative_and_representable( + prod, static_cast(exts.extent(i)))) + return false; + prod *= exts.extent(i); + } + } + + return true; +} + +template +MDSPAN_INLINE_FUNCTION constexpr bool +check_static_extents_and_left_padding_representability() { + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::check_mul_result_is_nonnegative_and_representable; + if constexpr (Extents::rank() < 2) { + return true; + } + + // We cannot check statically for sure if the product of the extents and padding value + // are representable if we have dynamic values -- this can only be checked by a precondition + // We can check if the product of only the static extents and potentially the padding value (if it is static) + // is representable though... + // We already checked that StaticPaddingValue is representable by index_type + + // get rid of NVCC warning "pointless comparison of unsigned integer with zero" + if constexpr ( Extents::rank() > 0 ) { + auto prod = (StaticPaddingValue != dynamic_extent) ? static_cast< CheckType >(StaticPaddingValue) : CheckType(1); + for (size_t i = 1; i < Extents::rank(); ++i) { + if (Extents::static_extent(i) == dynamic_extent) + continue; + if (!check_mul_result_is_nonnegative_and_representable(prod, static_cast< CheckType >(Extents::static_extent(i)))) + return false; + prod *= Extents::static_extent(i); + } + } + + return true; +} + +template +MDSPAN_INLINE_FUNCTION constexpr bool +check_extents_and_left_padding_representability(const Extents &exts, + [[maybe_unused]] size_t dynamic_padding_value) { + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::check_mul_result_is_nonnegative_and_representable; + + // get rid of NVCC warning "pointless comparison of unsigned integer with zero" + // And also a rank 1 layout cannot overflow + if constexpr ( Extents::rank() > 1 ) { + auto prod = static_cast(dynamic_padding_value); + for (size_t i = 1; i < Extents::rank(); ++i) { + if (!check_mul_result_is_nonnegative_and_representable( + prod, static_cast(exts.extent(i)))) + return false; + prod *= exts.extent(i); + } + } + + return true; +} + +template +MDSPAN_INLINE_FUNCTION constexpr bool +check_static_extents_and_right_padding_representability() { + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::check_mul_result_is_nonnegative_and_representable; + + // We cannot check statically for sure if the product of the extents and padding value + // are representable if we have dynamic values -- this can only be checked by a precondition + // We can check if the product of only the static extents and potentially the padding value (if it is static) + // is representable though... + // We already checked that StaticPaddingValue is representable by index_type + + // get rid of NVCC warning "pointless comparison of unsigned integer with zero" + // And also a rank 1 layout cannot overflow + if constexpr ( Extents::rank() > 1 ) { + auto prod = (StaticPaddingValue != dynamic_extent) ? static_cast< CheckType >(StaticPaddingValue) : CheckType(1); + for (size_t i = 0; i < Extents::rank() - 1; ++i) { + if (Extents::static_extent(i) == dynamic_extent) + continue; + if (!check_mul_result_is_nonnegative_and_representable(prod, static_cast< CheckType >(Extents::static_extent(i)))) + return false; + prod *= Extents::static_extent(i); + } + } + + return true; +} + +template +MDSPAN_INLINE_FUNCTION constexpr bool +check_extents_and_right_padding_representability(const Extents &exts, + [[maybe_unused]] size_t dynamic_padding_value) { + using MDSPAN_IMPL_STANDARD_NAMESPACE::detail::check_mul_result_is_nonnegative_and_representable; + + // get rid of NVCC warning "pointless comparison of unsigned integer with zero" + // And also a rank 1 layout cannot overflow + if constexpr ( Extents::rank() > 1 ) { + auto prod = static_cast(dynamic_padding_value); + for (size_t i = 0; i < Extents::rank() - 1; ++i) { + if (!check_mul_result_is_nonnegative_and_representable(prod, static_cast< CheckType >(exts.extent(i)))) + return false; + prod *= exts.extent(i); + } + } + + return true; +} +} // namespace detail + +template +template +class layout_left_padded::mapping { +public: + static constexpr size_t padding_value = PaddingValue; + + using extents_type = Extents; + using index_type = typename extents_type::index_type; + using size_type = typename extents_type::size_type; + using rank_type = typename extents_type::rank_type; + using layout_type = layout_left_padded; + +#ifndef MDSPAN_INTERNAL_TEST +private: +#endif // MDSPAN_INTERNAL_TEST + + static constexpr rank_type padded_stride_idx = detail::layout_padded_constants::padded_stride_idx; + static constexpr rank_type extent_to_pad_idx = detail::layout_padded_constants::extent_to_pad_idx; + + static_assert((padding_value != 0) + || (extents_type::static_extent(extent_to_pad_idx) == 0) + || (extents_type::static_extent(extent_to_pad_idx) == dynamic_extent), + "out of bounds access for rank 0"); + static_assert(detail::check_static_extents_representability(), "The size of the muiltidimensional index space given by the extents must be representable as a value of index_type"); + static_assert((padding_value == dynamic_extent) || MDSPAN_IMPL_STANDARD_NAMESPACE::detail::in_range(padding_value), "padding_value must be representable as a value of type index_type"); + + using padded_stride_type = detail::padded_extent< padding_value, extents_type, extent_to_pad_idx >; + + static constexpr size_t static_padding_stride = padded_stride_type::static_value(); + + static_assert(detail::check_static_extents_and_left_padding_representability() + && detail::check_static_extents_and_left_padding_representability(), + "the product of static_padding_stride and static extents 1 through rank must be representable as a value of type size_t and index_type"); + + typename padded_stride_type::static_array_type padded_stride = {}; + extents_type exts = {}; + + MDSPAN_INLINE_FUNCTION constexpr index_type + compute_offset(std::index_sequence<>) const { + return 0; + } + + template + MDSPAN_INLINE_FUNCTION constexpr index_type + compute_offset(std::index_sequence, IndexOffset index_offset) const { + return index_offset; + } + + template + MDSPAN_INLINE_FUNCTION constexpr index_type + compute_offset(std::index_sequence, + IndexOffsets... index_offsets) const { + index_type indices[] = {static_cast(index_offsets)...}; + // self-recursive fold trick from + // https://github.com/llvm/llvm-project/blob/96e1914aa2e6d8966acbfbe2f4d184201f1aa318/libcxx/include/mdspan/layout_left.h#L144 + index_type res = 0; + ((res = indices[extents_type::rank() - 1 - Ranks] + + ((extents_type::rank() - 1 - Ranks) == extent_to_pad_idx + ? padded_stride.value(0) + : exts.extent(extents_type::rank() - 1 - Ranks)) * + res), + ...); + return res; + } + +public: +#if !MDSPAN_HAS_CXX_20 || defined(__NVCC__) + MDSPAN_INLINE_FUNCTION + constexpr mapping() + : mapping(extents_type{}) + {} +#else + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr mapping() + requires(static_padding_stride != dynamic_extent) = default; + + MDSPAN_INLINE_FUNCTION + constexpr mapping() + requires(static_padding_stride == dynamic_extent) + : mapping(extents_type{}) + {} +#endif + + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(const mapping&) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping& operator=(const mapping&) noexcept = default; + + /** + * Initializes the mapping with the given extents. + * + * \param ext the given extents + */ + MDSPAN_INLINE_FUNCTION + constexpr mapping(const extents_type& ext) + : padded_stride(padded_stride_type::init_padding(ext)), exts(ext) + { + MDSPAN_IMPL_PRECONDITION(detail::check_extents_representability(ext)); + MDSPAN_IMPL_PRECONDITION( + detail::check_extents_and_left_padding_representability( + ext, padded_stride.value(0))); + } + + /** + * Initializes the mapping with the given extents and the specified padding value. + * + * This overload participates in overload resolution only if `is_convertible_v` + * is `true` and `is_nothrow_constructible_v` is `true` + * + * \param ext the given extents + * \param padding_value the padding value + */ + MDSPAN_TEMPLATE_REQUIRES( + class Size, + /* requires */ ( + std::is_convertible_v + && std::is_nothrow_constructible_v + ) + ) + MDSPAN_INLINE_FUNCTION + constexpr mapping(const extents_type &ext, Size dynamic_padding_value) + : padded_stride(padded_stride_type::init_padding(ext, dynamic_padding_value)), exts(ext) + { + assert((padding_value == dynamic_extent) || (static_cast(padding_value) == static_cast(dynamic_padding_value))); + MDSPAN_IMPL_PRECONDITION(detail::check_extents_representability(ext)); + MDSPAN_IMPL_PRECONDITION( + detail::check_extents_and_left_padding_representability( + ext, dynamic_padding_value)); + } + + /** + * Converting constructor from `layout_left::mapping`. + * + * This overload participates in overload resolution only if + * `is_constructible_v` is true. If + * `OtherExtents::rank() > 1` then one of `padding_value`, `static_extent(0)`, + * or `OtherExtents::static_extent(0)` must be `dynamic_extent`; otherwise, + * `OtherExtents::static_extent(0)` must be equal to the least multiple of + * `padding_value` greater than or equal to `extents_type::static_extent(0)` + */ + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ (std::is_constructible_v)) + MDSPAN_CONDITIONAL_EXPLICIT( + (!std::is_convertible_v)) + MDSPAN_INLINE_FUNCTION + constexpr mapping(const layout_left::mapping &other_mapping) + : padded_stride(padded_stride_type::init_padding( + other_mapping, + std::integral_constant{})), + exts(other_mapping.extents()) { + static_assert( + (OtherExtents::rank() > 1) || + (static_padding_stride != dynamic_extent) || + (OtherExtents::static_extent(extent_to_pad_idx) != dynamic_extent) || + (static_padding_stride == + OtherExtents::static_extent(extent_to_pad_idx))); + MDSPAN_IMPL_PRECONDITION(detail::check_extents_representability(exts)); + MDSPAN_IMPL_PRECONDITION( + detail::check_extents_and_left_padding_representability( + exts, padded_stride.value(0))); + } + + /** + * Converting constructor from `layout_stride::mapping`. + * + * This overload participates in overload resolution only if + * `is_constructible_v` is true + */ + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ (std::is_constructible_v)) + MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0)) + MDSPAN_INLINE_FUNCTION + constexpr mapping(const layout_stride::mapping &other_mapping) + : padded_stride(padded_stride_type::init_padding( + other_mapping, + std::integral_constant{})), + exts(other_mapping.extents()) { + MDSPAN_IMPL_PRECONDITION(detail::check_extents_representability(exts)); + MDSPAN_IMPL_PRECONDITION( + detail::check_extents_and_left_padding_representability( + exts, padded_stride.value(0))); + } + + /** + * Converting constructor from `layout_left_padded::mapping`. + * + * This overload participates in overload resolution only if + * `is_constructible_v` is true. Either + * `padding_value` or `OtherPaddingStride` must be `std::dynamic_extent`, or + * `padding_value == OtherPaddingStride`. + */ + MDSPAN_TEMPLATE_REQUIRES( + class Mapping, + /* requires */ (detail::is_layout_left_padded_mapping::value + &&std::is_constructible_v< + extents_type, typename Mapping::extents_type>)) + MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 1 && + (padding_value == dynamic_extent || + Mapping::padding_value == dynamic_extent))) + MDSPAN_INLINE_FUNCTION + constexpr mapping(const Mapping &other_mapping) + : padded_stride(padded_stride_type::init_padding( + other_mapping, + std::integral_constant{})), + exts(other_mapping.extents()) { + static_assert(padding_value == dynamic_extent || + Mapping::padding_value == dynamic_extent || + padding_value == Mapping::padding_value); + MDSPAN_IMPL_PRECONDITION(detail::check_extents_representability(exts)); + MDSPAN_IMPL_PRECONDITION( + detail::check_extents_and_left_padding_representability( + exts, padded_stride.value(0))); + } + + /** + * Converting constructor from `layout_right_padded::mapping`. + * + * This overload participates in overload resolution only if + * `extents_type::rank()` is 0 or 1 and `is_constructible_v` is `true`. + */ + MDSPAN_TEMPLATE_REQUIRES( + class Mapping, + /* requires */ (detail::is_layout_right_padded_mapping::value + &&extents_type::rank() <= 1 && + std::is_constructible_v)) + MDSPAN_CONDITIONAL_EXPLICIT( + (!std::is_convertible_v)) + MDSPAN_INLINE_FUNCTION + constexpr mapping(const Mapping &other_mapping) noexcept + : padded_stride(padded_stride_type::init_padding( + static_cast(other_mapping.extents()), + other_mapping.extents().extent(extent_to_pad_idx))), + exts(other_mapping.extents()) { + MDSPAN_IMPL_PRECONDITION(detail::check_extents_representability(exts)); + MDSPAN_IMPL_PRECONDITION( + detail::check_extents_and_left_padding_representability( + exts, padded_stride.value(0))); + } + + MDSPAN_INLINE_FUNCTION constexpr const extents_type & + extents() const noexcept { + return exts; + } + + constexpr std::array + strides() const noexcept { + if constexpr (extents_type::rank() == 0) { + return {}; + } else if constexpr (extents_type::rank() == 1) { + return {1}; + } else { + index_type value = 1; + std::array s{}; + s[extent_to_pad_idx] = value; + value *= padded_stride.value(0); + for (rank_type r = extent_to_pad_idx + 1; r < extents_type::rank() - 1; + ++r) { + s[r] = value; + value *= exts.extent(r); + } + s[extents_type::rank() - 1] = value; + return s; + } + } + + MDSPAN_INLINE_FUNCTION constexpr index_type + required_span_size() const noexcept { + if constexpr (extents_type::rank() == 0) { + return 1; + } else if constexpr (extents_type::rank() == 1) { + return exts.extent(0); + } else { + index_type value = padded_stride.value(0); + for (rank_type r = 1; r < extents_type::rank(); ++r) { + value *= exts.extent(r); + } + return value == 0 ? 0 : value + exts.extent(0) - padded_stride.value(0); + } + } + + /** + * Return the mapping given the provided indices per rank. + * + * This overload participates in overload resolution only if: + * - `sizeof...(Indices) == extents_type::rank()`, + * - `(is_convertible_v && ...) is true`, and + * - (is_nothrow_constructible_v && ...) is true. + */ + MDSPAN_TEMPLATE_REQUIRES( + class... Indices, + /* requires */ (sizeof...(Indices) == extents_type::rank() && + (::MDSPAN_IMPL_STANDARD_NAMESPACE::detail:: + are_valid_indices()))) + MDSPAN_INLINE_FUNCTION constexpr index_type + operator()(Indices... idxs) const noexcept { +#if !defined(NDEBUG) + ::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::check_all_indices(this->extents(), + idxs...); +#endif // ! NDEBUG + return compute_offset(std::index_sequence_for{}, idxs...); + } + + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { + return true; + } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { + return (extents_type::rank() <= rank_type(1)) || + (extents_type::static_extent(extent_to_pad_idx) != dynamic_extent && + extents_type::static_extent(extent_to_pad_idx) == + padded_stride_type::static_value()); + } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { + return true; + } + + MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { + return true; + } + MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const noexcept { + return (extents_type::rank() < 2) || + (exts.extent(extent_to_pad_idx) == padded_stride.value(0)); + } + MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { + return true; + } + + MDSPAN_INLINE_FUNCTION + constexpr index_type stride(rank_type r) const noexcept { + assert(r < extents_type::rank()); + if (r == 0) + return index_type(1); + + index_type value = padded_stride.value(0); + for (rank_type k = 1; k < r; k++) + value *= exts.extent(k); + + return value; + } + + /** + * Equality operator between `layout_left_padded`s + * + * This overload only participates in overload resolution if + * `OtherExtents::rank() == extents_type::rank()`. + * + * \note There is currently a difference from p2642r2, where this function is + * specified as taking `layout_left_padded< padding_value >::mapping< + * Extents>`. However, this makes `padding_value` non-deducible. + */ + MDSPAN_TEMPLATE_REQUIRES( + class Mapping, + /* requires */ (detail::is_layout_left_padded_mapping::value && + (Mapping::extents_type::rank() == extents_type::rank()))) + MDSPAN_INLINE_FUNCTION friend constexpr bool + operator==(const mapping &left, const Mapping &right) noexcept { + // Workaround for some compilers not short-circuiting properly with + // compile-time checks i.e. we can't access stride(_padding_stride_idx) of a + // rank 0 mapping + bool strides_equal = true; + if constexpr (extents_type::rank() > rank_type(1)) { + strides_equal = + left.stride(padded_stride_idx) == right.stride(padded_stride_idx); + } + return (left.extents() == right.extents()) && strides_equal; + } + +#if !MDSPAN_HAS_CXX_20 + /** + * Inequality operator between `layout_left_padded`s + * + * This overload only participates in overload resolution if + * `OtherExtents::rank() == extents_type::rank()`. + */ + MDSPAN_TEMPLATE_REQUIRES( + class Mapping, + /* requires */ (detail::is_layout_left_padded_mapping::value && + (Mapping::extents_type::rank() == extents_type::rank()))) + MDSPAN_INLINE_FUNCTION friend constexpr bool + operator!=(const mapping &left, const Mapping &right) noexcept { + return !(left == right); + } +#endif + + // [mdspan.submdspan.mapping], submdspan mapping specialization + template + MDSPAN_INLINE_FUNCTION + constexpr auto submdspan_mapping_impl( + SliceSpecifiers... slices) const; + + template + MDSPAN_INLINE_FUNCTION + friend constexpr auto submdspan_mapping( + const mapping& src, SliceSpecifiers... slices) { + return src.submdspan_mapping_impl(slices...); + } +}; + +template +template +class layout_right_padded::mapping { +public: + static constexpr size_t padding_value = PaddingValue; + + using extents_type = Extents; + using index_type = typename extents_type::index_type; + using size_type = typename extents_type::size_type; + using rank_type = typename extents_type::rank_type; + using layout_type = layout_right_padded; + +#ifndef MDSPAN_INTERNAL_TEST + private: +#endif // MDSPAN_INTERNAL_TEST + + static constexpr rank_type padded_stride_idx = detail::layout_padded_constants::padded_stride_idx; + static constexpr rank_type extent_to_pad_idx = detail::layout_padded_constants::extent_to_pad_idx; + + static_assert((padding_value != 0) + || (extents_type::static_extent(extent_to_pad_idx) == 0) + || (extents_type::static_extent(extent_to_pad_idx) == dynamic_extent), + "if padding stride is 0, static_extent(extent-to-pad-rank) must also be 0 or dynamic_extent"); + static_assert(detail::check_static_extents_representability(), "The size of the muiltidimensional index space given by the extents must be representable as a value of index_type"); + static_assert((padding_value == dynamic_extent) || MDSPAN_IMPL_STANDARD_NAMESPACE::detail::in_range(padding_value), "padding_value must be representable as a value of type index_type"); + + + using padded_stride_type = detail::padded_extent< padding_value, extents_type, extent_to_pad_idx >; + static constexpr size_t static_padding_stride = padded_stride_type::static_value(); + + static_assert(detail::check_static_extents_and_right_padding_representability() + && detail::check_static_extents_and_right_padding_representability(), + "the product of static_padding_stride and static extents 1 through rank must be representable as a value of type size_t and index_type"); + + typename padded_stride_type::static_array_type padded_stride = {}; + extents_type exts = {}; + + MDSPAN_INLINE_FUNCTION constexpr index_type + compute_offset(std::index_sequence<>) const { + return 0; + } + + template + MDSPAN_INLINE_FUNCTION constexpr index_type + compute_offset(std::index_sequence, IndexOffset index_offset) const { + return index_offset; + } + + template + MDSPAN_INLINE_FUNCTION constexpr index_type + compute_offset(std::index_sequence, + IndexOffsets... index_offsets) const { + // self-recursive fold trick from + // https://github.com/llvm/llvm-project/blob/4d9771741d40cc9cfcccb6b033f43689d36b705a/libcxx/include/mdspan/layout_right.h#L141 + index_type res = 0; + ((res = static_cast(index_offsets) + + (Ranks == extent_to_pad_idx ? padded_stride.value(0) + : exts.extent(Ranks)) * + res), + ...); + return res; + } + +public: +#if !MDSPAN_HAS_CXX_20 || defined(__NVCC__) + MDSPAN_INLINE_FUNCTION + constexpr mapping() + : mapping(extents_type{}) + {} +#else + MDSPAN_INLINE_FUNCTION_DEFAULTED + constexpr mapping() + requires(static_padding_stride != dynamic_extent) = default; + + MDSPAN_INLINE_FUNCTION + constexpr mapping() + requires(static_padding_stride == dynamic_extent) + : mapping(extents_type{}) + {} +#endif + + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(const mapping&) noexcept = default; + MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping& operator=(const mapping&) noexcept = default; + + /** + * Initializes the mapping with the given extents. + * + * \param ext the given extents + */ + MDSPAN_INLINE_FUNCTION + constexpr mapping(const extents_type &ext) + : padded_stride(padded_stride_type::init_padding(ext)), exts(ext) { + MDSPAN_IMPL_PRECONDITION(detail::check_extents_representability(ext)); + MDSPAN_IMPL_PRECONDITION( + detail::check_extents_and_right_padding_representability( + ext, padded_stride.value(0))); + } + + /** + * Initializes the mapping with the given extents and the specified padding value. + * + * This overload participates in overload resolution only if `is_convertible_v` + * is `true` and `is_nothrow_constructible_v` is `true` + * + * \param ext the given extents + * \param padding_value the padding value + */ + MDSPAN_TEMPLATE_REQUIRES( + class Size, + /* requires */ ( + std::is_convertible_v + && std::is_nothrow_constructible_v + ) + ) + MDSPAN_INLINE_FUNCTION + constexpr mapping(const extents_type &ext, Size dynamic_padding_value) + : padded_stride(padded_stride_type::init_padding(ext, static_cast(dynamic_padding_value))), + exts(ext) { + assert((padding_value == dynamic_extent) || + (static_cast(padding_value) == static_cast(dynamic_padding_value))); + MDSPAN_IMPL_PRECONDITION(detail::check_extents_representability(ext)); + MDSPAN_IMPL_PRECONDITION( + detail::check_extents_and_right_padding_representability( + ext, dynamic_padding_value)); + } + + /** + * Converting constructor from `layout_right::mapping`. + * + * This overload participates in overload resolution only if `is_constructible_v` is true. + * If `OtherExtents::rank() > 1` then one of `padding_value`, `static_extent(0)`, or `OtherExtents::static_extent(0)` must be `dynamic_extent`; + * otherwise, `OtherExtents::static_extent(0)` must be equal to the least multiple of `padding_value` greater than or equal to `extents_type::static_extent(0)` + */ + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ (std::is_constructible_v)) + MDSPAN_CONDITIONAL_EXPLICIT( + (!std::is_convertible_v)) + MDSPAN_INLINE_FUNCTION + constexpr mapping(const layout_right::mapping &other_mapping) + : padded_stride(padded_stride_type::init_padding( + other_mapping, + std::integral_constant{})), + exts(other_mapping.extents()) { + static_assert( + (OtherExtents::rank() > 1) || + (padded_stride_type::static_value() != dynamic_extent) || + (OtherExtents::static_extent(extent_to_pad_idx) != dynamic_extent) || + (padded_stride_type::static_value() == + OtherExtents::static_extent(extent_to_pad_idx))); + MDSPAN_IMPL_PRECONDITION(detail::check_extents_representability(exts)); + MDSPAN_IMPL_PRECONDITION( + detail::check_extents_and_right_padding_representability( + exts, padded_stride.value(0))); + } + + /** + * Converting constructor from `layout_stride::mapping`. + * + * This overload participates in overload resolution only if + * `is_constructible_v` is true + */ + MDSPAN_TEMPLATE_REQUIRES( + class OtherExtents, + /* requires */ (std::is_constructible_v)) + MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0)) + MDSPAN_INLINE_FUNCTION + constexpr mapping(const layout_stride::mapping &other_mapping) + : padded_stride(padded_stride_type::init_padding( + other_mapping, + std::integral_constant{})), + exts(other_mapping.extents()) { + MDSPAN_IMPL_PRECONDITION(detail::check_extents_representability(exts)); + MDSPAN_IMPL_PRECONDITION( + detail::check_extents_and_right_padding_representability( + exts, padded_stride.value(0))); + } + + /** + * Converting constructor from `layout_right_padded::mapping`. + * + * This overload participates in overload resolution only if + * `is_constructible_v` is true. Either + * `padding_value` or `OtherPaddingStride` must be `std::dynamic_extent`, or + * `padding_value == OtherPaddingStride`. + */ + MDSPAN_TEMPLATE_REQUIRES( + class Mapping, + /* requires */ (detail::is_layout_right_padded_mapping::value + &&std::is_constructible_v< + extents_type, typename Mapping::extents_type>)) + MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 1 && + (padding_value == dynamic_extent || + Mapping::padding_value == dynamic_extent))) + MDSPAN_INLINE_FUNCTION + constexpr mapping(const Mapping &other_mapping) + : padded_stride(padded_stride_type::init_padding( + other_mapping, + std::integral_constant{})), + exts(other_mapping.extents()) { + static_assert(padding_value == dynamic_extent || + Mapping::padding_value == dynamic_extent || + padding_value == Mapping::padding_value); + MDSPAN_IMPL_PRECONDITION(detail::check_extents_representability(exts)); + MDSPAN_IMPL_PRECONDITION( + detail::check_extents_and_right_padding_representability( + exts, padded_stride.value(0))); + } + + /** + * Converting constructor from `layout_left_padded::mapping`. + * + * This overload participates in overload resolution only if + * `extents_type::rank()` is 0 or 1 and `is_constructible_v` is `true`. + */ + MDSPAN_TEMPLATE_REQUIRES( + class Mapping, + /* requires */ (detail::is_layout_left_padded_mapping::value + &&extents_type::rank() <= 1 && + std::is_constructible_v)) + MDSPAN_CONDITIONAL_EXPLICIT( + (!std::is_convertible_v)) + MDSPAN_INLINE_FUNCTION + constexpr mapping(const Mapping &other_mapping) noexcept + : padded_stride(padded_stride_type::init_padding( + static_cast(other_mapping.extents()), + other_mapping.extents().extent(extent_to_pad_idx))), + exts(other_mapping.extents()) { + MDSPAN_IMPL_PRECONDITION(detail::check_extents_representability(exts)); + MDSPAN_IMPL_PRECONDITION( + detail::check_extents_and_right_padding_representability( + exts, padded_stride.value(0))); + } + + MDSPAN_INLINE_FUNCTION constexpr const extents_type & + extents() const noexcept { + return exts; + } + + constexpr std::array + strides() const noexcept { + if constexpr (extents_type::rank() == 0) { + return {}; + } else if constexpr (extents_type::rank() == 1) { + return {1}; + } else { + index_type value = 1; + std::array s{}; + s[extent_to_pad_idx] = value; + value *= padded_stride.value(0); + for (rank_type r = extent_to_pad_idx - 1; r > 0; --r) { + s[r] = value; + value *= exts.extent(r); + } + s[0] = value; + return s; + } + } + + MDSPAN_INLINE_FUNCTION constexpr index_type + required_span_size() const noexcept { + if constexpr (extents_type::rank() == 0) { + return 1; + } else if constexpr (extents_type::rank() == 1) { + return exts.extent(0); + } else { + index_type value = padded_stride.value(0); + for (rank_type r = 0; r < extent_to_pad_idx; ++r) { + value *= exts.extent(r); + } + return value == 0 ? 0 : value + exts.extent(extent_to_pad_idx) - padded_stride.value(0); + } + } + + /** + * Return the mapping given the provided indices per rank. + * + * This overload participates in overload resolution only if: + * - `sizeof...(Indices) == extents_type::rank()`, + * - `(is_convertible_v && ...) is true`, and + * - (is_nothrow_constructible_v && ...) is true. + */ + MDSPAN_TEMPLATE_REQUIRES( + class... Indices, + /* requires */ (sizeof...(Indices) == extents_type::rank() && + (::MDSPAN_IMPL_STANDARD_NAMESPACE::detail:: + are_valid_indices()))) + MDSPAN_INLINE_FUNCTION constexpr index_type + operator()(Indices... idxs) const noexcept { + return compute_offset(std::index_sequence_for{}, idxs...); + } + + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { + return true; + } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { + return (extents_type::rank() <= rank_type(1)) || + (extents_type::static_extent(extent_to_pad_idx) != dynamic_extent && + extents_type::static_extent(extent_to_pad_idx) == + padded_stride_type::static_value()); + } + MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { + return true; + } + + MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { + return true; + } + MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const noexcept { + return (extents_type::rank() < 2) || + (exts.extent(extent_to_pad_idx) == padded_stride.value(0)); + } + MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { + return true; + } + + MDSPAN_INLINE_FUNCTION constexpr index_type + stride(rank_type r) const noexcept { + assert(r < extents_type::rank()); + if (r == extents_type::rank() - 1) + return index_type(1); + + index_type value = padded_stride.value(0); + for (rank_type k = extents_type::rank() - 2; k > r; k--) + value *= exts.extent(k); + + return value; + } + + /** + * Equality operator between `layout_right_padded`s + * + * This overload only participates in overload resolution if + * `OtherExtents::rank() == extents_type::rank()`. + * + * \note There is currently a difference from p2642r2, where this function is + * specified as taking `layout_right_padded< padding_value >::mapping< + * Extents>`. However, this makes `padding_value` non-deducible. + */ + MDSPAN_TEMPLATE_REQUIRES( + class Mapping, + /* requires */ (detail::is_layout_right_padded_mapping::value && + (Mapping::extents_type::rank() == extents_type::rank()))) + MDSPAN_INLINE_FUNCTION friend constexpr bool + operator==(const mapping &left, const Mapping &right) noexcept { + // Workaround for some compilers not short-circuiting properly with + // compile-time checks i.e. we can't access stride(_padding_stride_idx) of a + // rank 0 mapping + bool strides_equal = true; + if constexpr (extents_type::rank() > rank_type(1)) { + strides_equal = + left.stride(padded_stride_idx) == right.stride(padded_stride_idx); + } + return (left.extents() == right.extents()) && strides_equal; + } + +#if !MDSPAN_HAS_CXX_20 + /** + * Inequality operator between `layout_right_padded`s + * + * This overload only participates in overload resolution if + * `OtherExtents::rank() == extents_type::rank()`. + */ + MDSPAN_TEMPLATE_REQUIRES( + class Mapping, + /* requires */ (detail::is_layout_right_padded_mapping::value && + (Mapping::extents_type::rank() == extents_type::rank()))) + MDSPAN_INLINE_FUNCTION friend constexpr bool + operator!=(const mapping &left, const Mapping &right) noexcept { + return !(left == right); + } +#endif + + // [mdspan.submdspan.mapping], submdspan mapping specialization + template + MDSPAN_INLINE_FUNCTION + constexpr auto submdspan_mapping_impl( + SliceSpecifiers... slices) const; + + template + MDSPAN_INLINE_FUNCTION + friend constexpr auto submdspan_mapping( + const mapping& src, SliceSpecifiers... slices) { + return src.submdspan_mapping_impl(slices...); + } +}; +} diff --git a/src/experimental/__p2642_bits/layout_padded_fwd.hpp b/src/experimental/__p2642_bits/layout_padded_fwd.hpp new file mode 100644 index 00000000..65312061 --- /dev/null +++ b/src/experimental/__p2642_bits/layout_padded_fwd.hpp @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#include +#include "../__p0009_bits/dynamic_extent.hpp" +#include "../__p0009_bits/utility.hpp" + +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + +template +struct layout_left_padded { + template + class mapping; +}; + +template +struct layout_right_padded { + template + class mapping; +}; + +namespace detail { +// The layout_padded_constants structs are only useful if rank > 1, otherwise they may wrap +template +struct layout_padded_constants; + +template +struct layout_padded_constants, ExtentsType> +{ + using rank_type = typename ExtentsType::rank_type; + static constexpr rank_type padded_stride_idx = 1; + static constexpr rank_type extent_to_pad_idx = 0; +}; + +template +struct layout_padded_constants, ExtentsType> +{ + using rank_type = typename ExtentsType::rank_type; + static constexpr rank_type padded_stride_idx = ExtentsType::rank() - 2; + static constexpr rank_type extent_to_pad_idx = ExtentsType::rank() - 1; +}; + +template +struct is_layout_left_padded : std::false_type {}; + +template +struct is_layout_left_padded> : std::true_type {}; + +template +struct is_layout_left_padded_mapping : std::false_type {}; + +template +struct is_layout_left_padded_mapping::template mapping>::value>> + : std::true_type {}; + +template +struct is_layout_right_padded : std::false_type {}; + +template +struct is_layout_right_padded> : std::true_type {}; + +template +struct is_layout_right_padded_mapping : std::false_type {}; + +template +struct is_layout_right_padded_mapping::template mapping>::value>> + : std::true_type {}; + + +template +MDSPAN_INLINE_FUNCTION +constexpr void check_padded_layout_converting_constructor_mandates(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<0>) {} + +template +MDSPAN_INLINE_FUNCTION +constexpr void check_padded_layout_converting_constructor_mandates(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<1>) {} + +template +MDSPAN_INLINE_FUNCTION +constexpr void check_padded_layout_converting_constructor_mandates(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank) +{ + using extents_type = typename PaddedLayoutMappingType::extents_type; + constexpr auto padding_value = PaddedLayoutMappingType::padding_value; + constexpr auto idx = layout_padded_constants::extent_to_pad_idx; + + constexpr auto statically_determinable = + (LayoutExtentsType::static_extent(idx) != dynamic_extent) && + (extents_type::static_extent(idx) != dynamic_extent) && + (padding_value != dynamic_extent); + + static_assert(!statically_determinable || + (padding_value == 0 + ? LayoutExtentsType::static_extent(idx) == 0 + : LayoutExtentsType::static_extent(idx) % padding_value == 0), + ""); +} + +template +MDSPAN_INLINE_FUNCTION +constexpr void check_padded_layout_converting_constructor_preconditions(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<0>, + const OtherMapping&) {} +template +MDSPAN_INLINE_FUNCTION +constexpr void check_padded_layout_converting_constructor_preconditions(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<1>, + const OtherMapping&) {} +template +MDSPAN_INLINE_FUNCTION +constexpr void check_padded_layout_converting_constructor_preconditions(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank, + const OtherMapping &other_mapping) { + constexpr auto padded_stride_idx = + layout_padded_constants::padded_stride_idx; + constexpr auto extent_to_pad_idx = layout_padded_constants::extent_to_pad_idx; + MDSPAN_IMPL_PRECONDITION(other_mapping.stride(padded_stride_idx) == other_mapping.extents().extent(extent_to_pad_idx)); +} + + +} +} diff --git a/src/experimental/mdarray b/src/experimental/mdarray new file mode 100644 index 00000000..b8177ecc --- /dev/null +++ b/src/experimental/mdarray @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#ifndef MDSPAN_IMPL_STANDARD_NAMESPACE + #define MDSPAN_IMPL_STANDARD_NAMESPACE std +#endif + +#ifndef MDSPAN_IMPL_PROPOSED_NAMESPACE + #define MDSPAN_IMPL_PROPOSED_NAMESPACE experimental +#endif + +#include "mdspan" +#include "../mdspan/mdarray.hpp" diff --git a/src/experimental/mdspan b/src/experimental/mdspan new file mode 100644 index 00000000..b4eb00ed --- /dev/null +++ b/src/experimental/mdspan @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#pragma once + +#ifndef MDSPAN_IMPL_STANDARD_NAMESPACE + #define MDSPAN_IMPL_STANDARD_NAMESPACE std +#endif + +#ifndef MDSPAN_IMPL_PROPOSED_NAMESPACE + #define MDSPAN_IMPL_PROPOSED_NAMESPACE experimental +#endif + +#include "../mdspan/mdspan.hpp" + +// backward compatibility import into experimental +namespace MDSPAN_IMPL_STANDARD_NAMESPACE { + namespace MDSPAN_IMPL_PROPOSED_NAMESPACE { + using ::MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan; + using ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents; + using ::MDSPAN_IMPL_STANDARD_NAMESPACE::layout_left; + using ::MDSPAN_IMPL_STANDARD_NAMESPACE::layout_right; + using ::MDSPAN_IMPL_STANDARD_NAMESPACE::layout_stride; + using ::MDSPAN_IMPL_STANDARD_NAMESPACE::default_accessor; + } +} diff --git a/src/mdspan/LICENSE b/src/mdspan/LICENSE new file mode 100644 index 00000000..7200d2f2 --- /dev/null +++ b/src/mdspan/LICENSE @@ -0,0 +1,228 @@ + ============================================================================== + Kokkos is under the Apache License v2.0 with LLVM Exceptions: + ============================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS Apache 2.0 + + ---- LLVM Exceptions to the Apache 2.0 License ---- + + As an exception, if, as a result of your compiling your source code, portions + of this Software are embedded into an Object form of such source code, you + may redistribute such embedded portions in such Object form without complying + with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + + In addition, if you combine or link compiled forms of this Software with + software that is licensed under the GPLv2 ("Combined Software") and if a + court of competent jurisdiction determines that the patent provision (Section + 3), the indemnity provision (Section 9) or other Section of the License + conflicts with the conditions of the GPLv2, you may retroactively and + prospectively choose to deem waived or otherwise exclude such Section(s) of + the License, but only in their entirety and only with respect to the Combined + Software. + + ============================================================================== + Software from third parties included in Kokkos: + ============================================================================== + + Kokkos contains third party software which is under different license + terms. All such code will be identified clearly using at least one of two + mechanisms: + 1) It will be in a separate directory tree with its own `LICENSE.txt` or + `LICENSE` file at the top containing the specific license and restrictions + which apply to that software, or + 2) It will contain specific license and restriction terms at the top of every + file. + + + THIS SOFTWARE IS PROVIDED BY NTESS "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NTESS OR THE + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Questions? Contact: + Christian R. Trott (crtrott@sandia.gov) and + Damien T. Lebrun-Grandie (lebrungrandt@ornl.gov) + + ************************************************************************ diff --git a/src/mdspan/mdarray.hpp b/src/mdspan/mdarray.hpp new file mode 100644 index 00000000..584605aa --- /dev/null +++ b/src/mdspan/mdarray.hpp @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#ifndef MDARRAY_HPP_ +#define MDARRAY_HPP_ + +#ifndef MDSPAN_IMPL_STANDARD_NAMESPACE + #define MDSPAN_IMPL_STANDARD_NAMESPACE Kokkos +#endif + +#ifndef MDSPAN_IMPL_PROPOSED_NAMESPACE + #define MDSPAN_IMPL_PROPOSED_NAMESPACE Experimental +#endif + +#include "mdspan.hpp" +#include "../experimental/__p1684_bits/mdarray.hpp" + +#endif // MDARRAY_HPP_ diff --git a/src/mdspan/mdspan.hpp b/src/mdspan/mdspan.hpp new file mode 100644 index 00000000..a437c423 --- /dev/null +++ b/src/mdspan/mdspan.hpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project + +#ifndef MDSPAN_HPP_ +#define MDSPAN_HPP_ + +#ifndef MDSPAN_IMPL_STANDARD_NAMESPACE + #define MDSPAN_IMPL_STANDARD_NAMESPACE Kokkos +#endif + +#ifndef MDSPAN_IMPL_PROPOSED_NAMESPACE + #define MDSPAN_IMPL_PROPOSED_NAMESPACE Experimental +#endif + +#include "../experimental/__p0009_bits/default_accessor.hpp" +#include "../experimental/__p0009_bits/full_extent_t.hpp" +#include "../experimental/__p0009_bits/mdspan.hpp" +#include "../experimental/__p0009_bits/dynamic_extent.hpp" +#include "../experimental/__p0009_bits/extents.hpp" +#include "../experimental/__p0009_bits/layout_stride.hpp" +#include "../experimental/__p0009_bits/layout_left.hpp" +#include "../experimental/__p0009_bits/layout_right.hpp" +#include "../experimental/__p0009_bits/macros.hpp" +#if MDSPAN_HAS_CXX_17 +#include "../experimental/__p2642_bits/layout_padded.hpp" +#include "../experimental/__p2630_bits/submdspan.hpp" +#endif +#include "../experimental/__p2389_bits/dims.hpp" + +#endif // MDSPAN_HPP_ From 2ae2a34e8562af626f1255a66b1e11b9cdbb13e9 Mon Sep 17 00:00:00 2001 From: AlexanderMitrofanov Date: Tue, 29 Sep 2026 19:52:42 +0200 Subject: [PATCH 6/8] Align matrix declarations and documentation with repository guidance --- AGENTS.md | 6 + ChangeLog | 9 +- src/IntaRNA/Matrix.h | 660 ++++++++++++++++++++++++++++++++++--------- 3 files changed, 537 insertions(+), 138 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 065b6f04..0803a4a2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,6 +33,12 @@ pkg-config and Autotools; OpenMP is used by the default multithreaded build. [conda-build-env.yml](conda-build-env.yml) supplies library/build dependencies, but does not select the C++ compiler. Use the platform setup in CI when needed. +Configure prefers native `std::mdspan` and otherwise uses the bundled Kokkos +headers. `INTARNA_USE_STD_MDSPAN` in the installed public configuration records +the choice; `--with-mdspan=std|kokkos` can select a backend explicitly. Preserve +the upstream headers and licenses in `src/mdspan` and `src/experimental` when +editing project code; their provenance is recorded in `doc/mdspan-storage.md`. + From the repository root, with dependencies available in standard locations: ```bash diff --git a/ChangeLog b/ChangeLog index c0c4c725..5a41fd17 100644 --- a/ChangeLog +++ b/ChangeLog @@ -26,7 +26,8 @@ - add repository-specific AI coding guidance in AGENTS.md (issue #247) - replace uBLAS storage matrices with std::vector and std::mdspan, retaining compact upper-band and triangular storage; configure selects native mdspan - or bundled Kokkos headers while preserving GCC 14 support + or bundled Kokkos headers while preserving GCC 14 support; document the + public matrix operations and align their layout with repository guidance - reduce temporary allocations and fix cleanup on error paths - remove redundant accessibility checks from predictor, helix and seed handling @@ -56,7 +57,7 @@ ################################################################################ ################################################################################ -260929 +260929 Alexander Mitrofanov * configure.ac, IntaRNA/Matrix.h, IntaRNA/intarna_config.h.in: + prefer native mdspan, falling back to bundled Kokkos with a public INTARNA_USE_STD_MDSPAN define; allow explicit --with-mdspan selection @@ -68,6 +69,10 @@ + restore GCC 14 release/debug builds and document automatic fallback + record a clean GCC 14/Kokkos comparison against uBLAS with all outputs matching across 160 executions; retain the original native measurements + * IntaRNA/Matrix.h, AGENTS.md: + + document public matrix operations and place explicit inline template + definitions after class declarations, following the repository guidance + + keep the guidance aligned with native/bundled mdspan selection (issue #246) 260928 * IntaRNA/Matrix.h and storage aliases: diff --git a/src/IntaRNA/Matrix.h b/src/IntaRNA/Matrix.h index 375a6d8b..94e9472d 100644 --- a/src/IntaRNA/Matrix.h +++ b/src/IntaRNA/Matrix.h @@ -46,57 +46,176 @@ inline std::size_t triangle(std::size_t n) { */ template class Matrix { + //! Owned contiguous storage. std::vector values; + //! Logical dimensions. std::size_t rows = 0, columns = 0; using Extents = matrix_detail::md::dextents; public: + //! Type of one stored cell. using value_type = T; + /** Construct an empty matrix. */ Matrix() = default; - Matrix(std::size_t rows, std::size_t columns, const T &value = T{}) - : values(matrix_detail::product(rows, columns), value), rows(rows), columns(columns) {} + /** + * Allocate a dense matrix initialized to one value. + * @param rows logical row count + * @param columns logical column count + * @param value initial value for each cell + * @throws std::length_error if the storage size overflows + */ + Matrix(std::size_t rows, std::size_t columns, const T &value = T{}); + /** Copy the shape and values into independent storage. */ Matrix(const Matrix &) = default; + /** Copy the shape and values into independent storage. + * @return this matrix + */ Matrix &operator=(const Matrix &) = default; - Matrix(Matrix &&other) noexcept : values(std::move(other.values)), - rows(std::exchange(other.rows, 0)), columns(std::exchange(other.columns, 0)) {} - Matrix &operator=(Matrix &&other) noexcept { - if (this != &other) { - Matrix moved(std::move(other)); - swap(moved); - } - return *this; - } - std::size_t size1() const noexcept { return rows; } - std::size_t size2() const noexcept { return columns; } - std::size_t storageSize() const noexcept { return values.size(); } - T &operator()(std::size_t i, std::size_t j) { - assert(i < rows && j < columns); - return matrix_detail::md::mdspan(values.data(), rows, columns)[i, j]; - } - const T &operator()(std::size_t i, std::size_t j) const { - assert(i < rows && j < columns); - return matrix_detail::md::mdspan(values.data(), rows, columns)[i, j]; - } - void clear() { std::fill(values.begin(), values.end(), T{}); } - void swap(Matrix &other) noexcept { - values.swap(other.values); - std::swap(rows, other.rows); - std::swap(columns, other.columns); + /** + * Take ownership of another matrix and leave it empty. + * @param other matrix whose storage is moved + */ + Matrix(Matrix &&other) noexcept; + /** + * Take ownership of another matrix; self-move leaves this matrix unchanged. + * @param other matrix whose storage is moved; emptied on a non-self move + * @return this matrix + */ + Matrix &operator=(Matrix &&other) noexcept; + /** + * Number of logical rows. + * @return logical row count + */ + std::size_t size1() const noexcept; + /** + * Number of logical columns. + * @return logical column count + */ + std::size_t size2() const noexcept; + /** + * Number of stored elements, including any band padding. + * @return stored element count + */ + std::size_t storageSize() const noexcept; + /** + * Access a cell within the logical shape. + * @param i zero-based row within the logical shape + * @param j zero-based column within the logical shape + * @return mutable reference to the cell + */ + T &operator()(std::size_t i, std::size_t j); + /** + * Access a cell within the logical shape. + * @param i zero-based row within the logical shape + * @param j zero-based column within the logical shape + * @return read-only reference to the cell + */ + const T &operator()(std::size_t i, std::size_t j) const; + /** + * Reset stored cells to default values without changing the shape. + */ + void clear(); + /** + * Exchange shape and owned storage. + * @param other matrix to exchange with + */ + void swap(Matrix &other) noexcept; + /** + * Resize, preserving overlapping logical cells when requested. + * @param newRows new row count + * @param newColumns new column count + * @param preserve retain overlapping cells and default-initialize new cells; + * otherwise callers must overwrite cells before reading + * @throws std::length_error if the storage size overflows + */ + void resize(std::size_t newRows, std::size_t newColumns, bool preserve = true); +}; + +template +inline +Matrix::Matrix(std::size_t rows, std::size_t columns, const T &value) + : values(matrix_detail::product(rows, columns), value), rows(rows), columns(columns) +{} + +template +inline +Matrix::Matrix(Matrix &&other) noexcept : values(std::move(other.values)), + rows(std::exchange(other.rows, 0)), columns(std::exchange(other.columns, 0)) +{} + +template +inline +Matrix &Matrix::operator=(Matrix &&other) noexcept +{ + if (this != &other) { + Matrix moved(std::move(other)); + swap(moved); } - void resize(std::size_t newRows, std::size_t newColumns, bool preserve = true) { - if (rows == newRows && columns == newColumns) return; - if (!preserve) { - values.resize(matrix_detail::product(newRows, newColumns)); - rows = newRows; - columns = newColumns; - return; - } - Matrix next(newRows, newColumns); - for (std::size_t i = 0; i < std::min(rows, newRows); ++i) - for (std::size_t j = 0; j < std::min(columns, newColumns); ++j) - next(i, j) = (*this)(i, j); - swap(next); + return *this; +} + +template +inline +std::size_t Matrix::size1() const noexcept +{ return rows; } + +template +inline +std::size_t Matrix::size2() const noexcept +{ return columns; } + +template +inline +std::size_t Matrix::storageSize() const noexcept +{ return values.size(); } + +template +inline +T &Matrix::operator()(std::size_t i, std::size_t j) +{ + assert(i < rows && j < columns); + return matrix_detail::md::mdspan(values.data(), rows, columns)[i, j]; +} + +template +inline +const T &Matrix::operator()(std::size_t i, std::size_t j) const +{ + assert(i < rows && j < columns); + return matrix_detail::md::mdspan(values.data(), rows, columns)[i, j]; +} + +template +inline +void Matrix::clear() +{ std::fill(values.begin(), values.end(), T{}); } + +template +inline +void Matrix::swap(Matrix &other) noexcept +{ + values.swap(other.values); + std::swap(rows, other.rows); + std::swap(columns, other.columns); +} + +template +inline +void Matrix::resize(std::size_t newRows, std::size_t newColumns, bool preserve) +{ + if (rows == newRows && columns == newColumns) return; + if (!preserve) { + values.resize(matrix_detail::product(newRows, newColumns)); + rows = newRows; + columns = newColumns; + return; } -}; + Matrix next(newRows, newColumns); + for (std::size_t i = 0; i < std::min(rows, newRows); ++i) + for (std::size_t j = 0; j < std::min(columns, newColumns); ++j) + next(i, j) = (*this)(i, j); + swap(next); +} + /** Square upper-triangular matrix with exactly n*(n+1)/2 stored cells. * Rows are packed consecutively. A one-dimensional mdspan addresses the @@ -105,68 +224,204 @@ class Matrix { */ template class UpperTriangularMatrix { + //! Owned contiguous storage. std::vector values; + //! Logical row and column count. std::size_t n = 0; using Extents = matrix_detail::md::dextents; - std::size_t offset(std::size_t i, std::size_t j) const noexcept { - const auto remaining = n - i; - // Constructor has already checked that every triangular size fits. - const auto tail = remaining % 2 == 0 - ? (remaining / 2) * (remaining + 1) : remaining * ((remaining + 1) / 2); - return values.size() - tail + j - i; - } - static std::size_t count(std::size_t rows, std::size_t columns) { - if (rows != columns) throw std::invalid_argument("upper-triangular matrix must be square"); - return matrix_detail::triangle(rows); - } + /** + * Offset of a stored upper-triangular cell in the packed allocation. + */ + std::size_t offset(std::size_t i, std::size_t j) const noexcept; + /** + * Validate a square shape and return its packed storage size. + */ + static std::size_t count(std::size_t rows, std::size_t columns); public: + //! Type of one stored cell. using value_type = T; + /** Construct an empty matrix. */ UpperTriangularMatrix() = default; - UpperTriangularMatrix(std::size_t rows, std::size_t columns) - : values(count(rows, columns)), n(rows) {} + /** + * Allocate a default-initialized square upper triangle. + * @param rows logical row count + * @param columns logical column count; must equal rows + * @throws std::invalid_argument if the shape is not square + * @throws std::length_error if the storage size overflows + */ + UpperTriangularMatrix(std::size_t rows, std::size_t columns); + /** Copy the shape and values into independent storage. */ UpperTriangularMatrix(const UpperTriangularMatrix &) = default; + /** Copy the shape and values into independent storage. + * @return this matrix + */ UpperTriangularMatrix &operator=(const UpperTriangularMatrix &) = default; - UpperTriangularMatrix(UpperTriangularMatrix &&other) noexcept - : values(std::move(other.values)), n(std::exchange(other.n, 0)) {} - UpperTriangularMatrix &operator=(UpperTriangularMatrix &&other) noexcept { - if (this != &other) { - UpperTriangularMatrix moved(std::move(other)); - swap(moved); - } - return *this; - } - std::size_t size1() const noexcept { return n; } - std::size_t size2() const noexcept { return n; } - std::size_t storageSize() const noexcept { return values.size(); } - T &operator()(std::size_t i, std::size_t j) { - assert(i <= j && j < n); - return matrix_detail::md::mdspan(values.data(), values.size())[offset(i, j)]; - } - const T &operator()(std::size_t i, std::size_t j) const { - assert(i < n && j < n); - static const T zero{}; - return i > j ? zero : matrix_detail::md::mdspan(values.data(), values.size())[offset(i, j)]; - } - void clear() { std::fill(values.begin(), values.end(), T{}); } - void swap(UpperTriangularMatrix &other) noexcept { - values.swap(other.values); - std::swap(n, other.n); + /** + * Take ownership of another matrix and leave it empty. + * @param other matrix whose storage is moved + */ + UpperTriangularMatrix(UpperTriangularMatrix &&other) noexcept; + /** + * Take ownership of another matrix; self-move leaves this matrix unchanged. + * @param other matrix whose storage is moved; emptied on a non-self move + * @return this matrix + */ + UpperTriangularMatrix &operator=(UpperTriangularMatrix &&other) noexcept; + /** + * Number of logical rows. + * @return logical row count + */ + std::size_t size1() const noexcept; + /** + * Number of logical columns. + * @return logical column count + */ + std::size_t size2() const noexcept; + /** + * Number of stored elements, including any band padding. + * @return stored element count + */ + std::size_t storageSize() const noexcept; + /** + * Access a cell with i <= j < size2(). + * @param i zero-based row within the logical shape + * @param j zero-based column within the logical shape + * @return mutable reference to the cell + */ + T &operator()(std::size_t i, std::size_t j); + /** + * Read a logical cell; structural zeros are returned outside stored cells. + * @param i zero-based row within the logical shape + * @param j zero-based column within the logical shape + * @return read-only reference to the cell + */ + const T &operator()(std::size_t i, std::size_t j) const; + /** + * Reset stored cells to default values without changing the shape. + */ + void clear(); + /** + * Exchange shape and owned storage. + * @param other matrix to exchange with + */ + void swap(UpperTriangularMatrix &other) noexcept; + /** + * Resize, preserving overlapping logical cells when requested. + * @param rows new row count + * @param columns new column count; must equal rows + * @param preserve retain overlapping cells and default-initialize new cells; + * otherwise callers must overwrite cells before reading + * @throws std::length_error if the storage size overflows + * @throws std::invalid_argument if the requested shape or band is unsupported + */ + void resize(std::size_t rows, std::size_t columns, bool preserve = true); +}; + +template +inline +std::size_t UpperTriangularMatrix::offset(std::size_t i, std::size_t j) const noexcept +{ + const auto remaining = n - i; + // Constructor has already checked that every triangular size fits. + const auto tail = remaining % 2 == 0 + ? (remaining / 2) * (remaining + 1) : remaining * ((remaining + 1) / 2); + return values.size() - tail + j - i; +} + +template +inline +std::size_t UpperTriangularMatrix::count(std::size_t rows, std::size_t columns) +{ + if (rows != columns) throw std::invalid_argument("upper-triangular matrix must be square"); + return matrix_detail::triangle(rows); +} + +template +inline +UpperTriangularMatrix::UpperTriangularMatrix(std::size_t rows, std::size_t columns) + : values(count(rows, columns)), n(rows) +{} + +template +inline +UpperTriangularMatrix::UpperTriangularMatrix(UpperTriangularMatrix &&other) noexcept + : values(std::move(other.values)), n(std::exchange(other.n, 0)) +{} + +template +inline +UpperTriangularMatrix &UpperTriangularMatrix::operator=(UpperTriangularMatrix &&other) noexcept +{ + if (this != &other) { + UpperTriangularMatrix moved(std::move(other)); + swap(moved); } - void resize(std::size_t rows, std::size_t columns, bool preserve = true) { - const auto cells = count(rows, columns); - if (rows == n) return; - if (!preserve) { - values.resize(cells); - n = rows; - return; - } - UpperTriangularMatrix next(rows, columns); - for (std::size_t i = 0; i < std::min(n, rows); ++i) - for (std::size_t j = i; j < std::min(n, rows); ++j) - next(i, j) = (*this)(i, j); - swap(next); + return *this; +} + +template +inline +std::size_t UpperTriangularMatrix::size1() const noexcept +{ return n; } + +template +inline +std::size_t UpperTriangularMatrix::size2() const noexcept +{ return n; } + +template +inline +std::size_t UpperTriangularMatrix::storageSize() const noexcept +{ return values.size(); } + +template +inline +T &UpperTriangularMatrix::operator()(std::size_t i, std::size_t j) +{ + assert(i <= j && j < n); + return matrix_detail::md::mdspan(values.data(), values.size())[offset(i, j)]; +} + +template +inline +const T &UpperTriangularMatrix::operator()(std::size_t i, std::size_t j) const +{ + assert(i < n && j < n); + static const T zero{}; + return i > j ? zero : matrix_detail::md::mdspan(values.data(), values.size())[offset(i, j)]; +} + +template +inline +void UpperTriangularMatrix::clear() +{ std::fill(values.begin(), values.end(), T{}); } + +template +inline +void UpperTriangularMatrix::swap(UpperTriangularMatrix &other) noexcept +{ + values.swap(other.values); + std::swap(n, other.n); +} + +template +inline +void UpperTriangularMatrix::resize(std::size_t rows, std::size_t columns, bool preserve) +{ + const auto cells = count(rows, columns); + if (rows == n) return; + if (!preserve) { + values.resize(cells); + n = rows; + return; } -}; + UpperTriangularMatrix next(rows, columns); + for (std::size_t i = 0; i < std::min(n, rows); ++i) + for (std::size_t j = i; j < std::min(n, rows); ++j) + next(i, j) = (*this)(i, j); + swap(next); +} + /** Upper band including the diagonal and 'upper' superdiagonals. * Stores rows * min(columns, upper+1) cells, never a dense square for a @@ -175,58 +430,191 @@ class UpperTriangularMatrix { */ template class UpperBandedMatrix { + //! Physical rows of the upper band, including row-end padding. Matrix band; + //! Logical column count, independent of stored band width. std::size_t columns = 0; - static std::size_t width(std::size_t columns, std::size_t lower, std::size_t upper) { - if (lower != 0) throw std::invalid_argument("upper-banded matrix requires lower=0"); - return upper >= columns ? columns : upper + 1; - } + /** + * Validate the lower bandwidth and clamp the stored width to the columns. + */ + static std::size_t width(std::size_t columns, std::size_t lower, std::size_t upper); public: + //! Type of one stored cell. using value_type = T; + /** Construct an empty matrix. */ UpperBandedMatrix() = default; - UpperBandedMatrix(std::size_t rows, std::size_t columns, std::size_t lower, std::size_t upper) - : band(rows, width(columns, lower, upper)), columns(columns) {} + /** + * Allocate a default-initialized upper band. + * @param rows logical row count + * @param columns logical column count + * @param lower lower bandwidth; must be zero + * @param upper number of stored superdiagonals + * @throws std::invalid_argument if lower is not zero + * @throws std::length_error if the storage size overflows + */ + UpperBandedMatrix(std::size_t rows, std::size_t columns, std::size_t lower, std::size_t upper); + /** Copy the shape and values into independent storage. */ UpperBandedMatrix(const UpperBandedMatrix &) = default; + /** Copy the shape and values into independent storage. + * @return this matrix + */ UpperBandedMatrix &operator=(const UpperBandedMatrix &) = default; - UpperBandedMatrix(UpperBandedMatrix &&other) noexcept - : band(std::move(other.band)), columns(std::exchange(other.columns, 0)) {} - UpperBandedMatrix &operator=(UpperBandedMatrix &&other) noexcept { - if (this != &other) { - UpperBandedMatrix moved(std::move(other)); - swap(moved); - } - return *this; - } - std::size_t size1() const noexcept { return band.size1(); } - std::size_t size2() const noexcept { return columns; } - std::size_t storageSize() const noexcept { return band.storageSize(); } - T &operator()(std::size_t i, std::size_t j) { - assert(i < size1() && j < columns && i <= j && j - i < band.size2()); - return band(i, j - i); - } - const T &operator()(std::size_t i, std::size_t j) const { - assert(i < size1() && j < columns); - static const T zero{}; - return i > j || j - i >= band.size2() ? zero : band(i, j - i); - } - void clear() { band.clear(); } - void swap(UpperBandedMatrix &other) noexcept { - band.swap(other.band); - std::swap(columns, other.columns); - } + /** + * Take ownership of another matrix and leave it empty. + * @param other matrix whose storage is moved + */ + UpperBandedMatrix(UpperBandedMatrix &&other) noexcept; + /** + * Take ownership of another matrix; self-move leaves this matrix unchanged. + * @param other matrix whose storage is moved; emptied on a non-self move + * @return this matrix + */ + UpperBandedMatrix &operator=(UpperBandedMatrix &&other) noexcept; + /** + * Number of logical rows. + * @return logical row count + */ + std::size_t size1() const noexcept; + /** + * Number of logical columns. + * @return logical column count + */ + std::size_t size2() const noexcept; + /** + * Number of stored elements, including any band padding. + * @return stored element count + */ + std::size_t storageSize() const noexcept; + /** + * Access a cell within the stored upper band. + * @param i zero-based row within the logical shape + * @param j zero-based column within the logical shape + * @return mutable reference to the cell + */ + T &operator()(std::size_t i, std::size_t j); + /** + * Read a logical cell; structural zeros are returned outside stored cells. + * @param i zero-based row within the logical shape + * @param j zero-based column within the logical shape + * @return read-only reference to the cell + */ + const T &operator()(std::size_t i, std::size_t j) const; + /** + * Reset stored cells to default values without changing the shape. + */ + void clear(); + /** + * Exchange shape and owned storage. + * @param other matrix to exchange with + */ + void swap(UpperBandedMatrix &other) noexcept; + /** + * Resize, preserving overlapping logical cells when requested. + * @param rows new row count + * @param newColumns new column count + * @param lower lower bandwidth; must be zero + * @param upper number of stored superdiagonals + * @param preserve retain overlapping cells and default-initialize new cells; + * otherwise callers must overwrite cells before reading + * @throws std::length_error if the storage size overflows + * @throws std::invalid_argument if the requested shape or band is unsupported + */ void resize(std::size_t rows, std::size_t newColumns, std::size_t lower, - std::size_t upper, bool preserve = true) { - UpperBandedMatrix next(rows, newColumns, lower, upper); - if (preserve) { - for (std::size_t i = 0; i < std::min(size1(), rows); ++i) - for (std::size_t d = 0; d < std::min(band.size2(), next.band.size2()) - && i < std::min(columns, newColumns) - && d < std::min(columns, newColumns) - i; ++d) - next.band(i, d) = band(i, d); - } - swap(next); - } + std::size_t upper, bool preserve = true); }; +template +inline +std::size_t UpperBandedMatrix::width(std::size_t columns, std::size_t lower, std::size_t upper) +{ + if (lower != 0) throw std::invalid_argument("upper-banded matrix requires lower=0"); + return upper >= columns ? columns : upper + 1; +} + +template +inline +UpperBandedMatrix::UpperBandedMatrix(std::size_t rows, std::size_t columns, std::size_t lower, std::size_t upper) + : band(rows, width(columns, lower, upper)), columns(columns) +{} + +template +inline +UpperBandedMatrix::UpperBandedMatrix(UpperBandedMatrix &&other) noexcept + : band(std::move(other.band)), columns(std::exchange(other.columns, 0)) +{} + +template +inline +UpperBandedMatrix &UpperBandedMatrix::operator=(UpperBandedMatrix &&other) noexcept +{ + if (this != &other) { + UpperBandedMatrix moved(std::move(other)); + swap(moved); + } + return *this; +} + +template +inline +std::size_t UpperBandedMatrix::size1() const noexcept +{ return band.size1(); } + +template +inline +std::size_t UpperBandedMatrix::size2() const noexcept +{ return columns; } + +template +inline +std::size_t UpperBandedMatrix::storageSize() const noexcept +{ return band.storageSize(); } + +template +inline +T &UpperBandedMatrix::operator()(std::size_t i, std::size_t j) +{ + assert(i < size1() && j < columns && i <= j && j - i < band.size2()); + return band(i, j - i); +} + +template +inline +const T &UpperBandedMatrix::operator()(std::size_t i, std::size_t j) const +{ + assert(i < size1() && j < columns); + static const T zero{}; + return i > j || j - i >= band.size2() ? zero : band(i, j - i); +} + +template +inline +void UpperBandedMatrix::clear() +{ band.clear(); } + +template +inline +void UpperBandedMatrix::swap(UpperBandedMatrix &other) noexcept +{ + band.swap(other.band); + std::swap(columns, other.columns); +} + +template +inline +void UpperBandedMatrix::resize(std::size_t rows, std::size_t newColumns, std::size_t lower, + std::size_t upper, bool preserve) +{ + UpperBandedMatrix next(rows, newColumns, lower, upper); + if (preserve) { + for (std::size_t i = 0; i < std::min(size1(), rows); ++i) + for (std::size_t d = 0; d < std::min(band.size2(), next.band.size2()) + && i < std::min(columns, newColumns) + && d < std::min(columns, newColumns) - i; ++d) + next.band(i, d) = band(i, d); + } + swap(next); +} + + } // namespace IntaRNA #endif From 841936561291f0c478036e92a1e1ae9a6231e4fd Mon Sep 17 00:00:00 2001 From: Martin Raden Date: Wed, 30 Sep 2026 10:23:12 +0200 Subject: [PATCH 7/8] cleanup --- doc/Makefile.am | 5 - doc/handson/README.md | 4 +- .../mdspan-storage-gcc14-samples.tsv | 0 doc/{ => refactor}/mdspan-storage-samples.tsv | 0 doc/{ => refactor}/mdspan-storage.md | 0 tests/Makefile.am | 3 +- tests/benchmark/compare-matrix-storage.py | 148 ------------------ .../benchmark/test_compare_matrix_storage.py | 97 ------------ 8 files changed, 3 insertions(+), 254 deletions(-) rename doc/{ => refactor}/mdspan-storage-gcc14-samples.tsv (100%) rename doc/{ => refactor}/mdspan-storage-samples.tsv (100%) rename doc/{ => refactor}/mdspan-storage.md (100%) delete mode 100644 tests/benchmark/compare-matrix-storage.py delete mode 100644 tests/benchmark/test_compare_matrix_storage.py diff --git a/doc/Makefile.am b/doc/Makefile.am index 854a9e2b..322e9d93 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -4,11 +4,6 @@ ################################################################ EXTRA_DIST = \ - mdspan-storage.md \ - mdspan-storage-samples.tsv \ - mdspan-storage-gcc14-samples.tsv \ - handson/fhlA.fasta \ - handson/OxyS.fasta \ conda.txt \ doxygen.cfg \ latex-deps/adjcalc.sty \ diff --git a/doc/handson/README.md b/doc/handson/README.md index 67288c7f..913cfae9 100644 --- a/doc/handson/README.md +++ b/doc/handson/README.md @@ -5,9 +5,9 @@ In the following, we list some examples how to use IntaRNA in specific applicati The examples are presented in detail in our publication - [How to do RNA-RNA interaction prediction? A use-case driven -handbook using IntaRNA](http://www.bioinf.uni-freiburg.de/Subpages/publications.html?de#Raden-IntaRNA-handson.abstract) +handbook using IntaRNA](https://doi.org/10.1007/978-1-0716-3519-3_9) - Martin Raden and Milad Miladi - - Springer (in press, DOI to come) + - In: Lorenz, R. (eds) RNA Folding. Methods in Molecular Biology, vol 2726. Humana, New York, NY. https://doi.org/10.1007/978-1-0716-3519-3_9 diff --git a/doc/mdspan-storage-gcc14-samples.tsv b/doc/refactor/mdspan-storage-gcc14-samples.tsv similarity index 100% rename from doc/mdspan-storage-gcc14-samples.tsv rename to doc/refactor/mdspan-storage-gcc14-samples.tsv diff --git a/doc/mdspan-storage-samples.tsv b/doc/refactor/mdspan-storage-samples.tsv similarity index 100% rename from doc/mdspan-storage-samples.tsv rename to doc/refactor/mdspan-storage-samples.tsv diff --git a/doc/mdspan-storage.md b/doc/refactor/mdspan-storage.md similarity index 100% rename from doc/mdspan-storage.md rename to doc/refactor/mdspan-storage.md diff --git a/tests/Makefile.am b/tests/Makefile.am index d890f212..400c30d9 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -72,5 +72,4 @@ runApiTests_CXXFLAGS = -I$(top_builddir)/src \ @AM_CXXFLAGS@ @CXXFLAGS@ \ -DELPP_NO_LOG_TO_FILE -# Reproducible before/after storage performance comparison (not a test gate). -EXTRA_DIST = benchmark/compare-matrix-storage.py benchmark/test_compare_matrix_storage.py + diff --git a/tests/benchmark/compare-matrix-storage.py b/tests/benchmark/compare-matrix-storage.py deleted file mode 100644 index d91909b8..00000000 --- a/tests/benchmark/compare-matrix-storage.py +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env python3 -"""Compare two identically built IntaRNA binaries; save every sample and output. - -Requires Python 3 and Linux /usr/bin/time. No optional Python packages. -Run after builds/tests finish, on an otherwise idle machine. -""" -import argparse -import hashlib -import json -import os -from pathlib import Path -import platform -import random -import signal -import statistics -import subprocess -import time - - -def sha(data): - return hashlib.sha256(data).hexdigest() - - -def run_timed(command, env, timeout=180): - """Bound the complete process group, including GNU time's child process.""" - with subprocess.Popen(command, env=env, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, start_new_session=True) as process: - try: - stdout, stderr = process.communicate(timeout=timeout) - except BaseException as error: - # The child is in a separate session, so terminal interrupts must - # also trigger explicit cleanup of the complete process group. - try: - os.killpg(process.pid, signal.SIGKILL) - except ProcessLookupError: - pass - stdout, stderr = process.communicate() - if isinstance(error, subprocess.TimeoutExpired): - raise subprocess.TimeoutExpired(command, timeout, output=stdout, - stderr=stderr) from error - raise - return subprocess.CompletedProcess(command, process.returncode, stdout, stderr) - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("baseline", type=Path) - parser.add_argument("candidate", type=Path) - parser.add_argument("output", type=Path) - parser.add_argument("--repetitions", type=int, default=7) - parser.add_argument("--cpu", type=int) - args = parser.parse_args() - if args.repetitions < 1: - parser.error("repetitions must be positive") - if args.cpu is not None: - os.sched_setaffinity(0, {args.cpu}) - out = args.output.resolve() - out.mkdir(parents=True, exist_ok=False) - binaries = {"baseline": args.baseline.resolve(), "candidate": args.candidate.resolve()} - env = dict(os.environ, LC_ALL="C", OPENBLAS_NUM_THREADS="1", OMP_DYNAMIC="FALSE") - root = Path(__file__).resolve().parents[2] - rng = random.Random(246) - target = "".join(rng.choices("ACGU", k=1000)) - query = "".join(rng.choices("ACGU", k=120)) - inputs = {} - for name, seq in [("t1000", target), ("t300", target[:300]), ("t80", target[:80]), - ("q120", query), ("q80", query[:80])]: - path = out / (name + ".fa") - path.write_text(">" + name + "\n" + seq + "\n") - inputs[name] = path - for name in ("fhlA", "OxyS"): - path = out / (name + ".fa") - path.write_bytes((root / "doc/handson" / (name + ".fasta")).read_bytes()) - inputs[name] = path - common = ["--threads=1", "--outMode=C", "--outCsvCols=id1,id2,start1,end1,start2,end2,E,bpList", - "--default-log-file=/dev/null"] - cases = [] - - def case(name, target_name, query_name, *options): - cases.append({"name": name, "args": ["--target=" + str(inputs[target_name]), - "--query=" + str(inputs[query_name]), *common, *options]}) - - case("biological-default", "fhlA", "OxyS") - case("banded-default", "t1000", "q120") - case("banded-narrow", "t1000", "q120", "--accL=30", "--accW=100") - case("dense-no-accessibility", "t1000", "q120", "--acc=N") - case("seed-bulges", "t300", "q120", "--seedMaxUP=2") - case("helix-block", "t300", "q120", "--model=B") - case("exact-mfe", "t80", "q80", "--mode=M", "--model=S", "--noSeed", "--intLenMax=30") - case("exact-ensemble", "t80", "q80", "--mode=M", "--model=P", "--noSeed", "--intLenMax=30") - case("seed-extension", "t300", "q120", "--mode=H", "--model=P", "--intLenMax=40") - case("triangular-base-pair", "t300", "q120", "--energy=B") - metadata = {"platform": platform.platform(), "affinity": sorted(os.sched_getaffinity(0)), - "repetitions": args.repetitions, "warmups": 1, "seed": 246, - "binaries": {name: {"path": str(path), "sha256": sha(path.read_bytes()), - "version": subprocess.check_output([str(path), "--version"], env=env).decode()} - for name, path in binaries.items()}, - "inputs": {name: sha(path.read_bytes()) for name, path in inputs.items()}, - "cases": cases, "environment": {key: env[key] for key in ("LC_ALL", "OPENBLAS_NUM_THREADS", "OMP_DYNAMIC")}} - (out / "metadata.json").write_text(json.dumps(metadata, indent=2) + "\n") - expected = {} - samples = [] - for repetition in range(args.repetitions + 1): - schedule = [(case, variant) for case in cases for variant in binaries] - rng.shuffle(schedule) - for current, variant in schedule: - stem = out / f"{current['name']}.{variant}.{repetition}" - command = ["/usr/bin/time", "-f", "%U %S %M", "-o", str(stem) + ".time", - str(binaries[variant]), *current["args"]] - started = time.perf_counter() - try: - result = run_timed(command, env=env) - except subprocess.TimeoutExpired as error: - Path(str(stem) + ".stdout").write_bytes(error.output or b"") - Path(str(stem) + ".stderr").write_bytes(error.stderr or b"") - raise RuntimeError(f"{stem.name} timed out after {error.timeout}s") from error - wall = time.perf_counter() - started - Path(str(stem) + ".stdout").write_bytes(result.stdout) - Path(str(stem) + ".stderr").write_bytes(result.stderr) - if result.returncode: - raise RuntimeError(f"{stem.name} failed: {result.stderr.decode()}") - digest = sha(result.stdout) - if digest != expected.setdefault(current["name"], digest): - raise RuntimeError(f"output mismatch: {stem.name}") - user, system, rss = map(float, Path(str(stem) + ".time").read_text().split()) - row = {"case": current["name"], "variant": variant, "repetition": repetition, - "warmup": repetition == 0, "wall_s": wall, "user_s": user, "system_s": system, - "max_rss_kib": int(rss), "sha256": digest} - samples.append(row) - with (out / "samples.jsonl").open("a") as handle: - handle.write(json.dumps(row) + "\n") - print(f"Completed round {repetition}/{args.repetitions}", flush=True) - summary = [] - for current in cases: - row = {"case": current["name"], "output_identical": True} - for variant in binaries: - selected = [x for x in samples if not x["warmup"] and x["case"] == current["name"] and x["variant"] == variant] - row[variant] = {key: statistics.median(x[key] for x in selected) for key in ("wall_s", "user_s", "max_rss_kib")} - row[variant]["wall_min_s"] = min(x["wall_s"] for x in selected) - row[variant]["wall_max_s"] = max(x["wall_s"] for x in selected) - row["wall_change_percent"] = 100 * (row["candidate"]["wall_s"] / row["baseline"]["wall_s"] - 1) - summary.append(row) - (out / "summary.json").write_text(json.dumps(summary, indent=2) + "\n") - print(json.dumps(summary, indent=2)) - - -if __name__ == "__main__": - main() diff --git a/tests/benchmark/test_compare_matrix_storage.py b/tests/benchmark/test_compare_matrix_storage.py deleted file mode 100644 index e64d1055..00000000 --- a/tests/benchmark/test_compare_matrix_storage.py +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env python3 -"""Linux regression checks for the benchmark's process-group timeout.""" -import importlib.util -import os -from pathlib import Path -import signal -import subprocess -import sys -import tempfile -import time -import unittest -from unittest.mock import patch - -sys.dont_write_bytecode = True -spec = importlib.util.spec_from_file_location( - "benchmark", Path(__file__).with_name("compare-matrix-storage.py")) -benchmark = importlib.util.module_from_spec(spec) -spec.loader.exec_module(benchmark) - - -class BenchmarkProcessTests(unittest.TestCase): - def assert_child_stopped(self, pid_file): - self.assertTrue(pid_file.exists(), "child must start before testing cleanup") - pid = int(pid_file.read_text()) - try: - state = Path(f"/proc/{pid}/stat").read_text().rsplit(")", 1)[1].split()[0] - except FileNotFoundError: - state = "gone" - self.assertIn(state, ("gone", "Z", "X"), "child is still running") - - def test_capture_exit_status_and_output(self): - result = benchmark.run_timed( - [sys.executable, "-c", "import sys; print('output'); print('error', file=sys.stderr); sys.exit(7)"], - os.environ, timeout=5) - self.assertEqual(result.returncode, 7) - self.assertEqual(result.stdout, b"output\n") - self.assertEqual(result.stderr, b"error\n") - - @unittest.skipUnless(sys.platform.startswith("linux"), "uses Linux /proc and GNU time") - def test_timeout_kills_child_of_time_wrapper(self): - with tempfile.TemporaryDirectory() as tmp: - pid_file = Path(tmp) / "child.pid" - child = ("import os,pathlib,time; pathlib.Path(" + repr(str(pid_file)) + - ").write_text(str(os.getpid())); print('started', flush=True); time.sleep(60)") - started = time.monotonic() - try: - with self.assertRaises(subprocess.TimeoutExpired) as caught: - benchmark.run_timed(["/usr/bin/time", sys.executable, "-c", child], - os.environ, timeout=2) - self.assertLess(time.monotonic() - started, 10) - self.assertIn(b"started", caught.exception.output) - self.assert_child_stopped(pid_file) - finally: - # Keep the regression test safe even against a broken runner. - if pid_file.exists(): - try: - os.kill(int(pid_file.read_text()), signal.SIGKILL) - except ProcessLookupError: - pass - - @unittest.skipUnless(sys.platform.startswith("linux"), "uses Linux /proc and GNU time") - def test_interrupt_kills_child_of_time_wrapper(self): - with tempfile.TemporaryDirectory() as tmp: - pid_file = Path(tmp) / "child.pid" - child = ("import os,pathlib,time; pathlib.Path(" + repr(str(pid_file)) + - ").write_text(str(os.getpid())); time.sleep(60)") - communicate = subprocess.Popen.communicate - interrupted = False - - def interrupt_once(process, *args, **kwargs): - nonlocal interrupted - if not interrupted: - interrupted = True - try: - communicate(process, timeout=2) - except subprocess.TimeoutExpired: - raise KeyboardInterrupt from None - return communicate(process, *args, **kwargs) - - started = time.monotonic() - try: - with patch.object(subprocess.Popen, "communicate", interrupt_once): - with self.assertRaises(KeyboardInterrupt): - benchmark.run_timed(["/usr/bin/time", sys.executable, "-c", child], - os.environ, timeout=5) - self.assertLess(time.monotonic() - started, 10) - self.assert_child_stopped(pid_file) - finally: - if pid_file.exists(): - try: - os.kill(int(pid_file.read_text()), signal.SIGKILL) - except ProcessLookupError: - pass - - -if __name__ == "__main__": - unittest.main() From 9e2ff4bddb4f2a30064681e68abe545d6bd70066 Mon Sep 17 00:00:00 2001 From: Martin Raden Date: Wed, 30 Sep 2026 10:27:28 +0200 Subject: [PATCH 8/8] remove benchmark from action --- .github/workflows/build.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bb437f12..31f22a56 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -84,10 +84,6 @@ jobs: "$CXX" --version "$CXX" --version | grep -q "Apple clang" - - name: Test benchmark process cleanup - if: matrix.compiler == 'gcc' - run: python3 tests/benchmark/test_compare_matrix_storage.py - - name: Bootstrap and configure run: | bash autotools-init.sh