[llvm-branch-commits] [clang] [clang-tools-extra] [compiler-rt] [libc] [libcxx] [lld] [lldb] [llvm] [mlir] [mlir][transform] Drop redundant padding_dimensions spec from pad_tiling_interface (PR #145257)
Nicolas Vasilache via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sun Jun 22 20:54:25 PDT 2025
https://github.com/nicolasvasilache created https://github.com/llvm/llvm-project/pull/145257
This revision aligns padding specification in pad_tiling_interface to that of tiling specification.
Dimensions that should be skipped are specified by "padding by 0".
Trailing dimensions that are ignored are automatically completed to "pad to 0".
>From 1bb2328fd3adf137cb32af4e3722a1b3e8a53a8e Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Sat, 21 Jun 2025 11:54:50 +0200
Subject: [PATCH 01/40] [libc++] Implement `views::join_with` (#65536)
* Implement "P2441R2 `views::join_with`" (https://wg21.link/P2441R2),
closes #105185
* Implement LWG4074 (https://wg21.link/LWG4074), closes #105346
* Complete implementation of "P2711R1 Making multi-param constructors of
views explicit" (https://wg21.link/P2711R1), closes #105252
* Complete implementation of "P2770R0 Stashing stashing iterators for
proper flattening" (https://wg21.link/P2770R0), closes #105250
---
libcxx/docs/FeatureTestMacroTable.rst | 2 +-
libcxx/docs/ReleaseNotes/21.rst | 3 +
libcxx/docs/Status/Cxx23Papers.csv | 6 +-
libcxx/docs/Status/Cxx2cIssues.csv | 2 +-
libcxx/include/CMakeLists.txt | 1 +
libcxx/include/__ranges/concepts.h | 40 ++
libcxx/include/__ranges/join_with_view.h | 460 ++++++++++++++++++
libcxx/include/module.modulemap.in | 1 +
libcxx/include/ranges | 10 +
libcxx/include/version | 2 +-
libcxx/modules/std/ranges.inc | 7 +-
.../deref.nodiscard.verify.cpp | 28 ++
.../eq.nodiscard.verify.cpp | 30 ++
.../iter_move.nodiscard.verify.cpp | 28 ++
.../no_unique_address.compile.pass.cpp | 56 +++
.../adaptor.nodiscard.verify.cpp | 33 ++
.../eq.nodiscard.verify.cpp | 35 ++
.../no_unique_address.compile.pass.cpp | 48 ++
.../base.nodiscard.verify.cpp | 30 ++
.../begin.nodiscard.verify.cpp | 28 ++
.../end.nodiscard.verify.cpp | 28 ++
.../no_unique_address.compile.pass.cpp | 47 ++
.../ranges.version.compile.pass.cpp | 32 +-
.../version.version.compile.pass.cpp | 32 +-
.../ctor.default.pass.cpp | 79 +++
.../ctor.not_const.pass.cpp | 111 +++++
.../decrement.pass.cpp | 283 +++++++++++
.../range.join.with.iterator/deref.pass.cpp | 225 +++++++++
.../range.join.with.iterator/eq.pass.cpp | 259 ++++++++++
.../increment.pass.cpp | 372 ++++++++++++++
.../iter_move.pass.cpp | 420 ++++++++++++++++
.../iter_swap.pass.cpp | 186 +++++++
.../types.compile.pass.cpp | 456 +++++++++++++++++
.../range.join.with.overview/adaptor.pass.cpp | 360 ++++++++++++++
.../range.join.with.overview/example.pass.cpp | 42 ++
.../ctor.default.pass.cpp | 37 ++
.../ctor.non_const.pass.cpp | 74 +++
.../range.join.with.sentinel/eq.pass.cpp | 109 +++++
.../range.join.with.view/base.pass.cpp | 132 +++++
.../range.join.with.view/begin.pass.cpp | 221 +++++++++
.../constraints.compile.pass.cpp | 289 +++++++++++
.../ctad.compile.pass.cpp | 230 +++++++++
.../ctor.default.pass.cpp | 77 +++
.../ctor.range.element.pass.cpp | 244 ++++++++++
.../ctor.range.pattern.pass.cpp | 111 +++++
.../range.join.with.view/end.pass.cpp | 232 +++++++++
.../inheritance.compile.pass.cpp | 38 ++
.../range.adaptors/range.join.with/types.h | 319 ++++++++++++
.../generate_feature_test_macro_components.py | 1 -
49 files changed, 5843 insertions(+), 53 deletions(-)
create mode 100644 libcxx/include/__ranges/join_with_view.h
create mode 100644 libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.iterator/deref.nodiscard.verify.cpp
create mode 100644 libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.iterator/eq.nodiscard.verify.cpp
create mode 100644 libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.iterator/iter_move.nodiscard.verify.cpp
create mode 100644 libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.iterator/no_unique_address.compile.pass.cpp
create mode 100644 libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.overview/adaptor.nodiscard.verify.cpp
create mode 100644 libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.sentinel/eq.nodiscard.verify.cpp
create mode 100644 libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.sentinel/no_unique_address.compile.pass.cpp
create mode 100644 libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.view/base.nodiscard.verify.cpp
create mode 100644 libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.view/begin.nodiscard.verify.cpp
create mode 100644 libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.view/end.nodiscard.verify.cpp
create mode 100644 libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.view/no_unique_address.compile.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/ctor.default.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/ctor.not_const.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/decrement.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/deref.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/eq.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/increment.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/iter_move.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/iter_swap.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/types.compile.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.overview/adaptor.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.overview/example.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.sentinel/ctor.default.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.sentinel/ctor.non_const.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.sentinel/eq.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/base.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/begin.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/constraints.compile.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctad.compile.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctor.default.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctor.range.element.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctor.range.pattern.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/end.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/inheritance.compile.pass.cpp
create mode 100644 libcxx/test/std/ranges/range.adaptors/range.join.with/types.h
diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index 5ebc9bb7dcda2..3c635e5e46bbd 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -376,7 +376,7 @@ Status
---------------------------------------------------------- -----------------
``__cpp_lib_ranges_iota`` ``202202L``
---------------------------------------------------------- -----------------
- ``__cpp_lib_ranges_join_with`` *unimplemented*
+ ``__cpp_lib_ranges_join_with`` ``202202L``
---------------------------------------------------------- -----------------
``__cpp_lib_ranges_repeat`` ``202207L``
---------------------------------------------------------- -----------------
diff --git a/libcxx/docs/ReleaseNotes/21.rst b/libcxx/docs/ReleaseNotes/21.rst
index 8661e5898fbc0..7e8570691200b 100644
--- a/libcxx/docs/ReleaseNotes/21.rst
+++ b/libcxx/docs/ReleaseNotes/21.rst
@@ -47,6 +47,9 @@ Implemented Papers
- P1222R4: A Standard ``flat_set`` (`Github <https://github.com/llvm/llvm-project/issues/105193>`__)
- P2897R7: ``aligned_accessor``: An mdspan accessor expressing pointer over-alignment (`Github <https://github.com/llvm/llvm-project/issues/118372>`__)
- P3247R2: Deprecate the notion of trivial types (`Github <https://github.com/llvm/llvm-project/issues/118387>`__)
+- P2441R2: ``views::join_with`` (`Github <https://github.com/llvm/llvm-project/issues/105185>`__)
+- P2711R1: Making multi-param constructors of ``views`` ``explicit`` (`Github <https://github.com/llvm/llvm-project/issues/105252>`__)
+- P2770R0: Stashing stashing ``iterators`` for proper flattening (`Github <https://github.com/llvm/llvm-project/issues/105250>`__)
Improvements and New Features
-----------------------------
diff --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv
index c26363bcda796..574675175a4cf 100644
--- a/libcxx/docs/Status/Cxx23Papers.csv
+++ b/libcxx/docs/Status/Cxx23Papers.csv
@@ -47,7 +47,7 @@
"`P2273R3 <https://wg21.link/P2273R3>`__","Making ``std::unique_ptr`` constexpr","2022-02 (Virtual)","|Complete|","16",""
"`P2387R3 <https://wg21.link/P2387R3>`__","Pipe support for user-defined range adaptors","2022-02 (Virtual)","|Complete|","19",""
"`P2440R1 <https://wg21.link/P2440R1>`__","``ranges::iota``, ``ranges::shift_left`` and ``ranges::shift_right``","2022-02 (Virtual)","|Partial|","","Only ``ranges::iota`` is implemented."
-"`P2441R2 <https://wg21.link/P2441R2>`__","``views::join_with``","2022-02 (Virtual)","|In Progress|","",""
+"`P2441R2 <https://wg21.link/P2441R2>`__","``views::join_with``","2022-02 (Virtual)","|Complete|","21",""
"`P2442R1 <https://wg21.link/P2442R1>`__","Windowing range adaptors: ``views::chunk`` and ``views::slide``","2022-02 (Virtual)","","",""
"`P2443R1 <https://wg21.link/P2443R1>`__","``views::chunk_by``","2022-02 (Virtual)","|Complete|","18",""
"","","","","",""
@@ -103,9 +103,9 @@
"`P2708R1 <https://wg21.link/P2708R1>`__","No Further Fundamentals TSes","2022-11 (Kona)","|Nothing To Do|","",""
"","","","","",""
"`P0290R4 <https://wg21.link/P0290R4>`__","``apply()`` for ``synchronized_value<T>``","2023-02 (Issaquah)","","",""
-"`P2770R0 <https://wg21.link/P2770R0>`__","Stashing stashing ``iterators`` for proper flattening","2023-02 (Issaquah)","|Partial|","","``join_with_view`` hasn't been done yet since this type isn't implemented yet"
+"`P2770R0 <https://wg21.link/P2770R0>`__","Stashing stashing ``iterators`` for proper flattening","2023-02 (Issaquah)","|Complete|","21",""
"`P2164R9 <https://wg21.link/P2164R9>`__","``views::enumerate``","2023-02 (Issaquah)","","",""
-"`P2711R1 <https://wg21.link/P2711R1>`__","Making multi-param constructors of ``views`` ``explicit``","2023-02 (Issaquah)","|In Progress|","","``join_with_view`` hasn't been done yet since this type isn't implemented yet"
+"`P2711R1 <https://wg21.link/P2711R1>`__","Making multi-param constructors of ``views`` ``explicit``","2023-02 (Issaquah)","|Complete|","21",""
"`P2609R3 <https://wg21.link/P2609R3>`__","Relaxing Ranges Just A Smidge","2023-02 (Issaquah)","|Complete|","20","Implemented as a DR in C++20. Other implementations will do the same."
"`P2713R1 <https://wg21.link/P2713R1>`__","Escaping improvements in ``std::format``","2023-02 (Issaquah)","|Complete|","19",""
"`P2675R1 <https://wg21.link/P2675R1>`__","``format``'s width estimation is too approximate and not forward compatible","2023-02 (Issaquah)","|Complete|","17",""
diff --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv
index fdf381862d87b..d3feecf6513e4 100644
--- a/libcxx/docs/Status/Cxx2cIssues.csv
+++ b/libcxx/docs/Status/Cxx2cIssues.csv
@@ -66,7 +66,7 @@
"`LWG4060 <https://wg21.link/LWG4060>`__","``submdspan`` preconditions do not forbid creating invalid pointer","2024-06 (St. Louis)","","",""
"`LWG4061 <https://wg21.link/LWG4061>`__","Should ``std::basic_format_context`` be default-constructible/copyable/movable?","2024-06 (St. Louis)","|Complete|","19",""
"`LWG4071 <https://wg21.link/LWG4071>`__","``reference_wrapper`` comparisons are not SFINAE-friendly","2024-06 (St. Louis)","|Complete|","19",""
-"`LWG4074 <https://wg21.link/LWG4074>`__","``compatible-joinable-ranges`` is underconstrained","2024-06 (St. Louis)","","",""
+"`LWG4074 <https://wg21.link/LWG4074>`__","``compatible-joinable-ranges`` is underconstrained","2024-06 (St. Louis)","|Complete|","21",""
"`LWG4076 <https://wg21.link/LWG4076>`__","``concat_view`` should be freestanding","2024-06 (St. Louis)","","",""
"`LWG4079 <https://wg21.link/LWG4079>`__","Missing Preconditions in ``concat_view::iterator``\`s conversion constructor","2024-06 (St. Louis)","","",""
"`LWG4082 <https://wg21.link/LWG4082>`__","``views::concat(r)`` is well-formed when ``r`` is an ``output_range``","2024-06 (St. Louis)","","",""
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 8931a1b35f6d3..e386f31386b60 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -706,6 +706,7 @@ set(files
__ranges/iota_view.h
__ranges/istream_view.h
__ranges/join_view.h
+ __ranges/join_with_view.h
__ranges/lazy_split_view.h
__ranges/movable_box.h
__ranges/non_propagating_cache.h
diff --git a/libcxx/include/__ranges/concepts.h b/libcxx/include/__ranges/concepts.h
index 674a3f359ff99..bf75fe8a6fef4 100644
--- a/libcxx/include/__ranges/concepts.h
+++ b/libcxx/include/__ranges/concepts.h
@@ -10,7 +10,9 @@
#ifndef _LIBCPP___RANGES_CONCEPTS_H
#define _LIBCPP___RANGES_CONCEPTS_H
+#include <__concepts/common_reference_with.h>
#include <__concepts/constructible.h>
+#include <__concepts/convertible_to.h>
#include <__concepts/movable.h>
#include <__concepts/same_as.h>
#include <__config>
@@ -25,6 +27,8 @@
#include <__ranges/enable_view.h>
#include <__ranges/size.h>
#include <__type_traits/add_pointer.h>
+#include <__type_traits/common_reference.h>
+#include <__type_traits/common_type.h>
#include <__type_traits/is_reference.h>
#include <__type_traits/remove_cvref.h>
#include <__type_traits/remove_reference.h>
@@ -133,6 +137,42 @@ concept viewable_range =
(is_lvalue_reference_v<_Tp> ||
(movable<remove_reference_t<_Tp>> && !__is_std_initializer_list<remove_cvref_t<_Tp>>))));
+# if _LIBCPP_STD_VER >= 23
+
+template <class... _Rs>
+using __concat_reference_t _LIBCPP_NODEBUG = common_reference_t<range_reference_t<_Rs>...>;
+
+template <class... _Rs>
+using __concat_value_t _LIBCPP_NODEBUG = common_type_t<range_value_t<_Rs>...>;
+
+template <class... _Rs>
+using __concat_rvalue_reference_t _LIBCPP_NODEBUG = common_reference_t<range_rvalue_reference_t<_Rs>...>;
+
+template <class _Ref, class _RRef, class _It>
+concept __concat_indirectly_readable_impl = requires(const _It __it) {
+ { *__it } -> convertible_to<_Ref>;
+ { ranges::iter_move(__it) } -> convertible_to<_RRef>;
+};
+
+template <class... _Rs>
+concept __concat_indirectly_readable =
+ common_reference_with<__concat_reference_t<_Rs...>&&, __concat_value_t<_Rs...>&> &&
+ common_reference_with<__concat_reference_t<_Rs...>&&, __concat_rvalue_reference_t<_Rs...>&&> &&
+ common_reference_with<__concat_rvalue_reference_t<_Rs...>&&, const __concat_value_t<_Rs...>&> &&
+ (__concat_indirectly_readable_impl<__concat_reference_t<_Rs...>,
+ __concat_rvalue_reference_t<_Rs...>,
+ iterator_t<_Rs>> &&
+ ...);
+
+template <class... _Rs>
+concept __concatable = requires {
+ typename __concat_reference_t<_Rs...>;
+ typename __concat_value_t<_Rs...>;
+ typename __concat_rvalue_reference_t<_Rs...>;
+} && __concat_indirectly_readable<_Rs...>;
+
+# endif // _LIBCPP_STD_VER >= 23
+
} // namespace ranges
#endif // _LIBCPP_STD_VER >= 20
diff --git a/libcxx/include/__ranges/join_with_view.h b/libcxx/include/__ranges/join_with_view.h
new file mode 100644
index 0000000000000..8ed989a664687
--- /dev/null
+++ b/libcxx/include/__ranges/join_with_view.h
@@ -0,0 +1,460 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___RANGES_JOIN_WITH_VIEW_H
+#define _LIBCPP___RANGES_JOIN_WITH_VIEW_H
+
+#include <__concepts/common_reference_with.h>
+#include <__concepts/common_with.h>
+#include <__concepts/constructible.h>
+#include <__concepts/convertible_to.h>
+#include <__concepts/derived_from.h>
+#include <__concepts/equality_comparable.h>
+#include <__config>
+#include <__functional/bind_back.h>
+#include <__iterator/concepts.h>
+#include <__iterator/incrementable_traits.h>
+#include <__iterator/iter_move.h>
+#include <__iterator/iter_swap.h>
+#include <__iterator/iterator_traits.h>
+#include <__memory/addressof.h>
+#include <__ranges/access.h>
+#include <__ranges/all.h>
+#include <__ranges/concepts.h>
+#include <__ranges/non_propagating_cache.h>
+#include <__ranges/range_adaptor.h>
+#include <__ranges/single_view.h>
+#include <__ranges/view_interface.h>
+#include <__type_traits/conditional.h>
+#include <__type_traits/decay.h>
+#include <__type_traits/is_reference.h>
+#include <__type_traits/maybe_const.h>
+#include <__utility/as_const.h>
+#include <__utility/as_lvalue.h>
+#include <__utility/empty.h>
+#include <__utility/forward.h>
+#include <__utility/move.h>
+#include <variant>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 23
+
+namespace ranges {
+template <class _Range>
+concept __bidirectional_common = bidirectional_range<_Range> && common_range<_Range>;
+
+template <input_range _View, forward_range _Pattern>
+ requires view<_View> && input_range<range_reference_t<_View>> && view<_Pattern> &&
+ __concatable<range_reference_t<_View>, _Pattern>
+class join_with_view : public view_interface<join_with_view<_View, _Pattern>> {
+ using _InnerRng _LIBCPP_NODEBUG = range_reference_t<_View>;
+
+ _LIBCPP_NO_UNIQUE_ADDRESS _View __base_ = _View();
+
+ static constexpr bool _UseOuterItCache = !forward_range<_View>;
+ using _OuterItCache _LIBCPP_NODEBUG =
+ _If<_UseOuterItCache, __non_propagating_cache<iterator_t<_View>>, __empty_cache>;
+ _LIBCPP_NO_UNIQUE_ADDRESS _OuterItCache __outer_it_;
+
+ static constexpr bool _UseInnerCache = !is_reference_v<_InnerRng>;
+ using _InnerCache _LIBCPP_NODEBUG =
+ _If<_UseInnerCache, __non_propagating_cache<remove_cvref_t<_InnerRng>>, __empty_cache>;
+ _LIBCPP_NO_UNIQUE_ADDRESS _InnerCache __inner_;
+
+ _LIBCPP_NO_UNIQUE_ADDRESS _Pattern __pattern_ = _Pattern();
+
+ template <bool _Const>
+ struct __iterator;
+
+ template <bool _Const>
+ struct __sentinel;
+
+public:
+ _LIBCPP_HIDE_FROM_ABI join_with_view()
+ requires default_initializable<_View> && default_initializable<_Pattern>
+ = default;
+
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit join_with_view(_View __base, _Pattern __pattern)
+ : __base_(std::move(__base)), __pattern_(std::move(__pattern)) {}
+
+ template <input_range _Range>
+ requires constructible_from<_View, views::all_t<_Range>> &&
+ constructible_from<_Pattern, single_view<range_value_t<_InnerRng>>>
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit join_with_view(_Range&& __r, range_value_t<_InnerRng> __e)
+ : __base_(views::all(std::forward<_Range>(__r))), __pattern_(views::single(std::move(__e))) {}
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _View base() const&
+ requires copy_constructible<_View>
+ {
+ return __base_;
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _View base() && { return std::move(__base_); }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto begin() {
+ if constexpr (forward_range<_View>) {
+ constexpr bool __use_const = __simple_view<_View> && is_reference_v<_InnerRng> && __simple_view<_Pattern>;
+ return __iterator<__use_const>{*this, ranges::begin(__base_)};
+ } else {
+ __outer_it_.__emplace(ranges::begin(__base_));
+ return __iterator<false>{*this};
+ }
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto begin() const
+ requires forward_range<const _View> && forward_range<const _Pattern> &&
+ is_reference_v<range_reference_t<const _View>> && input_range<range_reference_t<const _View>> &&
+ __concatable<range_reference_t<const _View>, const _Pattern>
+ {
+ return __iterator<true>{*this, ranges::begin(__base_)};
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto end() {
+ constexpr bool __use_const = __simple_view<_View> && __simple_view<_Pattern>;
+ if constexpr (forward_range<_View> && is_reference_v<_InnerRng> && forward_range<_InnerRng> &&
+ common_range<_View> && common_range<_InnerRng>)
+ return __iterator<__use_const>{*this, ranges::end(__base_)};
+ else
+ return __sentinel<__use_const>{*this};
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto end() const
+ requires forward_range<const _View> && forward_range<const _Pattern> &&
+ is_reference_v<range_reference_t<const _View>> && input_range<range_reference_t<const _View>> &&
+ __concatable<range_reference_t<const _View>, const _Pattern>
+ {
+ using _InnerConstRng = range_reference_t<const _View>;
+ if constexpr (forward_range<_InnerConstRng> && common_range<const _View> && common_range<_InnerConstRng>)
+ return __iterator<true>{*this, ranges::end(__base_)};
+ else
+ return __sentinel<true>{*this};
+ }
+};
+
+template <class _Range, class _Pattern>
+join_with_view(_Range&&, _Pattern&&) -> join_with_view<views::all_t<_Range>, views::all_t<_Pattern>>;
+
+template <input_range _Range>
+join_with_view(_Range&&, range_value_t<range_reference_t<_Range>>)
+ -> join_with_view<views::all_t<_Range>, single_view<range_value_t<range_reference_t<_Range>>>>;
+
+template <class _Base, class _PatternBase, class _InnerBase = range_reference_t<_Base>>
+struct __join_with_view_iterator_category {};
+
+template <class _Base, class _PatternBase, class _InnerBase>
+ requires is_reference_v<_InnerBase> && forward_range<_Base> && forward_range<_InnerBase>
+struct __join_with_view_iterator_category<_Base, _PatternBase, _InnerBase> {
+private:
+ static consteval auto __get_iterator_category() noexcept {
+ using _OuterC = iterator_traits<iterator_t<_Base>>::iterator_category;
+ using _InnerC = iterator_traits<iterator_t<_InnerBase>>::iterator_category;
+ using _PatternC = iterator_traits<iterator_t<_PatternBase>>::iterator_category;
+
+ if constexpr (!is_reference_v<common_reference_t<iter_reference_t<iterator_t<_InnerBase>>,
+ iter_reference_t<iterator_t<_PatternBase>>>>)
+ return input_iterator_tag{};
+ else if constexpr (derived_from<_OuterC, bidirectional_iterator_tag> &&
+ derived_from<_InnerC, bidirectional_iterator_tag> &&
+ derived_from<_PatternC, bidirectional_iterator_tag> && common_range<_InnerBase> &&
+ common_range<_PatternBase>)
+ return bidirectional_iterator_tag{};
+ else if constexpr (derived_from<_OuterC, forward_iterator_tag> && derived_from<_InnerC, forward_iterator_tag> &&
+ derived_from<_PatternC, forward_iterator_tag>)
+ return forward_iterator_tag{};
+ else
+ return input_iterator_tag{};
+ }
+
+public:
+ using iterator_category = decltype(__get_iterator_category());
+};
+
+template <input_range _View, forward_range _Pattern>
+ requires view<_View> && input_range<range_reference_t<_View>> && view<_Pattern> &&
+ __concatable<range_reference_t<_View>, _Pattern>
+template <bool _Const>
+struct join_with_view<_View, _Pattern>::__iterator
+ : public __join_with_view_iterator_category<__maybe_const<_Const, _View>, __maybe_const<_Const, _Pattern>> {
+private:
+ friend join_with_view;
+
+ using _Parent _LIBCPP_NODEBUG = __maybe_const<_Const, join_with_view>;
+ using _Base _LIBCPP_NODEBUG = __maybe_const<_Const, _View>;
+ using _InnerBase _LIBCPP_NODEBUG = range_reference_t<_Base>;
+ using _PatternBase _LIBCPP_NODEBUG = __maybe_const<_Const, _Pattern>;
+
+ using _OuterIter _LIBCPP_NODEBUG = iterator_t<_Base>;
+ using _InnerIter _LIBCPP_NODEBUG = iterator_t<_InnerBase>;
+ using _PatternIter _LIBCPP_NODEBUG = iterator_t<_PatternBase>;
+
+ static_assert(!_Const || forward_range<_Base>, "Const can only be true when Base models forward_range.");
+
+ static constexpr bool __ref_is_glvalue = is_reference_v<_InnerBase>;
+
+ _Parent* __parent_ = nullptr;
+
+ static constexpr bool _OuterIterPresent = forward_range<_Base>;
+ using _OuterIterType _LIBCPP_NODEBUG = _If<_OuterIterPresent, _OuterIter, std::__empty>;
+ _LIBCPP_NO_UNIQUE_ADDRESS _OuterIterType __outer_it_ = _OuterIterType();
+
+ variant<_PatternIter, _InnerIter> __inner_it_;
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __iterator(_Parent& __parent, _OuterIter __outer)
+ requires forward_range<_Base>
+ : __parent_(std::addressof(__parent)), __outer_it_(std::move(__outer)) {
+ if (__get_outer() != ranges::end(__parent_->__base_)) {
+ __inner_it_.template emplace<1>(ranges::begin(__update_inner()));
+ __satisfy();
+ }
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __iterator(_Parent& __parent)
+ requires(!forward_range<_Base>)
+ : __parent_(std::addressof(__parent)) {
+ if (__get_outer() != ranges::end(__parent_->__base_)) {
+ __inner_it_.template emplace<1>(ranges::begin(__update_inner()));
+ __satisfy();
+ }
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _OuterIter& __get_outer() {
+ if constexpr (forward_range<_Base>)
+ return __outer_it_;
+ else
+ return *__parent_->__outer_it_;
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr const _OuterIter& __get_outer() const {
+ if constexpr (forward_range<_Base>)
+ return __outer_it_;
+ else
+ return *__parent_->__outer_it_;
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto& __update_inner() {
+ if constexpr (__ref_is_glvalue)
+ return std::__as_lvalue(*__get_outer());
+ else
+ return __parent_->__inner_.__emplace_from([this]() -> decltype(auto) { return *__get_outer(); });
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto& __get_inner() {
+ if constexpr (__ref_is_glvalue)
+ return std::__as_lvalue(*__get_outer());
+ else
+ return *__parent_->__inner_;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr void __satisfy() {
+ while (true) {
+ if (__inner_it_.index() == 0) {
+ if (std::get<0>(__inner_it_) != ranges::end(__parent_->__pattern_))
+ break;
+
+ __inner_it_.template emplace<1>(ranges::begin(__update_inner()));
+ } else {
+ if (std::get<1>(__inner_it_) != ranges::end(__get_inner()))
+ break;
+
+ if (++__get_outer() == ranges::end(__parent_->__base_)) {
+ if constexpr (__ref_is_glvalue)
+ __inner_it_.template emplace<0>();
+
+ break;
+ }
+
+ __inner_it_.template emplace<0>(ranges::begin(__parent_->__pattern_));
+ }
+ }
+ }
+
+ [[nodiscard]] static consteval auto __get_iterator_concept() noexcept {
+ if constexpr (__ref_is_glvalue && bidirectional_range<_Base> && __bidirectional_common<_InnerBase> &&
+ __bidirectional_common<_PatternBase>)
+ return bidirectional_iterator_tag{};
+ else if constexpr (__ref_is_glvalue && forward_range<_Base> && forward_range<_InnerBase>)
+ return forward_iterator_tag{};
+ else
+ return input_iterator_tag{};
+ }
+
+public:
+ using iterator_concept = decltype(__get_iterator_concept());
+ using value_type = common_type_t<iter_value_t<_InnerIter>, iter_value_t<_PatternIter>>;
+ using difference_type =
+ common_type_t<iter_difference_t<_OuterIter>, iter_difference_t<_InnerIter>, iter_difference_t<_PatternIter>>;
+
+ _LIBCPP_HIDE_FROM_ABI __iterator() = default;
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __iterator(__iterator<!_Const> __i)
+ requires _Const && convertible_to<iterator_t<_View>, _OuterIter> &&
+ convertible_to<iterator_t<_InnerRng>, _InnerIter> && convertible_to<iterator_t<_Pattern>, _PatternIter>
+ : __parent_(__i.__parent_), __outer_it_(std::move(__i.__outer_it_)) {
+ if (__i.__inner_it_.index() == 0) {
+ __inner_it_.template emplace<0>(std::get<0>(std::move(__i.__inner_it_)));
+ } else {
+ __inner_it_.template emplace<1>(std::get<1>(std::move(__i.__inner_it_)));
+ }
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr decltype(auto) operator*() const {
+ using __reference = common_reference_t<iter_reference_t<_InnerIter>, iter_reference_t<_PatternIter>>;
+ return std::visit([](auto& __it) -> __reference { return *__it; }, __inner_it_);
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator++() {
+ std::visit([](auto& __it) { ++__it; }, __inner_it_);
+ __satisfy();
+ return *this;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr void operator++(int) { ++*this; }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __iterator operator++(int)
+ requires __ref_is_glvalue && forward_iterator<_OuterIter> && forward_iterator<_InnerIter>
+ {
+ __iterator __tmp = *this;
+ ++*this;
+ return __tmp;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator--()
+ requires __ref_is_glvalue
+ && bidirectional_range<_Base> && __bidirectional_common<_InnerBase> && __bidirectional_common<_PatternBase>
+ {
+ if (__outer_it_ == ranges::end(__parent_->__base_)) {
+ auto&& __inner = *--__outer_it_;
+ __inner_it_.template emplace<1>(ranges::end(__inner));
+ }
+
+ while (true) {
+ if (__inner_it_.index() == 0) {
+ auto& __it = std::get<0>(__inner_it_);
+ if (__it == ranges::begin(__parent_->__pattern_)) {
+ auto&& __inner = *--__outer_it_;
+ __inner_it_.template emplace<1>(ranges::end(__inner));
+ } else
+ break;
+ } else {
+ auto& __it = std::get<1>(__inner_it_);
+ auto&& __inner = *__outer_it_;
+ if (__it == ranges::begin(__inner))
+ __inner_it_.template emplace<0>(ranges::end(__parent_->__pattern_));
+ else
+ break;
+ }
+ }
+
+ std::visit([](auto& __it) { --__it; }, __inner_it_);
+ return *this;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __iterator operator--(int)
+ requires __ref_is_glvalue
+ && bidirectional_range<_Base> && __bidirectional_common<_InnerBase> && __bidirectional_common<_PatternBase>
+ {
+ __iterator __tmp = *this;
+ --*this;
+ return __tmp;
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __iterator& __x, const __iterator& __y)
+ requires __ref_is_glvalue && forward_range<_Base> && equality_comparable<_InnerIter>
+ {
+ return __x.__outer_it_ == __y.__outer_it_ && __x.__inner_it_ == __y.__inner_it_;
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr decltype(auto) iter_move(const __iterator& __x) {
+ using __rvalue_reference =
+ common_reference_t<iter_rvalue_reference_t<_InnerIter>, iter_rvalue_reference_t<_PatternIter>>;
+ return std::visit<__rvalue_reference>(ranges::iter_move, __x.__inner_it_);
+ }
+
+ _LIBCPP_HIDE_FROM_ABI friend constexpr void iter_swap(const __iterator& __x, const __iterator& __y)
+ requires indirectly_swappable<_InnerIter, _PatternIter>
+ {
+ std::visit(ranges::iter_swap, __x.__inner_it_, __y.__inner_it_);
+ }
+};
+
+template <input_range _View, forward_range _Pattern>
+ requires view<_View> && input_range<range_reference_t<_View>> && view<_Pattern> &&
+ __concatable<range_reference_t<_View>, _Pattern>
+template <bool _Const>
+struct join_with_view<_View, _Pattern>::__sentinel {
+private:
+ friend join_with_view;
+
+ using _Parent _LIBCPP_NODEBUG = __maybe_const<_Const, join_with_view>;
+ using _Base _LIBCPP_NODEBUG = __maybe_const<_Const, _View>;
+
+ _LIBCPP_NO_UNIQUE_ADDRESS sentinel_t<_Base> __end_ = sentinel_t<_Base>();
+
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __sentinel(_Parent& __parent) : __end_(ranges::end(__parent.__base_)) {}
+
+ template <bool _OtherConst>
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static constexpr auto& __get_outer_of(const __iterator<_OtherConst>& __x) {
+ return __x.__get_outer();
+ }
+
+public:
+ _LIBCPP_HIDE_FROM_ABI __sentinel() = default;
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __sentinel(__sentinel<!_Const> __s)
+ requires _Const && convertible_to<sentinel_t<_View>, sentinel_t<_Base>>
+ : __end_(std::move(__s.__end_)) {}
+
+ template <bool _OtherConst>
+ requires sentinel_for<sentinel_t<_Base>, iterator_t<__maybe_const<_OtherConst, _View>>>
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr bool
+ operator==(const __iterator<_OtherConst>& __x, const __sentinel& __y) {
+ return __get_outer_of(__x) == __y.__end_;
+ }
+};
+
+namespace views {
+namespace __join_with_view {
+struct __fn {
+ template <class _Range, class _Pattern>
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Range&& __range, _Pattern&& __pattern) const
+ noexcept(noexcept(/**/ join_with_view(std::forward<_Range>(__range), std::forward<_Pattern>(__pattern))))
+ -> decltype(/*--*/ join_with_view(std::forward<_Range>(__range), std::forward<_Pattern>(__pattern))) {
+ return /*-------------*/ join_with_view(std::forward<_Range>(__range), std::forward<_Pattern>(__pattern));
+ }
+
+ template <class _Pattern>
+ requires constructible_from<decay_t<_Pattern>, _Pattern>
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Pattern&& __pattern) const
+ noexcept(is_nothrow_constructible_v<decay_t<_Pattern>, _Pattern>) {
+ return __pipeable(std::__bind_back(*this, std::forward<_Pattern>(__pattern)));
+ }
+};
+} // namespace __join_with_view
+
+inline namespace __cpo {
+inline constexpr auto join_with = __join_with_view::__fn{};
+} // namespace __cpo
+} // namespace views
+} // namespace ranges
+
+#endif // _LIBCPP_STD_VER >= 23
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___RANGES_JOIN_WITH_VIEW_H
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index f5fd970934e9b..4a081e65cb7f1 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1865,6 +1865,7 @@ module std [system] {
module iota_view { header "__ranges/iota_view.h" }
module istream_view { header "__ranges/istream_view.h" }
module join_view { header "__ranges/join_view.h" }
+ module join_with_view { header "__ranges/join_with_view.h" }
module lazy_split_view {
header "__ranges/lazy_split_view.h"
export std.functional.bind_back
diff --git a/libcxx/include/ranges b/libcxx/include/ranges
index 49fea7c3f84ec..2a6321bd2c5d8 100644
--- a/libcxx/include/ranges
+++ b/libcxx/include/ranges
@@ -285,6 +285,15 @@ namespace std::ranges {
requires view<V> && input_range<range_reference_t<V>>
class join_view;
+ // [range.join.with], join with view
+ template<input_range V, forward_range Pattern>
+ requires view<V> && input_range<range_reference_t<V>>
+ && view<Pattern>
+ && concatable<range_reference_t<V>, Pattern>
+ class join_with_view; // since C++23
+
+ namespace views { inline constexpr unspecified join_with = unspecified; } // since C++23
+
// [range.lazy.split], lazy split view
template<class R>
concept tiny-range = see below; // exposition only
@@ -427,6 +436,7 @@ namespace std {
# include <__ranges/as_rvalue_view.h>
# include <__ranges/chunk_by_view.h>
# include <__ranges/from_range.h>
+# include <__ranges/join_with_view.h>
# include <__ranges/repeat_view.h>
# include <__ranges/to.h>
# include <__ranges/zip_view.h>
diff --git a/libcxx/include/version b/libcxx/include/version
index f430c7edff2b2..91fe48351e161 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -519,7 +519,7 @@ __cpp_lib_void_t 201411L <type_traits>
# define __cpp_lib_ranges_contains 202207L
# define __cpp_lib_ranges_find_last 202207L
# define __cpp_lib_ranges_iota 202202L
-// # define __cpp_lib_ranges_join_with 202202L
+# define __cpp_lib_ranges_join_with 202202L
# define __cpp_lib_ranges_repeat 202207L
// # define __cpp_lib_ranges_slide 202202L
# define __cpp_lib_ranges_starts_ends_with 202106L
diff --git a/libcxx/modules/std/ranges.inc b/libcxx/modules/std/ranges.inc
index a5e2a2b4583c1..adabeeb22d551 100644
--- a/libcxx/modules/std/ranges.inc
+++ b/libcxx/modules/std/ranges.inc
@@ -223,13 +223,16 @@ export namespace std {
namespace views {
using std::ranges::views::join;
} // namespace views
-#if 0
+
+#if _LIBCPP_STD_VER >= 23
+ // [range.join.with]
using std::ranges::join_with_view;
namespace views {
using std::ranges::views::join_with;
} // namespace views
-#endif
+#endif // _LIBCPP_STD_VER >= 23
+
using std::ranges::lazy_split_view;
// [range.split], split view
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.iterator/deref.nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.iterator/deref.nodiscard.verify.cpp
new file mode 100644
index 0000000000000..97133613bf58a
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.iterator/deref.nodiscard.verify.cpp
@@ -0,0 +1,28 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// Test the libc++ extension that std::ranges::join_with_view::iterator<Const>::operator* is marked as [[nodiscard]].
+
+#include <ranges>
+#include <utility>
+
+void test() {
+ char range[3][2] = {{'x', 'x'}, {'y', 'y'}, {'z', 'z'}};
+ char pattern[2] = {',', ' '};
+
+ std::ranges::join_with_view view(range, pattern);
+
+ // clang-format off
+ *view.begin(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ *std::as_const(view).begin(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ // clang-format on
+}
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.iterator/eq.nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.iterator/eq.nodiscard.verify.cpp
new file mode 100644
index 0000000000000..823d8def98085
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.iterator/eq.nodiscard.verify.cpp
@@ -0,0 +1,30 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// Test the libc++ extension that std::ranges::join_with_view::iterator<Const>::operator== is marked as [[nodiscard]].
+
+#include <ranges>
+#include <utility>
+
+void test() {
+ char16_t range[3][1] = {{u'x'}, {u'y'}, {u'z'}};
+ char16_t pattern[1] = {u'-'};
+
+ std::ranges::join_with_view view(range, pattern);
+
+ // clang-format off
+ (view.begin() == view.end()); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ (std::as_const(view).begin() == view.end()); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ (view.begin() == std::as_const(view).end()); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ (std::as_const(view).begin() == std::as_const(view).end()); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ // clang-format on
+}
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.iterator/iter_move.nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.iterator/iter_move.nodiscard.verify.cpp
new file mode 100644
index 0000000000000..9e046ef43fda3
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.iterator/iter_move.nodiscard.verify.cpp
@@ -0,0 +1,28 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// Test the libc++ extension that std::ranges::join_with_view::iterator<Const>::iter_move is marked as [[nodiscard]].
+
+#include <ranges>
+#include <utility>
+
+void test() {
+ long range[2][1] = {{0L}, {2L}};
+ long pattern[1] = {1L};
+
+ std::ranges::join_with_view view(range, pattern);
+
+ // clang-format off
+ iter_move(view.begin()); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ iter_move(std::as_const(view).begin()); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ // clang-format on
+}
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.iterator/no_unique_address.compile.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.iterator/no_unique_address.compile.pass.cpp
new file mode 100644
index 0000000000000..6b2abb5c80539
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.iterator/no_unique_address.compile.pass.cpp
@@ -0,0 +1,56 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// This test ensures that we use `[[no_unique_address]]` in `join_with_view::iterator`.
+
+#include <cstddef>
+#include <ranges>
+#include <variant>
+
+struct IntRange : std::ranges::view_base {
+ int* begin();
+ int* end();
+};
+
+class Iter {
+public:
+ using value_type = IntRange;
+ using difference_type = ptrdiff_t;
+
+ Iter& operator++();
+ void operator++(int);
+ value_type& operator*() const;
+ bool operator==(std::default_sentinel_t) const;
+
+private:
+ int* ptr_;
+};
+
+static_assert(std::input_iterator<Iter>);
+static_assert(!std::forward_iterator<Iter>);
+
+struct View : std::ranges::view_base {
+ Iter begin();
+ std::default_sentinel_t end();
+};
+
+static_assert(std::ranges::input_range<View>);
+static_assert(!std::ranges::forward_range<View>);
+
+using JWV = std::ranges::join_with_view<View, IntRange>;
+
+// Expected JWV::iterator layout:
+// _Parent* __parent_; // offset: 0
+// [[no_unique_address]] __empty __outer_it; // 0
+// variant<_PatternIter, _InnerIter> __pattern_; // sizeof(pointer)
+static_assert(sizeof(std::ranges::iterator_t<JWV>) ==
+ sizeof(void*) + sizeof(std::variant<int*, int*>)); // sizeof(__parent_) + sizeof(__inner_it_)
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.overview/adaptor.nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.overview/adaptor.nodiscard.verify.cpp
new file mode 100644
index 0000000000000..3efe77a3765d5
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.overview/adaptor.nodiscard.verify.cpp
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// Test the libc++ extension that std::views::join_with is marked as [[nodiscard]].
+
+#include <ranges>
+
+void test() {
+ int range[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
+ int pattern_base[2] = {-1, -1};
+ auto pattern = std::views::all(pattern_base);
+
+ // clang-format off
+ std::views::join_with(pattern); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::views::join_with(range, pattern); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ range | std::views::join_with(pattern); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::views::reverse | std::views::join_with(pattern); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+
+ std::views::join_with(0); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::views::join_with(range, 0); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ range | std::views::join_with(0); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::views::reverse | std::views::join_with(0); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ // clang-format on
+}
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.sentinel/eq.nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.sentinel/eq.nodiscard.verify.cpp
new file mode 100644
index 0000000000000..e7e4d262eedb9
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.sentinel/eq.nodiscard.verify.cpp
@@ -0,0 +1,35 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// Test the libc++ extension that std::ranges::join_with_view::sentinel<Const>::operator== is marked as [[nodiscard]].
+
+#include <array>
+#include <ranges>
+#include <utility>
+
+#include "test_iterators.h"
+#include "test_range.h"
+
+void test() {
+ std::array<test_range<cpp20_input_iterator>, 0> range;
+ std::array<int, 0> pattern;
+
+ std::ranges::join_with_view view(range, pattern);
+ static_assert(!std::ranges::common_range<decltype(view)>);
+
+ // clang-format off
+ (view.begin() == view.end()); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ (std::as_const(view).begin() == view.end()); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ (view.begin() == std::as_const(view).end()); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ (std::as_const(view).begin() == std::as_const(view).end()); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ // clang-format on
+}
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.sentinel/no_unique_address.compile.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.sentinel/no_unique_address.compile.pass.cpp
new file mode 100644
index 0000000000000..3badac162ce7c
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.sentinel/no_unique_address.compile.pass.cpp
@@ -0,0 +1,48 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// XFAIL: msvc
+
+// <ranges>
+
+// This test ensures that we use `[[no_unique_address]]` in `join_with_view::sentinel`.
+
+#include <cstddef>
+#include <ranges>
+#include <string_view>
+
+template <bool Const>
+struct Iter {
+ using value_type = std::string_view;
+ using difference_type = std::ptrdiff_t;
+
+ Iter& operator++();
+ Iter operator++(int);
+ value_type& operator*() const;
+ bool operator==(const Iter&) const;
+ bool operator==(std::default_sentinel_t) const;
+};
+
+struct View : std::ranges::view_base {
+ Iter<false> begin();
+ Iter<true> begin() const;
+ std::default_sentinel_t end() const;
+};
+
+using JWV = std::ranges::join_with_view<View, std::string_view>;
+
+template <class View>
+struct Test {
+ [[no_unique_address]] std::ranges::sentinel_t<View> se;
+ unsigned char pad;
+};
+
+static_assert(sizeof(Test<JWV>) == 1);
+static_assert(sizeof(Test<const JWV>) == 1);
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.view/base.nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.view/base.nodiscard.verify.cpp
new file mode 100644
index 0000000000000..ddf1ebdc5e46c
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.view/base.nodiscard.verify.cpp
@@ -0,0 +1,30 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// Test the libc++ extension that std::ranges::join_with_view::base is marked as [[nodiscard]].
+
+#include <ranges>
+#include <utility>
+
+void test() {
+ int range[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
+ int pattern[2] = {-1, -1};
+
+ std::ranges::join_with_view view(range, pattern);
+
+ // clang-format off
+ view.base(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::as_const(view).base(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::move(std::as_const(view)).base(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::move(view).base(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ // clang-format on
+}
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.view/begin.nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.view/begin.nodiscard.verify.cpp
new file mode 100644
index 0000000000000..858490a82c75d
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.view/begin.nodiscard.verify.cpp
@@ -0,0 +1,28 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// Test the libc++ extension that std::ranges::join_with_view::begin is marked as [[nodiscard]].
+
+#include <ranges>
+#include <utility>
+
+void test() {
+ int range[3][2] = {{1, 3}, {4, 6}, {7, 9}};
+ int pattern[1] = {-2};
+
+ std::ranges::join_with_view view(range, pattern);
+
+ // clang-format off
+ view.begin(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::as_const(view).begin(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ // clang-format on
+}
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.view/end.nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.view/end.nodiscard.verify.cpp
new file mode 100644
index 0000000000000..e57e0ee3f0d06
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.view/end.nodiscard.verify.cpp
@@ -0,0 +1,28 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// Test the libc++ extension that std::ranges::join_with_view::end is marked as [[nodiscard]].
+
+#include <ranges>
+#include <utility>
+
+void test() {
+ int range[3][2] = {{1, 2}, {4, 5}, {7, 8}};
+ int pattern[1] = {-3};
+
+ std::ranges::join_with_view view(range, pattern);
+
+ // clang-format off
+ view.end(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::as_const(view).end(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ // clang-format on
+}
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.view/no_unique_address.compile.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.view/no_unique_address.compile.pass.cpp
new file mode 100644
index 0000000000000..aa6eeafe4be13
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/range.join.with.view/no_unique_address.compile.pass.cpp
@@ -0,0 +1,47 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// XFAIL: msvc
+
+// <ranges>
+
+// This test ensures that we use `[[no_unique_address]]` in `join_with_view`.
+
+#include <ranges>
+#include <string>
+
+struct ForwardView : std::ranges::view_base {
+ std::string* begin() const;
+ std::string* end() const;
+};
+
+static_assert(std::ranges::forward_range<ForwardView>);
+static_assert(std::is_reference_v<std::ranges::range_reference_t<ForwardView>>);
+
+struct Pattern : std::ranges::view_base {
+ char* begin() const;
+ char* end() const;
+};
+
+template <class View>
+struct Test {
+ [[no_unique_address]] View view;
+ unsigned char pad;
+};
+
+using JWV = std::ranges::join_with_view<ForwardView, Pattern>;
+
+// Expected JWV layout:
+// [[no_unique_address]] _View __base_ // offset: 0
+// [[no_unique_address]] __empty_cache __outer_it; // 0
+// [[no_unique_address]] __empty_cache __inner_; // 1
+// [[no_unique_address]] _Patter __pattern_ // 0
+static_assert(sizeof(JWV) == 2);
+static_assert(sizeof(Test<JWV>) == 2);
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp
index c7c8112e123cd..4cf5178dd7b8f 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp
@@ -278,17 +278,11 @@
# error "__cpp_lib_ranges_concat should not be defined before c++26"
# endif
-# if !defined(_LIBCPP_VERSION)
-# ifndef __cpp_lib_ranges_join_with
-# error "__cpp_lib_ranges_join_with should be defined in c++23"
-# endif
-# if __cpp_lib_ranges_join_with != 202202L
-# error "__cpp_lib_ranges_join_with should have the value 202202L in c++23"
-# endif
-# else
-# ifdef __cpp_lib_ranges_join_with
-# error "__cpp_lib_ranges_join_with should not be defined because it is unimplemented in libc++!"
-# endif
+# ifndef __cpp_lib_ranges_join_with
+# error "__cpp_lib_ranges_join_with should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_join_with != 202202L
+# error "__cpp_lib_ranges_join_with should have the value 202202L in c++23"
# endif
# ifndef __cpp_lib_ranges_repeat
@@ -406,17 +400,11 @@
# endif
# endif
-# if !defined(_LIBCPP_VERSION)
-# ifndef __cpp_lib_ranges_join_with
-# error "__cpp_lib_ranges_join_with should be defined in c++26"
-# endif
-# if __cpp_lib_ranges_join_with != 202202L
-# error "__cpp_lib_ranges_join_with should have the value 202202L in c++26"
-# endif
-# else
-# ifdef __cpp_lib_ranges_join_with
-# error "__cpp_lib_ranges_join_with should not be defined because it is unimplemented in libc++!"
-# endif
+# ifndef __cpp_lib_ranges_join_with
+# error "__cpp_lib_ranges_join_with should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_join_with != 202202L
+# error "__cpp_lib_ranges_join_with should have the value 202202L in c++26"
# endif
# ifndef __cpp_lib_ranges_repeat
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index cef334f70c078..e546719142231 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -5633,17 +5633,11 @@
# error "__cpp_lib_ranges_iota should have the value 202202L in c++23"
# endif
-# if !defined(_LIBCPP_VERSION)
-# ifndef __cpp_lib_ranges_join_with
-# error "__cpp_lib_ranges_join_with should be defined in c++23"
-# endif
-# if __cpp_lib_ranges_join_with != 202202L
-# error "__cpp_lib_ranges_join_with should have the value 202202L in c++23"
-# endif
-# else
-# ifdef __cpp_lib_ranges_join_with
-# error "__cpp_lib_ranges_join_with should not be defined because it is unimplemented in libc++!"
-# endif
+# ifndef __cpp_lib_ranges_join_with
+# error "__cpp_lib_ranges_join_with should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_join_with != 202202L
+# error "__cpp_lib_ranges_join_with should have the value 202202L in c++23"
# endif
# ifndef __cpp_lib_ranges_repeat
@@ -7549,17 +7543,11 @@
# error "__cpp_lib_ranges_iota should have the value 202202L in c++26"
# endif
-# if !defined(_LIBCPP_VERSION)
-# ifndef __cpp_lib_ranges_join_with
-# error "__cpp_lib_ranges_join_with should be defined in c++26"
-# endif
-# if __cpp_lib_ranges_join_with != 202202L
-# error "__cpp_lib_ranges_join_with should have the value 202202L in c++26"
-# endif
-# else
-# ifdef __cpp_lib_ranges_join_with
-# error "__cpp_lib_ranges_join_with should not be defined because it is unimplemented in libc++!"
-# endif
+# ifndef __cpp_lib_ranges_join_with
+# error "__cpp_lib_ranges_join_with should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_join_with != 202202L
+# error "__cpp_lib_ranges_join_with should have the value 202202L in c++26"
# endif
# ifndef __cpp_lib_ranges_repeat
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/ctor.default.pass.cpp
new file mode 100644
index 0000000000000..0d9df493305da
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/ctor.default.pass.cpp
@@ -0,0 +1,79 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// iterator() = default;
+
+#include <ranges>
+
+#include <cassert>
+#include <initializer_list>
+#include <iterator>
+#include <type_traits>
+#include <utility>
+
+#include "../types.h"
+#include "test_comparisons.h"
+#include "test_iterators.h"
+
+constexpr bool test() {
+ { // `V` and `Pattern` model forward range
+ using Inner = BasicVectorView<int, ViewProperties{}, forward_iterator>;
+ using V = BasicVectorView<Inner, ViewProperties{}, forward_iterator>;
+ using Pattern = Inner;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+ using Iter = std::ranges::iterator_t<JWV>;
+ using ConstIter = std::ranges::iterator_t<const JWV>;
+
+ // Default constructor of iterator<false> should not be explicit
+ Iter iter = {};
+ assert(testEquality(iter, Iter{}, true));
+
+ // Default constructor of iterator<true> should not be explicit
+ ConstIter citer = {};
+ assert(testEquality(citer, ConstIter{}, true));
+ assert(testEquality(iter, citer, true));
+
+ std::ranges::join_with_view<V, Pattern> jwv(V{Inner{1, 2}, Inner{2, 1}}, Pattern{3, 3});
+ Iter jwv_iter = jwv.begin();
+ ConstIter jwv_citer = std::as_const(jwv).begin();
+ assert(testEquality(jwv_iter, jwv_citer, true));
+
+ assert(testEquality(jwv_iter, iter, false));
+ assert(testEquality(jwv_iter, citer, false));
+ assert(testEquality(jwv_citer, iter, false));
+ assert(testEquality(jwv_citer, citer, false));
+ }
+
+ { // `InnerIter` is not default constructible (does not model forward iterator, JWV cannot be const-accessed)
+ using Inner = BasicVectorView<char, ViewProperties{.common = false}, EqComparableInputIter>;
+ using V = BasicVectorView<Inner, ViewProperties{.common = false}, forward_iterator>;
+ using Pattern = BasicVectorView<char, ViewProperties{}, forward_iterator>;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+ using Iter = std::ranges::iterator_t<JWV>;
+
+ Iter iter;
+ assert(testEquality(iter, Iter{}, true));
+
+ std::ranges::join_with_view<V, Pattern> jwv(V{Inner{'a', 'b'}, Inner{'c', 'd'}}, Pattern{',', ' '});
+ Iter jwv_iter = jwv.begin();
+ assert(testEquality(jwv_iter, iter, false));
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/ctor.not_const.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/ctor.not_const.pass.cpp
new file mode 100644
index 0000000000000..0ca31392ed8fa
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/ctor.not_const.pass.cpp
@@ -0,0 +1,111 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// constexpr iterator(iterator<!Const> i)
+// requires Const && convertible_to<iterator_t<V>, OuterIter> &&
+// convertible_to<iterator_t<InnerRng>, InnerIter> &&
+// convertible_to<iterator_t<Pattern>, PatternIter>;
+
+#include <ranges>
+
+#include <cassert>
+#include <vector>
+
+#include "../types.h"
+
+constexpr bool test() {
+ { // Regular conversion from `!Const` to `Const` iterator
+ std::vector<std::vector<int>> vec = {{1, 2}, {3, 4}, {5, 6}};
+ int pattern = 0;
+ std::ranges::join_with_view jwv(vec, pattern);
+
+ using JWV = decltype(jwv);
+ using Iter = std::ranges::iterator_t<JWV>;
+ using CIter = std::ranges::iterator_t<const JWV>;
+ static_assert(!std::same_as<Iter, CIter>);
+ static_assert(std::convertible_to<Iter, CIter>);
+ static_assert(std::constructible_from<CIter, Iter>);
+
+ Iter it = jwv.begin();
+ assert(*it == 1);
+
+ const CIter cit1 = it; // `cit1` points to element of `V`; this constructor should not be explicit
+ assert(*cit1 == 1);
+ assert(cit1 == it);
+
+ std::ranges::advance(it, 2);
+ assert(*it == 0);
+ CIter cit2 = it; // `cit2` points to element of `Pattern`
+ assert(*cit2 == 0);
+ assert(cit2 == it);
+
+ ++it;
+ assert(*it == 3);
+ CIter cit3 = it;
+ assert(*cit3 == 3);
+ assert(cit3 == it);
+
+ --cit3;
+ assert(cit2 == cit3);
+ }
+
+ { // Test conversion from `Const` to `!Const` (should be invalid)
+ using V = std::vector<std::vector<int>>;
+ using Pattern = std::ranges::single_view<int>;
+ using JWV = std::ranges::join_with_view<std::views::all_t<V>, Pattern>;
+ using Iter = std::ranges::iterator_t<JWV>;
+ using CIter = std::ranges::iterator_t<const JWV>;
+ static_assert(!std::convertible_to<CIter, Iter>);
+ static_assert(!std::constructible_from<Iter, CIter>);
+ }
+
+ { // When `convertible_to<iterator_t<V>, OuterIter>` is not modeled
+ using Inner = std::vector<short>;
+ using V = ConstOppositeView<Inner>;
+ using Pattern = std::ranges::single_view<short>;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+ using Iter = std::ranges::iterator_t<JWV>;
+ using CIter = std::ranges::iterator_t<const JWV>;
+ static_assert(!std::convertible_to<CIter, Iter>);
+ static_assert(!std::constructible_from<Iter, CIter>);
+ }
+
+ { // When `convertible_to<iterator_t<InnerRng>, InnerIter>` is not modeled
+ using Inner = ConstOppositeView<long>;
+ using V = std::vector<Inner>;
+ using Pattern = std::ranges::single_view<long>;
+ using JWV = std::ranges::join_with_view<std::views::all_t<V>, Pattern>;
+ using Iter = std::ranges::iterator_t<JWV>;
+ using CIter = std::ranges::iterator_t<const JWV>;
+ static_assert(!std::convertible_to<CIter, Iter>);
+ static_assert(!std::constructible_from<Iter, CIter>);
+ }
+
+ { // When `convertible_to<iterator_t<Pattern>, PatternIter>` is not modeled
+ using V = std::vector<std::vector<long long>>;
+ using Pattern = ConstOppositeView<long long>;
+ using JWV = std::ranges::join_with_view<std::views::all_t<V>, Pattern>;
+ using Iter = std::ranges::iterator_t<JWV>;
+ using CIter = std::ranges::iterator_t<const JWV>;
+ static_assert(!std::convertible_to<CIter, Iter>);
+ static_assert(!std::constructible_from<Iter, CIter>);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/decrement.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/decrement.pass.cpp
new file mode 100644
index 0000000000000..207d60a7296fc
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/decrement.pass.cpp
@@ -0,0 +1,283 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// constexpr iterator& operator--()
+// requires ref-is-glvalue && bidirectional_range<Base> &&
+// bidirectional-common<InnerBase> && bidirectional-common<PatternBase>;
+// constexpr iterator operator--(int)
+// requires ref-is-glvalue && bidirectional_range<Base> &&
+// bidirectional-common<InnerBase> && bidirectional-common<PatternBase>;
+
+#include <ranges>
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <forward_list>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "../types.h"
+
+template <class I>
+concept CanPreDecrement = requires(I& i) {
+ { --i } -> std::same_as<I&>;
+};
+
+template <class I>
+concept CanPostDecrement = requires(I& i) {
+ { i-- } -> std::same_as<I>;
+};
+
+template <class I>
+concept CanDecrement = CanPreDecrement<I> && CanPostDecrement<I>;
+
+constexpr bool test() {
+ { // `V` and `Pattern` are not empty. Test return type too.
+ using V = std::ranges::owning_view<std::vector<std::string>>;
+ using Pattern = std::ranges::single_view<char>;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+
+ using Iter = std::ranges::iterator_t<JWV>;
+ using CIter = std::ranges::iterator_t<const JWV>;
+ static_assert(CanDecrement<Iter>);
+ static_assert(CanDecrement<CIter>);
+
+ JWV jwv(V{{"01", "23", "45"}}, Pattern{'_'});
+
+ {
+ auto it = jwv.end();
+ std::same_as<Iter&> decltype(auto) it_ref = --it;
+ assert(it_ref == it);
+ assert(*it == '5');
+ std::same_as<Iter> decltype(auto) it_copy = it--;
+ assert(--it_copy == it);
+ --it;
+ assert(*it == '_');
+ it--;
+ assert(*it == '3');
+ --it;
+ it--;
+ assert(*it == '_');
+ }
+
+ {
+ auto cit = std::as_const(jwv).end();
+ std::same_as<CIter&> decltype(auto) cit_ref = --cit;
+ assert(cit_ref == cit);
+ assert(*cit == '5');
+ std::same_as<CIter> decltype(auto) cit_copy = cit--;
+ assert(--cit_copy == cit);
+ --cit;
+ assert(*cit == '_');
+ cit--;
+ assert(*cit == '3');
+ --cit;
+ cit--;
+ assert(*cit == '_');
+ }
+
+ assert(std::ranges::equal(std::views::reverse(std::move(jwv)), std::string_view{"54_32_10"}));
+ }
+
+ { // `Pattern` is empty, `V` is not.
+ using Inner = std::array<int, 1>;
+ using V = std::ranges::owning_view<std::array<Inner, 3>>;
+ using Pattern = std::ranges::owning_view<std::array<int, 0>>;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+
+ JWV jwv(V{{Inner{-9}, Inner{-99}, Inner{-999}}}, Pattern{});
+
+ {
+ auto it = jwv.end();
+ --it;
+ assert(*it == -999);
+ it--;
+ assert(*it == -99);
+ --it;
+ assert(*it == -9);
+ assert(it == jwv.begin());
+ }
+
+ {
+ auto cit = std::as_const(jwv).end();
+ --cit;
+ assert(*cit == -999);
+ cit--;
+ assert(*cit == -99);
+ --cit;
+ assert(*cit == -9);
+ assert(cit == std::as_const(jwv).begin());
+ }
+ }
+
+#if !defined(TEST_COMPILER_GCC) // GCC c++/101777
+ { // `V` has empty subrange in the middle, `Pattern` is not empty. Try to go back and forth.
+ using V = std::array<std::vector<int>, 3>;
+ using Pattern = std::ranges::single_view<int>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, Pattern>;
+
+ JWV jwv(V{{{5}, {}, {125}}}, Pattern{1});
+
+ {
+ auto it = jwv.end();
+ --it;
+ assert(*it == 125);
+ it--;
+ assert(*it == 1);
+ --it;
+ assert(*it == 1);
+ it--;
+ assert(*it == 5);
+ ++it;
+ assert(*it == 1);
+ --it;
+ assert(*it == 5);
+ std::ranges::advance(it, 4);
+ it--;
+ assert(*it == 125);
+ }
+
+ {
+ auto cit = std::as_const(jwv).end();
+ --cit;
+ assert(*cit == 125);
+ cit--;
+ assert(*cit == 1);
+ --cit;
+ assert(*cit == 1);
+ cit--;
+ assert(*cit == 5);
+ ++cit;
+ assert(*cit == 1);
+ --cit;
+ assert(*cit == 5);
+ std::ranges::advance(cit, 4);
+ cit--;
+ assert(*cit == 125);
+ }
+ }
+
+ { // Only first element of `V` is not empty. `Pattern` is empty. Try to go back and forth.
+ using Inner = std::vector<int>;
+ using V = std::ranges::owning_view<std::array<Inner, 3>>;
+ using Pattern = std::ranges::empty_view<int>;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+
+ JWV jwv(V{{Inner{999}, {}, {}}}, Pattern{});
+
+ {
+ auto it = jwv.end();
+ --it;
+ assert(*it == 999);
+ ++it;
+ assert(it == jwv.end());
+ it--;
+ assert(*it == 999);
+ }
+
+ {
+ auto cit = std::as_const(jwv).end();
+ --cit;
+ assert(*cit == 999);
+ ++cit;
+ assert(cit == std::as_const(jwv).end());
+ cit--;
+ assert(*cit == 999);
+ }
+ }
+#endif // !defined(TEST_COMPILER_GCC)
+
+ { // `ref-is-glvalue` is false
+ using V = RvalueVector<std::vector<int>>;
+ using Pattern = std::ranges::empty_view<int>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, std::ranges::owning_view<Pattern>>;
+ using Iter = std::ranges::iterator_t<JWV>;
+ static_assert(!CanPreDecrement<Iter>);
+ static_assert(!CanPostDecrement<Iter>);
+ }
+
+ { // `Base` does not model bidirectional range
+ using V = std::ranges::owning_view<std::forward_list<std::vector<int>>>;
+ using Pattern = std::ranges::single_view<int>;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+ using Iter = std::ranges::iterator_t<JWV>;
+ using CIter = std::ranges::iterator_t<const JWV>;
+ static_assert(!CanPreDecrement<Iter>);
+ static_assert(!CanPostDecrement<Iter>);
+ static_assert(!CanPreDecrement<CIter>);
+ static_assert(!CanPostDecrement<CIter>);
+ }
+
+ { // InnerBase does not model bidirectional-common
+ { // InnerBase does not model bidirectional range
+ using V = std::ranges::owning_view<std::vector<std::forward_list<int>>>;
+ using Pattern = std::ranges::single_view<int>;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+ using Iter = std::ranges::iterator_t<JWV>;
+ using CIter = std::ranges::iterator_t<const JWV>;
+ static_assert(!CanPreDecrement<Iter>);
+ static_assert(!CanPostDecrement<Iter>);
+ static_assert(!CanPreDecrement<CIter>);
+ static_assert(!CanPostDecrement<CIter>);
+ }
+
+ { // InnerBase does not model common range
+ using InnerBase = BasicVectorView<int, ViewProperties{.common = false}, bidirectional_iterator>;
+ using V = std::ranges::owning_view<std::vector<InnerBase>>;
+ using Pattern = std::ranges::single_view<int>;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+ using Iter = std::ranges::iterator_t<JWV>;
+ using CIter = std::ranges::iterator_t<const JWV>;
+ static_assert(!CanPreDecrement<Iter>);
+ static_assert(!CanPostDecrement<Iter>);
+ static_assert(!CanPreDecrement<CIter>);
+ static_assert(!CanPostDecrement<CIter>);
+ }
+ }
+
+ { // PatternBase does not model bidirectional-common
+ { // PatternBase does not model bidirectional range
+ using V = std::ranges::owning_view<std::vector<std::vector<int>>>;
+ using Pattern = std::ranges::owning_view<std::forward_list<int>>;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+ using Iter = std::ranges::iterator_t<JWV>;
+ using CIter = std::ranges::iterator_t<const JWV>;
+ static_assert(!CanPreDecrement<Iter>);
+ static_assert(!CanPostDecrement<Iter>);
+ static_assert(!CanPreDecrement<CIter>);
+ static_assert(!CanPostDecrement<CIter>);
+ }
+
+ { // PatternBase does not model common range
+ using V = std::ranges::owning_view<std::vector<std::vector<int>>>;
+ using Pattern = BasicVectorView<int, ViewProperties{.common = false}, bidirectional_iterator>;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+ using Iter = std::ranges::iterator_t<JWV>;
+ using CIter = std::ranges::iterator_t<const JWV>;
+ static_assert(!CanPreDecrement<Iter>);
+ static_assert(!CanPostDecrement<Iter>);
+ static_assert(!CanPreDecrement<CIter>);
+ static_assert(!CanPostDecrement<CIter>);
+ }
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/deref.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/deref.pass.cpp
new file mode 100644
index 0000000000000..b2eeddd2941d6
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/deref.pass.cpp
@@ -0,0 +1,225 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// constexpr decltype(auto) operator*() const;
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <cstddef>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "../types.h"
+
+struct ProxyRef {
+ int& val;
+};
+
+class CommonProxyRef {
+public:
+ constexpr CommonProxyRef(ProxyRef i) : val(i.val) {}
+ constexpr CommonProxyRef(int i) : val(i) {}
+
+ constexpr int get() const { return val; }
+
+private:
+ int val;
+};
+
+template <template <class> class TQual, template <class> class UQual>
+struct std::basic_common_reference<ProxyRef, int, TQual, UQual> {
+ using type = CommonProxyRef;
+};
+
+template <template <class> class TQual, template <class> class UQual>
+struct std::basic_common_reference<int, ProxyRef, TQual, UQual> {
+ using type = CommonProxyRef;
+};
+
+static_assert(std::common_reference_with<int&, ProxyRef>);
+static_assert(std::common_reference_with<int&, CommonProxyRef>);
+
+class ProxyIter {
+public:
+ using value_type = int;
+ using difference_type = std::ptrdiff_t;
+
+ constexpr ProxyIter() : ptr_(nullptr) {}
+ constexpr explicit ProxyIter(int* p) : ptr_(p) {}
+
+ constexpr ProxyRef operator*() const { return ProxyRef{*ptr_}; }
+
+ constexpr ProxyIter& operator++() {
+ ++ptr_;
+ return *this;
+ }
+
+ constexpr ProxyIter operator++(int) {
+ ProxyIter tmp = *this;
+ ++ptr_;
+ return tmp;
+ }
+
+ constexpr bool operator==(const ProxyIter& other) const { return ptr_ == other.ptr_; }
+
+private:
+ int* ptr_;
+};
+
+static_assert(std::forward_iterator<ProxyIter>);
+
+constexpr bool test() {
+ { // Result of `operator*` is (maybe const) lvalue reference
+ using V = std::ranges::owning_view<std::vector<std::string>>;
+ using Pattern = std::ranges::owning_view<std::string>;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+
+ JWV jwv(V{{"ab", "cd", "ef"}}, Pattern{"><"});
+
+ {
+ auto it = jwv.begin();
+ std::same_as<char&> decltype(auto) v_ref = *std::as_const(it);
+ assert(v_ref == 'a');
+ std::ranges::advance(it, 2);
+ std::same_as<char&> decltype(auto) pattern_ref = *it;
+ assert(pattern_ref == '>');
+ }
+
+ {
+ auto cit = std::as_const(jwv).begin();
+ std::same_as<const char&> decltype(auto) cv_ref = *cit;
+ assert(cv_ref == 'a');
+ std::ranges::advance(cit, 3);
+ std::same_as<const char&> decltype(auto) cpattern_ref = *std::as_const(cit);
+ assert(cpattern_ref == '<');
+ }
+ }
+
+ { // Result of `operator*` is const lvalue reference
+ using V = std::ranges::owning_view<std::vector<std::string_view>>;
+ using Pattern = std::string_view;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+
+ JWV jwv(V{{"123", "456", "789"}}, Pattern{"._."});
+
+ {
+ auto it = jwv.begin();
+ std::same_as<const char&> decltype(auto) v_ref = *it;
+ assert(v_ref == '1');
+ std::ranges::advance(it, 3);
+ std::same_as<const char&> decltype(auto) pattern_ref = *std::as_const(it);
+ assert(pattern_ref == '.');
+ }
+
+ {
+ auto cit = std::as_const(jwv).begin();
+ std::same_as<const char&> decltype(auto) cv_ref = *std::as_const(cit);
+ assert(cv_ref == '1');
+ std::ranges::advance(cit, 4);
+ std::same_as<const char&> decltype(auto) cpattern_ref = *cit;
+ assert(cpattern_ref == '_');
+ }
+ }
+
+ { // Result of `operator*` is prvalue
+ using V = std::vector<std::string_view>;
+ using Pattern = RvalueVector<char>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, std::ranges::owning_view<Pattern>>;
+
+ JWV jwv(V{"x^2", "y^2", "z^2"}, Pattern{{' ', '+', ' '}});
+
+ {
+ auto it = jwv.begin();
+ std::same_as<char> decltype(auto) v_ref = *std::as_const(it);
+ assert(v_ref == 'x');
+ std::ranges::advance(it, 3);
+ std::same_as<char> decltype(auto) pattern_ref = *it;
+ assert(pattern_ref == ' ');
+ }
+
+ {
+ auto cit = std::as_const(jwv).begin();
+ std::same_as<char> decltype(auto) cv_ref = *cit;
+ assert(cv_ref == 'x');
+ std::ranges::advance(cit, 4);
+ std::same_as<char> decltype(auto) cpattern_ref = *std::as_const(cit);
+ assert(cpattern_ref == '+');
+ }
+ }
+
+ { // Result of `operator*` is (maybe const) rvalue reference
+ using Inner = std::ranges::as_rvalue_view<std::ranges::owning_view<std::string>>;
+ using V = std::ranges::owning_view<std::vector<Inner>>;
+ using Pattern = std::ranges::as_rvalue_view<std::ranges::owning_view<std::array<char, 2>>>;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+
+ std::vector<Inner> vec;
+ vec.emplace_back(Inner{{"x*y"}});
+ vec.emplace_back(Inner{{"y*z"}});
+ vec.emplace_back(Inner{{"z*x"}});
+ JWV jwv(V(std::move(vec)), Pattern(std::array{',', ' '}));
+
+ {
+ auto it = jwv.begin();
+ std::same_as<char&&> decltype(auto) v_ref = *it;
+ assert(v_ref == 'x');
+ std::ranges::advance(it, 3);
+ std::same_as<char&&> decltype(auto) pattern_ref = *std::as_const(it);
+ assert(pattern_ref == ',');
+ }
+
+ {
+ auto cit = std::as_const(jwv).begin();
+ std::same_as<const char&&> decltype(auto) cv_ref = *std::as_const(cit);
+ assert(cv_ref == 'x');
+ std::ranges::advance(cit, 4);
+ std::same_as<const char&&> decltype(auto) cpattern_ref = *cit;
+ assert(cpattern_ref == ' ');
+ }
+ }
+
+ { // Result of `operator*` is type different from range_reference_t<InnerRng> and range_reference_t<Pattern>
+ using Inner = std::vector<int>;
+ using V = std::vector<Inner>;
+ using Pattern = std::ranges::subrange<ProxyIter, ProxyIter>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, Pattern>;
+
+ static_assert(!std::same_as<std::ranges::range_reference_t<V>, std::ranges::range_reference_t<JWV>>);
+ static_assert(!std::same_as<std::ranges::range_reference_t<Pattern>, std::ranges::range_reference_t<JWV>>);
+
+ std::array<int, 2> pattern = {-1, -1};
+ Pattern pattern_as_subrange(ProxyIter{pattern.data()}, ProxyIter{pattern.data() + pattern.size()});
+
+ JWV jwv(V{Inner{1, 1}, Inner{2, 2}, Inner{3, 3}}, pattern_as_subrange);
+
+ auto it = jwv.begin();
+ std::same_as<CommonProxyRef> decltype(auto) v_ref = *it;
+ assert(v_ref.get() == 1);
+ std::ranges::advance(it, 7);
+ std::same_as<CommonProxyRef> decltype(auto) pattern_ref = *std::as_const(it);
+ assert(pattern_ref.get() == -1);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/eq.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/eq.pass.cpp
new file mode 100644
index 0000000000000..daf07c854933b
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/eq.pass.cpp
@@ -0,0 +1,259 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// friend constexpr bool operator==(const iterator& x, const iterator& y)
+// requires ref-is-glvalue && forward_range<Base> &&
+// equality_comparable<InnerIter>;
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <utility>
+
+#include "../types.h"
+#include "test_comparisons.h"
+
+template <class I1, class I2 = I1>
+concept CanEq = requires(const I1& i1, const I2& i2) {
+ { i1 == i2 } -> std::same_as<bool>;
+ { i2 == i1 } -> std::same_as<bool>;
+ { i1 != i2 } -> std::same_as<bool>;
+ { i2 != i1 } -> std::same_as<bool>;
+};
+
+constexpr bool test() {
+ { // `V` and `Pattern` are not empty. Test return types too.
+ using V = std::array<std::array<int, 2>, 3>;
+ using Pattern = std::array<long, 1>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, std::ranges::owning_view<Pattern>>;
+
+ using Iter = std::ranges::iterator_t<JWV>;
+ using CIter = std::ranges::iterator_t<const JWV>;
+ static_assert(!std::same_as<Iter, CIter>);
+ static_assert(CanEq<Iter>);
+ static_assert(CanEq<CIter>);
+ static_assert(CanEq<Iter, CIter>);
+
+ JWV jwv(V{{{9, 8}, {7, 6}, {5, 4}}}, Pattern{0L});
+
+ Iter it1 = jwv.begin();
+ assert(*it1 == 9);
+ assert(testEquality(it1, it1, true));
+
+ Iter it2 = std::ranges::prev(jwv.end());
+ assert(*it2 == 4);
+ assert(testEquality(it2, it2, true));
+ assert(testEquality(it1, it2, false));
+
+ CIter cit1 = std::as_const(jwv).begin();
+ assert(*cit1 == 9);
+ assert(testEquality(cit1, cit1, true));
+ assert(testEquality(it1, cit1, true));
+ assert(testEquality(it2, cit1, false));
+
+ CIter cit2 = std::ranges::prev(std::as_const(jwv).end());
+ assert(*cit2 == 4);
+ assert(testEquality(cit2, cit2, true));
+ assert(testEquality(cit1, cit2, false));
+ assert(testEquality(it1, cit2, false));
+ assert(testEquality(it2, cit2, true));
+
+ // `it1.inner_it_` and `it2.inner_it_` are equal, but `it1.outer_it_` and `it2.outer_it_` are not.
+ std::ranges::advance(it1, 2);
+ assert(*it1 == 0);
+ std::ranges::advance(it2, -2);
+ assert(*it2 == 0);
+ assert(testEquality(it1, it2, false));
+
+ // `cit1.inner_it_` and `cit2.inner_it_` are equal, but `cit1.outer_it_` and `cit2.outer_it_` are not.
+ std::ranges::advance(cit1, 2);
+ assert(*cit1 == 0);
+ assert(testEquality(it1, cit1, true));
+ std::ranges::advance(cit2, -2);
+ assert(*cit2 == 0);
+ assert(testEquality(it2, cit2, true));
+ assert(testEquality(cit1, cit2, false));
+
+ // `it1.inner_it_` and `it2.inner_it_` are equal, `it1.outer_it_` and `it2.outer_it_` are equal too.
+ // `it1.inner_it_index()` and `it2.inner_it_index()` are equal to 1.
+ ++it1;
+ assert(*it1 == 7);
+ std::ranges::advance(it2, -2);
+ assert(*it2 == 7);
+ assert(testEquality(it1, it2, true));
+
+ // `cit1.inner_it_` and `cit2.inner_it_` are equal, `cit1.outer_it_` and `cit2.outer_it_` are equal too.
+ // `cit1.inner_it_index()` and `cit2.inner_it_index()` are equal to 1.
+ ++cit1;
+ assert(*cit1 == 7);
+ assert(testEquality(it1, cit1, true));
+ std::ranges::advance(cit2, -2);
+ assert(*cit2 == 7);
+ assert(testEquality(it2, cit2, true));
+ assert(testEquality(cit1, cit2, true));
+
+ // `it1.inner_it_` and `it2.inner_it_` are equal, `it1.outer_it_` and `it2.outer_it_` are equal too.
+ // `it1.inner_it_index()` and `it2.inner_it_index()` are equal to 0.
+ --it1;
+ assert(*it1 == 0);
+ --it2;
+ assert(*it2 == 0);
+ assert(testEquality(it1, it2, true));
+
+ // `cit1.inner_it_` and `cit2.inner_it_` are equal, `cit1.outer_it_` and `cit2.outer_it_` are equal too.
+ // `cit1.inner_it_index()` and `cit2.inner_it_index()` are equal to 0.
+ --cit1;
+ assert(*cit1 == 0);
+ assert(testEquality(it1, cit1, true));
+ --cit2;
+ assert(*cit2 == 0);
+ assert(testEquality(it2, cit2, true));
+ assert(testEquality(cit2, cit2, true));
+ }
+
+ { // `InnerIter` models input iterator and equality comparable. `Pattern` is empty.
+ using Inner = BasicVectorView<int, ViewProperties{.common = false}, EqComparableInputIter>;
+ using V = std::vector<Inner>;
+ using Pattern = std::ranges::empty_view<int>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, std::ranges::owning_view<Pattern>>;
+
+ using Iter = std::ranges::iterator_t<JWV>;
+ using CIter = std::ranges::iterator_t<const JWV>;
+ static_assert(!std::same_as<Iter, CIter>);
+ static_assert(CanEq<Iter>);
+ static_assert(CanEq<CIter>);
+ static_assert(!CanEq<CIter, Iter>);
+
+ JWV jwv(V{Inner{1, 2}, Inner{5, 6}, Inner{9, 0}}, Pattern{});
+
+ {
+ Iter it1 = jwv.begin();
+ assert(*it1 == 1);
+ Iter it2 = std::ranges::next(jwv.begin(), 2);
+ assert(*it2 == 5);
+ assert(testEquality(it1, it2, false));
+ ++it1;
+ ++it1;
+ assert(testEquality(it1, it2, true));
+ ++it1;
+ assert(testEquality(it1, it2, false));
+ }
+
+ {
+ CIter cit1 = std::as_const(jwv).begin();
+ assert(*cit1 == 1);
+ CIter cit2 = std::ranges::next(std::as_const(jwv).begin(), 2);
+ assert(*cit2 == 5);
+ assert(testEquality(cit1, cit2, false));
+ ++cit1;
+ ++cit1;
+ assert(testEquality(cit1, cit2, true));
+ ++cit1;
+ assert(testEquality(cit1, cit2, false));
+ }
+ }
+
+ { // `Pattern` is not empty. Some elements of `V` are.
+ using Inner = BasicVectorView<int, ViewProperties{.common = false}, EqComparableInputIter>;
+ using V = BasicVectorView<Inner, ViewProperties{}, forward_iterator>;
+ using Pattern = BasicVectorView<int, ViewProperties{.common = false}, forward_iterator>;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+
+ using Iter = std::ranges::iterator_t<JWV>;
+ using CIter = std::ranges::iterator_t<const JWV>;
+ static_assert(!std::same_as<Iter, CIter>);
+ static_assert(CanEq<Iter>);
+ static_assert(CanEq<CIter>);
+ static_assert(!CanEq<CIter, Iter>);
+
+ JWV jwv(V{Inner{1}, Inner{}, Inner{27}}, Pattern{0});
+
+ {
+ Iter it1 = jwv.begin();
+ assert(*it1 == 1);
+ ++it1;
+ assert(*it1 == 0);
+ Iter it2 = jwv.begin();
+ assert(testEquality(it1, it2, false));
+ ++it2;
+ assert(testEquality(it1, it2, true));
+
+ ++it2;
+ assert(*it1 == *it2);
+ assert(testEquality(it1, it2, false));
+
+ std::ranges::advance(it1, 2);
+ ++it2;
+ assert(*it1 == *it2);
+ assert(testEquality(it1, it2, true));
+ }
+
+ {
+ CIter cit1 = std::as_const(jwv).begin();
+ assert(*cit1 == 1);
+ ++cit1;
+ assert(*cit1 == 0);
+ CIter cit2 = std::as_const(jwv).begin();
+ assert(testEquality(cit1, cit2, false));
+ ++cit2;
+ assert(testEquality(cit1, cit2, true));
+
+ ++cit2;
+ assert(*cit1 == *cit2);
+ assert(testEquality(cit1, cit2, false));
+
+ std::ranges::advance(cit1, 2);
+ ++cit2;
+ assert(*cit1 == *cit2);
+ assert(testEquality(cit1, cit2, true));
+ }
+ }
+
+ { // `ref-is-glvalue` is false
+ using Inner = std::vector<int>;
+ using V = RvalueVector<Inner>;
+ using Pattern = std::ranges::empty_view<int>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, std::ranges::owning_view<Pattern>>;
+ using Iter = std::ranges::iterator_t<JWV>;
+ static_assert(!CanEq<Iter>);
+ }
+
+ { // `Base` does not model forward range
+ using Inner = std::vector<int>;
+ using V = BasicVectorView<Inner, ViewProperties{}, DefaultCtorInputIter>;
+ using Pattern = std::ranges::empty_view<int>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, std::ranges::owning_view<Pattern>>;
+ using Iter = std::ranges::iterator_t<JWV>;
+ static_assert(!CanEq<Iter>);
+ }
+
+ { // `InnerIter` does not model equality comparable
+ using Inner = BasicVectorView<int, ViewProperties{.common = false}, cpp20_input_iterator>;
+ using V = std::vector<Inner>;
+ using Pattern = std::ranges::empty_view<int>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, std::ranges::owning_view<Pattern>>;
+ using Iter = std::ranges::iterator_t<JWV>;
+ using CIter = std::ranges::iterator_t<const JWV>;
+ static_assert(!CanEq<Iter>);
+ static_assert(!CanEq<CIter>);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/increment.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/increment.pass.cpp
new file mode 100644
index 0000000000000..af14b516b2deb
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/increment.pass.cpp
@@ -0,0 +1,372 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// constexpr iterator& operator++();
+// constexpr void operator++(int);
+// constexpr iterator operator++(int)
+// requires ref-is-glvalue && forward_iterator<OuterIter> &&
+// forward_iterator<InnerIter>;
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <type_traits>
+#include <vector>
+
+#include "../types.h"
+
+template <class I>
+concept CanPreIncrement = requires(I& i) { ++i; };
+
+template <class I>
+concept CanPostIncrement = requires(I& i) { i++; };
+
+template <bool RefIsGlvalue, class Inner>
+using VRange = std::conditional_t<RefIsGlvalue, std::vector<Inner>, RvalueVector<Inner>>;
+
+template <bool RefIsGlvalue>
+constexpr void test_pre_increment() {
+ { // `V` and `Pattern` are not empty. Test return type too.
+ using V = VRange<RefIsGlvalue, std::array<int, 2>>;
+ using Pattern = std::array<int, 2>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, std::ranges::owning_view<Pattern>>;
+
+ JWV jwv(V{{1, 1}, {2, 2}, {3, 3}}, Pattern{0, 0});
+
+ {
+ using Iter = std::ranges::iterator_t<JWV>;
+ static_assert(CanPreIncrement<Iter>);
+ static_assert(!CanPreIncrement<const Iter>);
+
+ auto it = jwv.begin();
+ assert(*it == 1);
+ std::same_as<Iter&> decltype(auto) it_ref = ++it;
+ if constexpr (RefIsGlvalue) {
+ assert(it_ref == it);
+ }
+
+ ++it;
+ assert(*it == 0);
+ ++it_ref;
+ ++it_ref;
+ assert(*it_ref == 2);
+ ++it;
+ ++it_ref;
+ assert(*it == 0);
+ }
+
+ if constexpr (RefIsGlvalue) {
+ using CIter = std::ranges::iterator_t<const JWV>;
+ static_assert(CanPreIncrement<CIter>);
+ static_assert(!CanPreIncrement<const CIter>);
+
+ auto cit = std::as_const(jwv).begin();
+ assert(*cit == 1);
+ std::same_as<CIter&> decltype(auto) cit_ref = ++cit;
+ assert(cit_ref == cit);
+ ++cit;
+ assert(*cit == 0);
+ ++cit_ref;
+ ++cit_ref;
+ assert(*cit_ref == 2);
+ ++cit;
+ ++cit_ref;
+ assert(*cit == 0);
+ }
+ }
+
+ { // `V` and `Pattern` are empty.
+ using V = VRange<RefIsGlvalue, std::ranges::empty_view<int>>;
+ using Pattern = std::ranges::empty_view<int>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, std::ranges::owning_view<Pattern>>;
+
+ JWV jwv = {};
+
+ {
+ auto it = jwv.begin();
+ assert(it == jwv.end());
+ }
+
+ if constexpr (RefIsGlvalue) {
+ auto cit = std::as_const(jwv).begin();
+ assert(cit == std::as_const(jwv).end());
+ }
+ }
+
+ { // `Pattern` is empty, `V` is not.
+ using V = VRange<RefIsGlvalue, std::vector<int>>;
+ using Pattern = std::vector<int>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, std::ranges::owning_view<Pattern>>;
+
+ JWV jwv(V{{{-1}, {-2}, {-3}}}, Pattern{});
+
+ {
+ auto it = jwv.begin();
+ assert(*it == -1);
+ ++it;
+ assert(*it == -2);
+ ++it;
+ assert(*it == -3);
+ ++it;
+ assert(it == jwv.end());
+ }
+
+ if constexpr (RefIsGlvalue) {
+ auto cit = std::as_const(jwv).begin();
+ assert(*cit == -1);
+ ++cit;
+ assert(*cit == -2);
+ ++cit;
+ assert(*cit == -3);
+ ++cit;
+ assert(cit == std::as_const(jwv).end());
+ }
+ }
+
+ { // `V` has empty subrange in the middle, `Pattern` is not empty.
+ using V = VRange<RefIsGlvalue, std::vector<int>>;
+ using Pattern = std::ranges::single_view<int>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, Pattern>;
+
+ JWV jwv(V{{1}, {}, {3}}, Pattern{0});
+
+ {
+ auto it = jwv.begin();
+ assert(*it == 1);
+ ++it;
+ assert(*it == 0);
+ ++it;
+ assert(*it == 0);
+ ++it;
+ assert(*it == 3);
+ }
+
+ if constexpr (RefIsGlvalue) {
+ auto cit = std::as_const(jwv).begin();
+ assert(*cit == 1);
+ ++cit;
+ assert(*cit == 0);
+ ++cit;
+ assert(*cit == 0);
+ ++cit;
+ assert(*cit == 3);
+ }
+ }
+
+ { // Only last element of `V` is not empty. `Pattern` is not empty.
+ using V = VRange<RefIsGlvalue, std::vector<int>>;
+ using Pattern = std::ranges::single_view<int>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, Pattern>;
+
+ JWV jwv(V{{}, {}, {555}}, Pattern{1});
+
+ {
+ auto it = jwv.begin();
+ assert(*it == 1);
+ ++it;
+ assert(*it == 1);
+ ++it;
+ assert(*it == 555);
+ ++it;
+ assert(it == jwv.end());
+ }
+
+ if constexpr (RefIsGlvalue) {
+ auto cit = std::as_const(jwv).begin();
+ assert(*cit == 1);
+ ++cit;
+ assert(*cit == 1);
+ ++cit;
+ assert(*cit == 555);
+ ++cit;
+ assert(cit == std::as_const(jwv).end());
+ }
+ }
+
+ { // Only first element of `V` is not empty. `Pattern` is empty.
+ using V = VRange<RefIsGlvalue, std::vector<int>>;
+ using Pattern = std::ranges::empty_view<int>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, Pattern>;
+
+ JWV jwv(V{{777}, {}, {}}, Pattern{});
+
+ {
+ auto it = jwv.begin();
+ assert(*it == 777);
+ ++it;
+ assert(it == jwv.end());
+ }
+
+ if constexpr (RefIsGlvalue) {
+ auto cit = std::as_const(jwv).begin();
+ assert(*cit == 777);
+ ++cit;
+ assert(cit == std::as_const(jwv).end());
+ }
+ }
+
+ { // Only last element of `V` is not empty. `Pattern` is empty. `V` models input range.
+ using V = BasicView<VRange<RefIsGlvalue, std::string>, ViewProperties{}, DefaultCtorInputIter>;
+ using Pattern = std::ranges::empty_view<char>;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+
+ JWV jwv(V{{}, {}, {'a'}}, Pattern{});
+
+ auto it = jwv.begin();
+ assert(*it == 'a');
+ ++it;
+ assert(it == jwv.end());
+ }
+
+ { // Only first element of `V` is not empty. `Pattern` is not empty. `V` models input range.
+ using V = BasicView<VRange<RefIsGlvalue, std::string>, ViewProperties{}, DefaultCtorInputIter>;
+ using Pattern = std::ranges::single_view<char>;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+
+ JWV jwv(V{{'b'}, {}, {}}, Pattern{'.'});
+
+ auto it = jwv.begin();
+ assert(*it == 'b');
+ ++it;
+ assert(*it == '.');
+ ++it;
+ assert(*it == '.');
+ ++it;
+ assert(it == jwv.end());
+ }
+}
+
+constexpr void test_post_increment() {
+ { // `V` and `Pattern` are not empty. Return type should be `iterator`.
+ using V = std::array<std::array<int, 3>, 2>;
+ using Pattern = std::array<int, 1>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, std::ranges::owning_view<Pattern>>;
+
+ using Iter = std::ranges::iterator_t<JWV>;
+ using CIter = std::ranges::iterator_t<const JWV>;
+ static_assert(CanPostIncrement<Iter>);
+ static_assert(!CanPostIncrement<const Iter>);
+ static_assert(CanPostIncrement<CIter>);
+ static_assert(!CanPostIncrement<const CIter>);
+
+ JWV jwv(V{{{6, 5, 4}, {3, 2, 1}}}, Pattern{-5});
+
+ {
+ auto it = jwv.begin();
+ assert(*it == 6);
+ std::same_as<Iter> decltype(auto) it_copy = it++;
+ assert(++it_copy == it);
+ it++;
+ it++;
+ assert(*it == -5);
+ it_copy++;
+ it_copy++;
+ assert(*it_copy == -5);
+ it++;
+ it_copy++;
+ assert(*it == 3);
+ assert(*it_copy == 3);
+ }
+
+ {
+ auto cit = std::as_const(jwv).begin();
+ assert(*cit == 6);
+ std::same_as<CIter> decltype(auto) cit_copy = cit++;
+ assert(++cit_copy == cit);
+ cit++;
+ cit++;
+ assert(*cit == -5);
+ cit_copy++;
+ cit_copy++;
+ assert(*cit_copy == -5);
+ cit++;
+ cit_copy++;
+ assert(*cit == 3);
+ assert(*cit_copy == 3);
+ }
+ }
+
+ { // `Pattern` is empty, `V` is not. Value of `ref-is-glvalue` is false (return type should be `void`).
+ using Inner = std::vector<int>;
+ using V = RvalueVector<Inner>;
+ using Pattern = std::ranges::empty_view<int>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, std::ranges::owning_view<Pattern>>;
+
+ JWV jwv(V{Inner{-3}, Inner{-2}, Inner{-1}}, Pattern{});
+
+ auto it = jwv.begin();
+ assert(*it == -3);
+ it++;
+ assert(*it == -2);
+ it++;
+ assert(*it == -1);
+ it++;
+ assert(it == jwv.end());
+ static_assert(std::is_void_v<decltype(it++)>);
+ }
+
+ { // `V` has empty subrange in the middle, `Pattern` is not empty.
+ // OuterIter does not model forward iterator (return type should be `void`).
+ using Inner = std::vector<int>;
+ using V = BasicVectorView<Inner, ViewProperties{.common = false}, cpp20_input_iterator>;
+ using Pattern = std::ranges::single_view<int>;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+
+ JWV jwv(V{Inner{7}, {}, Inner{9}}, Pattern{8});
+
+ auto it = jwv.begin();
+ assert(*it == 7);
+ it++;
+ assert(*it == 8);
+ it++;
+ assert(*it == 8);
+ it++;
+ assert(*it == 9);
+ it++;
+ assert(it == jwv.end());
+ static_assert(std::is_void_v<decltype(it++)>);
+ }
+
+#if !defined(TEST_COMPILER_GCC) // GCC c++/101777
+ { // Only first element of `V` is not empty. `Pattern` is empty. InnerIter does not model forward
+ // iterator (return type should be `void`).
+ using Inner = BasicVectorView<char32_t, ViewProperties{.common = false}, cpp17_input_iterator>;
+ using V = std::array<Inner, 3>;
+ using Pattern = std::ranges::empty_view<char32_t>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, Pattern>;
+
+ JWV jwv(V{Inner{U'?'}, Inner{}, Inner{}}, Pattern{});
+
+ auto it = jwv.begin();
+ assert(*it == U'?');
+ it++;
+ assert(it == jwv.end());
+ static_assert(std::is_void_v<decltype(it++)>);
+ }
+#endif // !defined(TEST_COMPILER_GCC)
+}
+
+constexpr bool test() {
+ test_pre_increment<false>();
+ test_pre_increment<true>();
+ test_post_increment();
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/iter_move.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/iter_move.pass.cpp
new file mode 100644
index 0000000000000..4a68f10b11c31
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/iter_move.pass.cpp
@@ -0,0 +1,420 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// friend constexpr decltype(auto) iter_move(const iterator& x);
+
+#include <ranges>
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <cstddef>
+#include <utility>
+#include <vector>
+
+#include "../types.h"
+
+class MoveOnlyInt {
+public:
+ enum Status { constructed, move_constructed, moved_from_this };
+
+ MoveOnlyInt() = default;
+ constexpr MoveOnlyInt(int val) : val_(val) {}
+
+ constexpr MoveOnlyInt(MoveOnlyInt&& other) noexcept : val_(other.val_), status_(move_constructed) {
+ other.val_ = -1;
+ other.status_ = moved_from_this;
+ }
+
+ constexpr MoveOnlyInt(const MoveOnlyInt&& other) noexcept : val_(other.val_), status_(move_constructed) {
+ other.val_ = -1;
+ other.status_ = moved_from_this;
+ }
+
+ MoveOnlyInt(const MoveOnlyInt&) { assert(false); } // Should never be called in this test.
+
+ MoveOnlyInt& operator=(MoveOnlyInt&&) { // Should never be called in this test.
+ assert(false);
+ return *this;
+ }
+
+ constexpr bool was_normally_constructed() const { return status_ == constructed; }
+ constexpr bool was_move_constructed() const { return status_ == move_constructed; }
+ constexpr bool was_moved_from() const { return status_ == moved_from_this; }
+
+ friend constexpr bool operator==(const MoveOnlyInt& left, int right) { return left.val_ == right; }
+ friend constexpr bool operator==(const MoveOnlyInt& left, const MoveOnlyInt& right) {
+ return left.val_ == right.val_;
+ }
+
+private:
+ mutable int val_ = -1;
+ mutable Status status_ = constructed;
+};
+
+static_assert(std::movable<MoveOnlyInt>);
+
+struct ProxyRvalueRef {
+ MoveOnlyInt&& val;
+};
+
+class CommonProxyRvalueRef {
+public:
+ constexpr CommonProxyRvalueRef(ProxyRvalueRef i) : val_(std::move(i.val)) {}
+ constexpr CommonProxyRvalueRef(MoveOnlyInt i) : val_(std::move(i)) {}
+
+ constexpr MoveOnlyInt&& get() { return std::move(val_); }
+
+private:
+ MoveOnlyInt val_;
+};
+
+template <template <class> class TQual, template <class> class UQual>
+struct std::basic_common_reference<ProxyRvalueRef, MoveOnlyInt, TQual, UQual> {
+ using type = CommonProxyRvalueRef;
+};
+
+template <template <class> class TQual, template <class> class UQual>
+struct std::basic_common_reference<MoveOnlyInt, ProxyRvalueRef, TQual, UQual> {
+ using type = CommonProxyRvalueRef;
+};
+
+static_assert(std::common_reference_with<MoveOnlyInt&&, ProxyRvalueRef>);
+static_assert(std::common_reference_with<MoveOnlyInt&&, CommonProxyRvalueRef>);
+
+class ProxyIter {
+public:
+ using value_type = MoveOnlyInt;
+ using difference_type = std::ptrdiff_t;
+
+ constexpr ProxyIter() : ptr_(nullptr) {}
+ constexpr explicit ProxyIter(MoveOnlyInt* it) : ptr_(std::move(it)) {}
+
+ constexpr decltype(auto) operator*() const { return *ptr_; }
+
+ constexpr ProxyIter& operator++() {
+ ++ptr_;
+ return *this;
+ }
+
+ constexpr ProxyIter operator++(int) {
+ ProxyIter copy = *this;
+ ++ptr_;
+ return copy;
+ }
+
+ constexpr ProxyIter& operator--() {
+ --ptr_;
+ return *this;
+ }
+
+ constexpr ProxyIter operator--(int) {
+ ProxyIter copy = *this;
+ --ptr_;
+ return copy;
+ }
+
+ friend bool operator==(const ProxyIter&, const ProxyIter&) = default;
+
+ friend constexpr ProxyRvalueRef iter_move(const ProxyIter iter) {
+ return ProxyRvalueRef{std::ranges::iter_move(iter.ptr_)};
+ }
+
+private:
+ MoveOnlyInt* ptr_;
+};
+
+static_assert(std::forward_iterator<ProxyIter>);
+
+template <std::forward_iterator Iter>
+class IterMoveTrackingIterator {
+public:
+ using value_type = std::iter_value_t<Iter>;
+ using difference_type = std::iter_difference_t<Iter>;
+
+ IterMoveTrackingIterator() = default;
+ constexpr explicit IterMoveTrackingIterator(Iter iter, bool* flag = nullptr) : iter_(std::move(iter)), flag_(flag) {}
+
+ constexpr IterMoveTrackingIterator& operator++() {
+ ++iter_;
+ return *this;
+ }
+
+ constexpr IterMoveTrackingIterator operator++(int) {
+ auto tmp = *this;
+ ++*this;
+ return tmp;
+ }
+
+ constexpr decltype(auto) operator*() const { return *iter_; }
+
+ constexpr bool operator==(const IterMoveTrackingIterator& other) const { return iter_ == other.iter_; }
+
+ friend constexpr decltype(auto) iter_move(const IterMoveTrackingIterator& iter) {
+ assert(iter.flag_ != nullptr);
+ *iter.flag_ = true;
+ return std::ranges::iter_move(iter.iter_);
+ }
+
+private:
+ Iter iter_ = Iter();
+ bool* flag_ = nullptr;
+};
+
+static_assert(std::forward_iterator<IterMoveTrackingIterator<int*>> &&
+ !std::bidirectional_iterator<IterMoveTrackingIterator<int*>>);
+
+constexpr bool test() {
+ { // Test `iter_move` when result is true rvalue reference. Test return types.
+ using V = std::array<std::array<char, 1>, 2>;
+ using Pattern = std::array<char, 1>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, std::ranges::owning_view<Pattern>>;
+
+ JWV jwv(V{{{'0'}, {'1'}}}, Pattern{','});
+
+ {
+ auto it = jwv.begin();
+ std::same_as<char&&> decltype(auto) v_rref1 = iter_move(it);
+ std::same_as<char&&> decltype(auto) v_rref2 = iter_move(std::as_const(it));
+ std::same_as<char&&> decltype(auto) v_rref3 = std::ranges::iter_move(it);
+ std::same_as<char&&> decltype(auto) v_rref4 = std::ranges::iter_move(std::as_const(it));
+ assert(std::ranges::equal(std::array{v_rref1, v_rref2, v_rref3, v_rref4}, std::views::repeat('0', 4)));
+
+ ++it; // `it` points to element of `Pattern` from here
+ std::same_as<char&&> decltype(auto) pattern_rref1 = iter_move(it);
+ std::same_as<char&&> decltype(auto) pattern_rref2 = iter_move(std::as_const(it));
+ std::same_as<char&&> decltype(auto) pattern_rref3 = std::ranges::iter_move(it);
+ std::same_as<char&&> decltype(auto) pattern_rref4 = std::ranges::iter_move(std::as_const(it));
+ assert(std::ranges::equal(
+ std::array{pattern_rref1, pattern_rref2, pattern_rref3, pattern_rref4}, std::views::repeat(',', 4)));
+ }
+
+ {
+ auto cit = std::prev(std::as_const(jwv).end());
+ std::same_as<const char&&> decltype(auto) cv_rref1 = iter_move(cit);
+ std::same_as<const char&&> decltype(auto) cv_rref2 = iter_move(std::as_const(cit));
+ std::same_as<const char&&> decltype(auto) cv_rref3 = std::ranges::iter_move(cit);
+ std::same_as<const char&&> decltype(auto) cv_rref4 = std::ranges::iter_move(std::as_const(cit));
+ assert(std::ranges::equal(std::array{cv_rref1, cv_rref2, cv_rref3, cv_rref4}, std::views::repeat('1', 4)));
+
+ cit--; // `cit` points to element of `Pattern` from here
+ std::same_as<const char&&> decltype(auto) cpattern_rref1 = iter_move(cit);
+ std::same_as<const char&&> decltype(auto) cpattern_rref2 = iter_move(std::as_const(cit));
+ std::same_as<const char&&> decltype(auto) cpattern_rref3 = std::ranges::iter_move(cit);
+ std::same_as<const char&&> decltype(auto) cpattern_rref4 = std::ranges::iter_move(std::as_const(cit));
+ assert(std::ranges::equal(
+ std::array{cpattern_rref1, cpattern_rref2, cpattern_rref3, cpattern_rref4}, std::views::repeat(',', 4)));
+ }
+ }
+
+ { // Test `iter_move` when result is true rvalue reference. Test moving.
+ using Inner = std::vector<MoveOnlyInt>;
+ using V = std::vector<Inner>;
+ using Pattern = std::vector<MoveOnlyInt>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, std::ranges::owning_view<Pattern>>;
+
+ V v;
+ v.reserve(2);
+ v.emplace_back(std::ranges::to<Inner>(std::views::iota(0, 4)));
+ v.emplace_back(std::ranges::to<Inner>(std::views::iota(12, 16)));
+ JWV jwv(std::move(v), std::ranges::to<Pattern>(std::views::iota(4, 12)));
+ assert(std::ranges::all_of(jwv, &MoveOnlyInt::was_normally_constructed));
+
+ {
+ std::vector<MoveOnlyInt> values;
+ values.reserve(8);
+
+ auto it = jwv.begin();
+ values.emplace_back(iter_move(it));
+ ++it;
+ values.emplace_back(iter_move(std::as_const(it)));
+ it++;
+ values.emplace_back(std::ranges::iter_move(it));
+ ++it;
+ values.emplace_back(std::ranges::iter_move(std::as_const(it)));
+ it++; // `it` points to element of `Pattern` from here
+ values.emplace_back(iter_move(it));
+ ++it;
+ values.emplace_back(iter_move(std::as_const(it)));
+ it++;
+ values.emplace_back(std::ranges::iter_move(it));
+ ++it;
+ values.emplace_back(std::ranges::iter_move(std::as_const(it)));
+
+ assert(std::ranges::equal(values, std::views::iota(0, 8)));
+ assert(std::ranges::all_of(values, &MoveOnlyInt::was_move_constructed));
+ }
+
+ {
+ std::vector<MoveOnlyInt> values;
+ values.reserve(8);
+
+ auto cit = std::prev(std::as_const(jwv).end());
+ values.emplace_back(iter_move(cit));
+ cit--;
+ values.emplace_back(iter_move(std::as_const(cit)));
+ --cit;
+ values.emplace_back(std::ranges::iter_move(cit));
+ cit--;
+ values.emplace_back(std::ranges::iter_move(std::as_const(cit)));
+ --cit; // `it` points to element of `Pattern` from here
+ values.emplace_back(iter_move(cit));
+ cit--;
+ values.emplace_back(iter_move(std::as_const(cit)));
+ --cit;
+ values.emplace_back(std::ranges::iter_move(cit));
+ cit--;
+ values.emplace_back(std::ranges::iter_move(std::as_const(cit)));
+
+ assert(std::ranges::equal(std::views::reverse(values), std::views::iota(8, 16)));
+ assert(std::ranges::all_of(values, &MoveOnlyInt::was_move_constructed));
+ }
+
+ assert(std::ranges::all_of(jwv, &MoveOnlyInt::was_moved_from));
+ }
+
+ { // Test `iter_move` when result is proxy rvalue reference type, which is different from
+ // range_rvalue_reference_t<InnerRng> and range_rvalue_reference_t<Pattern>.
+ using Inner = std::vector<MoveOnlyInt>;
+ using V = std::vector<Inner>;
+ using Pattern = std::ranges::subrange<ProxyIter, ProxyIter>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, Pattern>;
+
+ static_assert(!std::same_as<std::ranges::range_rvalue_reference_t<V>, std::ranges::range_rvalue_reference_t<JWV>>);
+ static_assert(
+ !std::same_as<std::ranges::range_rvalue_reference_t<Pattern>, std::ranges::range_rvalue_reference_t<JWV>>);
+ static_assert(std::same_as<CommonProxyRvalueRef, std::ranges::range_rvalue_reference_t<JWV>>);
+
+ V v;
+ v.reserve(2);
+ v.emplace_back(std::ranges::to<Inner>(std::views::iota(0, 4)));
+ v.emplace_back(std::ranges::to<Inner>(std::views::iota(12, 16)));
+
+ auto pattern = std::ranges::to<std::vector<MoveOnlyInt>>(std::views::iota(4, 12));
+ Pattern pattern_as_subrange(ProxyIter{pattern.data()}, ProxyIter{pattern.data() + pattern.size()});
+
+ JWV jwv(std::move(v), pattern_as_subrange);
+ assert(std::ranges::all_of(jwv, &MoveOnlyInt::was_normally_constructed));
+
+ {
+ std::vector<MoveOnlyInt> values;
+ values.reserve(8);
+
+ auto it = jwv.begin();
+ std::same_as<CommonProxyRvalueRef> decltype(auto) rref1 = iter_move(it);
+ values.emplace_back(rref1.get());
+ ++it;
+ std::same_as<CommonProxyRvalueRef> decltype(auto) rref2 = iter_move(std::as_const(it));
+ values.emplace_back(rref2.get());
+ it++;
+ std::same_as<CommonProxyRvalueRef> decltype(auto) rref3 = std::ranges::iter_move(it);
+ values.emplace_back(rref3.get());
+ ++it;
+ std::same_as<CommonProxyRvalueRef> decltype(auto) rref4 = std::ranges::iter_move(std::as_const(it));
+ values.emplace_back(rref4.get());
+ it++; // `it` points to element of `Pattern` from here
+ std::same_as<CommonProxyRvalueRef> decltype(auto) rref5 = iter_move(it);
+ values.emplace_back(rref5.get());
+ ++it;
+ std::same_as<CommonProxyRvalueRef> decltype(auto) rref6 = iter_move(std::as_const(it));
+ values.emplace_back(rref6.get());
+ it++;
+ std::same_as<CommonProxyRvalueRef> decltype(auto) rref7 = std::ranges::iter_move(it);
+ values.emplace_back(rref7.get());
+ ++it;
+ std::same_as<CommonProxyRvalueRef> decltype(auto) rref8 = std::ranges::iter_move(std::as_const(it));
+ values.emplace_back(rref8.get());
+
+ assert(std::ranges::equal(values, std::views::iota(0, 8)));
+ assert(std::ranges::all_of(values, &MoveOnlyInt::was_move_constructed));
+ }
+
+ {
+ std::vector<MoveOnlyInt> values;
+ values.reserve(8);
+
+ auto cit = std::prev(std::as_const(jwv).end());
+ std::same_as<CommonProxyRvalueRef> decltype(auto) rref1 = iter_move(cit);
+ values.emplace_back(rref1.get());
+ cit--;
+ std::same_as<CommonProxyRvalueRef> decltype(auto) rref2 = iter_move(std::as_const(cit));
+ values.emplace_back(rref2.get());
+ --cit;
+ std::same_as<CommonProxyRvalueRef> decltype(auto) rref3 = std::ranges::iter_move(cit);
+ values.emplace_back(rref3.get());
+ cit--;
+ std::same_as<CommonProxyRvalueRef> decltype(auto) rref4 = std::ranges::iter_move(std::as_const(cit));
+ values.emplace_back(rref4.get());
+ --cit; // `it` points to element of `Pattern` from here
+ std::same_as<CommonProxyRvalueRef> decltype(auto) rref5 = iter_move(cit);
+ values.emplace_back(rref5.get());
+ cit--;
+ std::same_as<CommonProxyRvalueRef> decltype(auto) rref6 = iter_move(std::as_const(cit));
+ values.emplace_back(rref6.get());
+ --cit;
+ std::same_as<CommonProxyRvalueRef> decltype(auto) rref7 = std::ranges::iter_move(cit);
+ values.emplace_back(rref7.get());
+ cit--;
+ std::same_as<CommonProxyRvalueRef> decltype(auto) rref8 = std::ranges::iter_move(std::as_const(cit));
+ values.emplace_back(rref8.get());
+
+ assert(std::ranges::equal(std::views::reverse(values), std::views::iota(8, 16)));
+ assert(std::ranges::all_of(values, &MoveOnlyInt::was_move_constructed));
+ }
+
+ assert(std::ranges::all_of(jwv, &MoveOnlyInt::was_moved_from));
+ }
+
+ { // Make sure `iter_move` calls underlying's iterator `iter_move` (not `std::move(*i)`).
+ using Inner = std::vector<int>;
+ using InnerTrackingIter = IterMoveTrackingIterator<Inner::iterator>;
+ using TrackingInner = std::ranges::subrange<InnerTrackingIter>;
+ using Pattern = std::array<int, 1>;
+ using PatternTrackingIter = IterMoveTrackingIterator<Pattern::iterator>;
+ using TrackingPattern = std::ranges::subrange<PatternTrackingIter>;
+ using JWV = std::ranges::join_with_view<std::span<TrackingInner>, TrackingPattern>;
+
+ std::array<Inner, 2> v{{{1}, {2}}};
+ Pattern pat{-1};
+
+ bool v_moved = false;
+ std::array<TrackingInner, 2> tracking_v{
+ TrackingInner(InnerTrackingIter(v[0].begin(), &v_moved), InnerTrackingIter(v[0].end())),
+ TrackingInner(InnerTrackingIter(v[1].begin()), InnerTrackingIter(v[1].end()))};
+
+ bool pat_moved = false;
+ TrackingPattern tracking_pat(PatternTrackingIter(pat.begin(), &pat_moved), PatternTrackingIter(pat.end()));
+
+ JWV jwv(tracking_v, tracking_pat);
+ auto it = jwv.begin();
+
+ // Test calling `iter_move` when `it` points to element of `v`
+ assert(!v_moved);
+ assert(iter_move(it) == 1);
+ assert(v_moved);
+
+ // Test calling `iter_move` when `it` points to element of `pat`
+ ++it;
+ assert(!pat_moved);
+ assert(iter_move(it) == -1);
+ assert(pat_moved);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/iter_swap.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/iter_swap.pass.cpp
new file mode 100644
index 0000000000000..7385921752283
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/iter_swap.pass.cpp
@@ -0,0 +1,186 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// friend constexpr void iter_swap(const iterator& x, const iterator& y);
+
+#include <ranges>
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <span>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+template <class I>
+concept CanIterSwap = requires(I i) { iter_swap(i); };
+
+enum class SwapKind { no_swap, with_same_type, with_different_type };
+enum class IterKind { inner_view, pattern };
+
+template <std::forward_iterator Iter, IterKind Kind>
+class IterSwapTrackingIterator {
+public:
+ using value_type = std::iter_value_t<Iter>;
+ using difference_type = std::iter_difference_t<Iter>;
+
+ constexpr Iter get_iter() const { return iter_; }
+
+ constexpr SwapKind* get_flag() const { return flag_; }
+
+ IterSwapTrackingIterator() = default;
+ constexpr explicit IterSwapTrackingIterator(Iter iter, SwapKind* flag = nullptr)
+ : iter_(std::move(iter)), flag_(flag) {}
+
+ constexpr IterSwapTrackingIterator& operator++() {
+ ++iter_;
+ return *this;
+ }
+
+ constexpr IterSwapTrackingIterator operator++(int) {
+ auto tmp = *this;
+ ++*this;
+ return tmp;
+ }
+
+ constexpr decltype(auto) operator*() const { return *iter_; }
+
+ constexpr bool operator==(const IterSwapTrackingIterator& other) const { return iter_ == other.iter_; }
+
+ friend constexpr decltype(auto) iter_swap(const IterSwapTrackingIterator& lhs, const IterSwapTrackingIterator& rhs) {
+ assert(lhs.flag_ != nullptr && rhs.flag_ != nullptr);
+ *lhs.flag_ = *rhs.flag_ = SwapKind::with_same_type;
+ return std::ranges::iter_swap(lhs.iter_, rhs.iter_);
+ }
+
+ template <std::indirectly_swappable<Iter> OtherIter, IterKind OtherKind>
+ friend constexpr decltype(auto)
+ iter_swap(const IterSwapTrackingIterator& lhs, const IterSwapTrackingIterator<OtherIter, OtherKind>& rhs) {
+ assert(lhs.flag_ != nullptr && rhs.get_flag() != nullptr);
+ *lhs.flag_ = *rhs.get_flag() = SwapKind::with_different_type;
+ return std::ranges::iter_swap(lhs.iter_, rhs.get_iter());
+ }
+
+private:
+ Iter iter_ = Iter();
+ SwapKind* flag_ = nullptr;
+};
+
+static_assert(std::forward_iterator<IterSwapTrackingIterator<int*, IterKind::inner_view>> &&
+ !std::bidirectional_iterator<IterSwapTrackingIterator<int*, IterKind::inner_view>>);
+
+constexpr bool test() {
+ { // Test common usage
+ using V = std::vector<std::string>;
+ using Pattern = std::string;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, std::ranges::owning_view<Pattern>>;
+ using namespace std::string_view_literals;
+
+ JWV jwv(V{"std", "ranges", "views", "join_with_view"}, Pattern{":: "});
+ assert(std::ranges::equal(jwv, "std:: ranges:: views:: join_with_view"sv));
+
+ auto it = jwv.begin();
+ iter_swap(it, std::ranges::next(it, 2)); // Swap elements of the same inner range.
+ assert(std::ranges::equal(jwv, "dts:: ranges:: views:: join_with_view"sv));
+
+ std::ranges::advance(it, 3);
+ iter_swap(std::as_const(it), std::ranges::next(it, 2)); // Swap elements of the pattern.
+ assert(std::ranges::equal(jwv, "dts ::ranges ::views ::join_with_view"sv));
+
+ std::ranges::advance(it, 3);
+ const auto it2 = jwv.begin();
+ iter_swap(std::as_const(it), it2); // Swap elements of different inner ranges.
+ assert(std::ranges::equal(jwv, "rts ::danges ::views ::join_with_view"sv));
+
+ std::ranges::advance(it, 6);
+ iter_swap(std::as_const(it), it2); // Swap element from inner range with element from the pattern.
+ assert(std::ranges::equal(jwv, " tsr::dangesr::viewsr::join_with_view"sv));
+
+ static_assert(std::is_void_v<decltype(iter_swap(it, it))>);
+ static_assert(std::is_void_v<decltype(iter_swap(it2, it2))>);
+ static_assert(!CanIterSwap<std::ranges::iterator_t<const JWV>>);
+ static_assert(!CanIterSwap<const std::ranges::iterator_t<const JWV>>);
+ }
+
+ { // Make sure `iter_swap` calls underlying's iterator `iter_swap` (not `ranges::swap(*i1, *i2)`).
+ using Inner = std::vector<int>;
+ using InnerTrackingIter = IterSwapTrackingIterator<Inner::iterator, IterKind::inner_view>;
+ using TrackingInner = std::ranges::subrange<InnerTrackingIter>;
+ using Pattern = std::array<int, 2>;
+ using PatternTrackingIter = IterSwapTrackingIterator<Pattern::iterator, IterKind::pattern>;
+ using TrackingPattern = std::ranges::subrange<PatternTrackingIter>;
+ using JWV = std::ranges::join_with_view<std::span<TrackingInner>, TrackingPattern>;
+
+ std::array<Inner, 3> v{{{1, 2, 3}, {4, 5}}};
+ Pattern pat{-1, -2};
+
+ SwapKind v_swap_kind = SwapKind::no_swap;
+ std::array<TrackingInner, 2> tracking_v{
+ TrackingInner(InnerTrackingIter(v[0].begin(), &v_swap_kind), InnerTrackingIter(v[0].end())),
+ TrackingInner(InnerTrackingIter(v[1].begin(), &v_swap_kind), InnerTrackingIter(v[1].end()))};
+
+ SwapKind pat_swap_kind = SwapKind::no_swap;
+ TrackingPattern tracking_pat(PatternTrackingIter(pat.begin(), &pat_swap_kind), PatternTrackingIter(pat.end()));
+
+ JWV jwv(tracking_v, tracking_pat);
+ auto it1 = jwv.begin();
+ auto it2 = std::ranges::next(it1);
+
+ // Test calling `iter_swap` when both `it1` and `it2` point to elements of `v`.
+ assert(v_swap_kind == SwapKind::no_swap);
+ iter_swap(it1, it2);
+ assert(*it1 == 2 && *it2 == 1);
+ assert(v_swap_kind == SwapKind::with_same_type && pat_swap_kind == SwapKind::no_swap);
+
+ // Test calling `iter_swap` when `it1` points to element of `v` and `it2` points to element of `pat`.
+ std::ranges::advance(it2, 2);
+ v_swap_kind = SwapKind::no_swap;
+ assert(pat_swap_kind == SwapKind::no_swap);
+ iter_swap(it1, it2);
+ assert(*it1 == -1 && *it2 == 2);
+ assert(v_swap_kind == SwapKind::with_different_type && pat_swap_kind == SwapKind::with_different_type);
+
+ // Test calling `iter_swap` when `it1` and `it2` point to elements of `pat`.
+ std::ranges::advance(it1, 4);
+ v_swap_kind = pat_swap_kind = SwapKind::no_swap;
+ iter_swap(it1, it2);
+ assert(*it1 == 2 && *it2 == -2);
+ assert(v_swap_kind == SwapKind::no_swap && pat_swap_kind == SwapKind::with_same_type);
+
+ // Test calling `iter_swap` when `it1` points to element of `pat` and `it2` points to element of `v`.
+ std::ranges::advance(it2, 3);
+ v_swap_kind = pat_swap_kind = SwapKind::no_swap;
+ iter_swap(it1, it2);
+ assert(*it1 == 5 && *it2 == 2);
+ assert(v_swap_kind == SwapKind::with_different_type && pat_swap_kind == SwapKind::with_different_type);
+ }
+
+ { // InnerIter and PatternIter don't model indirectly swappable
+ using JWV = std::ranges::join_with_view<std::span<std::string>, std::string_view>;
+ static_assert(!CanIterSwap<std::ranges::iterator_t<JWV>>);
+ static_assert(!CanIterSwap<const std::ranges::iterator_t<JWV>>);
+ static_assert(!CanIterSwap<std::ranges::iterator_t<const JWV>>);
+ static_assert(!CanIterSwap<const std::ranges::iterator_t<const JWV>>);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/types.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/types.compile.pass.cpp
new file mode 100644
index 0000000000000..a373e17dddbf3
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.iterator/types.compile.pass.cpp
@@ -0,0 +1,456 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// using iterator_concept = see below;
+// using iterator_category = see below; // not always present
+// using value_type = see below;
+// using difference_type = see below;
+
+#include <ranges>
+
+#include <iterator>
+#include <vector>
+
+#include "../types.h"
+#include "test_iterators.h"
+
+namespace test_iterator_concept {
+template <template <class> class InnerIt>
+using InnerRange = BasicView<std::vector<int>, ViewProperties{}, InnerIt>;
+
+template <template <class> class It, template <class> class InnerIt>
+using View = BasicView<std::vector<InnerRange<InnerIt>>, ViewProperties{}, It>;
+
+template <template <class> class It, template <class> class InnerIt>
+using RvalueView = BasicView<RvalueVector<InnerRange<InnerIt>>, ViewProperties{}, It>;
+
+template <template <class> class It>
+using Pattern = BasicView<std::vector<int>, ViewProperties{}, It>;
+
+template <class V, class Pat>
+using IteratorConcept = std::ranges::iterator_t<std::ranges::join_with_view<V, Pat>>::iterator_concept;
+
+template <class V, class Pat, class Concept>
+concept IteratorConceptIs = std::same_as<IteratorConcept<V, Pat>, Concept>;
+
+// When `iterator<false>::iterator_concept` is `bidirectional_iterator_tag`
+static_assert(IteratorConceptIs<View<bidirectional_iterator, bidirectional_iterator>,
+ Pattern<bidirectional_iterator>,
+ std::bidirectional_iterator_tag>);
+
+// When `iterator<false>::iterator_concept` is `forward_iterator_tag`
+static_assert(IteratorConceptIs<View<forward_iterator, bidirectional_iterator>,
+ Pattern<bidirectional_iterator>,
+ std::forward_iterator_tag>);
+static_assert(IteratorConceptIs<View<bidirectional_iterator, forward_iterator>,
+ Pattern<bidirectional_iterator>,
+ std::forward_iterator_tag>);
+static_assert(IteratorConceptIs<View<bidirectional_iterator, bidirectional_iterator>,
+ Pattern<forward_iterator>,
+ std::forward_iterator_tag>);
+static_assert(IteratorConceptIs<View<forward_iterator, forward_iterator>,
+ Pattern<bidirectional_iterator>,
+ std::forward_iterator_tag>);
+static_assert(IteratorConceptIs<View<forward_iterator, bidirectional_iterator>,
+ Pattern<forward_iterator>,
+ std::forward_iterator_tag>);
+static_assert(IteratorConceptIs<View<bidirectional_iterator, forward_iterator>,
+ Pattern<forward_iterator>,
+ std::forward_iterator_tag>);
+static_assert(IteratorConceptIs<View<forward_iterator, forward_iterator>, //
+ Pattern<forward_iterator>,
+ std::forward_iterator_tag>);
+
+// When `iterator<false>::iterator_concept` is `input_iterator_tag`
+static_assert(IteratorConceptIs<View<DefaultCtorInputIter, forward_iterator>,
+ Pattern<forward_iterator>,
+ std::input_iterator_tag>);
+static_assert(IteratorConceptIs<View<forward_iterator, DefaultCtorInputIter>,
+ Pattern<forward_iterator>,
+ std::input_iterator_tag>);
+static_assert(IteratorConceptIs<View<DefaultCtorInputIter, DefaultCtorInputIter>,
+ Pattern<forward_iterator>,
+ std::input_iterator_tag>);
+static_assert(IteratorConceptIs<RvalueView<bidirectional_iterator, bidirectional_iterator>,
+ Pattern<bidirectional_iterator>,
+ std::input_iterator_tag>);
+static_assert(IteratorConceptIs<RvalueView<forward_iterator, forward_iterator>,
+ Pattern<forward_iterator>,
+ std::input_iterator_tag>);
+
+template <class V, class Pat>
+using ConstIteratorConcept = std::ranges::iterator_t<const std::ranges::join_with_view<V, Pat>>::iterator_concept;
+
+template <class V, class Pat, class Concept>
+concept ConstIteratorConceptIs = std::same_as<ConstIteratorConcept<V, Pat>, Concept>;
+
+// When `iterator<true>::iterator_concept` is `bidirectional_iterator_tag`
+static_assert(ConstIteratorConceptIs<View<bidirectional_iterator, bidirectional_iterator>,
+ Pattern<bidirectional_iterator>,
+ std::bidirectional_iterator_tag>);
+
+// When `iterator<true>::iterator_concept` is `forward_iterator_tag`
+static_assert(ConstIteratorConceptIs<View<forward_iterator, bidirectional_iterator>,
+ Pattern<bidirectional_iterator>,
+ std::forward_iterator_tag>);
+static_assert(ConstIteratorConceptIs<View<bidirectional_iterator, forward_iterator>,
+ Pattern<bidirectional_iterator>,
+ std::forward_iterator_tag>);
+static_assert(ConstIteratorConceptIs<View<bidirectional_iterator, bidirectional_iterator>,
+ Pattern<forward_iterator>,
+ std::forward_iterator_tag>);
+static_assert(ConstIteratorConceptIs<View<forward_iterator, forward_iterator>,
+ Pattern<bidirectional_iterator>,
+ std::forward_iterator_tag>);
+static_assert(ConstIteratorConceptIs<View<forward_iterator, bidirectional_iterator>,
+ Pattern<forward_iterator>,
+ std::forward_iterator_tag>);
+static_assert(ConstIteratorConceptIs<View<bidirectional_iterator, forward_iterator>,
+ Pattern<forward_iterator>,
+ std::forward_iterator_tag>);
+static_assert(ConstIteratorConceptIs<View<forward_iterator, forward_iterator>,
+ Pattern<forward_iterator>,
+ std::forward_iterator_tag>);
+
+// `iterator<true>::iterator_concept` cannot be `input_iterator_tag`
+} // namespace test_iterator_concept
+
+namespace test_iterator_category {
+template <template <class> class InnerIt>
+using InnerRange = BasicView<std::vector<float>, ViewProperties{}, InnerIt>;
+
+template <bool Common, template <class> class InnerIt>
+using MaybeCommonInnerRange = BasicView<std::vector<float>, ViewProperties{.common = Common}, InnerIt>;
+
+template <template <class> class It, template <class> class InnerIt>
+using View = BasicView<std::vector<InnerRange<InnerIt>>, ViewProperties{}, It>;
+
+template <template <class> class It, template <class> class InnerIt>
+using RvalueView = BasicView<RvalueVector<InnerRange<InnerIt>>, ViewProperties{}, It>;
+
+template <bool Common, template <class> class It, bool CommonInner, template <class> class InnerIt>
+using MaybeCommonView =
+ BasicView<std::vector<MaybeCommonInnerRange<CommonInner, InnerIt>>, ViewProperties{.common = Common}, It>;
+
+template <template <class> class It>
+using Pattern = BasicView<std::vector<float>, ViewProperties{}, It>;
+
+template <template <class> class It>
+using RvaluePattern = BasicView<RvalueVector<float>, ViewProperties{}, It>;
+
+template <bool Common, template <class> class It>
+using MaybeCommonPattern = BasicView<std::vector<float>, ViewProperties{.common = Common}, It>;
+
+template <class V, class Pattern>
+using IteratorCategory = std::ranges::iterator_t<std::ranges::join_with_view<V, Pattern>>::iterator_category;
+
+template <class V, class Pattern>
+concept HasIteratorCategory = requires { typename IteratorCategory<V, Pattern>; };
+
+template <class V, class Pat, class Category>
+concept IteratorCategoryIs = std::same_as<IteratorCategory<V, Pat>, Category>;
+
+// When `iterator<false>::iterator_category` is not defined
+static_assert(!HasIteratorCategory<View<cpp20_input_iterator, forward_iterator>, Pattern<forward_iterator>>);
+static_assert(!HasIteratorCategory<View<forward_iterator, cpp20_input_iterator>, Pattern<forward_iterator>>);
+static_assert(!HasIteratorCategory<View<forward_iterator, forward_iterator>, Pattern<cpp20_input_iterator>>);
+static_assert(!HasIteratorCategory<RvalueView<forward_iterator, forward_iterator>, Pattern<forward_iterator>>);
+static_assert(HasIteratorCategory<View<forward_iterator, forward_iterator>, Pattern<forward_iterator>>);
+
+// When
+// is_reference_v<common_reference_t<iter_reference_t<InnerIter>,
+// iter_reference_t<PatternIter>>>
+// has different values for `iterator<false>`
+static_assert(IteratorCategoryIs<View<forward_iterator, forward_iterator>,
+ RvaluePattern<forward_iterator>,
+ std::input_iterator_tag>);
+
+// When `iterator<false>::iterator_category` is `bidirectional_iterator_tag`
+static_assert(IteratorCategoryIs<MaybeCommonView<true, bidirectional_iterator, true, bidirectional_iterator>,
+ MaybeCommonPattern<true, bidirectional_iterator>,
+ std::bidirectional_iterator_tag>);
+static_assert(IteratorCategoryIs<MaybeCommonView<false, bidirectional_iterator, true, bidirectional_iterator>,
+ MaybeCommonPattern<true, bidirectional_iterator>,
+ std::bidirectional_iterator_tag>);
+
+// When `iterator<false>::iterator_category` is `forward_iterator_tag`
+static_assert(IteratorCategoryIs<MaybeCommonView<true, forward_iterator, true, bidirectional_iterator>,
+ MaybeCommonPattern<true, bidirectional_iterator>,
+ std::forward_iterator_tag>);
+static_assert(IteratorCategoryIs<MaybeCommonView<true, bidirectional_iterator, true, forward_iterator>,
+ MaybeCommonPattern<true, bidirectional_iterator>,
+ std::forward_iterator_tag>);
+static_assert(IteratorCategoryIs<MaybeCommonView<true, bidirectional_iterator, true, bidirectional_iterator>,
+ MaybeCommonPattern<true, forward_iterator>,
+ std::forward_iterator_tag>);
+static_assert(IteratorCategoryIs<MaybeCommonView<true, bidirectional_iterator, false, bidirectional_iterator>,
+ MaybeCommonPattern<true, bidirectional_iterator>,
+ std::forward_iterator_tag>);
+static_assert(IteratorCategoryIs<MaybeCommonView<true, bidirectional_iterator, true, bidirectional_iterator>,
+ MaybeCommonPattern<false, bidirectional_iterator>,
+ std::forward_iterator_tag>);
+static_assert(IteratorCategoryIs<MaybeCommonView<false, forward_iterator, false, forward_iterator>,
+ MaybeCommonPattern<false, forward_iterator>,
+ std::forward_iterator_tag>);
+
+// When `iterator<false>::iterator_category` is `input_iterator_tag`
+static_assert(IteratorCategoryIs<View<ForwardIteratorWithInputCategory, forward_iterator>,
+ Pattern<forward_iterator>,
+ std::input_iterator_tag>);
+static_assert(IteratorCategoryIs<View<forward_iterator, ForwardIteratorWithInputCategory>,
+ Pattern<forward_iterator>,
+ std::input_iterator_tag>);
+static_assert(IteratorCategoryIs<View<forward_iterator, forward_iterator>,
+ Pattern<ForwardIteratorWithInputCategory>,
+ std::input_iterator_tag>);
+static_assert(IteratorCategoryIs<View<ForwardIteratorWithInputCategory, ForwardIteratorWithInputCategory>,
+ Pattern<ForwardIteratorWithInputCategory>,
+ std::input_iterator_tag>);
+
+template <class V, class Pattern>
+using ConstIteratorCategory = std::ranges::iterator_t<const std::ranges::join_with_view<V, Pattern>>::iterator_category;
+
+template <class V, class Pattern>
+concept HasConstIteratorCategory = requires { typename ConstIteratorCategory<V, Pattern>; };
+
+template <class V, class Pat, class Category>
+concept ConstIteratorCategoryIs = std::same_as<ConstIteratorCategory<V, Pat>, Category>;
+
+// `iterator<true>::iterator_category` is not defined in those
+// cases because `join_with_view<V, Pattern>` cannot const-accessed
+static_assert(!HasConstIteratorCategory<View<cpp20_input_iterator, forward_iterator>, Pattern<forward_iterator>>);
+static_assert(!HasConstIteratorCategory<View<forward_iterator, cpp20_input_iterator>, Pattern<forward_iterator>>);
+static_assert(!HasConstIteratorCategory<View<forward_iterator, forward_iterator>, Pattern<cpp20_input_iterator>>);
+static_assert(!HasConstIteratorCategory<RvalueView<forward_iterator, forward_iterator>, Pattern<forward_iterator>>);
+static_assert(HasConstIteratorCategory<View<forward_iterator, forward_iterator>, Pattern<forward_iterator>>);
+
+// When
+// is_reference_v<common_reference_t<iter_reference_t<InnerIter>,
+// iter_reference_t<PatternIter>>>
+// has different values for `iterator<true>`
+static_assert(ConstIteratorCategoryIs<View<forward_iterator, forward_iterator>,
+ RvaluePattern<forward_iterator>,
+ std::input_iterator_tag>);
+
+// When `iterator<true>::iterator_category` is `bidirectional_iterator_tag`
+static_assert(ConstIteratorCategoryIs<MaybeCommonView<true, bidirectional_iterator, true, bidirectional_iterator>,
+ MaybeCommonPattern<true, bidirectional_iterator>,
+ std::bidirectional_iterator_tag>);
+static_assert(ConstIteratorCategoryIs<MaybeCommonView<false, bidirectional_iterator, true, bidirectional_iterator>,
+ MaybeCommonPattern<true, bidirectional_iterator>,
+ std::bidirectional_iterator_tag>);
+static_assert(ConstIteratorCategoryIs<
+ BasicVectorView<
+ BasicVectorView<float, ViewProperties{.common = true}, forward_iterator, bidirectional_iterator>,
+ ViewProperties{.common = true},
+ forward_iterator,
+ bidirectional_iterator>,
+ BasicVectorView<float, ViewProperties{.common = true}, forward_iterator, bidirectional_iterator>,
+ std::bidirectional_iterator_tag>);
+
+// When `iterator<true>::iterator_category` is `forward_iterator_tag`
+static_assert(ConstIteratorCategoryIs<MaybeCommonView<true, forward_iterator, true, bidirectional_iterator>,
+ MaybeCommonPattern<true, bidirectional_iterator>,
+ std::forward_iterator_tag>);
+static_assert(ConstIteratorCategoryIs<MaybeCommonView<true, bidirectional_iterator, true, forward_iterator>,
+ MaybeCommonPattern<true, bidirectional_iterator>,
+ std::forward_iterator_tag>);
+static_assert(ConstIteratorCategoryIs<MaybeCommonView<true, bidirectional_iterator, true, bidirectional_iterator>,
+ MaybeCommonPattern<true, forward_iterator>,
+ std::forward_iterator_tag>);
+static_assert(ConstIteratorCategoryIs<MaybeCommonView<true, bidirectional_iterator, false, bidirectional_iterator>,
+ MaybeCommonPattern<true, bidirectional_iterator>,
+ std::forward_iterator_tag>);
+static_assert(ConstIteratorCategoryIs<MaybeCommonView<true, bidirectional_iterator, true, bidirectional_iterator>,
+ MaybeCommonPattern<false, bidirectional_iterator>,
+ std::forward_iterator_tag>);
+static_assert(ConstIteratorCategoryIs<MaybeCommonView<false, forward_iterator, false, forward_iterator>,
+ MaybeCommonPattern<false, forward_iterator>,
+ std::forward_iterator_tag>);
+static_assert(
+ ConstIteratorCategoryIs<
+ BasicVectorView<BasicVectorView<float, ViewProperties{}, ForwardIteratorWithInputCategory, forward_iterator>,
+ ViewProperties{},
+ ForwardIteratorWithInputCategory,
+ forward_iterator>,
+ BasicVectorView<float, ViewProperties{}, ForwardIteratorWithInputCategory, forward_iterator>,
+ std::forward_iterator_tag>);
+
+// When `iterator<true>::iterator_category` is `input_iterator_tag`
+static_assert(ConstIteratorCategoryIs<View<ForwardIteratorWithInputCategory, forward_iterator>,
+ Pattern<forward_iterator>,
+ std::input_iterator_tag>);
+static_assert(ConstIteratorCategoryIs<View<forward_iterator, ForwardIteratorWithInputCategory>,
+ Pattern<forward_iterator>,
+ std::input_iterator_tag>);
+static_assert(ConstIteratorCategoryIs<View<forward_iterator, forward_iterator>,
+ Pattern<ForwardIteratorWithInputCategory>,
+ std::input_iterator_tag>);
+static_assert(ConstIteratorCategoryIs<View<ForwardIteratorWithInputCategory, ForwardIteratorWithInputCategory>,
+ Pattern<ForwardIteratorWithInputCategory>,
+ std::input_iterator_tag>);
+static_assert(ConstIteratorCategoryIs<
+ BasicVectorView<
+ BasicVectorView<float, ViewProperties{}, DefaultCtorInputIter, ForwardIteratorWithInputCategory>,
+ ViewProperties{},
+ DefaultCtorInputIter,
+ ForwardIteratorWithInputCategory>,
+ BasicVectorView<float, ViewProperties{}, ForwardIteratorWithInputCategory>,
+ std::input_iterator_tag>);
+} // namespace test_iterator_category
+
+namespace test_value_type {
+template <class ValueType, class ConstValueType = ValueType>
+struct View : std::ranges::view_base {
+ struct InnerRange : std::ranges::view_base {
+ ValueType* begin();
+ ValueType* end();
+ ConstValueType* begin() const;
+ ConstValueType* end() const;
+ };
+
+ InnerRange* begin();
+ InnerRange* end();
+ const InnerRange* begin() const;
+ const InnerRange* end() const;
+};
+
+template <class ValueType, class ConstValueType = ValueType>
+using Pattern = View<ValueType, ConstValueType>::InnerRange;
+
+template <class V, class Pat>
+using IteratorValueType = std::ranges::iterator_t<std::ranges::join_with_view<V, Pat>>::value_type;
+
+template <class V, class Pat, class ValueType>
+concept IteratorValueTypeIs = std::same_as<IteratorValueType<V, Pat>, ValueType>;
+
+// Test that `iterator<false>::value_type` is equal to
+// common_type_t<iter_value_t<InnerIter>, iter_value_t<PatternIter>>
+static_assert(IteratorValueTypeIs<View<int>, Pattern<int>, int>);
+static_assert(IteratorValueTypeIs<View<int>, Pattern<long>, long>);
+static_assert(IteratorValueTypeIs<View<long>, Pattern<int>, long>);
+static_assert(IteratorValueTypeIs<View<std::nullptr_t>, Pattern<void*>, void*>);
+static_assert(IteratorValueTypeIs<View<std::tuple<long, int>>, Pattern<std::tuple<int, long>>, std::tuple<long, long>>);
+
+template <class V, class Pat>
+using ConstIteratorValueType = std::ranges::iterator_t<const std::ranges::join_with_view<V, Pat>>::value_type;
+
+template <class V, class Pat, class ValueType>
+concept ConstIteratorValueTypeIs = std::same_as<ConstIteratorValueType<V, Pat>, ValueType>;
+
+// Test that `iterator<true>::value_type` is equal to
+// common_type_t<iter_value_t<InnerIter>, iter_value_t<PatternIter>>
+static_assert(ConstIteratorValueTypeIs<View<int>, Pattern<int>, int>);
+static_assert(ConstIteratorValueTypeIs<View<int>, Pattern<long>, long>);
+static_assert(ConstIteratorValueTypeIs<View<long>, Pattern<int>, long>);
+static_assert(ConstIteratorValueTypeIs<View<std::nullptr_t>, Pattern<void*>, void*>);
+static_assert(
+ ConstIteratorValueTypeIs<View<std::tuple<long, int>>, Pattern<std::tuple<int, long>>, std::tuple<long, long>>);
+
+// Test value types of non-simple const ranges
+static_assert(ConstIteratorValueTypeIs<View<short, int>, Pattern<short, int>, int>);
+static_assert(ConstIteratorValueTypeIs<View<short, int>, Pattern<int, long>, long>);
+static_assert(ConstIteratorValueTypeIs<View<int, long>, Pattern<short, int>, long>);
+static_assert(ConstIteratorValueTypeIs<View<int, std::nullptr_t>, Pattern<int, void*>, void*>);
+static_assert(ConstIteratorValueTypeIs<View<std::tuple<long, int>, std::pair<long, int>>,
+ Pattern<std::tuple<int, long>, std::pair<int, long>>,
+ std::pair<long, long>>);
+} // namespace test_value_type
+
+namespace test_difference_type {
+template <class DifferenceType, class ValueType>
+struct Iter {
+ using value_type = std::remove_const_t<ValueType>;
+ using difference_type = DifferenceType;
+
+ ValueType& operator*() const;
+ Iter& operator++();
+ Iter operator++(int);
+ bool operator==(const Iter&) const;
+};
+
+static_assert(std::forward_iterator<Iter<int, void*>>);
+
+template <class DifferenceType,
+ class InnerDifferenceType,
+ class ConstDifferenceType = DifferenceType,
+ class InnerConstDifferenceType = InnerDifferenceType>
+struct View : std::ranges::view_base {
+ struct InnerRange : std::ranges::view_base {
+ Iter<InnerDifferenceType, float> begin();
+ Iter<InnerDifferenceType, float> end();
+ Iter<InnerConstDifferenceType, double> begin() const;
+ Iter<InnerConstDifferenceType, double> end() const;
+ };
+
+ Iter<DifferenceType, InnerRange> begin();
+ Iter<DifferenceType, InnerRange> end();
+ Iter<ConstDifferenceType, const InnerRange> begin() const;
+ Iter<ConstDifferenceType, const InnerRange> end() const;
+};
+
+template <class DifferenceType, class ConstDifferenceType = DifferenceType>
+struct Pattern : std::ranges::view_base {
+ Iter<DifferenceType, float> begin();
+ Iter<DifferenceType, float> end();
+ Iter<ConstDifferenceType, double> begin() const;
+ Iter<ConstDifferenceType, double> end() const;
+};
+
+template <class V, class Pat>
+using IteratorDifferenceType = std::ranges::iterator_t<std::ranges::join_with_view<V, Pat>>::difference_type;
+
+template <class V, class Pat, class DifferenceType>
+concept IteratorDifferenceTypeIs = std::same_as<IteratorDifferenceType<V, Pat>, DifferenceType>;
+
+// Test that `iterator<false>::difference_type` is equal to
+// common_type_t<
+// iter_difference_t<OuterIter>,
+// iter_difference_t<InnerIter>,
+// iter_difference_t<PatternIter>>
+static_assert(IteratorDifferenceTypeIs<View<int, int>, Pattern<int>, int>);
+static_assert(IteratorDifferenceTypeIs<View<signed char, signed char>, Pattern<signed char>, signed char>);
+static_assert(IteratorDifferenceTypeIs<View<short, short>, Pattern<short>, short>);
+static_assert(IteratorDifferenceTypeIs<View<signed char, short>, Pattern<short>, int>);
+static_assert(IteratorDifferenceTypeIs<View<signed char, short>, Pattern<int>, int>);
+static_assert(IteratorDifferenceTypeIs<View<long long, long>, Pattern<int>, long long>);
+static_assert(IteratorDifferenceTypeIs<View<long, long long>, Pattern<int>, long long>);
+
+template <class V, class Pat>
+using ConstIteratorDifferenceType = std::ranges::iterator_t<const std::ranges::join_with_view<V, Pat>>::difference_type;
+
+template <class V, class Pat, class DifferenceType>
+concept ConstIteratorDifferenceTypeIs = std::same_as<ConstIteratorDifferenceType<V, Pat>, DifferenceType>;
+
+// Test that `iterator<true>::difference_type` is equal to
+// common_type_t<
+// iter_difference_t<OuterIter>,
+// iter_difference_t<InnerIter>,
+// iter_difference_t<PatternIter>>
+static_assert(ConstIteratorDifferenceTypeIs<View<int, int>, Pattern<int>, int>);
+static_assert(ConstIteratorDifferenceTypeIs<View<signed char, signed char>, Pattern<signed char>, signed char>);
+static_assert(ConstIteratorDifferenceTypeIs<View<short, short>, Pattern<short>, short>);
+static_assert(ConstIteratorDifferenceTypeIs<View<signed char, short>, Pattern<short>, int>);
+static_assert(ConstIteratorDifferenceTypeIs<View<signed char, short>, Pattern<int>, int>);
+static_assert(ConstIteratorDifferenceTypeIs<View<long long, long>, Pattern<int>, long long>);
+static_assert(ConstIteratorDifferenceTypeIs<View<long, long long>, Pattern<int>, long long>);
+
+// Test difference types of non-simple const ranges
+static_assert(ConstIteratorDifferenceTypeIs<View<short, short, int, int>, Pattern<short, int>, int>);
+static_assert(
+ ConstIteratorDifferenceTypeIs<View<int, short, signed char, signed char>, Pattern<long, signed char>, signed char>);
+static_assert(ConstIteratorDifferenceTypeIs<View<long, long long, signed char, short>, Pattern<long, short>, int>);
+static_assert(ConstIteratorDifferenceTypeIs<View<short, short, long long, long>, Pattern<short, int>, long long>);
+static_assert(ConstIteratorDifferenceTypeIs<View<signed char, signed char, long, long long>,
+ Pattern<signed char, int>,
+ long long>);
+} // namespace test_difference_type
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.overview/adaptor.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.overview/adaptor.pass.cpp
new file mode 100644
index 0000000000000..da53bfaaa5a52
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.overview/adaptor.pass.cpp
@@ -0,0 +1,360 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// std::views::join_with_view
+
+#include <ranges>
+
+#include <memory>
+#include <span>
+#include <string_view>
+#include <utility>
+
+#include "test_iterators.h"
+
+template <class View, class T>
+concept CanBePiped = requires(View&& view, T&& t) {
+ { std::forward<View>(view) | std::forward<T>(t) };
+};
+
+struct Range : std::ranges::view_base {
+ using Iterator = forward_iterator<std::string_view*>;
+ using Sentinel = sentinel_wrapper<Iterator>;
+ constexpr explicit Range(std::string_view* b, std::string_view* e) : begin_(b), end_(e) {}
+ constexpr Iterator begin() const { return Iterator(begin_); }
+ constexpr Sentinel end() const { return Sentinel(Iterator(end_)); }
+
+private:
+ std::string_view* begin_;
+ std::string_view* end_;
+};
+
+struct Pattern : std::ranges::view_base {
+ using Iterator = forward_iterator<const char*>;
+ using Sentinel = sentinel_wrapper<Iterator>;
+ static constexpr std::string_view pat{", "};
+
+ constexpr Pattern() = default;
+ constexpr Iterator begin() const { return Iterator(pat.data()); }
+ constexpr Sentinel end() const { return Sentinel(Iterator(pat.data() + pat.size())); }
+};
+
+struct NonCopyablePattern : Pattern {
+ NonCopyablePattern(const NonCopyablePattern&) = delete;
+};
+
+template <typename View>
+constexpr void compareViews(View v, std::string_view list) {
+ auto b1 = v.begin();
+ auto e1 = v.end();
+ auto b2 = list.begin();
+ auto e2 = list.end();
+ for (; b1 != e1 && b2 != e2; ++b1, ++b2) {
+ assert(*b1 == *b2);
+ }
+ assert(b1 == e1);
+ assert(b2 == e2);
+}
+
+constexpr int absoluteValue(int x) { return x < 0 ? -x : x; }
+
+template <class T>
+constexpr const T&& asConstRvalue(T&& t) {
+ return static_cast<const T&&>(t);
+}
+
+constexpr void test_adaptor_with_pattern(std::span<std::string_view> buff) {
+ // Test `views::join_with(pattern)(v)`
+ {
+ using Result = std::ranges::join_with_view<Range, Pattern>;
+ const Range range(buff.data(), buff.data() + buff.size());
+ Pattern pattern;
+
+ {
+ // 'views::join_with(pattern)' - &&
+ std::same_as<Result> decltype(auto) result = std::views::join_with(pattern)(range);
+ compareViews(result, "abcd, ef, ghij, kl");
+ }
+ {
+ // 'views::join_with(pattern)' - const&&
+ std::same_as<Result> decltype(auto) result = asConstRvalue(std::views::join_with(pattern))(range);
+ compareViews(result, "abcd, ef, ghij, kl");
+ }
+ {
+ // 'views::join_with(pattern)' - &
+ auto partial = std::views::join_with(pattern);
+ std::same_as<Result> decltype(auto) result = partial(range);
+ compareViews(result, "abcd, ef, ghij, kl");
+ }
+ {
+ // 'views::join_with(pattern)' - const&
+ auto const partial = std::views::join_with(pattern);
+ std::same_as<Result> decltype(auto) result = partial(range);
+ compareViews(result, "abcd, ef, ghij, kl");
+ }
+ }
+
+ // Test `v | views::join_with(pattern)`
+ {
+ using Result = std::ranges::join_with_view<Range, Pattern>;
+ const Range range(buff.data(), buff.data() + buff.size());
+ Pattern pattern;
+
+ {
+ // 'views::join_with(pattern)' - &&
+ std::same_as<Result> decltype(auto) result = range | std::views::join_with(pattern);
+ compareViews(result, "abcd, ef, ghij, kl");
+ }
+ {
+ // 'views::join_with(pattern)' - const&&
+ std::same_as<Result> decltype(auto) result = range | asConstRvalue(std::views::join_with(pattern));
+ compareViews(result, "abcd, ef, ghij, kl");
+ }
+ {
+ // 'views::join_with(pattern)' - &
+ auto partial = std::views::join_with(pattern);
+ std::same_as<Result> decltype(auto) result = range | partial;
+ compareViews(result, "abcd, ef, ghij, kl");
+ }
+ {
+ // 'views::join_with(pattern)' - const&
+ auto const partial = std::views::join_with(pattern);
+ std::same_as<Result> decltype(auto) result = range | partial;
+ compareViews(result, "abcd, ef, ghij, kl");
+ }
+ }
+
+ // Test `views::join_with(v, pattern)` range adaptor object
+ {
+ using Result = std::ranges::join_with_view<Range, Pattern>;
+ const Range range(buff.data(), buff.data() + buff.size());
+ Pattern pattern;
+
+ {
+ // 'views::join_with' - &&
+ auto range_adaptor = std::views::join_with;
+ std::same_as<Result> decltype(auto) result = std::move(range_adaptor)(range, pattern);
+ compareViews(result, "abcd, ef, ghij, kl");
+ }
+ {
+ // 'views::join_with' - const&&
+ const auto range_adaptor = std::views::join_with;
+ std::same_as<Result> decltype(auto) result = std::move(range_adaptor)(range, pattern);
+ compareViews(result, "abcd, ef, ghij, kl");
+ }
+ {
+ // 'views::join_with' - &
+ auto range_adaptor = std::views::join_with;
+ std::same_as<Result> decltype(auto) result = range_adaptor(range, pattern);
+ compareViews(result, "abcd, ef, ghij, kl");
+ }
+ {
+ // 'views::join_with' - const&
+ const auto range_adaptor = std::views::join_with;
+ std::same_as<Result> decltype(auto) result = range_adaptor(range, pattern);
+ compareViews(result, "abcd, ef, ghij, kl");
+ }
+ }
+
+ // Test `adaptor | views::join_with(pattern)`
+ {
+ auto pred = [](std::string_view s) { return s.size() >= 3; };
+ using Result = std::ranges::join_with_view<std::ranges::filter_view<Range, decltype(pred)>, Pattern>;
+ const Range range(buff.data(), buff.data() + buff.size());
+ Pattern pattern;
+
+ {
+ std::same_as<Result> decltype(auto) result = range | std::views::filter(pred) | std::views::join_with(pattern);
+ compareViews(result, "abcd, ghij");
+ }
+ {
+ const auto partial = std::views::filter(pred) | std::views::join_with(pattern);
+ std::same_as<Result> decltype(auto) result = range | partial;
+ compareViews(result, "abcd, ghij");
+ }
+ }
+}
+
+constexpr void test_adaptor_with_single_element(std::span<std::string_view> buff) {
+ // Test `views::join_with(element)(v)`
+ {
+ using Result = std::ranges::join_with_view<Range, std::ranges::single_view<char>>;
+ const Range range(buff.data(), buff.data() + buff.size());
+ const char element = '.';
+
+ {
+ // 'views::join_with(element)' - &&
+ std::same_as<Result> decltype(auto) result = std::views::join_with(element)(range);
+ compareViews(result, "abcd.ef.ghij.kl");
+ }
+ {
+ // 'views::join_with(element)' - const&&
+ std::same_as<Result> decltype(auto) result = asConstRvalue(std::views::join_with(element))(range);
+ compareViews(result, "abcd.ef.ghij.kl");
+ }
+ {
+ // 'views::join_with(element)' - &
+ auto partial = std::views::join_with(element);
+ std::same_as<Result> decltype(auto) result = partial(range);
+ compareViews(result, "abcd.ef.ghij.kl");
+ }
+ {
+ // 'views::join_with(element)' - const&
+ const auto partial = std::views::join_with(element);
+ std::same_as<Result> decltype(auto) result = partial(range);
+ compareViews(result, "abcd.ef.ghij.kl");
+ }
+ }
+
+ // Test `v | views::join_with(element)`
+ {
+ using Result = std::ranges::join_with_view<Range, std::ranges::single_view<char>>;
+ const Range range(buff.data(), buff.data() + buff.size());
+ const char element = '.';
+
+ {
+ // 'views::join_with(element)' - &&
+ std::same_as<Result> decltype(auto) result = range | std::views::join_with(element);
+ compareViews(result, "abcd.ef.ghij.kl");
+ }
+ {
+ // 'views::join_with(element)' - const&&
+ std::same_as<Result> decltype(auto) result = range | asConstRvalue(std::views::join_with(element));
+ compareViews(result, "abcd.ef.ghij.kl");
+ }
+ {
+ // 'views::join_with(element)' - &
+ auto partial = std::views::join_with(element);
+ std::same_as<Result> decltype(auto) result = range | partial;
+ compareViews(result, "abcd.ef.ghij.kl");
+ }
+ {
+ // 'views::join_with(element)' - const&
+ const auto partial = std::views::join_with(element);
+ std::same_as<Result> decltype(auto) result = range | partial;
+ compareViews(result, "abcd.ef.ghij.kl");
+ }
+ }
+
+ // Test `views::join_with(v, element)` range adaptor object
+ {
+ using Result = std::ranges::join_with_view<Range, std::ranges::single_view<char>>;
+ const Range range(buff.data(), buff.data() + buff.size());
+ const char element = '.';
+
+ {
+ // 'views::join_with' - &&
+ auto range_adaptor = std::views::join_with;
+ std::same_as<Result> decltype(auto) result = std::move(range_adaptor)(range, element);
+ compareViews(result, "abcd.ef.ghij.kl");
+ }
+ {
+ // 'views::join_with' - const&&
+ const auto range_adaptor = std::views::join_with;
+ std::same_as<Result> decltype(auto) result = std::move(range_adaptor)(range, element);
+ compareViews(result, "abcd.ef.ghij.kl");
+ }
+ {
+ // 'views::join_with' - &
+ auto range_adaptor = std::views::join_with;
+ std::same_as<Result> decltype(auto) result = range_adaptor(range, element);
+ compareViews(result, "abcd.ef.ghij.kl");
+ }
+ {
+ // 'views::join_with' - const&
+ const auto range_adaptor = std::views::join_with;
+ std::same_as<Result> decltype(auto) result = range_adaptor(range, element);
+ compareViews(result, "abcd.ef.ghij.kl");
+ }
+ }
+
+ // Test `adaptor | views::join_with(element)`
+ {
+ auto pred = [](std::string_view s) { return s.size() >= 3; };
+ using Result =
+ std::ranges::join_with_view<std::ranges::filter_view<Range, decltype(pred)>, std::ranges::single_view<char>>;
+ const Range range(buff.data(), buff.data() + buff.size());
+ const char element = '.';
+
+ {
+ std::same_as<Result> decltype(auto) result = range | std::views::filter(pred) | std::views::join_with(element);
+ compareViews(result, "abcd.ghij");
+ }
+ {
+ const auto partial = std::views::filter(pred) | std::views::join_with(element);
+ std::same_as<Result> decltype(auto) result = range | partial;
+ compareViews(result, "abcd.ghij");
+ }
+ }
+}
+
+constexpr bool test() {
+ std::string_view buff[] = {"abcd", "ef", "ghij", "kl"};
+
+ // Test range adaptor object
+ {
+ using RangeAdaptorObject = decltype(std::views::join_with);
+ static_assert(std::is_const_v<RangeAdaptorObject>);
+
+ // The type of a customization point object, ignoring cv-qualifiers, shall model semiregular
+ static_assert(std::semiregular<std::remove_const<RangeAdaptorObject>>);
+ }
+
+ test_adaptor_with_pattern(buff);
+ test_adaptor_with_single_element(buff);
+
+ // Test that one can call std::views::join_with with arbitrary stuff, as long as we
+ // don't try to actually complete the call by passing it a range.
+ //
+ // That makes no sense and we can't do anything with the result, but it's valid.
+ {
+ long array[3] = {1, 2, 3};
+ [[maybe_unused]] auto partial = std::views::join_with(std::move(array));
+ }
+
+ // Test SFINAE friendliness
+ {
+ struct NotAView {};
+
+ static_assert(!CanBePiped<Range, decltype(std::views::join_with)>);
+ static_assert(CanBePiped<Range, decltype(std::views::join_with(Pattern{}))>);
+ static_assert(CanBePiped<Range, decltype(std::views::join_with('.'))>);
+ static_assert(!CanBePiped<NotAView, decltype(std::views::join_with(Pattern{}))>);
+ static_assert(!CanBePiped<NotAView, decltype(std::views::join_with('.'))>);
+ static_assert(!CanBePiped<std::initializer_list<char>, decltype(std::views::join_with(Pattern{}))>);
+ static_assert(!CanBePiped<std::initializer_list<char>, decltype(std::views::join_with('.'))>);
+ static_assert(!CanBePiped<Range, decltype(std::views::join_with(NotAView{}))>);
+
+ static_assert(!std::is_invocable_v<decltype(std::views::join_with)>);
+ static_assert(!std::is_invocable_v<decltype(std::views::join_with), Pattern, Range>);
+ static_assert(!std::is_invocable_v<decltype(std::views::join_with), char, Range>);
+ static_assert(std::is_invocable_v<decltype(std::views::join_with), Range, Pattern>);
+ static_assert(std::is_invocable_v<decltype(std::views::join_with), Range, char>);
+ static_assert(!std::is_invocable_v<decltype(std::views::join_with), Range, Pattern, Pattern>);
+ static_assert(!std::is_invocable_v<decltype(std::views::join_with), Range, char, char>);
+ static_assert(!std::is_invocable_v<decltype(std::views::join_with), NonCopyablePattern>);
+ }
+
+ {
+ static_assert(std::is_same_v<decltype(std::ranges::views::join_with), decltype(std::views::join_with)>);
+ assert(std::addressof(std::ranges::views::join_with) == std::addressof(std::views::join_with));
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.overview/example.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.overview/example.pass.cpp
new file mode 100644
index 0000000000000..1f7b889e2602e
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.overview/example.pass.cpp
@@ -0,0 +1,42 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// [Example 1:
+// vector<string> vs = {"the", "quick", "brown", "fox"};
+// for (char c : vs | views::join_with('-')) {
+// cout << c;
+// }
+// // The above prints the-quick-brown-fox
+// - end example]
+
+#include <ranges>
+
+#include <cassert>
+#include <string>
+#include <vector>
+
+constexpr bool test() {
+ std::vector<std::string> vs = {"the", "quick", "brown", "fox"};
+ std::string result;
+ for (char c : vs | std::views::join_with('-')) {
+ result += c;
+ }
+
+ return result == "the-quick-brown-fox";
+}
+
+int main(int, char**) {
+ assert(test());
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.sentinel/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.sentinel/ctor.default.pass.cpp
new file mode 100644
index 0000000000000..634db24e02f28
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.sentinel/ctor.default.pass.cpp
@@ -0,0 +1,37 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// sentinel() = default;
+
+#include <ranges>
+
+#include "../types.h"
+
+constexpr bool test() {
+ using Inner = BasicVectorView<char, ViewProperties{.common = false}, forward_iterator>;
+ using V = BasicVectorView<Inner, ViewProperties{}, forward_iterator>;
+ using Pattern = Inner;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+ static_assert(!std::ranges::common_range<JWV>);
+
+ [[maybe_unused]] std::ranges::sentinel_t<JWV> se;
+ [[maybe_unused]] std::ranges::sentinel_t<const JWV> cse;
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.sentinel/ctor.non_const.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.sentinel/ctor.non_const.pass.cpp
new file mode 100644
index 0000000000000..14688ba7c538f
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.sentinel/ctor.non_const.pass.cpp
@@ -0,0 +1,74 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// constexpr sentinel(sentinel<!Const> s)
+// requires Const && convertible_to<sentinel_t<V>, sentinel_t<Base>>;
+
+#include <ranges>
+
+#include <type_traits>
+#include <vector>
+
+#include "../types.h"
+#include "test_iterators.h"
+
+constexpr bool test() {
+ { // Regular conversion from `!Const` to `Const` sentinel
+ using Inner = BasicVectorView<int, ViewProperties{.common = false}, forward_iterator>;
+ std::vector<Inner> vec = {Inner{11, 12}, Inner{13, 14}};
+
+ std::ranges::join_with_view jwv(vec, 0);
+ using JWV = decltype(jwv);
+ static_assert(!std::ranges::common_range<JWV>);
+
+ using Sent = std::ranges::sentinel_t<JWV>;
+ using CSent = std::ranges::sentinel_t<const JWV>;
+ static_assert(!std::same_as<Sent, CSent>);
+
+ Sent se = jwv.end();
+ [[maybe_unused]] CSent cse = se;
+ }
+
+ { // Test conversion from `Const` to `!Const` (should be invalid)
+ using Inner = BasicVectorView<int, ViewProperties{.common = false}, forward_iterator>;
+ using V = std::vector<Inner>;
+ using Pattern = std::ranges::single_view<int>;
+ using JWV = std::ranges::join_with_view<std::views::all_t<V>, Pattern>;
+ static_assert(!std::ranges::common_range<JWV>);
+
+ using Sent = std::ranges::sentinel_t<JWV>;
+ using CSent = std::ranges::sentinel_t<const JWV>;
+ static_assert(!std::convertible_to<CSent, Sent>);
+ static_assert(!std::constructible_from<Sent, CSent>);
+ }
+
+ { // When `convertible_to<sentinel_t<V>, sentinel_t<Base>>` is not modeled
+ using V = ConstOppositeView<std::vector<long>>;
+ using Pattern = std::ranges::single_view<long>;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+ static_assert(!std::ranges::common_range<JWV>);
+
+ using Sent = std::ranges::sentinel_t<JWV>;
+ using CSent = std::ranges::sentinel_t<const JWV>;
+ static_assert(!std::convertible_to<CSent, Sent>);
+ static_assert(!std::constructible_from<Sent, CSent>);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.sentinel/eq.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.sentinel/eq.pass.cpp
new file mode 100644
index 0000000000000..44fb25b403ba1
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.sentinel/eq.pass.cpp
@@ -0,0 +1,109 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// template<bool OtherConst>
+// requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
+// friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);
+
+#include <ranges>
+
+#include <cassert>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "../types.h"
+#include "test_comparisons.h"
+#include "test_iterators.h"
+
+struct NonCrossConstComparableView : std::ranges::view_base {
+ using NonConstRange = std::vector<int>;
+ NonConstRange* begin();
+ sentinel_wrapper<NonConstRange*> end();
+
+ using ConstRange = BasicVectorView<int, ViewProperties{}, forward_iterator>;
+ ConstRange* begin() const;
+ sentinel_wrapper<ConstRange*> end() const;
+};
+
+static_assert(std::ranges::range<NonCrossConstComparableView>);
+static_assert(std::ranges::range<const NonCrossConstComparableView>);
+
+constexpr bool test() {
+ using Inner = BasicVectorView<int, ViewProperties{.common = false}, cpp20_input_iterator>;
+ using V = std::vector<Inner>;
+ using Pattern = std::ranges::single_view<int>;
+ using JWV = std::ranges::join_with_view<std::ranges::owning_view<V>, Pattern>;
+ static_assert(!std::ranges::common_range<JWV>);
+
+ using Iter = std::ranges::iterator_t<JWV>;
+ using CIter = std::ranges::iterator_t<const JWV>;
+ static_assert(!std::same_as<Iter, CIter>);
+
+ using Sent = std::ranges::sentinel_t<JWV>;
+ using CSent = std::ranges::sentinel_t<const JWV>;
+ static_assert(!std::same_as<Sent, CSent>);
+
+ { // Compare iterator<Const> with sentinel<Const>
+ { // Const == true
+ AssertEqualityReturnBool<CIter, CSent>();
+ const JWV jwv(V{Inner{1, 2}, Inner{4}}, 3);
+ assert(testEquality(std::ranges::next(jwv.begin(), 4), jwv.end(), true));
+ assert(testEquality(jwv.begin(), jwv.end(), false));
+ }
+
+ { // Const == false
+ AssertEqualityReturnBool<Iter, Sent>();
+ JWV jwv(V{Inner{5}, Inner{7, 8}}, 6);
+ assert(testEquality(std::ranges::next(jwv.begin(), 4), jwv.end(), true));
+ assert(testEquality(std::ranges::next(jwv.begin(), 2), jwv.end(), false));
+ }
+ }
+
+ { // Compare iterator<Const> with sentinel<!Const>
+ { // Const == true
+ AssertEqualityReturnBool<CIter, Sent>();
+ JWV jwv(V{Inner{9, 10}, Inner{12}}, 11);
+ assert(testEquality(std::ranges::next(std::as_const(jwv).begin(), 4), jwv.end(), true));
+ assert(testEquality(std::ranges::next(std::as_const(jwv).begin(), 2), jwv.end(), false));
+ }
+
+ { // Const == false
+ AssertEqualityReturnBool<Iter, CSent>();
+ JWV jwv(V{Inner{13}, Inner{15, 16}}, 14);
+ assert(testEquality(std::ranges::next(jwv.begin(), 4), std::as_const(jwv).end(), true));
+ assert(testEquality(std::ranges::next(jwv.begin(), 3), std::as_const(jwv).end(), false));
+ }
+ }
+
+ { // Check invalid comparisons between iterator<Const> and sentinel<!Const>
+ using JWV2 = std::ranges::join_with_view<NonCrossConstComparableView, Pattern>;
+ static_assert(!std::ranges::common_range<JWV2>);
+
+ static_assert(!weakly_equality_comparable_with<std::ranges::iterator_t<const JWV2>, std::ranges::sentinel_t<JWV2>>);
+ static_assert(!weakly_equality_comparable_with<std::ranges::iterator_t<JWV2>, std::ranges::sentinel_t<const JWV2>>);
+
+ // Those should be valid
+ static_assert(weakly_equality_comparable_with<std::ranges::iterator_t<JWV2>, std::ranges::sentinel_t<JWV2>>);
+ static_assert(
+ weakly_equality_comparable_with<std::ranges::iterator_t<const JWV2>, std::ranges::sentinel_t<const JWV2>>);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/base.pass.cpp
new file mode 100644
index 0000000000000..4e79b0fadfce4
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/base.pass.cpp
@@ -0,0 +1,132 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// constexpr View base() const& requires copy_constructible<View>;
+// constexpr View base() &&;
+
+#include <ranges>
+
+#include <cassert>
+#include <utility>
+#include <vector>
+
+using InnerRange = std::vector<int>;
+
+struct Range : std::ranges::view_base {
+ constexpr explicit Range(InnerRange* b, InnerRange* e) : begin_(b), end_(e) {}
+ constexpr Range(const Range& other) : begin_(other.begin_), end_(other.end_), was_copy_initialized_(true) {}
+ constexpr Range(Range&& other) : begin_(other.begin_), end_(other.end_), was_move_initialized_(true) {}
+ Range& operator=(const Range&) = default;
+ Range& operator=(Range&&) = default;
+ constexpr InnerRange* begin() const { return begin_; }
+ constexpr InnerRange* end() const { return end_; }
+
+ InnerRange* begin_;
+ InnerRange* end_;
+ bool was_copy_initialized_ = false;
+ bool was_move_initialized_ = false;
+};
+
+static_assert(std::ranges::view<Range>);
+static_assert(std::ranges::input_range<Range>);
+
+struct Pattern : std::ranges::view_base {
+ static constexpr int pat[2] = {0, 0};
+ constexpr const int* begin() const { return pat; }
+ constexpr const int* end() const { return pat + 2; }
+};
+
+static_assert(std::ranges::view<Pattern>);
+static_assert(std::ranges::forward_range<Pattern>);
+
+template <class Tp>
+struct NonCopyableRange : std::ranges::view_base {
+ NonCopyableRange(const NonCopyableRange&) = delete;
+ NonCopyableRange(NonCopyableRange&&) = default;
+ NonCopyableRange& operator=(const NonCopyableRange&) = default;
+ NonCopyableRange& operator=(NonCopyableRange&&) = default;
+ Tp* begin() const;
+ Tp* end() const;
+};
+
+static_assert(!std::copy_constructible<NonCopyableRange<InnerRange>>);
+static_assert(!std::copy_constructible<NonCopyableRange<int>>);
+
+template <typename T>
+concept CanCallBaseOn = requires(T&& t) { std::forward<T>(t).base(); };
+
+constexpr bool test() {
+ InnerRange buff[3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
+ Pattern pattern;
+
+ { // Check the const& overload
+ Range range(buff, buff + 3);
+ std::ranges::join_with_view<Range, Pattern> view(range, pattern);
+ std::same_as<Range> decltype(auto) result = view.base();
+ assert(result.was_copy_initialized_);
+ assert(result.begin() == buff);
+ assert(result.end() == buff + 3);
+ }
+
+ { // Check the const& overload on const `view`
+ Range range(buff, buff + 3);
+ const std::ranges::join_with_view<Range, Pattern> view(range, pattern);
+ std::same_as<Range> decltype(auto) result = view.base();
+ assert(result.was_copy_initialized_);
+ assert(result.begin() == buff);
+ assert(result.end() == buff + 3);
+ }
+
+ { // Check the && overload
+ Range range(buff, buff + 3);
+ std::ranges::join_with_view<Range, Pattern> view(range, pattern);
+ std::same_as<Range> decltype(auto) result = std::move(view).base();
+ assert(result.was_move_initialized_);
+ assert(result.begin() == buff);
+ assert(result.end() == buff + 3);
+ }
+
+ { // Ensure the const& overload is not considered when the base is not copy-constructible
+ static_assert(!CanCallBaseOn<const std::ranges::join_with_view<NonCopyableRange<InnerRange>, Pattern>&>);
+ static_assert(!CanCallBaseOn<std::ranges::join_with_view<NonCopyableRange<InnerRange>, Pattern>&>);
+ static_assert(!CanCallBaseOn<const std::ranges::join_with_view<NonCopyableRange<InnerRange>, Pattern>&&>);
+ static_assert(CanCallBaseOn<std::ranges::join_with_view<NonCopyableRange<InnerRange>, Pattern>&&>);
+ static_assert(CanCallBaseOn<std::ranges::join_with_view<NonCopyableRange<InnerRange>, Pattern>>);
+ }
+
+ { // Ensure the const& overload does not depend on Pattern's copy-constructability
+ static_assert(CanCallBaseOn<const std::ranges::join_with_view<Range, NonCopyableRange<int>>&>);
+ static_assert(CanCallBaseOn<std::ranges::join_with_view<Range, NonCopyableRange<int>>&>);
+ static_assert(CanCallBaseOn<const std::ranges::join_with_view<Range, NonCopyableRange<int>>&&>);
+ static_assert(CanCallBaseOn<std::ranges::join_with_view<Range, NonCopyableRange<int>>&&>);
+ static_assert(CanCallBaseOn<std::ranges::join_with_view<Range, NonCopyableRange<int>>>);
+ }
+
+ { // Check above two at the same time
+ static_assert(
+ !CanCallBaseOn<const std::ranges::join_with_view<NonCopyableRange<InnerRange>, NonCopyableRange<int>>&>);
+ static_assert(!CanCallBaseOn<std::ranges::join_with_view<NonCopyableRange<InnerRange>, NonCopyableRange<int>>&>);
+ static_assert(
+ !CanCallBaseOn<const std::ranges::join_with_view< NonCopyableRange<InnerRange>, NonCopyableRange<int>>&&>);
+ static_assert(CanCallBaseOn<std::ranges::join_with_view<NonCopyableRange<InnerRange>, NonCopyableRange<int>>&&>);
+ static_assert(CanCallBaseOn<std::ranges::join_with_view<NonCopyableRange<InnerRange>, NonCopyableRange<int>>>);
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/begin.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/begin.pass.cpp
new file mode 100644
index 0000000000000..22872c20773a4
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/begin.pass.cpp
@@ -0,0 +1,221 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// constexpr auto begin();
+// constexpr auto begin() const
+// requires forward_range<const V> &&
+// forward_range<const Pattern> &&
+// is_reference_v<range_reference_t<const V>> &&
+// input_range<range_reference_t<const V>>;
+
+#include <ranges>
+
+#include <algorithm>
+#include <array>
+#include <string>
+#include <vector>
+
+#include "../types.h"
+#include "test_iterators.h"
+
+template <bool Simple>
+using MaybeSimpleForwardView = BasicView<std::vector<std::string>, ViewProperties{.simple = Simple}, forward_iterator>;
+
+template <bool Simple>
+using MaybeSimpleForwardRvalueView =
+ BasicView<RvalueVector<std::string>, ViewProperties{.simple = Simple}, forward_iterator>;
+
+template <bool Simple>
+using MaybeSimplePattern = BasicView<std::string, ViewProperties{.simple = Simple}, forward_iterator>;
+
+template <class V, class Pattern>
+concept JoinWithViewHasConstBegin = requires(const std::ranges::join_with_view<V, Pattern> jwv) {
+ { jwv.begin() } -> std::input_iterator;
+};
+
+constexpr void test_begin() {
+ using Str = std::string;
+ using Vec = std::vector<Str>;
+
+ { // `V` models simple-view
+ // `is_reference_v<InnerRng>` is true
+ // `Pattern` models simple-view
+ // `V` and `Pattern` contain some elements
+ using V = MaybeSimpleForwardView<true>;
+ using Pattern = MaybeSimplePattern<true>;
+ std::ranges::join_with_view<V, Pattern> jwv(V(Vec{"A", "B", "C"}), Pattern(Str{">>"}));
+ auto it = jwv.begin();
+ assert(std::ranges::equal(std::views::counted(it, 7), Str{"A>>B>>C"}));
+ }
+
+ { // `V` does not model simple-view
+ // `is_reference_v<InnerRng>` is true
+ // `Pattern` models simple-view
+ // `V` and `Pattern` are empty
+ using V = MaybeSimpleForwardView<false>;
+ using Pattern = MaybeSimplePattern<true>;
+ std::ranges::join_with_view<V, Pattern> jwv(V(Vec{}), Pattern(Str{}));
+ auto it = jwv.begin();
+ assert(it == jwv.end());
+ }
+
+ { // `V` models simple-view
+ // `is_reference_v<InnerRng>` is false
+ // `Pattern` models simple-view
+ // `V` contains two elements, `Pattern` is empty
+ using V = MaybeSimpleForwardRvalueView<true>;
+ using Pattern = MaybeSimplePattern<true>;
+ std::ranges::join_with_view<V, Pattern> jwv(V(Vec{"1", "2"}), Pattern(Str{""}));
+ auto it = jwv.begin();
+ assert(*it == '1');
+ assert(*++it == '2');
+ }
+
+ { // `V` models simple-view
+ // `is_reference_v<InnerRng>` is true
+ // `Pattern` does not model simple-view
+ // `V` contains one element, `Pattern` is empty
+ using V = MaybeSimpleForwardView<true>;
+ using Pattern = MaybeSimplePattern<false>;
+ std::ranges::join_with_view<V, Pattern> jwv(V(Vec{"07"}), Pattern(Str{}));
+ auto it = jwv.begin();
+ assert(*it++ == '0');
+ assert(*it == '7');
+ }
+
+ { // `V` does not model simple-view
+ // `is_reference_v<InnerRng>` is false
+ // `Pattern` models simple-view
+ // `V` contains three elements (2nd is empty), `Pattern` is not empty
+ using V = MaybeSimpleForwardRvalueView<false>;
+ using Pattern = MaybeSimplePattern<true>;
+ std::ranges::join_with_view<V, Pattern> jwv(V(Vec{"A", "", "C"}), Pattern(Str{"--"}));
+ auto it = jwv.begin();
+ assert(std::ranges::equal(std::views::counted(it, 6), Str("A----C")));
+ }
+
+ { // `V` does not model simple-view
+ // `is_reference_v<InnerRng>` is true
+ // `Pattern` does not model simple-view
+ // `V` contains some empty elements, `Pattern` is not empty
+ using V = MaybeSimpleForwardView<false>;
+ using Pattern = MaybeSimplePattern<false>;
+ std::ranges::join_with_view<V, Pattern> jwv(V(Vec{"", "", ""}), Pattern(Str{"-"}));
+ auto it = jwv.begin();
+ assert(*it++ == '-');
+ assert(*it == '-');
+ }
+
+ { // `V` models simple-view
+ // `is_reference_v<InnerRng>` is false
+ // `Pattern` does not model simple-view
+ // `V` contains two elements, `Pattern` is not empty
+ using V = MaybeSimpleForwardRvalueView<true>;
+ using Pattern = MaybeSimplePattern<false>;
+ std::ranges::join_with_view<V, Pattern> jwv(V(Vec{"X", "Z"}), Pattern(Str{"Y"}));
+ auto it = jwv.begin();
+ assert(*it == 'X');
+ assert(*++it == 'Y');
+ assert(*++it == 'Z');
+ }
+
+ { // `V` does not model simple-view
+ // `is_reference_v<InnerRng>` is false
+ // `Pattern` does not model simple-view
+ // `V` contains two empty elements, `Pattern` is not empty
+ using V = MaybeSimpleForwardRvalueView<false>;
+ using Pattern = MaybeSimplePattern<false>;
+ std::ranges::join_with_view<V, Pattern> jwv(V(Vec{"", ""}), Pattern(Str{"?"}));
+ auto it = jwv.begin();
+ assert(*it == '?');
+ assert(++it == jwv.end());
+ }
+
+ { // `V` does not model forward range
+ // `V` contains some empty elements, `Pattern` is empty
+ using V = BasicView<Vec, ViewProperties{.common = false}, cpp20_input_iterator>;
+ using Pattern = MaybeSimplePattern<false>;
+ std::ranges::join_with_view<V, Pattern> jwv(V(Vec{"", "", ""}), Pattern(Str{""}));
+ auto it = jwv.begin();
+ assert(it == jwv.end());
+ }
+}
+
+constexpr void test_const_begin() {
+ using Vec = std::vector<std::array<int, 2>>;
+ using Pat = std::array<int, 2>;
+
+ { // `const V` models forward range
+ // `const Pattern` models forward range
+ // `is_reference_v<range_reference_t<const V>>` is true
+ // `range_reference_t<const V>` models input range
+ using V = BasicView<Vec, ViewProperties{}, forward_iterator>;
+ using Pattern = BasicView<Pat, ViewProperties{}, forward_iterator>;
+
+ const std::ranges::join_with_view<V, Pattern> jwv{V{Vec{std::array{1, 2}, std::array{3, 4}}}, Pattern{Pat{0, 0}}};
+ auto it = jwv.begin();
+ assert(std::ranges::equal(std::views::counted(it, 6), std::array{1, 2, 0, 0, 3, 4}));
+ }
+
+ // `const V` does not model forward range
+ // `const Pattern` models forward range
+ // `is_reference_v<range_reference_t<const V>>` is true
+ // `range_reference_t<const V>` models input range
+ static_assert(!JoinWithViewHasConstBegin<BasicView<Vec, ViewProperties{.common = false}, cpp20_input_iterator>,
+ BasicView<Pat, ViewProperties{}, forward_iterator>>);
+
+ // `const V` models forward range
+ // `const Pattern` does not model forward range
+ // `is_reference_v<range_reference_t<const V>>` is true
+ // `range_reference_t<const V>` models input range
+ static_assert(!JoinWithViewHasConstBegin<BasicView<Vec, ViewProperties{}, forward_iterator>,
+ BasicView<Pat, ViewProperties{.common = false}, cpp20_input_iterator>>);
+
+ // `const V` models forward range
+ // `const Pattern` models forward range
+ // `is_reference_v<range_reference_t<const V>>` is false
+ // `range_reference_t<const V>` models input range
+ static_assert(
+ !JoinWithViewHasConstBegin<BasicView<RvalueVector<std::vector<int>>, ViewProperties{}, forward_iterator>,
+ BasicView<Pat, ViewProperties{}, forward_iterator>>);
+
+ // `const V` models forward range
+ // `const Pattern` models forward range
+ // `is_reference_v<range_reference_t<const V>>` is true
+ // `range_reference_t<const V>` does not model input range
+ static_assert(!JoinWithViewHasConstBegin<
+ BasicView<std::vector<InputRangeButOutputWhenConst<int>>, ViewProperties{}, forward_iterator>,
+ BasicView<Pat, ViewProperties{}, forward_iterator>>);
+
+ // `concatable<range_reference_t<const V>, const Pattern>` is not satisfied
+ // See also LWG-4074: compatible-joinable-ranges is underconstrained
+ static_assert(!JoinWithViewHasConstBegin<BasicVectorView<int, ViewProperties{}, forward_iterator>,
+ lwg4074::PatternWithProxyConstAccess>);
+
+ // Check situation when iterators returned by `begin()` and `begin() const` are the same
+ using JWV = std::ranges::join_with_view<MaybeSimpleForwardView<true>, MaybeSimplePattern<true>>;
+ static_assert(std::same_as<std::ranges::iterator_t<JWV&>, std::ranges::iterator_t<const JWV&>>);
+}
+
+constexpr bool test() {
+ test_begin();
+ test_const_begin();
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/constraints.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/constraints.compile.pass.cpp
new file mode 100644
index 0000000000000..86d624941d22f
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/constraints.compile.pass.cpp
@@ -0,0 +1,289 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// template <input_range V, forward_range Pattern>
+// requires view<V> && input_range<range_reference_t<V>> && view<Pattern> &&
+// compatible-joinable-ranges<range_reference_t<V>, Pattern>
+// class join_with_view;
+
+#include <ranges>
+
+#include <cstddef>
+#include <vector>
+
+#include "test_iterators.h"
+#include "../types.h"
+
+template <class View, class Pattern>
+concept CanFormJoinWithView = requires { typename std::ranges::join_with_view<View, Pattern>; };
+
+// join_with_view is not valid when `V` is not an input_range
+namespace test_when_view_is_not_an_input_range {
+struct View : std::ranges::view_base {
+ using It = cpp20_output_iterator<std::vector<int>*>;
+ It begin();
+ sentinel_wrapper<It> end();
+};
+
+struct Pattern : std::ranges::view_base {
+ int* begin();
+ int* end();
+};
+
+static_assert(std::ranges::range<View>);
+static_assert(!std::ranges::input_range<View>);
+static_assert(std::ranges::view<View>);
+static_assert(std::ranges::forward_range<Pattern>);
+static_assert(std::ranges::view<Pattern>);
+static_assert(!CanFormJoinWithView<View, Pattern>);
+} // namespace test_when_view_is_not_an_input_range
+
+// join_with_view is not valid when `Pattern` is not a forward_range
+namespace test_when_pattern_is_not_a_forward_range {
+struct View : std::ranges::view_base {
+ std::vector<float>* begin();
+ std::vector<float>* end();
+};
+
+struct Pattern : std::ranges::view_base {
+ using It = cpp20_input_iterator<float*>;
+ It begin();
+ sentinel_wrapper<It> end();
+};
+
+static_assert(std::ranges::input_range<View>);
+static_assert(std::ranges::view<View>);
+static_assert(!std::ranges::forward_range<Pattern>);
+static_assert(std::ranges::view<Pattern>);
+static_assert(!CanFormJoinWithView<View, Pattern>);
+} // namespace test_when_pattern_is_not_a_forward_range
+
+// join_with_view is not valid when `V` does not model std::ranges::view
+namespace test_when_view_does_not_model_view {
+struct View {
+ std::vector<double>* begin();
+ std::vector<double>* end();
+};
+
+struct Pattern : std::ranges::view_base {
+ double* begin();
+ double* end();
+};
+
+static_assert(std::ranges::input_range<View>);
+static_assert(!std::ranges::view<View>);
+static_assert(std::ranges::forward_range<Pattern>);
+static_assert(std::ranges::view<Pattern>);
+static_assert(!CanFormJoinWithView<View, Pattern>);
+} // namespace test_when_view_does_not_model_view
+
+// join_with_view is not valid when `range_reference_t` of `V` is not an input_range
+namespace test_when_range_reference_t_of_view_is_not_an_input_range {
+struct InnerRange {
+ using It = cpp20_output_iterator<long*>;
+ It begin();
+ sentinel_wrapper<It> end();
+};
+
+struct View : std::ranges::view_base {
+ InnerRange* begin();
+ InnerRange* end();
+};
+
+struct Pattern : std::ranges::view_base {
+ long* begin();
+ long* end();
+};
+
+static_assert(std::ranges::range<InnerRange>);
+static_assert(!std::ranges::input_range<InnerRange>);
+static_assert(std::ranges::input_range<View>);
+static_assert(std::ranges::view<View>);
+static_assert(std::ranges::forward_range<Pattern>);
+static_assert(std::ranges::view<Pattern>);
+static_assert(!CanFormJoinWithView<View, Pattern>);
+} // namespace test_when_range_reference_t_of_view_is_not_an_input_range
+
+// join_with_view is not valid when `Pattern` does not model std::ranges::view
+namespace test_when_pattern_does_not_model_view {
+struct View : std::ranges::view_base {
+ std::vector<short>* begin();
+ std::vector<short>* end();
+};
+
+struct Pattern {
+ short* begin();
+ short* end();
+};
+
+static_assert(std::ranges::input_range<View>);
+static_assert(std::ranges::view<View>);
+static_assert(std::ranges::forward_range<Pattern>);
+static_assert(!std::ranges::view<Pattern>);
+static_assert(!CanFormJoinWithView<View, Pattern>);
+} // namespace test_when_pattern_does_not_model_view
+
+// join_with_view is not valid when `range_reference_t<View>` and pattern
+// does not model together compatible-joinable-ranges
+namespace test_when_used_ranges_are_not_concatable {
+using std::ranges::range_reference_t;
+using std::ranges::range_rvalue_reference_t;
+using std::ranges::range_value_t;
+
+template <class InnerRange>
+struct View : std::ranges::view_base {
+ InnerRange* begin();
+ InnerRange* end();
+};
+
+namespace no_concat_reference_t {
+struct ValueType {};
+
+struct InnerRange {
+ struct It {
+ using difference_type = std::ptrdiff_t;
+ using value_type = ValueType;
+ struct reference {
+ operator value_type();
+ };
+
+ It& operator++();
+ void operator++(int);
+ reference operator*() const;
+ };
+
+ It begin();
+ sentinel_wrapper<It> end();
+};
+
+struct Pattern : std::ranges::view_base {
+ struct It {
+ using difference_type = std::ptrdiff_t;
+ using value_type = ValueType;
+ struct reference {
+ operator value_type();
+ };
+
+ It& operator++();
+ It operator++(int);
+ reference operator*() const;
+ bool operator==(const It&) const;
+ friend value_type&& iter_move(const It&);
+ };
+
+ It begin();
+ It end();
+};
+
+static_assert(std::ranges::input_range<InnerRange>);
+static_assert(std::ranges::forward_range<Pattern>);
+static_assert(std::ranges::view<Pattern>);
+static_assert(!std::common_reference_with<range_reference_t<InnerRange>, range_reference_t<Pattern>>);
+static_assert(std::common_with<range_value_t<InnerRange>, range_value_t<Pattern>>);
+static_assert(std::common_reference_with<range_rvalue_reference_t<InnerRange>, range_rvalue_reference_t<Pattern>>);
+static_assert(!CanFormJoinWithView<View<InnerRange>, Pattern>);
+} // namespace no_concat_reference_t
+
+namespace no_concat_value_t {
+struct InnerRange {
+ struct It {
+ using difference_type = std::ptrdiff_t;
+ struct value_type {};
+
+ struct reference {
+ operator value_type();
+ operator float();
+ };
+
+ It& operator++();
+ void operator++(int);
+ reference operator*() const;
+ };
+
+ It begin();
+ sentinel_wrapper<It> end();
+};
+
+struct Pattern : std::ranges::view_base {
+ const float* begin();
+ const float* end();
+};
+
+static_assert(std::ranges::input_range<InnerRange>);
+static_assert(std::ranges::forward_range<Pattern>);
+static_assert(std::ranges::view<Pattern>);
+static_assert(std::common_reference_with<range_reference_t<InnerRange>, range_reference_t<Pattern>>);
+static_assert(!std::common_with<range_value_t<InnerRange>, range_value_t<Pattern>>);
+static_assert(std::common_reference_with<range_rvalue_reference_t<InnerRange>, range_rvalue_reference_t<Pattern>>);
+static_assert(!CanFormJoinWithView<View<InnerRange>, Pattern>);
+} // namespace no_concat_value_t
+
+namespace no_concat_rvalue_reference_t {
+struct InnerRange {
+ using It = cpp20_input_iterator<int*>;
+ It begin();
+ sentinel_wrapper<It> end();
+};
+
+struct Pattern : std::ranges::view_base {
+ struct It {
+ using difference_type = std::ptrdiff_t;
+ struct value_type {
+ operator int() const;
+ };
+
+ struct rvalue_reference {
+ operator value_type();
+ };
+
+ It& operator++();
+ It operator++(int);
+ value_type& operator*() const;
+ bool operator==(const It&) const;
+ friend rvalue_reference iter_move(const It&);
+ };
+
+ It begin();
+ It end();
+};
+
+static_assert(std::ranges::input_range<InnerRange>);
+static_assert(std::ranges::forward_range<Pattern>);
+static_assert(std::ranges::view<Pattern>);
+static_assert(std::common_reference_with<range_reference_t<InnerRange>, range_reference_t<Pattern>>);
+static_assert(std::common_with<range_value_t<InnerRange>, range_value_t<Pattern>>);
+static_assert(!std::common_reference_with<range_rvalue_reference_t<InnerRange>, range_rvalue_reference_t<Pattern>>);
+static_assert(!CanFormJoinWithView<View<InnerRange>, Pattern>);
+} // namespace no_concat_rvalue_reference_t
+
+namespace not_concat_indirectly_readable { // Required after LWG-4074 ("compatible-joinable-ranges is underconstrained")
+struct InnerRange {
+ using It = cpp20_input_iterator<int*>;
+ It begin();
+ sentinel_wrapper<It> end();
+};
+
+struct Pattern : std::ranges::view_base {
+ lwg4074::Iter begin();
+ lwg4074::Iter end();
+};
+
+static_assert(std::ranges::input_range<InnerRange>);
+static_assert(std::ranges::forward_range<Pattern>);
+static_assert(std::ranges::view<Pattern>);
+static_assert(std::common_reference_with<range_reference_t<InnerRange>, range_reference_t<Pattern>>);
+static_assert(std::common_with<range_value_t<InnerRange>, range_value_t<Pattern>>);
+static_assert(std::common_reference_with<range_rvalue_reference_t<InnerRange>, range_rvalue_reference_t<Pattern>>);
+LIBCPP_STATIC_ASSERT(!std::ranges::__concat_indirectly_readable<InnerRange, Pattern>);
+static_assert(!CanFormJoinWithView<View<InnerRange>, Pattern>);
+} // namespace not_concat_indirectly_readable
+} // namespace test_when_used_ranges_are_not_concatable
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctad.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctad.compile.pass.cpp
new file mode 100644
index 0000000000000..07b70bc39cda4
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctad.compile.pass.cpp
@@ -0,0 +1,230 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// template<class R, class P>
+// join_with_view(R&&, P&&) -> join_with_view<views::all_t<R>, views::all_t<P>>;
+//
+// template<input_range R>
+// join_with_view(R&&, range_value_t<range_reference_t<R>>)
+// -> join_with_view<views::all_t<R>, single_view<range_value_t<range_reference_t<R>>>>;
+
+#include <ranges>
+
+#include <deque>
+#include <type_traits>
+
+#include "test_iterators.h"
+
+struct View : std::ranges::view_base {
+ using It = cpp20_input_iterator<std::deque<int>*>;
+
+ View() = default;
+ It begin() const;
+ sentinel_wrapper<It> end() const;
+};
+
+static_assert(std::ranges::input_range<View>);
+static_assert(std::ranges::view<View>);
+
+struct Pattern : std::ranges::view_base {
+ Pattern() = default;
+ forward_iterator<int*> begin();
+ forward_iterator<int*> end();
+};
+
+static_assert(std::ranges::forward_range<Pattern>);
+static_assert(std::ranges::view<Pattern>);
+
+// A range that is not a view
+struct Range {
+ using It = cpp20_input_iterator<std::deque<int>*>;
+
+ Range() = default;
+ It begin() const;
+ sentinel_wrapper<It> end() const;
+};
+
+static_assert(std::ranges::input_range<Range>);
+static_assert(!std::ranges::view<Range>);
+
+// A pattern that is not a view
+struct RangePattern {
+ RangePattern() = default;
+ forward_iterator<int*> begin();
+ forward_iterator<int*> end();
+};
+
+static_assert(std::ranges::forward_range<RangePattern>);
+static_assert(!std::ranges::view<RangePattern>);
+
+void test_range_and_pattern_deduction_guide() {
+ { // Both `v` and `pat` model `std::ranges::view`.
+ {
+ View v;
+ Pattern pat;
+ std::ranges::join_with_view view(v, pat);
+ static_assert(std::is_same_v<decltype(view), std::ranges::join_with_view<View, Pattern>>);
+ }
+ {
+ View v;
+ std::ranges::join_with_view view(v, Pattern{});
+ static_assert(std::is_same_v<decltype(view), std::ranges::join_with_view<View, Pattern>>);
+ }
+ {
+ Pattern pat;
+ std::ranges::join_with_view view(View{}, pat);
+ static_assert(std::is_same_v<decltype(view), std::ranges::join_with_view<View, Pattern>>);
+ }
+ {
+ std::ranges::join_with_view view(View{}, Pattern{});
+ static_assert(std::is_same_v<decltype(view), std::ranges::join_with_view<View, Pattern>>);
+ }
+ }
+
+ { // Only `pat` models `std::ranges::view`.
+ {
+ Range v;
+ Pattern pat;
+ std::ranges::join_with_view view(v, pat);
+ static_assert(std::is_same_v<decltype(view), std::ranges::join_with_view<std::ranges::ref_view<Range>, Pattern>>);
+ }
+ {
+ Range v;
+ std::ranges::join_with_view view(v, Pattern{});
+ static_assert(std::is_same_v<decltype(view), std::ranges::join_with_view<std::ranges::ref_view<Range>, Pattern>>);
+ }
+ {
+ Pattern pat;
+ std::ranges::join_with_view view(Range{}, pat);
+ static_assert(
+ std::is_same_v<decltype(view), std::ranges::join_with_view<std::ranges::owning_view<Range>, Pattern>>);
+ }
+ {
+ std::ranges::join_with_view view(Range{}, Pattern{});
+ static_assert(
+ std::is_same_v<decltype(view), std::ranges::join_with_view<std::ranges::owning_view<Range>, Pattern>>);
+ }
+ }
+
+ { // Only `v` models `std::ranges::view`.
+ {
+ View v;
+ RangePattern pat;
+ std::ranges::join_with_view view(v, pat);
+ static_assert(
+ std::is_same_v<decltype(view), std::ranges::join_with_view<View, std::ranges::ref_view<RangePattern>>>);
+ }
+ {
+ View v;
+ std::ranges::join_with_view view(v, RangePattern{});
+ static_assert(
+ std::is_same_v<decltype(view), std::ranges::join_with_view<View, std::ranges::owning_view<RangePattern>>>);
+ }
+ {
+ RangePattern pat;
+ std::ranges::join_with_view view(View{}, pat);
+ static_assert(
+ std::is_same_v<decltype(view), std::ranges::join_with_view<View, std::ranges::ref_view<RangePattern>>>);
+ }
+ {
+ std::ranges::join_with_view view(View{}, RangePattern{});
+ static_assert(
+ std::is_same_v<decltype(view), std::ranges::join_with_view<View, std::ranges::owning_view<RangePattern>>>);
+ }
+ }
+
+ { // Both `v` and `pat` don't model `std::ranges::view`.
+ {
+ Range r;
+ RangePattern pat;
+ std::ranges::join_with_view view(r, pat);
+ static_assert(std::is_same_v<
+ decltype(view),
+ std::ranges::join_with_view<std::ranges::ref_view<Range>, std::ranges::ref_view<RangePattern>>>);
+ }
+ {
+ Range r;
+ std::ranges::join_with_view view(r, RangePattern{});
+ static_assert(std::is_same_v<
+ decltype(view),
+ std::ranges::join_with_view<std::ranges::ref_view<Range>, std::ranges::owning_view<RangePattern>>>);
+ }
+ {
+ RangePattern pat;
+ std::ranges::join_with_view view(Range{}, pat);
+ static_assert(std::is_same_v<
+ decltype(view),
+ std::ranges::join_with_view<std::ranges::owning_view<Range>, std::ranges::ref_view<RangePattern>>>);
+ }
+ {
+ std::ranges::join_with_view view(Range{}, RangePattern{});
+ static_assert(
+ std::is_same_v<
+ decltype(view),
+ std::ranges::join_with_view<std::ranges::owning_view<Range>, std::ranges::owning_view<RangePattern>>>);
+ }
+ }
+}
+
+void test_range_and_element_deduction_guide() {
+ { // Element is lvalue
+ int elem = 0;
+
+ {
+ View v;
+ std::ranges::join_with_view view(v, elem);
+ static_assert(std::is_same_v<decltype(view), std::ranges::join_with_view<View, std::ranges::single_view<int>>>);
+ }
+ {
+ std::ranges::join_with_view view(View{}, elem);
+ static_assert(std::is_same_v<decltype(view), std::ranges::join_with_view<View, std::ranges::single_view<int>>>);
+ }
+ {
+ Range r;
+ std::ranges::join_with_view view(r, elem);
+ static_assert(
+ std::is_same_v<decltype(view),
+ std::ranges::join_with_view<std::ranges::ref_view<Range>, std::ranges::single_view<int>>>);
+ }
+ {
+ std::ranges::join_with_view view(Range{}, elem);
+ static_assert(
+ std::is_same_v<decltype(view),
+ std::ranges::join_with_view<std::ranges::owning_view<Range>, std::ranges::single_view<int>>>);
+ }
+ }
+
+ { // Element is rvalue
+ {
+ View v;
+ std::ranges::join_with_view view(v, 1);
+ static_assert(std::is_same_v<decltype(view), std::ranges::join_with_view<View, std::ranges::single_view<int>>>);
+ }
+ {
+ std::ranges::join_with_view view(View{}, 1);
+ static_assert(std::is_same_v<decltype(view), std::ranges::join_with_view<View, std::ranges::single_view<int>>>);
+ }
+ {
+ Range r;
+ std::ranges::join_with_view view(r, 1);
+ static_assert(
+ std::is_same_v<decltype(view),
+ std::ranges::join_with_view<std::ranges::ref_view<Range>, std::ranges::single_view<int>>>);
+ }
+ {
+ std::ranges::join_with_view view(Range{}, 1);
+ static_assert(
+ std::is_same_v<decltype(view),
+ std::ranges::join_with_view<std::ranges::owning_view<Range>, std::ranges::single_view<int>>>);
+ }
+ }
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctor.default.pass.cpp
new file mode 100644
index 0000000000000..2177ea48ce851
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctor.default.pass.cpp
@@ -0,0 +1,77 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// join_with_view()
+// requires default_initializable<V> && default_initializable<Pattern> = default;
+
+#include <ranges>
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <type_traits>
+
+static constexpr auto view = std::to_array<std::array<int, 2>>({{1, 2}, {3, 4}, {5, 6}});
+
+struct TrivialView : std::ranges::view_base {
+ int val_; // intentionally uninitialized
+
+ constexpr auto begin() { return view.data(); }
+ constexpr auto end() { return view.data() + view.size(); }
+};
+
+static_assert(std::is_trivially_copyable_v<TrivialView> && std::is_trivially_default_constructible_v<TrivialView>);
+
+struct NonDefaultConstructibleView : TrivialView {
+ NonDefaultConstructibleView(int);
+};
+
+struct TrivialPattern : std::ranges::view_base {
+ int val_; // intentionally uninitialized
+
+ constexpr int* begin() { return &val_; }
+ constexpr int* end() { return &val_ + 1; }
+};
+
+static_assert(std::is_trivially_copyable_v<TrivialPattern> &&
+ std::is_trivially_default_constructible_v<TrivialPattern>);
+
+struct NonDefaultConstructiblePattern : TrivialPattern {
+ NonDefaultConstructiblePattern(int);
+};
+
+constexpr bool test() {
+ { // Check if `base_` and `pattern_` are value initialised
+ std::ranges::join_with_view<TrivialView, TrivialPattern> v;
+ assert(std::move(v).base().val_ == 0);
+ assert(std::ranges::equal(v, std::array{1, 2, 0, 3, 4, 0, 5, 6}));
+ }
+
+ { // Default constructor should not be explicit
+ [[maybe_unused]] std::ranges::join_with_view<TrivialView, TrivialPattern> v = {};
+ }
+
+ static_assert(std::default_initializable<std::ranges::join_with_view<TrivialView, TrivialPattern>>);
+ static_assert(!std::default_initializable<std::ranges::join_with_view<TrivialView, NonDefaultConstructiblePattern>>);
+ static_assert(!std::default_initializable<std::ranges::join_with_view<NonDefaultConstructibleView, TrivialPattern>>);
+ static_assert(!std::default_initializable<
+ std::ranges::join_with_view<NonDefaultConstructibleView, NonDefaultConstructiblePattern>>);
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctor.range.element.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctor.range.element.pass.cpp
new file mode 100644
index 0000000000000..7266912e41fdf
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctor.range.element.pass.cpp
@@ -0,0 +1,244 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// template<input_range R>
+// requires constructible_from<V, views::all_t<R>> &&
+// constructible_from<Pattern, single_view<range_value_t<InnerRng>>>
+// constexpr explicit join_with_view(R&& r, range_value_t<InnerRng> e);
+
+#include <ranges>
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <type_traits>
+#include <utility>
+
+#include "../types.h"
+#include "test_iterators.h"
+#include "test_range.h"
+
+struct MoveOnlyInt {
+ MoveOnlyInt() = default;
+ MoveOnlyInt(MoveOnlyInt&&) = default;
+ MoveOnlyInt& operator=(MoveOnlyInt&&) = default;
+
+ constexpr MoveOnlyInt(int val) : val_(val) {}
+ constexpr operator int() const { return val_; }
+
+ int val_ = 0;
+};
+
+template <>
+struct std::common_type<MoveOnlyInt, int> {
+ using type = int;
+};
+
+template <>
+struct std::common_type<int, MoveOnlyInt> {
+ using type = int;
+};
+
+struct OutputView : std::ranges::view_base {
+ using It = cpp20_output_iterator<int*>;
+ It begin() const;
+ sentinel_wrapper<It> end() const;
+};
+
+static_assert(std::ranges::output_range<OutputView, int>);
+static_assert(std::ranges::view<OutputView>);
+
+struct InputRange {
+ using It = cpp20_input_iterator<int*>;
+ It begin() const;
+ sentinel_wrapper<It> end() const;
+};
+
+struct InputView : InputRange, std::ranges::view_base {};
+
+static_assert(std::ranges::input_range<InputRange>);
+static_assert(std::ranges::input_range<const InputRange>);
+static_assert(std::ranges::view<InputView>);
+static_assert(std::ranges::input_range<InputView>);
+static_assert(std::ranges::input_range<const InputView>);
+
+class View : public std::ranges::view_base {
+ using OuterRange = std::array<std::array<MoveOnlyInt, 2>, 3>;
+
+ static constexpr OuterRange range_on_input_view = {{{1, 1}, {1, 1}, {1, 1}}};
+ static constexpr OuterRange range_on_ref_input_range = {{{2, 2}, {2, 2}, {2, 2}}};
+ static constexpr OuterRange range_on_const_ref_input_range = {{{3, 3}, {3, 3}, {3, 3}}};
+ static constexpr OuterRange range_on_owning_input_range = {{{4, 4}, {4, 4}, {4, 4}}};
+
+ const OuterRange* r_;
+
+public:
+ // Those functions should never be called in this test.
+ View(View&&) { assert(false); }
+ View(OutputView) { assert(false); }
+ View& operator=(View&&) {
+ assert(false);
+ return *this;
+ }
+
+ constexpr explicit View(InputView) : r_(&range_on_input_view) {}
+ constexpr explicit View(InputRange) = delete;
+ constexpr explicit View(std::ranges::ref_view<InputRange>) : r_(&range_on_ref_input_range) {}
+ constexpr explicit View(std::ranges::ref_view<const InputRange>) : r_(&range_on_const_ref_input_range) {}
+ constexpr explicit View(std::ranges::owning_view<InputRange>) : r_(&range_on_owning_input_range) {}
+
+ constexpr auto begin() const { return r_->begin(); }
+ constexpr auto end() const { return r_->end(); }
+};
+
+static_assert(std::ranges::input_range<View>);
+static_assert(std::ranges::input_range<const View>);
+
+class Pattern : public std::ranges::view_base {
+ int val_;
+
+public:
+ // Those functions should never be called in this test.
+ Pattern(Pattern&&) { assert(false); }
+ template <class T>
+ Pattern(const std::ranges::single_view<T>&) {
+ assert(false);
+ }
+ Pattern& operator=(Pattern&&) {
+ assert(false);
+ return *this;
+ }
+
+ template <class T>
+ constexpr explicit Pattern(std::ranges::single_view<T>&& v) : val_(v[0]) {}
+
+ constexpr const int* begin() const { return &val_; }
+ constexpr const int* end() const { return &val_ + 1; }
+};
+
+static_assert(std::ranges::forward_range<Pattern>);
+static_assert(std::ranges::forward_range<const Pattern>);
+
+constexpr void test_ctor_with_view_and_element() {
+ // Check construction from `r` and `e`, when `r` models `std::ranges::view`
+
+ { // `r` and `e` are glvalues
+ InputView r;
+ int e = 0;
+ std::ranges::join_with_view<View, Pattern> jwv(r, e);
+ assert(std::ranges::equal(jwv, std::array{1, 1, 0, 1, 1, 0, 1, 1}));
+ }
+
+ { // `r` and `e` are const glvalues
+ const InputView r;
+ const int e = 1;
+ std::ranges::join_with_view<View, Pattern> jwv(r, e);
+ assert(std::ranges::equal(jwv, std::array{1, 1, 1, 1, 1, 1, 1, 1}));
+ }
+
+ { // `r` and `e` are prvalues
+ std::ranges::join_with_view<View, Pattern> jwv(InputView{}, MoveOnlyInt{2});
+ assert(std::ranges::equal(jwv, std::array{1, 1, 2, 1, 1, 2, 1, 1}));
+ }
+
+ { // `r` and `e` are xvalues
+ InputView r;
+ MoveOnlyInt e = 3;
+ std::ranges::join_with_view<View, Pattern> jwv(std::move(r), std::move(e));
+ assert(std::ranges::equal(jwv, std::array{1, 1, 3, 1, 1, 3, 1, 1}));
+ }
+
+ // Check explicitness
+ static_assert(ConstructionIsExplicit<std::ranges::join_with_view<View, Pattern>, InputView, MoveOnlyInt>);
+ static_assert(ConstructionIsExplicit<std::ranges::join_with_view<View, Pattern>, InputView, int>);
+ static_assert(ConstructionIsExplicit<std::ranges::join_with_view<View, Pattern>, InputView&, int&>);
+ static_assert(ConstructionIsExplicit<std::ranges::join_with_view<View, Pattern>, const InputView, const int>);
+ static_assert(ConstructionIsExplicit<std::ranges::join_with_view<View, Pattern>, const InputView&, const int&>);
+}
+
+constexpr void test_ctor_with_non_view_and_element() {
+ // Check construction from `r` and `e`, when `r` does not model `std::ranges::view`
+
+ { // `r` and `e` are glvalues
+ InputRange r;
+ int e = 0;
+ std::ranges::join_with_view<View, Pattern> jwv(r, e);
+ assert(std::ranges::equal(jwv, std::array{2, 2, 0, 2, 2, 0, 2, 2}));
+ }
+
+ { // `r` and `e` are const glvalues
+ const InputRange r;
+ const int e = 1;
+ std::ranges::join_with_view<View, Pattern> jwv(r, e);
+ assert(std::ranges::equal(jwv, std::array{3, 3, 1, 3, 3, 1, 3, 3}));
+ }
+
+ { // `r` and `e` are prvalues
+ std::ranges::join_with_view<View, Pattern> jwv(InputRange{}, MoveOnlyInt{2});
+ assert(std::ranges::equal(jwv, std::array{4, 4, 2, 4, 4, 2, 4, 4}));
+ }
+
+ { // `r` and `e` are xvalues
+ InputRange r;
+ MoveOnlyInt e = 3;
+ std::ranges::join_with_view<View, Pattern> jwv(std::move(r), std::move(e));
+ assert(std::ranges::equal(jwv, std::array{4, 4, 3, 4, 4, 3, 4, 4}));
+ }
+
+ // Check explicitness
+ static_assert(ConstructionIsExplicit<std::ranges::join_with_view<View, Pattern>, InputRange, MoveOnlyInt>);
+ static_assert(ConstructionIsExplicit<std::ranges::join_with_view<View, Pattern>, InputRange, int>);
+ static_assert(ConstructionIsExplicit<std::ranges::join_with_view<View, Pattern>, InputRange&, int&>);
+ static_assert(ConstructionIsExplicit<std::ranges::join_with_view<View, Pattern>, const InputRange&, const int&>);
+}
+
+constexpr void test_constraints() {
+ { // `R` is not an input range
+ using R = OutputView;
+ static_assert(!std::ranges::input_range<R>);
+ static_assert(std::constructible_from<View, std::views::all_t<R>>);
+ static_assert(std::constructible_from<Pattern, std::ranges::single_view<int>>);
+ static_assert(!std::constructible_from<std::ranges::join_with_view<View, Pattern>, R, int>);
+ }
+
+ { // `V` is not constructible from `views::all_t<R>`
+ using R = test_range<cpp20_input_iterator>;
+ static_assert(std::ranges::input_range<R>);
+ static_assert(!std::constructible_from<View, std::views::all_t<R>>);
+ static_assert(std::constructible_from<Pattern, std::ranges::single_view<int>>);
+ static_assert(!std::constructible_from<std::ranges::join_with_view<View, Pattern>, R, int>);
+ }
+
+ { // `Pattern` is not constructible from `single_view<range_value_t<InnerRng>>`
+ using R = InputView;
+ using Pat = test_view<forward_iterator>;
+ static_assert(std::ranges::input_range<R>);
+ static_assert(std::constructible_from<View, std::views::all_t<R>>);
+ static_assert(!std::constructible_from<Pat, std::ranges::single_view<int>>);
+ static_assert(!std::constructible_from<std::ranges::join_with_view<View, Pat>, R, int>);
+ }
+}
+
+constexpr bool test() {
+ test_ctor_with_view_and_element();
+ test_ctor_with_non_view_and_element();
+ test_constraints();
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctor.range.pattern.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctor.range.pattern.pass.cpp
new file mode 100644
index 0000000000000..d3ee228ab0eb8
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/ctor.range.pattern.pass.cpp
@@ -0,0 +1,111 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// constexpr explicit join_with_view(V base, Pattern pattern);
+
+#include <ranges>
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <utility>
+
+#include "../types.h"
+
+class View : public std::ranges::view_base {
+ using OuterRange = std::array<std::array<int, 2>, 3>;
+
+ static constexpr OuterRange default_range = {{{1, 2}, {3, 4}, {5, 6}}};
+ static constexpr OuterRange range_on_move = {{{6, 5}, {4, 3}, {2, 1}}};
+
+ const OuterRange* r_ = &default_range;
+
+public:
+ View() = default;
+ constexpr View(const View&) : r_(&default_range) {}
+ constexpr View(View&&) : r_(&range_on_move) {}
+
+ constexpr View& operator=(View) {
+ r_ = &default_range;
+ return *this;
+ }
+
+ constexpr auto begin() { return r_->begin(); }
+ constexpr auto end() { return r_->end(); }
+};
+
+class Pattern : public std::ranges::view_base {
+ using PatternRange = std::array<int, 2>;
+
+ static constexpr PatternRange default_range = {0, 0};
+ static constexpr PatternRange range_on_move = {7, 7};
+
+ const PatternRange* val_ = &default_range;
+
+public:
+ Pattern() = default;
+ constexpr Pattern(const Pattern&) : val_(&default_range) {}
+ constexpr Pattern(Pattern&&) : val_(&range_on_move) {}
+
+ constexpr Pattern& operator=(Pattern) {
+ val_ = &default_range;
+ return *this;
+ }
+
+ constexpr auto begin() { return val_->begin(); }
+ constexpr auto end() { return val_->end(); }
+};
+
+constexpr bool test() {
+ { // Check construction from `view` and `pattern`
+ { // `view` and `pattern` are glvalues
+ View v;
+ Pattern p;
+ std::ranges::join_with_view<View, Pattern> jwv(v, p);
+ assert(std::ranges::equal(jwv, std::array{6, 5, 7, 7, 4, 3, 7, 7, 2, 1}));
+ }
+
+ { // `view` and `pattern` are const glvalues
+ const View v;
+ const Pattern p;
+ std::ranges::join_with_view<View, Pattern> jwv(v, p);
+ assert(std::ranges::equal(jwv, std::array{6, 5, 7, 7, 4, 3, 7, 7, 2, 1}));
+ }
+
+ { // `view` and `pattern` are prvalues
+ std::ranges::join_with_view<View, Pattern> jwv(View{}, Pattern{});
+ assert(std::ranges::equal(jwv, std::array{6, 5, 7, 7, 4, 3, 7, 7, 2, 1}));
+ }
+
+ { // `view` and `pattern` are xvalues
+ View v;
+ Pattern p;
+ std::ranges::join_with_view<View, Pattern> jwv(std::move(v), std::move(p));
+ assert(std::ranges::equal(jwv, std::array{6, 5, 7, 7, 4, 3, 7, 7, 2, 1}));
+ }
+ }
+
+ // Check explicitness
+ static_assert(ConstructionIsExplicit<std::ranges::join_with_view<View, Pattern>, View, Pattern>);
+ static_assert(ConstructionIsExplicit<std::ranges::join_with_view<View, Pattern>, View&, Pattern&>);
+ static_assert(ConstructionIsExplicit<std::ranges::join_with_view<View, Pattern>, const View, const Pattern>);
+ static_assert(ConstructionIsExplicit<std::ranges::join_with_view<View, Pattern>, const View&, const Pattern&>);
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/end.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/end.pass.cpp
new file mode 100644
index 0000000000000..c6e973abfface
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/end.pass.cpp
@@ -0,0 +1,232 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// constexpr auto end();
+// constexpr auto end() const
+// requires forward_range<const V> && forward_range<const Pattern> &&
+// is_reference_v<range_reference_t<const V>> &&
+// input_range<range_reference_t<const V>>;
+
+// ADDITIONAL_COMPILE_FLAGS(has-fconstexpr-steps): -fconstexpr-steps=10000000
+
+#include <ranges>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "../types.h"
+#include "test_iterators.h"
+
+template <class V, class Pattern>
+concept JoinWithViewHasConstEnd = requires(const std::ranges::join_with_view<V, Pattern> jwv) { jwv.end(); };
+
+template <size_t Bits>
+ requires(Bits < (1 << 7))
+constexpr void test_end() {
+ constexpr bool v_models_forward_range = static_cast<bool>(Bits & (1 << 0));
+ constexpr bool inner_range_is_reference = static_cast<bool>(Bits & (1 << 1));
+ constexpr bool inner_range_models_forward_range = static_cast<bool>(Bits & (1 << 2));
+ constexpr bool v_models_common_range = static_cast<bool>(Bits & (1 << 3));
+ constexpr bool inner_range_models_common_range = static_cast<bool>(Bits & (1 << 4));
+ constexpr bool v_models_simple_range = static_cast<bool>(Bits & (1 << 5));
+ constexpr bool pattern_models_simple_range = static_cast<bool>(Bits & (1 << 6));
+
+ constexpr ViewProperties inner_range_props{.common = inner_range_models_common_range};
+ using InnerRange =
+ std::conditional_t<inner_range_models_forward_range,
+ BasicView<std::vector<int>, inner_range_props, forward_iterator>,
+ BasicView<std::vector<int>, inner_range_props, DefaultCtorInputIter>>;
+
+ constexpr ViewProperties v_props{.simple = v_models_simple_range, .common = v_models_common_range};
+ using UnderlyingV = std::conditional_t<inner_range_is_reference, std::vector<InnerRange>, RvalueVector<InnerRange>>;
+ using V = std::conditional_t<v_models_forward_range,
+ BasicView<UnderlyingV, v_props, forward_iterator>,
+ BasicView<UnderlyingV, v_props, DefaultCtorInputIter>>;
+
+ using UnderlyingPattern = std::vector<int>;
+ using Pattern = BasicView<UnderlyingPattern, ViewProperties{.simple = pattern_models_simple_range}, forward_iterator>;
+
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+ using Iter = std::ranges::iterator_t<JWV>;
+
+ // Test when `JWV` models common range
+ static_assert(std::same_as<Iter, std::ranges::sentinel_t<JWV>> ==
+ (v_models_forward_range && inner_range_is_reference && inner_range_models_forward_range &&
+ v_models_common_range && inner_range_models_common_range));
+
+ { // `V` and `Pattern` are empty
+ V v{};
+ Pattern pattern{};
+ JWV jwv(std::move(v), std::move(pattern));
+ Iter it = jwv.begin();
+ std::sentinel_for<Iter> decltype(auto) se = jwv.end();
+ assert(it == se);
+ }
+
+ { // `V` is empty, `Pattern` contains some elements
+ V v{};
+ Pattern pattern{std::vector<int>{0}};
+ JWV jwv(std::move(v), std::move(pattern));
+ Iter it = jwv.begin();
+ std::sentinel_for<Iter> decltype(auto) se = jwv.end();
+ assert(it == se);
+ }
+
+ { // `V` is not empty, `Pattern is empty`
+ V v{UnderlyingV{
+ std::vector<InnerRange>{InnerRange(std::vector<int>{1, 2, 3}), InnerRange(std::vector<int>{4, 5, 6})}}};
+ Pattern pattern{};
+ JWV jwv(std::move(v), std::move(pattern));
+ Iter it = jwv.begin();
+ std::sentinel_for<Iter> decltype(auto) se = jwv.end();
+ assert(std::ranges::next(it, 6) == se);
+ }
+
+ { // `V` and `Pattern` are not empty
+ V v{UnderlyingV{std::vector<InnerRange>{
+ InnerRange(std::vector<int>{6, 5}),
+ InnerRange(std::vector<int>{4, 3}),
+ InnerRange(std::vector<int>{2, 1, 0})}}};
+ Pattern pattern{std::vector<int>{-1, -1}};
+ JWV jwv(std::move(v), std::move(pattern));
+ Iter it = jwv.begin();
+ std::sentinel_for<Iter> decltype(auto) se = jwv.end();
+ assert(std::ranges::next(it, 11) == se);
+ }
+}
+
+template <std::size_t Bits>
+ requires(Bits < (1 << 7))
+constexpr void test_const_end() {
+ constexpr bool const_v_models_forward_range = static_cast<bool>(Bits & (1 << 0));
+ constexpr bool const_pattern_models_forward_range = static_cast<bool>(Bits & (1 << 1));
+ constexpr bool inner_const_range_is_reference = static_cast<bool>(Bits & (1 << 2));
+ constexpr bool inner_const_range_models_input_range = static_cast<bool>(Bits & (1 << 3));
+ constexpr bool inner_const_range_models_forward_range = static_cast<bool>(Bits & (1 << 4));
+ constexpr bool const_v_models_common_range = static_cast<bool>(Bits & (1 << 5));
+ constexpr bool inner_const_range_models_common_range = static_cast<bool>(Bits & (1 << 6));
+
+ constexpr ViewProperties inner_range_props{.common = inner_const_range_models_common_range};
+ using InnerRange =
+ std::conditional_t<inner_const_range_models_forward_range,
+ BasicView<std::vector<int>, inner_range_props, forward_iterator>,
+ std::conditional_t<inner_const_range_models_input_range,
+ BasicView<std::vector<int>, inner_range_props, DefaultCtorInputIter>,
+ InputRangeButOutputWhenConst<int>>>;
+
+ constexpr ViewProperties v_props{.common = const_v_models_common_range};
+ using UnderlyingV =
+ std::conditional_t<inner_const_range_is_reference, std::vector<InnerRange>, RvalueVector<InnerRange>>;
+ using V = std::conditional_t<const_v_models_forward_range,
+ BasicView<UnderlyingV, v_props, forward_iterator>,
+ BasicView<UnderlyingV, v_props, DefaultCtorInputIter>>;
+ using Pattern =
+ std::conditional_t<const_pattern_models_forward_range,
+ BasicView<std::vector<int>, ViewProperties{}, forward_iterator>,
+ ForwardViewButInputWhenConst<int>>;
+
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+ static_assert(JoinWithViewHasConstEnd<V, Pattern> ==
+ (const_v_models_forward_range && const_pattern_models_forward_range && inner_const_range_is_reference &&
+ (inner_const_range_models_input_range || inner_const_range_models_forward_range)));
+ static_assert(JoinWithViewHasConstEnd<V, Pattern> == std::ranges::range<const JWV>);
+
+ if constexpr (std::ranges::range<const JWV>) {
+ using ConstIter = std::ranges::iterator_t<const JWV>;
+
+ // Test when `const JWV` models common range
+ static_assert(std::same_as<ConstIter, std::ranges::sentinel_t<const JWV>> ==
+ (inner_const_range_models_forward_range && const_v_models_common_range &&
+ inner_const_range_models_common_range));
+
+ { // `const V` and `const Pattern` are empty
+ V v{};
+ Pattern pattern{};
+ const JWV jwv(std::move(v), std::move(pattern));
+ ConstIter it = jwv.begin();
+ std::sentinel_for<ConstIter> decltype(auto) se = jwv.end();
+ assert(it == se);
+ }
+
+ { // `const V` is empty, `const Pattern` contains some elements
+ V v{};
+ Pattern pattern{std::vector<int>{1}};
+ const JWV jwv(std::move(v), std::move(pattern));
+ ConstIter it = jwv.begin();
+ std::sentinel_for<ConstIter> decltype(auto) se = jwv.end();
+ assert(it == se);
+ }
+
+ { // `const V` is not empty, `const Pattern is empty`
+ V v{UnderlyingV{
+ std::vector<InnerRange>{InnerRange(std::vector<int>{1, 2, 3}), InnerRange(std::vector<int>{4, 5, 6})}}};
+ Pattern pattern{};
+ const JWV jwv(std::move(v), std::move(pattern));
+ ConstIter it = jwv.begin();
+ std::sentinel_for<ConstIter> decltype(auto) se = jwv.end();
+ assert(std::ranges::next(it, 6) == se);
+ }
+
+ { // `const V` and `const Pattern` are not empty
+ V v{UnderlyingV{std::vector<InnerRange>{
+ InnerRange(std::vector<int>{1}), InnerRange(std::vector<int>{2, 2}), InnerRange(std::vector<int>{3, 3, 3})}}};
+ Pattern pattern{std::vector<int>{0}};
+ const JWV jwv(std::move(v), std::move(pattern));
+ ConstIter it = jwv.begin();
+ std::sentinel_for<ConstIter> decltype(auto) se = jwv.end();
+ assert(std::ranges::next(it, 8) == se);
+ }
+ }
+}
+
+constexpr bool test() {
+ []<std::size_t... Bits>(std::index_sequence<Bits...>) {
+ (test_end<Bits>(), ...);
+ (test_const_end<Bits>(), ...);
+ }(std::make_index_sequence<(1 << 7)>{});
+
+ { // Check situation when iterators returned by `end()` and `end() const` are of the same type
+ using V = BasicView<std::vector<std::string>, ViewProperties{.simple = true}, forward_iterator>;
+ using Pattern = BasicView<std::string, ViewProperties{.simple = true}, forward_iterator>;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+ using Sentinel = std::ranges::sentinel_t<JWV&>;
+ using ConstSentinel = std::ranges::sentinel_t<const JWV&>;
+ static_assert(std::input_iterator<Sentinel>);
+ static_assert(std::input_iterator<ConstSentinel>);
+ static_assert(std::same_as<Sentinel, ConstSentinel>);
+ }
+
+ { // Check situation when sentinels returned by `end()` and `end() const` are of the same type
+ using V = BasicView<std::vector<std::string>, ViewProperties{.simple = true, .common = false}, forward_iterator>;
+ using Pattern = BasicView<std::string, ViewProperties{.simple = true}, forward_iterator>;
+ using JWV = std::ranges::join_with_view<V, Pattern>;
+ using Sentinel = std::ranges::sentinel_t<JWV&>;
+ using ConstSentinel = std::ranges::sentinel_t<const JWV&>;
+ static_assert(!std::input_iterator<Sentinel>);
+ static_assert(!std::input_iterator<ConstSentinel>);
+ static_assert(std::same_as<Sentinel, ConstSentinel>);
+ }
+
+ // Check LWG-4074: compatible-joinable-ranges is underconstrained
+ static_assert(!JoinWithViewHasConstEnd<BasicVectorView<int, ViewProperties{}, forward_iterator>,
+ lwg4074::PatternWithProxyConstAccess>);
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/inheritance.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/inheritance.compile.pass.cpp
new file mode 100644
index 0000000000000..dff8817666531
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/range.join.with.view/inheritance.compile.pass.cpp
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// class join_with_view : public view_interface<join_with_view<V, Pattern>>
+
+#include <ranges>
+
+#include <concepts>
+#include <string>
+#include <vector>
+
+template <class T>
+struct View : std::ranges::view_base {
+ std::vector<T>* begin();
+ std::vector<T>* end();
+};
+
+template <class T>
+struct Pattern : std::ranges::view_base {
+ T* begin();
+ T* end();
+};
+
+template <class T>
+using JoinWithView = std::ranges::join_with_view<View<T>, Pattern<T>>;
+
+static_assert(std::derived_from<JoinWithView<int>, std::ranges::view_interface<JoinWithView<int>>>);
+static_assert(std::derived_from<JoinWithView<void*>, std::ranges::view_interface<JoinWithView<void*>>>);
+static_assert(std::derived_from<JoinWithView<std::string>, std::ranges::view_interface<JoinWithView<std::string>>>);
diff --git a/libcxx/test/std/ranges/range.adaptors/range.join.with/types.h b/libcxx/test/std/ranges/range.adaptors/range.join.with/types.h
new file mode 100644
index 0000000000000..588c647cef23a
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.join.with/types.h
@@ -0,0 +1,319 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_JOIN_WITH_TYPES_H
+#define TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_JOIN_WITH_TYPES_H
+
+#include <cstddef>
+#include <initializer_list>
+#include <ranges>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "test_range.h"
+
+template <class Tp>
+void pass_value(Tp);
+
+template <class Tp, class... Args>
+concept ConstructionIsExplicit = std::constructible_from<Tp, Args...> && !requires(Args&&... args) {
+ pass_value<Tp>({std::forward<Args>(args)...});
+};
+
+struct ViewProperties {
+ bool simple = false;
+ bool common = true;
+};
+
+template <std::ranges::input_range Data,
+ ViewProperties Prop,
+ template <class...> class It,
+ template <class...> class ConstIt = It>
+class BasicView : public std::ranges::view_base {
+ Data data_;
+
+public:
+ constexpr BasicView()
+ requires std::default_initializable<Data>
+ = default;
+
+ template <class R>
+ constexpr explicit BasicView(R&& r)
+ requires requires { std::ranges::to<Data>(std::forward<R>(r)); }
+ /*******/ : data_(std::ranges::to<Data>(std::forward<R>(r))) {}
+
+ constexpr explicit BasicView(std::initializer_list<std::ranges::range_value_t<Data>> il)
+ : data_(std::ranges::to<Data>(il)) {}
+
+ constexpr auto begin()
+ requires(!Prop.simple)
+ {
+ return It(data_.begin());
+ }
+
+ constexpr auto end()
+ requires(!Prop.simple)
+ {
+ if constexpr (Prop.common)
+ return It(data_.end());
+ else
+ return sentinel_wrapper(It(data_.end()));
+ }
+
+ constexpr auto begin() const { return ConstIt(data_.begin()); }
+
+ constexpr auto end() const {
+ if constexpr (Prop.common)
+ return ConstIt(data_.end());
+ else
+ return sentinel_wrapper(ConstIt(data_.end()));
+ }
+};
+
+template <class Tp, ViewProperties Prop, template <class...> class It, template <class...> class ConstIt = It>
+using BasicVectorView = BasicView<std::vector<Tp>, Prop, It, ConstIt>;
+
+struct AsPrvalue {
+ template <class Tp>
+ constexpr auto operator()(Tp&& t) const {
+ return std::forward<Tp>(t);
+ }
+};
+
+template <class Tp>
+class RvalueVector {
+ using Vec = std::vector<Tp>;
+ std::ranges::transform_view<std::ranges::owning_view<Vec>, AsPrvalue> range_;
+
+public:
+ constexpr RvalueVector() = default;
+ constexpr explicit RvalueVector(Vec vec) : range_(std::move(vec), AsPrvalue{}) {}
+ constexpr explicit RvalueVector(std::initializer_list<Tp> il) : RvalueVector(Vec(il)) {}
+
+ constexpr auto begin() { return range_.begin(); }
+ constexpr auto end() { return range_.end(); }
+ constexpr auto begin() const { return range_.begin(); }
+ constexpr auto end() const { return range_.end(); }
+};
+
+template <class It>
+class DefaultCtorInputIter {
+ It it_ = It();
+
+public:
+ using value_type = std::iter_value_t<It>;
+ using difference_type = std::iter_difference_t<It>;
+
+ DefaultCtorInputIter() = default;
+ constexpr explicit DefaultCtorInputIter(It it) : it_(it) {}
+
+ constexpr DefaultCtorInputIter& operator++() {
+ ++it_;
+ return *this;
+ }
+
+ constexpr void operator++(int) { ++*this; }
+ constexpr decltype(auto) operator*() const { return *it_; }
+ constexpr bool operator==(const DefaultCtorInputIter&) const = default;
+};
+
+template <class It>
+DefaultCtorInputIter(It) -> DefaultCtorInputIter<It>;
+
+template <class Tp>
+class InputRangeButOutputWhenConst {
+ using Vec = std::vector<Tp>;
+ std::ranges::ref_view<Vec> range_;
+
+public:
+ constexpr explicit InputRangeButOutputWhenConst(Vec& vec) : range_(vec) {}
+
+ constexpr auto begin() { return cpp20_input_iterator(range_.begin()); }
+ constexpr auto end() { return sentinel_wrapper(cpp20_input_iterator(range_.end())); }
+ constexpr auto begin() const { return cpp20_output_iterator(range_.begin()); }
+ constexpr auto end() const { return sentinel_wrapper(cpp20_output_iterator(range_.end())); }
+};
+
+template <class Tp>
+using ForwardViewButInputWhenConst =
+ BasicVectorView<Tp, ViewProperties{.common = false}, forward_iterator, cpp20_input_iterator>;
+
+template <class It>
+class ForwardIteratorWithInputCategory {
+ It it_ = It();
+
+public:
+ using value_type = std::iter_value_t<It>;
+ using difference_type = std::iter_difference_t<It>;
+ using iterator_concept = std::forward_iterator_tag;
+ using iterator_category = std::input_iterator_tag;
+
+ ForwardIteratorWithInputCategory() = default;
+ explicit ForwardIteratorWithInputCategory(It it);
+
+ std::iter_reference_t<It> operator*() const;
+ ForwardIteratorWithInputCategory& operator++();
+ ForwardIteratorWithInputCategory operator++(int);
+ bool operator==(const ForwardIteratorWithInputCategory&) const;
+};
+
+template <class It>
+explicit ForwardIteratorWithInputCategory(It) -> ForwardIteratorWithInputCategory<It>;
+
+template <class It>
+class EqComparableInputIter {
+ It it_;
+
+public:
+ using value_type = std::iter_value_t<It>;
+ using difference_type = std::iter_difference_t<It>;
+
+ constexpr explicit EqComparableInputIter(It it) : it_(it) {}
+
+ constexpr decltype(auto) operator*() const { return *it_; }
+ constexpr EqComparableInputIter& operator++() {
+ ++it_;
+ return *this;
+ }
+ constexpr void operator++(int) { ++it_; }
+
+ friend constexpr It base(const EqComparableInputIter& i) { return i.it_; }
+
+ friend constexpr bool operator==(const EqComparableInputIter& left, const EqComparableInputIter& right) {
+ return left.it_ == right.it_;
+ }
+};
+
+template <class It>
+EqComparableInputIter(It) -> EqComparableInputIter<It>;
+
+template <class Val>
+struct ConstOppositeView : std::ranges::view_base {
+ const Val* begin();
+ sentinel_wrapper<const Val*> end();
+ Val* begin() const;
+ sentinel_wrapper<Val*> end() const;
+};
+
+namespace lwg4074 { // Helpers for LWG-4074 ("compatible-joinable-ranges is underconstrained")
+struct CommonReference;
+
+struct Value {
+ Value(int);
+};
+
+struct Reference {
+ operator Value() const;
+ operator CommonReference() const;
+};
+
+struct CommonReference {
+ CommonReference(int);
+};
+
+struct Iter {
+ using value_type = Value;
+ using difference_type = std::ptrdiff_t;
+
+ Iter& operator++();
+ Iter operator++(int);
+ Reference operator*() const;
+ bool operator==(const Iter&) const;
+};
+
+struct PatternWithProxyConstAccess {
+ int* begin();
+ int* end();
+ Iter begin() const;
+ Iter end() const;
+};
+} // namespace lwg4074
+
+template <template <class> class TQual, template <class> class UQual>
+struct std::basic_common_reference<lwg4074::Reference, int, TQual, UQual> {
+ using type = lwg4074::CommonReference;
+};
+
+template <template <class> class TQual, template <class> class UQual>
+struct std::basic_common_reference<int, lwg4074::Reference, TQual, UQual> {
+ using type = lwg4074::CommonReference;
+};
+
+namespace selftest {
+using BV1 = BasicView<std::string, ViewProperties{.simple = true}, forward_iterator>;
+static_assert(std::ranges::forward_range<BV1>);
+static_assert(!std::ranges::bidirectional_range<BV1>);
+static_assert(std::ranges::common_range<BV1>);
+static_assert(simple_view<BV1>);
+
+using BV2 =
+ BasicView<RvalueVector<std::string>, ViewProperties{.simple = false, .common = false}, cpp20_input_iterator>;
+static_assert(std::ranges::input_range<BV2>);
+static_assert(!std::ranges::forward_range<BV2>);
+static_assert(!std::ranges::common_range<BV2>);
+static_assert(!std::is_reference_v<std::ranges::range_reference_t<BV2>>);
+static_assert(!simple_view<BV2>);
+
+using RV = RvalueVector<int>;
+static_assert(std::movable<RV>);
+static_assert(std::ranges::random_access_range<RV>);
+static_assert(std::ranges::random_access_range<const RV>);
+static_assert(!std::is_reference_v<std::ranges::range_reference_t<RV>>);
+static_assert(!std::is_reference_v<std::ranges::range_reference_t<const RV>>);
+
+using DCII = DefaultCtorInputIter<int*>;
+static_assert(std::default_initializable<DCII>);
+static_assert(std::sentinel_for<DCII, DCII>);
+static_assert(std::input_iterator<DCII>);
+static_assert(!std::forward_iterator<DCII>);
+
+using IRBOWC = InputRangeButOutputWhenConst<int>;
+static_assert(std::ranges::input_range<IRBOWC>);
+static_assert(std::ranges::output_range<const IRBOWC&, int>);
+
+using FVBIWC = ForwardViewButInputWhenConst<int>;
+static_assert(std::default_initializable<FVBIWC>);
+static_assert(std::ranges::view<FVBIWC>);
+static_assert(std::ranges::forward_range<FVBIWC>);
+static_assert(!std::ranges::common_range<FVBIWC>);
+static_assert(std::ranges::input_range<const FVBIWC&>);
+static_assert(!std::ranges::forward_range<const FVBIWC&>);
+static_assert(!std::ranges::common_range<const FVBIWC&>);
+
+using FIWIC = ForwardIteratorWithInputCategory<long*>;
+static_assert(std::forward_iterator<FIWIC>);
+static_assert(std::same_as<FIWIC::iterator_category, std::input_iterator_tag>);
+static_assert(std::same_as<FIWIC::iterator_category, std::iterator_traits<FIWIC>::iterator_category>);
+
+using ECII = EqComparableInputIter<int*>;
+static_assert(std::input_iterator<ECII>);
+static_assert(!std::forward_iterator<ECII>);
+static_assert(std::equality_comparable<ECII>);
+
+using COV = ConstOppositeView<int>;
+static_assert(std::ranges::view<COV>);
+static_assert(std::ranges::range<const COV>);
+static_assert(!std::ranges::common_range<COV>);
+static_assert(!std::ranges::common_range<const COV>);
+static_assert(std::convertible_to<std::ranges::iterator_t<const COV>, std::ranges::iterator_t<COV>>);
+static_assert(!std::convertible_to<std::ranges::iterator_t<COV>, std::ranges::iterator_t<const COV>>);
+
+static_assert(std::common_with<lwg4074::Value, int>);
+static_assert(std::common_with<lwg4074::Value, lwg4074::Reference>);
+static_assert(std::common_reference_with<lwg4074::Reference, int&>);
+static_assert(std::common_reference_with<lwg4074::Reference, lwg4074::CommonReference>);
+static_assert(std::forward_iterator<lwg4074::Iter>);
+static_assert(std::ranges::forward_range<lwg4074::PatternWithProxyConstAccess>);
+static_assert(std::ranges::forward_range<const lwg4074::PatternWithProxyConstAccess>);
+} // namespace selftest
+
+#endif // TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_JOIN_WITH_TYPES_H
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index 849a96b9585b8..edd7b124a1fb3 100644
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -1115,7 +1115,6 @@ def add_version_header(tc):
"name": "__cpp_lib_ranges_join_with",
"values": {"c++23": 202202},
"headers": ["ranges"],
- "unimplemented": True,
},
{
"name": "__cpp_lib_ranges_repeat",
>From 369e8403b616a168cdebbde9813f4a64d33618b7 Mon Sep 17 00:00:00 2001
From: LLVM GN Syncbot <llvmgnsyncbot at gmail.com>
Date: Sat, 21 Jun 2025 09:55:19 +0000
Subject: [PATCH 02/40] [gn build] Port 1bb2328fd3ad
---
llvm/utils/gn/secondary/libcxx/include/BUILD.gn | 1 +
1 file changed, 1 insertion(+)
diff --git a/llvm/utils/gn/secondary/libcxx/include/BUILD.gn b/llvm/utils/gn/secondary/libcxx/include/BUILD.gn
index 41516d677c45a..7b9966d9bfc58 100644
--- a/llvm/utils/gn/secondary/libcxx/include/BUILD.gn
+++ b/llvm/utils/gn/secondary/libcxx/include/BUILD.gn
@@ -1363,6 +1363,7 @@ if (current_toolchain == default_toolchain) {
"__ranges/iota_view.h",
"__ranges/istream_view.h",
"__ranges/join_view.h",
+ "__ranges/join_with_view.h",
"__ranges/lazy_split_view.h",
"__ranges/movable_box.h",
"__ranges/non_propagating_cache.h",
>From 1b5d6ec6855369d109fcb740ecd3812231b7a279 Mon Sep 17 00:00:00 2001
From: Baranov Victor <bar.victor.2002 at gmail.com>
Date: Sat, 21 Jun 2025 13:14:19 +0300
Subject: [PATCH 03/40] [clang-tidy] count class member initializers as
statements in 'readability-function-size' (#131669)
Improve `readability-function-size` by counting class member
initializers as statements.
Relates to https://github.com/llvm/llvm-project/issues/131126
---
.../readability/FunctionSizeCheck.cpp | 15 +++-
.../readability/FunctionSizeCheck.h | 2 +
clang-tools-extra/docs/ReleaseNotes.rst | 5 ++
.../checks/readability/function-size.rst | 5 ++
.../function-size-no-member-init-as-stmts.cpp | 73 +++++++++++++++++++
.../checkers/readability/function-size.cpp | 57 +++++++++++++++
6 files changed, 156 insertions(+), 1 deletion(-)
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/readability/function-size-no-member-init-as-stmts.cpp
diff --git a/clang-tools-extra/clang-tidy/readability/FunctionSizeCheck.cpp b/clang-tools-extra/clang-tidy/readability/FunctionSizeCheck.cpp
index 3313bcb39b7f3..8e3a2e306dbf7 100644
--- a/clang-tools-extra/clang-tidy/readability/FunctionSizeCheck.cpp
+++ b/clang-tools-extra/clang-tidy/readability/FunctionSizeCheck.cpp
@@ -108,6 +108,14 @@ class FunctionASTVisitor : public RecursiveASTVisitor<FunctionASTVisitor> {
return true;
}
+ bool TraverseConstructorInitializer(CXXCtorInitializer *Init) {
+ if (CountMemberInitAsStmt)
+ ++Info.Statements;
+
+ Base::TraverseConstructorInitializer(Init);
+ return true;
+ }
+
struct FunctionInfo {
unsigned Lines = 0;
unsigned Statements = 0;
@@ -120,6 +128,7 @@ class FunctionASTVisitor : public RecursiveASTVisitor<FunctionASTVisitor> {
llvm::BitVector TrackedParent;
unsigned StructNesting = 0;
unsigned CurrentNestingLevel = 0;
+ bool CountMemberInitAsStmt;
};
} // namespace
@@ -135,7 +144,9 @@ FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context)
NestingThreshold(
Options.get("NestingThreshold", DefaultNestingThreshold)),
VariableThreshold(
- Options.get("VariableThreshold", DefaultVariableThreshold)) {}
+ Options.get("VariableThreshold", DefaultVariableThreshold)),
+ CountMemberInitAsStmt(
+ Options.get("CountMemberInitAsStmt", DefaultCountMemberInitAsStmt)) {}
void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "LineThreshold", LineThreshold);
@@ -144,6 +155,7 @@ void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "ParameterThreshold", ParameterThreshold);
Options.store(Opts, "NestingThreshold", NestingThreshold);
Options.store(Opts, "VariableThreshold", VariableThreshold);
+ Options.store(Opts, "CountMemberInitAsStmt", CountMemberInitAsStmt);
}
void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) {
@@ -160,6 +172,7 @@ void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) {
FunctionASTVisitor Visitor;
Visitor.Info.NestingThreshold = NestingThreshold.value_or(-1);
+ Visitor.CountMemberInitAsStmt = CountMemberInitAsStmt;
Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func));
auto &FI = Visitor.Info;
diff --git a/clang-tools-extra/clang-tidy/readability/FunctionSizeCheck.h b/clang-tools-extra/clang-tidy/readability/FunctionSizeCheck.h
index 106c69ff07393..f668ab18fea52 100644
--- a/clang-tools-extra/clang-tidy/readability/FunctionSizeCheck.h
+++ b/clang-tools-extra/clang-tidy/readability/FunctionSizeCheck.h
@@ -47,6 +47,7 @@ class FunctionSizeCheck : public ClangTidyCheck {
const std::optional<unsigned> ParameterThreshold;
const std::optional<unsigned> NestingThreshold;
const std::optional<unsigned> VariableThreshold;
+ const bool CountMemberInitAsStmt;
static constexpr std::optional<unsigned> DefaultLineThreshold = std::nullopt;
static constexpr std::optional<unsigned> DefaultStatementThreshold = 800U;
@@ -58,6 +59,7 @@ class FunctionSizeCheck : public ClangTidyCheck {
std::nullopt;
static constexpr std::optional<unsigned> DefaultVariableThreshold =
std::nullopt;
+ static constexpr bool DefaultCountMemberInitAsStmt = true;
};
} // namespace clang::tidy::readability
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 7c0c534dbc738..4801dab8c1bd5 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -278,6 +278,11 @@ Changes in existing checks
<clang-tidy/checks/readability/convert-member-functions-to-static>` check by
fixing false positives on member functions with an explicit object parameter.
+- Improved :doc:`readability-function-size
+ <clang-tidy/checks/readability/function-size>` check by adding new option
+ `CountMemberInitAsStmt` that allows counting class member initializers in
+ constructors as statements.
+
- Improved :doc:`readability-math-missing-parentheses
<clang-tidy/checks/readability/math-missing-parentheses>` check by fixing
false negatives where math expressions are the operand of assignment operators
diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/function-size.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/function-size.rst
index 133bd3e9c8cbe..253e7c483cb85 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/readability/function-size.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/readability/function-size.rst
@@ -43,3 +43,8 @@ Options
The default is `none` (ignore the number of variables).
Please note that function parameters and variables declared in lambdas,
GNU Statement Expressions, and nested class inline functions are not counted.
+
+.. option:: CountMemberInitAsStmt
+
+ When `true`, count class member initializers in constructors as statements.
+ Default is `true`.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/function-size-no-member-init-as-stmts.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/function-size-no-member-init-as-stmts.cpp
new file mode 100644
index 0000000000000..d335988e5e033
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/function-size-no-member-init-as-stmts.cpp
@@ -0,0 +1,73 @@
+// RUN: %check_clang_tidy %s readability-function-size %t -- \
+// RUN: -config='{CheckOptions: { \
+// RUN: readability-function-size.LineThreshold: 0, \
+// RUN: readability-function-size.StatementThreshold: 0, \
+// RUN: readability-function-size.BranchThreshold: 0, \
+// RUN: readability-function-size.ParameterThreshold: 5, \
+// RUN: readability-function-size.NestingThreshold: 2, \
+// RUN: readability-function-size.VariableThreshold: 1, \
+// RUN: readability-function-size.CountMemberInitAsStmt: false \
+// RUN: }}'
+
+// Bad formatting is intentional, don't run clang-format over the whole file!
+
+void foo1() {
+}
+
+void foo2() {;}
+// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'foo2' exceeds recommended size/complexity thresholds [readability-function-size]
+// CHECK-MESSAGES: :[[@LINE-2]]:6: note: 1 statements (threshold 0)
+
+struct A {
+ A(int c, int d) : a(0), b(c) { ; }
+ int a;
+ int b;
+};
+// CHECK-MESSAGES: :[[@LINE-4]]:3: warning: function 'A' exceeds recommended size/complexity thresholds [readability-function-size]
+// CHECK-MESSAGES: :[[@LINE-5]]:3: note: 1 statements (threshold 0)
+
+struct B {
+ B(int x, int y, int z) : a(x + y * z), b(), c_a(y, z) {
+ ;
+ }
+ int a;
+ int b;
+ A c_a;
+};
+// CHECK-MESSAGES: :[[@LINE-7]]:3: warning: function 'B' exceeds recommended size/complexity thresholds [readability-function-size]
+// CHECK-MESSAGES: :[[@LINE-8]]:3: note: 2 lines including whitespace and comments (threshold 0)
+// CHECK-MESSAGES: :[[@LINE-9]]:3: note: 1 statements (threshold 0)
+
+struct C : A, B {
+ // 0 statements
+ C() : A(0, 4), B(1, 2, 3) {}
+};
+
+template<typename T>
+struct TemplateC {
+ // 0 statements
+ TemplateC() : a(3) {}
+ T a;
+};
+
+template<typename T>
+struct TemplateD {
+ template<typename U>
+ TemplateD(U&& val) : member(val) {
+ ;
+ }
+
+ T member;
+};
+// CHECK-MESSAGES: :[[@LINE-6]]:3: warning: function 'TemplateD<T>' exceeds recommended size/complexity thresholds [readability-function-size]
+// CHECK-MESSAGES: :[[@LINE-7]]:3: note: 2 lines including whitespace and comments (threshold 0)
+// CHECK-MESSAGES: :[[@LINE-8]]:3: note: 1 statements (threshold 0)
+
+void instantiate() {
+ TemplateC<int> c;
+ TemplateD<int> d(5);
+}
+// CHECK-MESSAGES: :[[@LINE-4]]:6: warning: function 'instantiate' exceeds recommended size/complexity thresholds [readability-function-size]
+// CHECK-MESSAGES: :[[@LINE-5]]:6: note: 3 lines including whitespace and comments (threshold 0)
+// CHECK-MESSAGES: :[[@LINE-6]]:6: note: 2 statements (threshold 0)
+// CHECK-MESSAGES: :[[@LINE-7]]:6: note: 2 variables (threshold 1)
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/function-size.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/function-size.cpp
index 45b2604b43d03..9364fa3077da6 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/readability/function-size.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/function-size.cpp
@@ -319,3 +319,60 @@ void variables_16() {
// CHECK-MESSAGES: :[[@LINE-5]]:6: note: 3 lines including whitespace and comments (threshold 0)
// CHECK-MESSAGES: :[[@LINE-6]]:6: note: 4 statements (threshold 0)
// CHECK-MESSAGES: :[[@LINE-7]]:6: note: 2 variables (threshold 1)
+
+struct A {
+ A(int c, int d) : a(0), b(c) { ; }
+ int a;
+ int b;
+};
+// CHECK-MESSAGES: :[[@LINE-4]]:3: warning: function 'A' exceeds recommended size/complexity thresholds [readability-function-size]
+// CHECK-MESSAGES: :[[@LINE-5]]:3: note: 3 statements (threshold 0)
+
+struct B {
+ B(int x, int y, int z) : a(x + y * z), b(), c_a(y, z) {
+ ;
+ }
+ int a;
+ int b;
+ A c_a;
+};
+// CHECK-MESSAGES: :[[@LINE-7]]:3: warning: function 'B' exceeds recommended size/complexity thresholds [readability-function-size]
+// CHECK-MESSAGES: :[[@LINE-8]]:3: note: 2 lines including whitespace and comments (threshold 0)
+// CHECK-MESSAGES: :[[@LINE-9]]:3: note: 4 statements (threshold 0)
+
+struct C : A, B {
+ C() : A(0, 4), B(1, 2, 3) {}
+};
+// CHECK-MESSAGES: :[[@LINE-2]]:3: warning: function 'C' exceeds recommended size/complexity thresholds [readability-function-size]
+// CHECK-MESSAGES: :[[@LINE-3]]:3: note: 2 statements (threshold 0)
+
+template<typename T>
+struct TemplateC {
+ // 0 statements
+ TemplateC() : a(3) {}
+ T a;
+};
+// CHECK-MESSAGES: :[[@LINE-3]]:3: warning: function 'TemplateC<T>' exceeds recommended size/complexity thresholds [readability-function-size]
+// CHECK-MESSAGES: :[[@LINE-4]]:3: note: 1 statements (threshold 0)
+
+template<typename T>
+struct TemplateD {
+ template<typename U>
+ TemplateD(U&& val) : member(val) {
+ ;
+ }
+
+ T member;
+};
+// CHECK-MESSAGES: :[[@LINE-6]]:3: warning: function 'TemplateD<T>' exceeds recommended size/complexity thresholds [readability-function-size]
+// CHECK-MESSAGES: :[[@LINE-7]]:3: note: 2 lines including whitespace and comments (threshold 0)
+// CHECK-MESSAGES: :[[@LINE-8]]:3: note: 2 statements (threshold 0)
+
+void instantiate() {
+ TemplateC<int> c;
+ TemplateD<int> d(5);
+}
+// CHECK-MESSAGES: :[[@LINE-4]]:6: warning: function 'instantiate' exceeds recommended size/complexity thresholds [readability-function-size]
+// CHECK-MESSAGES: :[[@LINE-5]]:6: note: 3 lines including whitespace and comments (threshold 0)
+// CHECK-MESSAGES: :[[@LINE-6]]:6: note: 2 statements (threshold 0)
+// CHECK-MESSAGES: :[[@LINE-7]]:6: note: 2 variables (threshold 1)
>From ea321392ebc487c1000e43576f44af99edf28a5f Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Sat, 21 Jun 2025 18:58:56 +0800
Subject: [PATCH 04/40] [C++][Modules] A module directive may only appear as
the first preprocessing tokens in a file (#144233)
This PR is 2nd part of
[P1857R3](https://github.com/llvm/llvm-project/pull/107168)
implementation, and mainly implement the restriction `A module directive
may only appear as the first preprocessing tokens in a file (excluding
the global module fragment.)`:
[cpp.pre](https://eel.is/c++draft/cpp.pre):
```
module-file:
pp-global-module-fragment[opt] pp-module group[opt] pp-private-module-fragment[opt]
```
We also refine tests use `split-file` instead of conditional macro.
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/include/clang/Lex/Lexer.h | 3 +
clang/include/clang/Lex/Preprocessor.h | 17 ++
clang/include/clang/Lex/Token.h | 12 +-
clang/include/clang/Sema/Sema.h | 3 +-
clang/lib/Lex/Lexer.cpp | 13 ++
clang/lib/Lex/PPDirectives.cpp | 3 +
clang/lib/Lex/PPMacroExpansion.cpp | 3 +
clang/lib/Lex/Preprocessor.cpp | 2 +
clang/lib/Parse/Parser.cpp | 7 +-
clang/lib/Sema/SemaModule.cpp | 15 +-
clang/test/CXX/basic/basic.link/p1.cpp | 143 ++++++++++----
clang/test/CXX/basic/basic.link/p2.cpp | 26 +--
.../basic.scope/basic.scope.namespace/p2.cpp | 82 ++++++---
.../CXX/module/basic/basic.def.odr/p6.cppm | 174 ++++++++++++++----
.../basic/basic.link/module-declaration.cpp | 64 ++++---
clang/test/CXX/module/cpp.pre/module_decl.cpp | 8 +
.../dcl.module/dcl.module.import/p1.cppm | 34 +++-
.../dcl.module/dcl.module.interface/p1.cppm | 39 ++--
.../test/CXX/module/dcl.dcl/dcl.module/p1.cpp | 44 +++--
.../test/CXX/module/dcl.dcl/dcl.module/p5.cpp | 65 +++++--
clang/test/CXX/module/module.interface/p2.cpp | 26 ++-
clang/test/CXX/module/module.unit/p8.cpp | 48 +++--
clang/test/Driver/modules.cpp | 31 ++--
clang/test/Modules/named-modules-adl-3.cppm | 1 +
clang/test/Modules/reserved-names-1.cppm | 10 +
.../reserved-names-system-header-1.cpp | 1 +
.../reserved-names-system-header-2.cpp | 1 +
clang/test/SemaCXX/modules.cppm | 75 +++++---
...-aware-new-delete-transparent-contexts.cpp | 20 +-
clang/unittests/Lex/LexerTest.cpp | 47 ++++-
30 files changed, 713 insertions(+), 304 deletions(-)
create mode 100644 clang/test/CXX/module/cpp.pre/module_decl.cpp
diff --git a/clang/include/clang/Lex/Lexer.h b/clang/include/clang/Lex/Lexer.h
index bb65ae010cffa..ca812ba1583fb 100644
--- a/clang/include/clang/Lex/Lexer.h
+++ b/clang/include/clang/Lex/Lexer.h
@@ -143,6 +143,9 @@ class Lexer : public PreprocessorLexer {
/// True if this is the first time we're lexing the input file.
bool IsFirstTimeLexingFile;
+ /// True if current lexing token is the first pp-token.
+ bool IsFirstPPToken;
+
// NewLinePtr - A pointer to new line character '\n' being lexed. For '\r\n',
// it also points to '\n.'
const char *NewLinePtr;
diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h
index 78be2bd64d61c..47830b428c8ad 100644
--- a/clang/include/clang/Lex/Preprocessor.h
+++ b/clang/include/clang/Lex/Preprocessor.h
@@ -350,6 +350,9 @@ class Preprocessor {
/// Whether the last token we lexed was an '@'.
bool LastTokenWasAt = false;
+ /// First pp-token in current translation unit.
+ std::optional<Token> FirstPPToken;
+
/// A position within a C++20 import-seq.
class StdCXXImportSeq {
public:
@@ -1766,6 +1769,20 @@ class Preprocessor {
std::optional<LexEmbedParametersResult> LexEmbedParameters(Token &Current,
bool ForHasEmbed);
+ /// Whether the preprocessor already seen the first pp-token in main file.
+ bool hasSeenMainFileFirstPPToken() const { return FirstPPToken.has_value(); }
+
+ /// Record first pp-token and check if it has a Token::FirstPPToken flag.
+ void HandleMainFileFirstPPToken(const Token &Tok) {
+ if (!hasSeenMainFileFirstPPToken() && Tok.isFirstPPToken() &&
+ SourceMgr.isWrittenInMainFile(Tok.getLocation()))
+ FirstPPToken = Tok;
+ }
+
+ Token getMainFileFirstPPToken() const {
+ assert(FirstPPToken && "First main file pp-token doesn't exists");
+ return *FirstPPToken;
+ }
bool LexAfterModuleImport(Token &Result);
void CollectPpImportSuffix(SmallVectorImpl<Token> &Toks);
diff --git a/clang/include/clang/Lex/Token.h b/clang/include/clang/Lex/Token.h
index 4f29fb7d11415..d4dfd7b44d9af 100644
--- a/clang/include/clang/Lex/Token.h
+++ b/clang/include/clang/Lex/Token.h
@@ -86,9 +86,12 @@ class Token {
// macro stringizing or charizing operator.
CommaAfterElided = 0x200, // The comma following this token was elided (MS).
IsEditorPlaceholder = 0x400, // This identifier is a placeholder.
- IsReinjected = 0x800, // A phase 4 token that was produced before and
- // re-added, e.g. via EnterTokenStream. Annotation
- // tokens are *not* reinjected.
+
+ IsReinjected = 0x800, // A phase 4 token that was produced before and
+ // re-added, e.g. via EnterTokenStream. Annotation
+ // tokens are *not* reinjected.
+ FirstPPToken = 0x1000, // This token is the first pp token in the
+ // translation unit.
};
tok::TokenKind getKind() const { return Kind; }
@@ -318,6 +321,9 @@ class Token {
/// represented as characters between '<#' and '#>' in the source code. The
/// lexer uses identifier tokens to represent placeholders.
bool isEditorPlaceholder() const { return getFlag(IsEditorPlaceholder); }
+
+ /// Returns true if this token is the first pp-token.
+ bool isFirstPPToken() const { return getFlag(FirstPPToken); }
};
/// Information about the conditional stack (\#if directives)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 29452bb37260d..9397546c8fc5d 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -9822,7 +9822,8 @@ class Sema final : public SemaBase {
DeclGroupPtrTy ActOnModuleDecl(SourceLocation StartLoc,
SourceLocation ModuleLoc, ModuleDeclKind MDK,
ModuleIdPath Path, ModuleIdPath Partition,
- ModuleImportState &ImportState);
+ ModuleImportState &ImportState,
+ bool IntroducerIsFirstPPToken);
/// The parser has processed a global-module-fragment declaration that begins
/// the definition of the global module fragment of the current module unit.
diff --git a/clang/lib/Lex/Lexer.cpp b/clang/lib/Lex/Lexer.cpp
index 93200458f04b4..b61ea3b1614c7 100644
--- a/clang/lib/Lex/Lexer.cpp
+++ b/clang/lib/Lex/Lexer.cpp
@@ -174,6 +174,8 @@ void Lexer::InitLexer(const char *BufStart, const char *BufPtr,
ExtendedTokenMode = 0;
NewLinePtr = nullptr;
+
+ IsFirstPPToken = true;
}
/// Lexer constructor - Create a new lexer object for the specified buffer
@@ -3725,6 +3727,11 @@ bool Lexer::Lex(Token &Result) {
HasLeadingEmptyMacro = false;
}
+ if (IsFirstPPToken) {
+ Result.setFlag(Token::FirstPPToken);
+ IsFirstPPToken = false;
+ }
+
bool atPhysicalStartOfLine = IsAtPhysicalStartOfLine;
IsAtPhysicalStartOfLine = false;
bool isRawLex = isLexingRawMode();
@@ -3732,6 +3739,10 @@ bool Lexer::Lex(Token &Result) {
bool returnedToken = LexTokenInternal(Result, atPhysicalStartOfLine);
// (After the LexTokenInternal call, the lexer might be destroyed.)
assert((returnedToken || !isRawLex) && "Raw lex must succeed");
+
+ if (returnedToken && Result.isFirstPPToken() && PP &&
+ !PP->hasSeenMainFileFirstPPToken())
+ PP->HandleMainFileFirstPPToken(Result);
return returnedToken;
}
@@ -4535,6 +4546,8 @@ const char *Lexer::convertDependencyDirectiveToken(
Result.setFlag((Token::TokenFlags)DDTok.Flags);
Result.setLength(DDTok.Length);
BufferPtr = TokPtr + DDTok.Length;
+ if (PP && !PP->hasSeenMainFileFirstPPToken() && Result.isFirstPPToken())
+ PP->HandleMainFileFirstPPToken(Result);
return TokPtr;
}
diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp
index 04a30f66fb736..70934b9b1dec3 100644
--- a/clang/lib/Lex/PPDirectives.cpp
+++ b/clang/lib/Lex/PPDirectives.cpp
@@ -1242,6 +1242,9 @@ void Preprocessor::HandleDirective(Token &Result) {
// pp-directive.
bool ReadAnyTokensBeforeDirective =CurPPLexer->MIOpt.getHasReadAnyTokensVal();
+ if (!hasSeenMainFileFirstPPToken())
+ HandleMainFileFirstPPToken(Result);
+
// Save the '#' token in case we need to return it later.
Token SavedHash = Result;
diff --git a/clang/lib/Lex/PPMacroExpansion.cpp b/clang/lib/Lex/PPMacroExpansion.cpp
index 37ac1bf07e9c0..97bdeb873d699 100644
--- a/clang/lib/Lex/PPMacroExpansion.cpp
+++ b/clang/lib/Lex/PPMacroExpansion.cpp
@@ -469,6 +469,9 @@ bool Preprocessor::HandleMacroExpandedIdentifier(Token &Identifier,
// to disable the optimization in this case.
if (CurPPLexer) CurPPLexer->MIOpt.ExpandedMacro();
+ if (!hasSeenMainFileFirstPPToken())
+ HandleMainFileFirstPPToken(Identifier);
+
// If this is a builtin macro, like __LINE__ or _Pragma, handle it specially.
if (MI->isBuiltinMacro()) {
if (Callbacks)
diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp
index 21fc7a2b6fae2..18b2f5f02d6c7 100644
--- a/clang/lib/Lex/Preprocessor.cpp
+++ b/clang/lib/Lex/Preprocessor.cpp
@@ -247,6 +247,8 @@ void Preprocessor::DumpToken(const Token &Tok, bool DumpFlags) const {
llvm::errs() << " [LeadingSpace]";
if (Tok.isExpandDisabled())
llvm::errs() << " [ExpandDisabled]";
+ if (Tok.isFirstPPToken())
+ llvm::errs() << " [First pp-token]";
if (Tok.needsCleaning()) {
const char *Start = SourceMgr.getCharacterData(Tok.getLocation());
llvm::errs() << " [UnClean='" << StringRef(Start, Tok.getLength())
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index 788ed79e0c1fa..18f399aca59e8 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -2340,7 +2340,8 @@ void Parser::ParseMicrosoftIfExistsExternalDeclaration() {
Parser::DeclGroupPtrTy
Parser::ParseModuleDecl(Sema::ModuleImportState &ImportState) {
- SourceLocation StartLoc = Tok.getLocation();
+ Token Introducer = Tok;
+ SourceLocation StartLoc = Introducer.getLocation();
Sema::ModuleDeclKind MDK = TryConsumeToken(tok::kw_export)
? Sema::ModuleDeclKind::Interface
@@ -2359,7 +2360,7 @@ Parser::ParseModuleDecl(Sema::ModuleImportState &ImportState) {
// Parse a global-module-fragment, if present.
if (getLangOpts().CPlusPlusModules && Tok.is(tok::semi)) {
SourceLocation SemiLoc = ConsumeToken();
- if (ImportState != Sema::ModuleImportState::FirstDecl) {
+ if (!Introducer.isFirstPPToken()) {
Diag(StartLoc, diag::err_global_module_introducer_not_at_start)
<< SourceRange(StartLoc, SemiLoc);
return nullptr;
@@ -2416,7 +2417,7 @@ Parser::ParseModuleDecl(Sema::ModuleImportState &ImportState) {
ExpectAndConsumeSemi(diag::err_module_expected_semi);
return Actions.ActOnModuleDecl(StartLoc, ModuleLoc, MDK, Path, Partition,
- ImportState);
+ ImportState, Introducer.isFirstPPToken());
}
Decl *Parser::ParseModuleImport(SourceLocation AtLoc,
diff --git a/clang/lib/Sema/SemaModule.cpp b/clang/lib/Sema/SemaModule.cpp
index 54ee0486763b1..fe70ce3fba6a5 100644
--- a/clang/lib/Sema/SemaModule.cpp
+++ b/clang/lib/Sema/SemaModule.cpp
@@ -263,11 +263,11 @@ static bool DiagReservedModuleName(Sema &S, const IdentifierInfo *II,
Sema::DeclGroupPtrTy
Sema::ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc,
ModuleDeclKind MDK, ModuleIdPath Path,
- ModuleIdPath Partition, ModuleImportState &ImportState) {
+ ModuleIdPath Partition, ModuleImportState &ImportState,
+ bool IntroducerIsFirstPPToken) {
assert(getLangOpts().CPlusPlusModules &&
"should only have module decl in standard C++ modules");
- bool IsFirstDecl = ImportState == ModuleImportState::FirstDecl;
bool SeenGMF = ImportState == ModuleImportState::GlobalFragment;
// If any of the steps here fail, we count that as invalidating C++20
// module state;
@@ -333,14 +333,11 @@ Sema::ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc,
SeenGMF == (bool)this->TheGlobalModuleFragment) &&
"mismatched global module state");
- // In C++20, the module-declaration must be the first declaration if there
- // is no global module fragment.
- if (getLangOpts().CPlusPlusModules && !IsFirstDecl && !SeenGMF) {
+ // In C++20, A module directive may only appear as the first preprocessing
+ // tokens in a file (excluding the global module fragment.).
+ if (getLangOpts().CPlusPlusModules && !IntroducerIsFirstPPToken && !SeenGMF) {
Diag(ModuleLoc, diag::err_module_decl_not_at_start);
- SourceLocation BeginLoc =
- ModuleScopes.empty()
- ? SourceMgr.getLocForStartOfFile(SourceMgr.getMainFileID())
- : ModuleScopes.back().BeginLoc;
+ SourceLocation BeginLoc = PP.getMainFileFirstPPToken().getLocation();
if (BeginLoc.isValid()) {
Diag(BeginLoc, diag::note_global_module_introducer_missing)
<< FixItHint::CreateInsertion(BeginLoc, "module;\n");
diff --git a/clang/test/CXX/basic/basic.link/p1.cpp b/clang/test/CXX/basic/basic.link/p1.cpp
index c6a119aa7f47c..26a5f025f42fe 100644
--- a/clang/test/CXX/basic/basic.link/p1.cpp
+++ b/clang/test/CXX/basic/basic.link/p1.cpp
@@ -1,57 +1,128 @@
-// RUN: %clang_cc1 -std=c++2a -verify %s
-// RUN: %clang_cc1 -std=c++2a -verify -DNO_GLOBAL_FRAG %s
-// RUN: %clang_cc1 -std=c++2a -verify -DNO_MODULE_DECL %s
-// RUN: %clang_cc1 -std=c++2a -verify -DNO_PRIVATE_FRAG %s
-// RUN: %clang_cc1 -std=c++2a -verify -DNO_MODULE_DECL -DNO_PRIVATE_FRAG %s
-// RUN: %clang_cc1 -std=c++2a -verify -DNO_GLOBAL_FRAG -DNO_PRIVATE_FRAG %s
-// RUN: %clang_cc1 -std=c++2a -verify -DNO_GLOBAL_FRAG -DNO_MODULE_DECL %s
-// RUN: %clang_cc1 -std=c++2a -verify -DNO_GLOBAL_FRAG -DNO_MODULE_DECL -DNO_PRIVATE_FRAG %s
-// RUN: %clang_cc1 -std=c++2a -verify -DEXPORT_FRAGS %s
-
-#ifndef NO_GLOBAL_FRAG
-#ifdef EXPORT_FRAGS
-export // expected-error {{global module fragment cannot be exported}}
-#endif
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+
+// RUN: %clang_cc1 -std=c++2a -verify %t/M.cppm
+// RUN: %clang_cc1 -std=c++2a -verify %t/NoGlobalFrag.cppm
+// RUN: %clang_cc1 -std=c++2a -verify %t/NoModuleDecl.cppm
+// RUN: %clang_cc1 -std=c++2a -verify %t/NoPrivateFrag.cppm
+// RUN: %clang_cc1 -std=c++2a -verify %t/NoModuleDeclAndNoPrivateFrag.cppm
+// RUN: %clang_cc1 -std=c++2a -verify %t/NoGlobalFragAndNoPrivateFrag.cppm
+// RUN: %clang_cc1 -std=c++2a -verify %t/NoGlobalFragAndNoModuleDecl.cppm
+// RUN: %clang_cc1 -std=c++2a -verify %t/NoGlobalFragAndNoModuleDeclAndNoPrivateFrag.cppm
+// RUN: %clang_cc1 -std=c++2a -verify %t/ExportFrags.cppm
+
+//--- M.cppm
module;
-#ifdef NO_MODULE_DECL
-// expected-error at -2 {{missing 'module' declaration at end of global module fragment introduced here}}
-#endif
-#endif
+extern int a; // #a1
+export module Foo;
+
+int a; // expected-error {{declaration of 'a' in module Foo follows declaration in the global module}}
+ // expected-note@#a1 {{previous decl}}
+extern int b;
+
+module; // expected-error {{'module;' introducing a global module fragment can appear only at the start of the translation unit}}
+module :private; // #priv-frag
+int b; // ok
+module :private; // expected-error {{private module fragment redefined}}
+ // expected-note@#priv-frag {{previous definition is here}}
+
+//--- NoGlobalFrag.cppm
+
+extern int a; // #a1
+export module Foo; // expected-error {{module declaration must occur at the start of the translation unit}}
+ // expected-note at -2 {{add 'module;' to the start of the file to introduce a global module fragment}}
+
+// expected-error@#a2 {{declaration of 'a' in module Foo follows declaration in the global module}}
+// expected-note@#a1 {{previous decl}}
+
+int a; // #a2
+extern int b;
+module; // expected-error {{'module;' introducing a global module fragment can appear only at the start of the translation unit}}
+module :private; // #priv-frag
+int b; // ok
+module :private; // expected-error {{private module fragment redefined}}
+// expected-note@#priv-frag {{previous definition is here}}
+//--- NoModuleDecl.cppm
+module; // expected-error {{missing 'module' declaration at end of global module fragment introduced here}}
extern int a; // #a1
+int a; // #a2
+extern int b;
+module; // expected-error {{'module;' introducing a global module fragment can appear only at the start of the translation unit}}
+module :private; // expected-error {{private module fragment declaration with no preceding module declaration}}
+int b; // ok
-#ifndef NO_MODULE_DECL
+//--- NoPrivateFrag.cppm
+module;
+extern int a; // #a1
export module Foo;
-#ifdef NO_GLOBAL_FRAG
-// expected-error at -2 {{module declaration must occur at the start of the translation unit}}
+
+// expected-error@#a2 {{declaration of 'a' in module Foo follows declaration in the global module}}
+// expected-note@#a1 {{previous decl}}
+int a; // #a2
+extern int b;
+
+module; // expected-error {{'module;' introducing a global module fragment can appear only at the start of the translation unit}}
+int b; // ok
+
+
+//--- NoModuleDeclAndNoPrivateFrag.cppm
+module; // expected-error {{missing 'module' declaration at end of global module fragment introduced here}}
+extern int a; // #a1
+int a; // #a2
+extern int b;
+
+module; // expected-error {{'module;' introducing a global module fragment can appear only at the start of the translation unit}}
+
+int b; // ok
+
+//--- NoGlobalFragAndNoPrivateFrag.cppm
+extern int a; // #a1
+export module Foo; // expected-error {{module declaration must occur at the start of the translation unit}}
// expected-note at 1 {{add 'module;' to the start of the file to introduce a global module fragment}}
-#endif
// expected-error@#a2 {{declaration of 'a' in module Foo follows declaration in the global module}}
// expected-note@#a1 {{previous decl}}
-#endif
int a; // #a2
extern int b;
module; // expected-error {{'module;' introducing a global module fragment can appear only at the start of the translation unit}}
-#ifndef NO_PRIVATE_FRAG
-#ifdef EXPORT_FRAGS
-export // expected-error {{private module fragment cannot be exported}}
-#endif
+int b; // ok
+
+//--- NoGlobalFragAndNoModuleDecl.cppm
+extern int a; // #a1
+int a; // #a2
+extern int b;
+module; // expected-error {{'module;' introducing a global module fragment can appear only at the start of the translation unit}}
module :private; // #priv-frag
-#ifdef NO_MODULE_DECL
-// expected-error at -2 {{private module fragment declaration with no preceding module declaration}}
-#endif
-#endif
+// expected-error at -1 {{private module fragment declaration with no preceding module declaration}}
+int b; // ok
+
+//--- NoGlobalFragAndNoModuleDeclAndNoPrivateFrag.cppm
+extern int a; // #a1
+int a; // #a2
+extern int b;
+
+module; // expected-error {{'module;' introducing a global module fragment can appear only at the start of the translation unit}}
int b; // ok
+//--- ExportFrags.cppm
+export module; // expected-error {{global module fragment cannot be exported}}
+extern int a; // #a1
+export module Foo;
+// expected-error@#a2 {{declaration of 'a' in module Foo follows declaration in the global module}}
+// expected-note@#a1 {{previous decl}}
-#ifndef NO_PRIVATE_FRAG
-#ifndef NO_MODULE_DECL
+int a; // #a2
+extern int b;
+
+module; // expected-error {{'module;' introducing a global module fragment can appear only at the start of the translation unit}}
+
+module :private; // #priv-frag
+
+int b; // ok
module :private; // expected-error {{private module fragment redefined}}
-// expected-note@#priv-frag {{previous definition is here}}
-#endif
-#endif
+ // expected-note@#priv-frag {{previous definition is here}}
diff --git a/clang/test/CXX/basic/basic.link/p2.cpp b/clang/test/CXX/basic/basic.link/p2.cpp
index ccad42022ee80..94cbc62490b2f 100644
--- a/clang/test/CXX/basic/basic.link/p2.cpp
+++ b/clang/test/CXX/basic/basic.link/p2.cpp
@@ -1,16 +1,16 @@
-// RUN: %clang_cc1 -std=c++2a -DEXPORT %s -verify
-// RUN: %clang_cc1 -std=c++2a -DEXPORT %s -emit-module-interface -o %t.pcm
-// RUN: %clang_cc1 -std=c++2a -UEXPORT %s -verify -fmodule-file=M=%t.pcm
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+
+// RUN: %clang_cc1 -std=c++2a %t/pmf_in_interface.cpp -verify
+// RUN: %clang_cc1 -std=c++2a %t/pmf_in_interface.cpp -emit-module-interface -o %t.pcm
+// RUN: %clang_cc1 -std=c++2a %t/pmf_in_implementation.cpp -verify -fmodule-file=M=%t.pcm
-#ifdef EXPORT
-// expected-no-diagnostics
-export
-#else
-// expected-note at +2 {{add 'export' here}}
-#endif
-module M;
-#ifndef EXPORT
-// expected-error at +2 {{private module fragment in module implementation unit}}
-#endif
+//--- pmf_in_interface.cpp
+// expected-no-diagnostics
+export module M;
module :private;
+
+//--- pmf_in_implementation.cpp
+module M; // expected-note {{add 'export' here}}
+module :private; // expected-error {{private module fragment in module implementation unit}}
diff --git a/clang/test/CXX/basic/basic.scope/basic.scope.namespace/p2.cpp b/clang/test/CXX/basic/basic.scope/basic.scope.namespace/p2.cpp
index d70eb7de22c6a..fd0038b3f7745 100644
--- a/clang/test/CXX/basic/basic.scope/basic.scope.namespace/p2.cpp
+++ b/clang/test/CXX/basic/basic.scope/basic.scope.namespace/p2.cpp
@@ -1,14 +1,16 @@
// RUN: rm -rf %t
-// RUN: mkdir -p %t
-// RUN: echo '#ifndef FOO_H' > %t/foo.h
-// RUN: echo '#define FOO_H' >> %t/foo.h
-// RUN: echo 'extern int in_header;' >> %t/foo.h
-// RUN: echo '#endif' >> %t/foo.h
-// RUN: %clang_cc1 -std=c++2a -I%t -emit-module-interface -DINTERFACE %s -o %t.pcm
-// RUN: %clang_cc1 -std=c++2a -I%t -fmodule-file=A=%t.pcm -DIMPLEMENTATION %s -verify -fno-modules-error-recovery
-// RUN: %clang_cc1 -std=c++2a -I%t -fmodule-file=A=%t.pcm %s -verify -fno-modules-error-recovery
-
-#ifdef INTERFACE
+// RUN: split-file %s %t
+// RUN: %clang_cc1 -std=c++2a -I%t -emit-module-interface %t/interface.cppm -o %t.pcm
+// RUN: %clang_cc1 -std=c++2a -I%t -fmodule-file=A=%t.pcm %t/implA.cppm -verify -fno-modules-error-recovery
+// RUN: %clang_cc1 -std=c++2a -I%t -fmodule-file=A=%t.pcm %t/implB.cppm -verify -fno-modules-error-recovery
+
+//--- foo.h
+#ifndef FOO_H
+#define FOO_H
+extern int in_header;
+#endif
+
+//--- interface.cppm
module;
#include "foo.h"
// FIXME: The following need to be moved to a header file. The global module
@@ -22,11 +24,9 @@ static int internal;
module :private;
int not_exported_private;
static int internal_private;
-#else
-#ifdef IMPLEMENTATION
+//--- implA.cppm
module;
-#endif
void test_early() {
in_header = 1; // expected-error {{use of undeclared identifier 'in_header'}}
@@ -46,11 +46,7 @@ void test_early() {
internal_private = 1; // expected-error {{undeclared identifier}}
}
-#ifdef IMPLEMENTATION
module A;
-#else
-import A;
-#endif
void test_late() {
in_header = 1; // expected-error {{missing '#include "foo.h"'; 'in_header' must be declared before it is used}}
@@ -61,20 +57,54 @@ void test_late() {
exported = 1;
not_exported = 1;
-#ifndef IMPLEMENTATION
- // expected-error at -2 {{use of undeclared identifier 'not_exported'; did you mean 'exported'?}}
- // expected-note at p2.cpp:18 {{'exported' declared here}}
-#endif
internal = 1; // expected-error {{use of undeclared identifier 'internal'}}
not_exported_private = 1;
-#ifndef IMPLEMENTATION
- // FIXME: should not be visible here
- // expected-error at -3 {{undeclared identifier}}
-#endif
internal_private = 1; // expected-error {{use of undeclared identifier 'internal_private'}}
}
-#endif
+//--- implB.cppm
+module;
+
+void test_early() {
+ in_header = 1; // expected-error {{use of undeclared identifier 'in_header'}}
+ // expected-note@* {{not visible}}
+
+ global_module_fragment = 1; // expected-error {{use of undeclared identifier 'global_module_fragment'}}
+
+ exported = 1; // expected-error {{use of undeclared identifier 'exported'}}
+
+ not_exported = 1; // expected-error {{use of undeclared identifier 'not_exported'}}
+
+ // FIXME: We need better diagnostic message for static variable.
+ internal = 1; // expected-error {{use of undeclared identifier 'internal'}}
+
+ not_exported_private = 1; // expected-error {{undeclared identifier}}
+
+ internal_private = 1; // expected-error {{undeclared identifier}}
+}
+
+export module B;
+import A;
+
+void test_late() {
+ in_header = 1; // expected-error {{missing '#include "foo.h"'; 'in_header' must be declared before it is used}}
+ // expected-note@* {{not visible}}
+
+ global_module_fragment = 1; // expected-error {{missing '#include'; 'global_module_fragment' must be declared before it is used}}
+
+ exported = 1;
+
+ not_exported = 1; // expected-error {{use of undeclared identifier 'not_exported'; did you mean 'exported'?}}
+ // expected-note@* {{'exported' declared here}}
+
+ internal = 1; // expected-error {{use of undeclared identifier 'internal'}}
+
+ not_exported_private = 1;
+ // FIXME: should not be visible here
+ // expected-error at -2 {{undeclared identifier}}
+
+ internal_private = 1; // expected-error {{use of undeclared identifier 'internal_private'}}
+}
\ No newline at end of file
diff --git a/clang/test/CXX/module/basic/basic.def.odr/p6.cppm b/clang/test/CXX/module/basic/basic.def.odr/p6.cppm
index 8e7917dc63ea5..c532e7ad40a10 100644
--- a/clang/test/CXX/module/basic/basic.def.odr/p6.cppm
+++ b/clang/test/CXX/module/basic/basic.def.odr/p6.cppm
@@ -3,29 +3,28 @@
// RUN: split-file %s %t
//
// RUN: %clang_cc1 -std=c++20 -verify %t/global-vs-module.cppm
-// RUN: %clang_cc1 -std=c++20 -verify %t/global-vs-module.cppm -DEXPORT
-// RUN: %clang_cc1 -std=c++20 -verify %t/global-vs-module.cppm -DUSING
+// RUN: %clang_cc1 -std=c++20 -verify %t/global-vs-module-export.cppm
+// RUN: %clang_cc1 -std=c++20 -verify %t/global-vs-module-using.cppm
//
-// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/global-vs-module.cppm -o %t/M.pcm -DNO_GLOBAL -DEXPORT
+// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/M.cppm -o %t/M.pcm
// RUN: %clang_cc1 -std=c++20 -verify %t/module-vs-global.cpp -fmodule-file=M=%t/M.pcm
//
// Some of the following tests intentionally have no -verify in their RUN
// lines; we are testing that those cases do not produce errors.
//
-// RUN: %clang_cc1 -std=c++20 %t/module-vs-module.cpp -fmodule-file=M=%t/M.pcm -DMODULE_INTERFACE -verify
-// RUN: %clang_cc1 -std=c++20 %t/module-vs-module.cpp -fmodule-file=M=%t/M.pcm -DMODULE_INTERFACE -DNO_IMPORT
+// RUN: %clang_cc1 -std=c++20 %t/module-vs-module-interface.cpp -fmodule-file=M=%t/M.pcm -verify
+// RUN: %clang_cc1 -std=c++20 %t/module-vs-module-interface.cpp -fmodule-file=M=%t/M.pcm -DNO_IMPORT
//
-// RUN: %clang_cc1 -std=c++20 %t/module-vs-module.cpp -fmodule-file=M=%t/M.pcm -emit-module-interface -o %t/N.pcm -DMODULE_INTERFACE -DNO_ERRORS
-// RUN: %clang_cc1 -std=c++20 %t/module-vs-module.cpp -fmodule-file=M=%t/M.pcm -fmodule-file=N=%t/N.pcm -verify
+// RUN: %clang_cc1 -std=c++20 %t/module-vs-module-interface.cpp -fmodule-file=M=%t/M.pcm -emit-module-interface -o %t/N.pcm -DNO_ERRORS
+// RUN: %clang_cc1 -std=c++20 %t/module-vs-module-impl.cpp -fmodule-file=M=%t/M.pcm -fmodule-file=N=%t/N.pcm -verify
//
-// RUN: %clang_cc1 -std=c++20 %t/module-vs-module.cpp -fmodule-file=M=%t/M.pcm -fmodule-file=N=%t/N.pcm -DNO_IMPORT -verify
+// RUN: %clang_cc1 -std=c++20 %t/module-vs-module-impl.cpp -fmodule-file=M=%t/M.pcm -fmodule-file=N=%t/N.pcm -DNO_IMPORT -verify
//
-// RUN: %clang_cc1 -std=c++20 %t/module-vs-module.cpp -fmodule-file=M=%t/M.pcm -emit-module-interface -o %t/N-no-M.pcm -DMODULE_INTERFACE -DNO_ERRORS -DNO_IMPORT
-// RUN: %clang_cc1 -std=c++20 %t/module-vs-module.cpp -fmodule-file=M=%t/M.pcm -fmodule-file=N=%t/N-no-M.pcm -verify
-// RUN: %clang_cc1 -std=c++20 %t/module-vs-module.cpp -fmodule-file=N=%t/N-no-M.pcm -DNO_IMPORT
+// RUN: %clang_cc1 -std=c++20 %t/module-vs-module-interface.cpp -fmodule-file=M=%t/M.pcm -emit-module-interface -o %t/N-no-M.pcm -DNO_ERRORS -DNO_IMPORT
+// RUN: %clang_cc1 -std=c++20 %t/module-vs-module-impl.cpp -fmodule-file=M=%t/M.pcm -fmodule-file=N=%t/N-no-M.pcm -verify
+// RUN: %clang_cc1 -std=c++20 %t/module-vs-module-impl.cpp -fmodule-file=N=%t/N-no-M.pcm -DNO_IMPORT
//--- global-vs-module.cppm
-#ifndef NO_GLOBAL
module;
extern int var; // expected-note {{previous declaration is here}}
int func(); // expected-note {{previous declaration is here}}
@@ -40,11 +39,75 @@ template<typename> using type_tpl = int; // expected-note {{previous declaration
typedef int type;
namespace ns { using ::func; }
namespace ns_alias = ns;
-#endif
export module M;
-#ifdef USING
+extern int var; // expected-error {{declaration of 'var' in module M follows declaration in the global module}}
+int func(); // expected-error {{declaration of 'func' in module M follows declaration in the global module}}
+struct str; // expected-error {{declaration of 'str' in module M follows declaration in the global module}}
+using type = int;
+
+template<typename> extern int var_tpl; // expected-error {{declaration of 'var_tpl' in module M follows declaration in the global module}}
+template<typename> int func_tpl(); // expected-error {{declaration of 'func_tpl' in module M follows declaration in the global module}}
+template<typename> struct str_tpl; // expected-error {{declaration of 'str_tpl' in module M follows declaration in the global module}}
+template<typename> using type_tpl = int; // expected-error {{declaration of 'type_tpl' in module M follows declaration in the global module}}
+
+typedef int type;
+namespace ns { using ::func; }
+namespace ns_alias = ns;
+
+//--- global-vs-module-export.cppm
+module;
+extern int var; // expected-note {{previous declaration is here}}
+int func(); // expected-note {{previous declaration is here}}
+struct str; // expected-note {{previous declaration is here}}
+using type = int;
+
+template<typename> extern int var_tpl; // expected-note {{previous declaration is here}}
+template<typename> int func_tpl(); // expected-note {{previous declaration is here}}
+template<typename> struct str_tpl; // expected-note {{previous declaration is here}}
+template<typename> using type_tpl = int; // expected-note {{previous declaration is here}}
+
+typedef int type;
+namespace ns { using ::func; }
+namespace ns_alias = ns;
+
+export module M;
+
+export {
+extern int var; // expected-error {{declaration of 'var' in module M follows declaration in the global module}}
+int func(); // expected-error {{declaration of 'func' in module M follows declaration in the global module}}
+struct str; // expected-error {{declaration of 'str' in module M follows declaration in the global module}}
+using type = int;
+
+template<typename> extern int var_tpl; // expected-error {{declaration of 'var_tpl' in module M follows declaration in the global module}}
+template<typename> int func_tpl(); // expected-error {{declaration of 'func_tpl' in module M follows declaration in the global module}}
+template<typename> struct str_tpl; // expected-error {{declaration of 'str_tpl' in module M follows declaration in the global module}}
+template<typename> using type_tpl = int; // expected-error {{declaration of 'type_tpl' in module M follows declaration in the global module}}
+
+typedef int type;
+namespace ns { using ::func; }
+namespace ns_alias = ns;
+}
+
+//--- global-vs-module-using.cppm
+module;
+extern int var; // expected-note {{previous declaration is here}}
+int func(); // expected-note {{previous declaration is here}}
+struct str; // expected-note {{previous declaration is here}}
+using type = int;
+
+template<typename> extern int var_tpl; // expected-note {{previous declaration is here}}
+template<typename> int func_tpl(); // expected-note {{previous declaration is here}}
+template<typename> struct str_tpl; // expected-note {{previous declaration is here}}
+template<typename> using type_tpl = int; // expected-note {{previous declaration is here}}
+
+typedef int type;
+namespace ns { using ::func; }
+namespace ns_alias = ns;
+
+export module M;
+
using ::var;
using ::func;
using ::str;
@@ -53,11 +116,6 @@ using ::var_tpl;
using ::func_tpl;
using ::str_tpl;
using ::type_tpl;
-#endif
-
-#ifdef EXPORT
-export {
-#endif
extern int var; // expected-error {{declaration of 'var' in module M follows declaration in the global module}}
int func(); // expected-error {{declaration of 'func' in module M follows declaration in the global module}}
@@ -73,51 +131,87 @@ typedef int type;
namespace ns { using ::func; }
namespace ns_alias = ns;
-#ifdef EXPORT
+//--- M.cppm
+export module M;
+
+export {
+extern int var; // expected-error {{declaration of 'var' in module M follows declaration in the global module}}
+int func(); // expected-error {{declaration of 'func' in module M follows declaration in the global module}}
+struct str; // expected-error {{declaration of 'str' in module M follows declaration in the global module}}
+using type = int;
+
+template<typename> extern int var_tpl; // expected-error {{declaration of 'var_tpl' in module M follows declaration in the global module}}
+template<typename> int func_tpl(); // expected-error {{declaration of 'func_tpl' in module M follows declaration in the global module}}
+template<typename> struct str_tpl; // expected-error {{declaration of 'str_tpl' in module M follows declaration in the global module}}
+template<typename> using type_tpl = int; // expected-error {{declaration of 'type_tpl' in module M follows declaration in the global module}}
+
+typedef int type;
+namespace ns { using ::func; }
+namespace ns_alias = ns;
}
-#endif
//--- module-vs-global.cpp
+module;
import M;
-extern int var; // expected-error {{declaration of 'var' in the global module follows declaration in module M}} expected-note at global-vs-module.cppm:35 {{previous}}
-int func(); // expected-error {{declaration of 'func' in the global module follows declaration in module M}} expected-note at global-vs-module.cppm:36 {{previous}}
-struct str; // expected-error {{declaration of 'str' in the global module follows declaration in module M}} expected-note at global-vs-module.cppm:37 {{previous}}
+extern int var; // expected-error {{declaration of 'var' in the global module follows declaration in module M}} expected-note at M.cppm:4 {{previous}}
+int func(); // expected-error {{declaration of 'func' in the global module follows declaration in module M}} expected-note at M.cppm:5 {{previous}}
+struct str; // expected-error {{declaration of 'str' in the global module follows declaration in module M}} expected-note at M.cppm:6 {{previous}}
using type = int;
-template<typename> extern int var_tpl; // expected-error {{declaration of 'var_tpl' in the global module follows declaration in module M}} expected-note at global-vs-module.cppm:40 {{previous}}
-template<typename> int func_tpl(); // expected-error {{declaration of 'func_tpl' in the global module follows declaration in module M}} expected-note at global-vs-module.cppm:41 {{previous}}
-template<typename> struct str_tpl; // expected-error {{declaration of 'str_tpl' in the global module follows declaration in module M}} expected-note at global-vs-module.cppm:42 {{previous}}
-template<typename> using type_tpl = int; // expected-error {{declaration of 'type_tpl' in the global module follows declaration in module M}} expected-note at global-vs-module.cppm:43 {{previous}}
+template<typename> extern int var_tpl; // expected-error {{declaration of 'var_tpl' in the global module follows declaration in module M}} expected-note at M.cppm:9 {{previous}}
+template<typename> int func_tpl(); // expected-error {{declaration of 'func_tpl' in the global module follows declaration in module M}} expected-note at M.cppm:10 {{previous}}
+template<typename> struct str_tpl; // expected-error {{declaration of 'str_tpl' in the global module follows declaration in module M}} expected-note at M.cppm:11 {{previous}}
+template<typename> using type_tpl = int; // expected-error {{declaration of 'type_tpl' in the global module follows declaration in module M}} expected-note at M.cppm:12 {{previous}}
typedef int type;
namespace ns { using ::func; }
namespace ns_alias = ns;
-//--- module-vs-module.cpp
-#ifdef MODULE_INTERFACE
export module N;
-#else
-module N;
-#endif
+
+//--- module-vs-module-interface.cpp
+export module N;
#ifndef NO_IMPORT
import M;
#endif
#ifndef NO_ERRORS
-extern int var; // expected-error {{declaration of 'var' in module N follows declaration in module M}} expected-note at global-vs-module.cppm:35 {{previous}}
-int func(); // expected-error {{declaration of 'func' in module N follows declaration in module M}} expected-note at global-vs-module.cppm:36 {{previous}}
-struct str; // expected-error {{declaration of 'str' in module N follows declaration in module M}} expected-note at global-vs-module.cppm:37 {{previous}}
+extern int var; // expected-error {{declaration of 'var' in module N follows declaration in module M}} expected-note at M.cppm:4 {{previous}}
+int func(); // expected-error {{declaration of 'func' in module N follows declaration in module M}} expected-note at M.cppm:5 {{previous}}
+struct str; // expected-error {{declaration of 'str' in module N follows declaration in module M}} expected-note at M.cppm:6 {{previous}}
using type = int;
-template<typename> extern int var_tpl; // expected-error {{declaration of 'var_tpl' in module N follows declaration in module M}} expected-note at global-vs-module.cppm:40 {{previous}}
-template<typename> int func_tpl(); // expected-error {{declaration of 'func_tpl' in module N follows declaration in module M}} expected-note at global-vs-module.cppm:41 {{previous}}
-template<typename> struct str_tpl; // expected-error {{declaration of 'str_tpl' in module N follows declaration in module M}} expected-note at global-vs-module.cppm:42 {{previous}}
-template<typename> using type_tpl = int; // expected-error {{declaration of 'type_tpl' in module N follows declaration in module M}} expected-note at global-vs-module.cppm:43 {{previous}}
+template<typename> extern int var_tpl; // expected-error {{declaration of 'var_tpl' in module N follows declaration in module M}} expected-note at M.cppm:9 {{previous}}
+template<typename> int func_tpl(); // expected-error {{declaration of 'func_tpl' in module N follows declaration in module M}} expected-note at M.cppm:10 {{previous}}
+template<typename> struct str_tpl; // expected-error {{declaration of 'str_tpl' in module N follows declaration in module M}} expected-note at M.cppm:11 {{previous}}
+template<typename> using type_tpl = int; // expected-error {{declaration of 'type_tpl' in module N follows declaration in module M}} expected-note at M.cppm:12 {{previous}}
typedef int type;
namespace ns { using ::func; }
namespace ns_alias = ns;
#endif
+//--- module-vs-module-impl.cpp
+module N;
+
+#ifndef NO_IMPORT
+import M;
+#endif
+
+#ifndef NO_ERRORS
+extern int var; // expected-error {{declaration of 'var' in module N follows declaration in module M}} expected-note at M.cppm:4 {{previous}}
+int func(); // expected-error {{declaration of 'func' in module N follows declaration in module M}} expected-note at M.cppm:5 {{previous}}
+struct str; // expected-error {{declaration of 'str' in module N follows declaration in module M}} expected-note at M.cppm:6 {{previous}}
+using type = int;
+
+template<typename> extern int var_tpl; // expected-error {{declaration of 'var_tpl' in module N follows declaration in module M}} expected-note at M.cppm:9 {{previous}}
+template<typename> int func_tpl(); // expected-error {{declaration of 'func_tpl' in module N follows declaration in module M}} expected-note at M.cppm:10 {{previous}}
+template<typename> struct str_tpl; // expected-error {{declaration of 'str_tpl' in module N follows declaration in module M}} expected-note at M.cppm:11 {{previous}}
+template<typename> using type_tpl = int; // expected-error {{declaration of 'type_tpl' in module N follows declaration in module M}} expected-note at M.cppm:12 {{previous}}
+
+typedef int type;
+namespace ns { using ::func; }
+namespace ns_alias = ns;
+#endif
diff --git a/clang/test/CXX/module/basic/basic.link/module-declaration.cpp b/clang/test/CXX/module/basic/basic.link/module-declaration.cpp
index d71358cc7a571..4bdcc9e5f278e 100644
--- a/clang/test/CXX/module/basic/basic.link/module-declaration.cpp
+++ b/clang/test/CXX/module/basic/basic.link/module-declaration.cpp
@@ -8,27 +8,19 @@
// RUN: %clang_cc1 -std=c++20 -emit-module-interface -fmodule-file=x=%t/x.pcm %t/x.y.cppm -o %t/x.y.pcm
//
// Module implementation for unknown and known module. (The former is ill-formed.)
-// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify -x c++ %t/M.cpp \
-// RUN: -DTEST=1 -DEXPORT= -DMODULE_NAME=z
-// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x=%t/x.pcm -fmodule-file=x.y=%t/x.y.pcm -verify -x c++ %t/M.cpp \
-// RUN: -DTEST=2 -DEXPORT= -DMODULE_NAME=x
+// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify -x c++ %t/z_impl.cppm
+// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x=%t/x.pcm -fmodule-file=x.y=%t/x.y.pcm -verify -x c++ %t/x_impl.cppm
//
// Module interface for unknown and known module. (The latter is ill-formed due to
// redefinition.)
-// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/M.cpp \
-// RUN: -DTEST=3 -DEXPORT=export -DMODULE_NAME=z
-// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/M.cpp \
-// RUN: -DTEST=4 -DEXPORT=export -DMODULE_NAME=x
+// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/z_interface.cppm
+// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/x_interface.cppm
//
// Miscellaneous syntax.
-// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/M.cpp \
-// RUN: -DTEST=7 -DEXPORT=export -DMODULE_NAME='z elderberry'
-// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/M.cpp \
-// RUN: -DTEST=8 -DEXPORT=export -DMODULE_NAME='z [[]]'
-// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/M.cpp \
-// RUN: -DTEST=9 -DEXPORT=export -DMODULE_NAME='z [[fancy]]'
-// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/M.cpp \
-// RUN: -DTEST=10 -DEXPORT=export -DMODULE_NAME='z [[maybe_unused]]'
+// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/invalid_module_name.cppm
+// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/empty_attribute.cppm
+// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/fancy_attribute.cppm
+// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/maybe_unused_attribute.cppm
//--- x.cppm
export module x;
@@ -38,17 +30,31 @@ int a, b;
export module x.y;
int c;
-//--- M.cpp
-
-EXPORT module MODULE_NAME;
-#if TEST == 7
-// expected-error at -2 {{expected ';'}} expected-error at -2 {{a type specifier is required}}
-#elif TEST == 9
-// expected-warning at -4 {{unknown attribute 'fancy' ignored}}
-#elif TEST == 10
-// expected-error-re at -6 {{'maybe_unused' attribute cannot be applied to a module{{$}}}}
-#elif TEST == 1
-// expected-error at -8 {{module 'z' not found}}
-#else
+//--- z_impl.cppm
+module z; // expected-error {{module 'z' not found}}
+
+//--- x_impl.cppm
+// expected-no-diagnostics
+module x;
+
+//--- z_interface.cppm
// expected-no-diagnostics
-#endif
+export module z;
+
+//--- x_interface.cppm
+// expected-no-diagnostics
+export module x;
+
+//--- invalid_module_name.cppm
+export module z elderberry; // expected-error {{expected ';'}} \
+ // expected-error {{a type specifier is required}}
+
+//--- empty_attribute.cppm
+// expected-no-diagnostics
+export module z [[]];
+
+//--- fancy_attribute.cppm
+export module z [[fancy]]; // expected-warning {{unknown attribute 'fancy' ignored}}
+
+//--- maybe_unused_attribute.cppm
+export module z [[maybe_unused]]; // expected-error-re {{'maybe_unused' attribute cannot be applied to a module{{$}}}}
diff --git a/clang/test/CXX/module/cpp.pre/module_decl.cpp b/clang/test/CXX/module/cpp.pre/module_decl.cpp
new file mode 100644
index 0000000000000..6238347c167ac
--- /dev/null
+++ b/clang/test/CXX/module/cpp.pre/module_decl.cpp
@@ -0,0 +1,8 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: %clang_cc1 -std=c++20 -emit-module-interface %s -verify -o %t/M.pcm
+
+// This is a comment
+#define I32 int // expected-note {{add 'module;' to the start of the file to introduce a global module fragment}}
+export module M; // expected-error {{module declaration must occur at the start of the translation unit}}
+export I32 i32;
diff --git a/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.import/p1.cppm b/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.import/p1.cppm
index 3670f9430ed4b..f65f050a3c7bd 100644
--- a/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.import/p1.cppm
+++ b/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.import/p1.cppm
@@ -6,10 +6,10 @@
// RUN: %clang_cc1 -std=c++20 -emit-module-interface -fmodule-file=x=%t/x.pcm %t/x.y.cppm -o %t/x.y.pcm
// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/a.b.cppm -o %t/a.b.pcm
//
-// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -fmodule-file=x=%t/x.pcm -verify %t/test.cpp \
-// RUN: -DMODULE_NAME=z -DINTERFACE
// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -fmodule-file=x=%t/x.pcm \
-// RUN: -fmodule-file=a.b=%t/a.b.pcm -verify %t/test.cpp -DMODULE_NAME=a.b
+// RUN: -verify %t/test.interface.cpp
+// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -fmodule-file=x=%t/x.pcm \
+// RUN: -fmodule-file=a.b=%t/a.b.pcm -verify %t/test.implementation.cpp
// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -fmodule-file=x=%t/x.pcm -verify %t/test.x.cpp
//--- x.cppm
@@ -33,19 +33,33 @@ int use_2 = b; // ok
// There is no relation between module x and module x.y.
int use_3 = c; // expected-error {{use of undeclared identifier 'c'}}
-//--- test.cpp
-#ifdef INTERFACE
-export module MODULE_NAME;
-#else
-module MODULE_NAME;
-#endif
+//--- test.interface.cpp
+export module z;
+
+import x;
+
+import x [[]];
+import x [[foo]]; // expected-warning {{unknown attribute 'foo' ignored}}
+import x [[noreturn]]; // expected-error {{'noreturn' attribute cannot be applied to a module import}}
+import x [[blarg::noreturn]]; // expected-warning-re {{unknown attribute 'blarg::noreturn' ignored{{.*}}}}
+
+import x.y;
+import x.; // expected-error {{expected a module name after 'import'}}
+import .x; // expected-error {{expected a module name after 'import'}}
+
+import blarg; // expected-error {{module 'blarg' not found}}
+
+int use_4 = c; // ok
+
+//--- test.implementation.cpp
+module a.b;
import x;
import x [[]];
import x [[foo]]; // expected-warning {{unknown attribute 'foo' ignored}}
import x [[noreturn]]; // expected-error {{'noreturn' attribute cannot be applied to a module import}}
-import x [[blarg::noreturn]]; // expected-warning {{unknown attribute 'blarg::noreturn' ignored}}
+import x [[blarg::noreturn]]; // expected-warning-re {{unknown attribute 'blarg::noreturn' ignored{{.*}}}}
import x.y;
import x.; // expected-error {{expected a module name after 'import'}}
diff --git a/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.interface/p1.cppm b/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.interface/p1.cppm
index 84ef85126c369..2158d7fa84b86 100644
--- a/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.interface/p1.cppm
+++ b/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.interface/p1.cppm
@@ -1,29 +1,26 @@
-// RUN: %clang_cc1 -std=c++20 %s -verify -emit-module-interface -o /dev/null
-// RUN: %clang_cc1 -std=c++20 %s -DINTERFACE -verify -emit-module-interface -o %t
-// RUN: %clang_cc1 -std=c++20 %s -DIMPLEMENTATION -verify -fmodule-file=A=%t -o /dev/null
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+
+// RUN: %clang_cc1 -std=c++20 %t/ExportDeclNotInModulePurview.cppm -verify -emit-module-interface -o /dev/null
+// RUN: %clang_cc1 -std=c++20 %t/A.cppm -verify -emit-module-interface -o %t/A.pcm
+// RUN: %clang_cc1 -std=c++20 %t/AddExport.cppm -verify -fmodule-file=A=%t/A.pcm -o /dev/null
//
-// RUN: %clang_cc1 -std=c++20 %s -DBUILT_AS_INTERFACE -emit-module-interface -verify -o /dev/null
-// RUN: %clang_cc1 -std=c++20 %s -DINTERFACE -DBUILT_AS_INTERFACE -emit-module-interface -verify -o /dev/null
-// RUN: %clang_cc1 -std=c++20 %s -DIMPLEMENTATION -DBUILT_AS_INTERFACE -emit-module-interface -verify -o /dev/null
+// RUN: %clang_cc1 -std=c++20 %t/AddExport2.cppm -emit-module-interface -verify -o /dev/null
-#if INTERFACE
+//--- ExportDeclNotInModulePurview.cppm
+// expected-error@* {{missing 'export module' declaration in module interface unit}}
+export int b; // expected-error {{export declaration can only be used within a module purview}}
+
+//--- A.cppm
// expected-no-diagnostics
export module A;
-#elif IMPLEMENTATION
-module A; // #module-decl
- #ifdef BUILT_AS_INTERFACE
- // expected-error at -2 {{missing 'export' specifier in module declaration while building module interface}}
- #define INTERFACE
- #endif
-#else // Not in a module
-// expected-error@* {{missing 'export module' declaration in module interface unit}}
-#endif
+export int a;
-#ifndef INTERFACE
+//--- AddExport.cppm
+module A; // #module-decl
export int b; // expected-error {{export declaration can only be used within a module purview}}
-#ifdef IMPLEMENTATION
// expected-note@#module-decl {{add 'export' here}}
-#endif
-#else
+
+//--- AddExport2.cppm
+module A; // expected-error {{missing 'export' specifier in module declaration while building module interface}}
export int a;
-#endif
diff --git a/clang/test/CXX/module/dcl.dcl/dcl.module/p1.cpp b/clang/test/CXX/module/dcl.dcl/dcl.module/p1.cpp
index db86b5dd34c38..95d087e0f6c78 100644
--- a/clang/test/CXX/module/dcl.dcl/dcl.module/p1.cpp
+++ b/clang/test/CXX/module/dcl.dcl/dcl.module/p1.cpp
@@ -1,14 +1,30 @@
-// RUN: %clang_cc1 -std=c++20 -verify %s -DFOO=export -DBAR=export
-// RUN: %clang_cc1 -std=c++20 -verify %s -DFOO=export -DBAR=
-// RUN: %clang_cc1 -std=c++20 %s -DFOO=export -emit-module-interface -o %t
-// RUN: %clang_cc1 -std=c++20 %s -fmodule-file=foo=%t -DFOO=
-// RUN: %clang_cc1 -std=c++20 %s -fmodule-file=foo=%t -DBAR=export
-// RUN: %clang_cc1 -std=c++20 -verify %s -fmodule-file=foo=%t -DFOO= -DBAR=export
-
-#ifdef FOO
-FOO module foo; // expected-note {{previous module declaration is here}}
-#endif
-
-#ifdef BAR
-BAR module bar; // expected-error {{translation unit contains multiple module declarations}}
-#endif
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+
+// RUN: %clang_cc1 -std=c++20 -verify %t/A.cppm
+// RUN: %clang_cc1 -std=c++20 -verify %t/B.cppm
+// RUN: %clang_cc1 -std=c++20 %t/C.cppm -emit-module-interface -o %t/C.pcm
+// RUN: %clang_cc1 -std=c++20 %t/D.cppm -fmodule-file=foo=%t/C.pcm
+// RUN: %clang_cc1 -std=c++20 %t/E.cppm -fmodule-file=foo=%t/C.pcm
+// RUN: %clang_cc1 -std=c++20 -verify %t/F.cppm -fmodule-file=foo=%t/C.pcm
+
+//--- A.cppm
+export module foo; // expected-note {{previous module declaration is here}}
+export module bar; // expected-error {{translation unit contains multiple module declarations}}
+
+//--- B.cppm
+export module foo; // expected-note {{previous module declaration is here}}
+module bar; // expected-error {{translation unit contains multiple module declarations}}
+
+//--- C.cppm
+export module foo;
+
+//--- D.cppm
+module foo;
+
+//--- E.cppm
+export module bar;
+
+//--- F.cppm
+module foo; // expected-note {{previous module declaration is here}}
+export module bar; // expected-error {{translation unit contains multiple module declarations}}
diff --git a/clang/test/CXX/module/dcl.dcl/dcl.module/p5.cpp b/clang/test/CXX/module/dcl.dcl/dcl.module/p5.cpp
index ca100443a4c67..a0d30233809f9 100644
--- a/clang/test/CXX/module/dcl.dcl/dcl.module/p5.cpp
+++ b/clang/test/CXX/module/dcl.dcl/dcl.module/p5.cpp
@@ -1,22 +1,47 @@
// RUN: rm -rf %t
-// RUN: %clang_cc1 -std=c++20 -emit-module-interface %s -o %t -DINTERFACE
-// RUN: %clang_cc1 -std=c++20 -fmodule-file=Foo=%t %s -verify -DIMPLEMENTATION
-// RUN: %clang_cc1 -std=c++20 -fmodule-file=Foo=%t %s -verify -DEARLY_IMPLEMENTATION
-// RUN: %clang_cc1 -std=c++20 -fmodule-file=Foo=%t %s -verify -DUSER
+// RUN: split-file %s %t
+// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/interface.cppm -o %t/interface.pcm
+// RUN: %clang_cc1 -std=c++20 -fmodule-file=Foo=%t/interface.pcm %t/implementation.cppm -verify -DIMPLEMENTATION
+// RUN: %clang_cc1 -std=c++20 -fmodule-file=Foo=%t/interface.pcm %t/early_impl.cppm -verify -DEARLY_IMPLEMENTATION
+// RUN: %clang_cc1 -std=c++20 -fmodule-file=Foo=%t/interface.pcm %t/user.cppm -verify -DUSER
+
+//--- interface.cppm
// expected-no-diagnostics
+module;
-#if defined(INTERFACE) || defined(EARLY_IMPLEMENTATION) || defined(IMPLEMENTATION)
+template<typename T> struct type_template {
+ typedef T type;
+ void f(type);
+};
+
+template<typename T> void type_template<T>::f(type) {}
+
+template<int = 0, typename = int, template<typename> class = type_template>
+struct default_template_args {};
+
+export module Foo;
+
+//--- implementation.cppm
+// expected-no-diagnostics
module;
-#endif
-#ifdef USER
-import Foo;
-#endif
+template<typename T> struct type_template {
+ typedef T type;
+ void f(type);
+};
+
+template<typename T> void type_template<T>::f(type) {}
+
+template<int = 0, typename = int, template<typename> class = type_template>
+struct default_template_args {};
-#ifdef EARLY_IMPLEMENTATION
module Foo;
-#endif
+
+//--- early_impl.cppm
+// expected-no-diagnostics
+module;
+module Foo;
template<typename T> struct type_template {
typedef T type;
@@ -28,10 +53,16 @@ template<typename T> void type_template<T>::f(type) {}
template<int = 0, typename = int, template<typename> class = type_template>
struct default_template_args {};
-#ifdef INTERFACE
-export module Foo;
-#endif
+//--- user.cppm
+// expected-no-diagnostics
+import Foo;
-#ifdef IMPLEMENTATION
-module Foo;
-#endif
+template<typename T> struct type_template {
+ typedef T type;
+ void f(type);
+};
+
+template<typename T> void type_template<T>::f(type) {}
+
+template<int = 0, typename = int, template<typename> class = type_template>
+struct default_template_args {};
diff --git a/clang/test/CXX/module/module.interface/p2.cpp b/clang/test/CXX/module/module.interface/p2.cpp
index 4f06b9f386869..8221c400ecd62 100644
--- a/clang/test/CXX/module/module.interface/p2.cpp
+++ b/clang/test/CXX/module/module.interface/p2.cpp
@@ -1,24 +1,26 @@
// RUN: rm -rf %t
// RUN: mkdir -p %t
+// RUN: split-file %s %t
+//
// RUN: %clang_cc1 -std=c++20 -x c++-header %S/Inputs/header.h -emit-header-unit -o %t/h.pcm
-// RUN: %clang_cc1 -std=c++20 %s -DX_INTERFACE -emit-module-interface -o %t/x.pcm
-// RUN: %clang_cc1 -std=c++20 %s -DY_INTERFACE -emit-module-interface -o %t/y.pcm
-// RUN: %clang_cc1 -std=c++20 %s -DINTERFACE -fmodule-file=X=%t/x.pcm -fmodule-file=Y=%t/y.pcm -emit-module-interface -o %t/m.pcm
-// RUN: %clang_cc1 -std=c++20 %s -DIMPLEMENTATION -I%S/Inputs -fmodule-file=%t/h.pcm \
+// RUN: %clang_cc1 -std=c++20 %t/x.cppm -emit-module-interface -o %t/x.pcm
+// RUN: %clang_cc1 -std=c++20 %t/y.cppm -emit-module-interface -o %t/y.pcm
+// RUN: %clang_cc1 -std=c++20 %t/interface.cppm -fmodule-file=X=%t/x.pcm -fmodule-file=Y=%t/y.pcm -emit-module-interface -o %t/m.pcm
+// RUN: %clang_cc1 -std=c++20 %t/impl.cppm -I%S/Inputs -fmodule-file=%t/h.pcm \
// RUN: -fmodule-file=X=%t/x.pcm -fmodule-file=Y=%t/y.pcm -fmodule-file=p2=%t/m.pcm -verify \
// RUN: -Wno-experimental-header-units
-// RUN: %clang_cc1 -std=c++20 %s -DUSER -I%S/Inputs -fmodule-file=%t/h.pcm -fmodule-file=p2=%t/m.pcm \
+// RUN: %clang_cc1 -std=c++20 %t/user.cppm -I%S/Inputs -fmodule-file=%t/h.pcm -fmodule-file=p2=%t/m.pcm \
// RUN: -fmodule-file=X=%t/x.pcm -fmodule-file=Y=%t/y.pcm -Wno-experimental-header-units -verify
-#if defined(X_INTERFACE)
+//--- x.cppm
export module X;
export int x;
-#elif defined(Y_INTERFACE)
+//--- y.cppm
export module Y;
export int y;
-#elif defined(INTERFACE)
+//--- interface.cppm
export module p2;
export import X;
import Y; // not exported
@@ -39,7 +41,7 @@ namespace C {}
namespace D { int f(); }
export namespace D {}
-#elif defined(IMPLEMENTATION)
+//--- impl.cppm
module p2;
import "header.h";
@@ -66,7 +68,7 @@ void use() {
int use_header() { return foo + bar::baz(); }
-#elif defined(USER)
+//--- user.cppm
import p2;
import "header.h";
@@ -96,7 +98,3 @@ void use() {
}
int use_header() { return foo + bar::baz(); }
-
-#else
-#error unknown mode
-#endif
diff --git a/clang/test/CXX/module/module.unit/p8.cpp b/clang/test/CXX/module/module.unit/p8.cpp
index a5c01c493558e..fb190257d3a23 100644
--- a/clang/test/CXX/module/module.unit/p8.cpp
+++ b/clang/test/CXX/module/module.unit/p8.cpp
@@ -1,37 +1,45 @@
-// RUN: echo 'export module foo; export int n;' > %t.cppm
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: split-file %s %t
+// RUN: echo 'export module foo;' > %t.cppm
+// RUN: echo 'export int n;' >> %t.cppm
// RUN: %clang_cc1 -std=c++2a %t.cppm -emit-module-interface -o %t.pcm
-// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=0 %s
-// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=1 %s
-// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=2 %s
-// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=3 %s
-// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=4 %s
-// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=5 %s
-
-#if MODE == 0
+// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=0 %t/A.cppm
+// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=1 %t/B.cppm
+// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=2 %t/C.cppm
+// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=3 %t/D.cppm
+// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=4 %t/E.cppm
+// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=5 %t/F.cppm
+
+//--- A.cppm
// no module declaration
+// expected-no-diagnostics
-#elif MODE == 1
+//--- B.cppm
// expected-no-diagnostics
module foo; // Implementation, implicitly imports foo.
#define IMPORTED
-#elif MODE == 2
+int k = n;
+
+//--- C.cppm
export module foo;
-#elif MODE == 3
+int k = n; // expected-error {{use of undeclared identifier 'n'}}
+
+//--- D.cppm
export module bar; // A different module
-#elif MODE == 4
+int k = n; // expected-error {{use of undeclared identifier 'n'}}
+
+//--- E.cppm
module foo:bar; // Partition implementation
//#define IMPORTED (we don't import foo here)
-#elif MODE == 5
+int k = n; // expected-error {{use of undeclared identifier 'n'}}
+
+//--- F.cppm
export module foo:bar; // Partition interface
//#define IMPORTED (we don't import foo here)
-#endif
-
-int k = n;
-#ifndef IMPORTED
-// expected-error at -2 {{use of undeclared identifier 'n'}}
-#endif
+int k = n; // expected-error {{use of undeclared identifier 'n'}}
diff --git a/clang/test/Driver/modules.cpp b/clang/test/Driver/modules.cpp
index b0d1f2280d254..088a73230f81e 100644
--- a/clang/test/Driver/modules.cpp
+++ b/clang/test/Driver/modules.cpp
@@ -1,43 +1,48 @@
// RUN: rm -rf %t
// RUN: mkdir %t
+// RUN: split-file %s %t
// Check compiling a module interface to a .pcm file.
//
-// RUN: %clang -std=c++2a -x c++-module --precompile %s -o %t/module.pcm -v 2>&1 | FileCheck %s --check-prefix=CHECK-PRECOMPILE
-// RUN: %clang -std=gnu++2a -x c++-module --precompile %s -o %t/module-gnu.pcm -v 2>&1 | FileCheck %s --check-prefix=CHECK-PRECOMPILE
+// RUN: %clang -std=c++2a -x c++-module --precompile %t/foo.cpp -o %t/foo.pcm -v 2>&1 | FileCheck %s --check-prefix=CHECK-PRECOMPILE
+// RUN: %clang -std=gnu++2a -x c++-module --precompile %t/foo.cpp -o %t/foo-gnu.pcm -v 2>&1 | FileCheck %s --check-prefix=CHECK-PRECOMPILE
//
// CHECK-PRECOMPILE: -cc1 {{.*}} -emit-module-interface
// CHECK-PRECOMPILE-SAME: -o {{.*}}.pcm
// CHECK-PRECOMPILE-SAME: -x c++
-// CHECK-PRECOMPILE-SAME: modules.cpp
+// CHECK-PRECOMPILE-SAME: foo.cpp
// Check compiling a .pcm file to a .o file.
//
-// RUN: %clang -std=c++2a %t/module.pcm -S -o %t/module.pcm.o -v 2>&1 | FileCheck %s --check-prefix=CHECK-COMPILE
+// RUN: %clang -std=c++2a %t/foo.pcm -S -o %t/foo.pcm.o -v 2>&1 | FileCheck %s --check-prefix=CHECK-COMPILE
//
// CHECK-COMPILE: -cc1 {{.*}} {{-emit-obj|-S}}
-// CHECK-COMPILE-SAME: -o {{.*}}module{{2*}}.pcm.o
+// CHECK-COMPILE-SAME: -o {{.*}}foo{{2*}}.pcm.o
// CHECK-COMPILE-SAME: -x pcm
// CHECK-COMPILE-SAME: {{.*}}.pcm
// Check use of a .pcm file in another compilation.
//
-// RUN: %clang -std=c++2a -fmodule-file=%t/module.pcm -Dexport= %s -S -o %t/module.o -v 2>&1 | FileCheck %s --check-prefix=CHECK-USE
-// RUN: %clang -std=c++20 -fmodule-file=%t/module.pcm -Dexport= %s -S -o %t/module.o -v 2>&1 | FileCheck %s --check-prefix=CHECK-USE
-// RUN: %clang -std=gnu++20 -fmodule-file=%t/module-gnu.pcm -Dexport= %s -S -o %t/module.o -v 2>&1 | FileCheck %s --check-prefix=CHECK-USE
+// RUN: %clang -std=c++2a -fmodule-file=foo=%t/foo.pcm %t/foo_impl.cpp -S -o %t/module.o -v 2>&1 | FileCheck %s --check-prefix=CHECK-USE
+// RUN: %clang -std=c++20 -fmodule-file=foo=%t/foo.pcm %t/foo_impl.cpp -S -o %t/module.o -v 2>&1 | FileCheck %s --check-prefix=CHECK-USE
+// RUN: %clang -std=gnu++20 -fmodule-file=foo=%t/foo-gnu.pcm %t/foo_impl.cpp -S -o %t/module.o -v 2>&1 | FileCheck %s --check-prefix=CHECK-USE
//
// CHECK-USE: -cc1 {{.*}} {{-emit-obj|-S}}
-// CHECK-USE-SAME: -fmodule-file={{.*}}.pcm
+// CHECK-USE-SAME: -fmodule-file=foo={{.*}}.pcm
// CHECK-USE-SAME: -o {{.*}}.{{o|s}}{{"?}} {{.*}}-x c++
-// CHECK-USE-SAME: modules.cpp
+// CHECK-USE-SAME: foo_impl.cpp
// Check combining precompile and compile steps works.
//
-// RUN: %clang -std=c++2a -x c++-module %s -S -o %t/module2.pcm.o -v 2>&1 | FileCheck %s --check-prefix=CHECK-PRECOMPILE --check-prefix=CHECK-COMPILE
+// RUN: %clang -std=c++2a -x c++-module %t/foo.cpp -S -o %t/foo2.pcm.o -v 2>&1 | FileCheck %s --check-prefix=CHECK-PRECOMPILE --check-prefix=CHECK-COMPILE
// Check that .cppm is treated as a module implicitly.
//
-// RUN: cp %s %t/module.cppm
-// RUN: %clang -std=c++2a --precompile %t/module.cppm -o %t/module.pcm -v 2>&1 | FileCheck %s --check-prefix=CHECK-PRECOMPILE
+// RUN: cp %t/foo.cpp %t/foo.cppm
+// RUN: %clang -std=c++2a --precompile %t/foo.cppm -o %t/foo.pcm -v 2>&1 | FileCheck %s --check-prefix=CHECK-PRECOMPILE
+//--- foo.cpp
export module foo;
+
+//--- foo_impl.cpp
+module foo;
diff --git a/clang/test/Modules/named-modules-adl-3.cppm b/clang/test/Modules/named-modules-adl-3.cppm
index d70946fa068b3..a3644b45a5347 100644
--- a/clang/test/Modules/named-modules-adl-3.cppm
+++ b/clang/test/Modules/named-modules-adl-3.cppm
@@ -58,6 +58,7 @@ void b(T x) {
}
//--- c.cppm
+module;
#ifdef EXPORT_OPERATOR
// expected-no-diagnostics
#endif
diff --git a/clang/test/Modules/reserved-names-1.cppm b/clang/test/Modules/reserved-names-1.cppm
index e780f1e35b3b7..35b264bcb573b 100644
--- a/clang/test/Modules/reserved-names-1.cppm
+++ b/clang/test/Modules/reserved-names-1.cppm
@@ -88,12 +88,14 @@ export module module; // expected-error {{'module' is an invalid name for a modu
export module import; // expected-error {{'import' is an invalid name for a module}}
//--- _Test.cppm
+module;
#ifdef NODIAGNOSTICS
// expected-no-diagnostics
#endif
export module _Test; // loud-warning {{'_Test' is a reserved name for a module}}
//--- __test.cppm
+module;
#ifdef NODIAGNOSTICS
// expected-no-diagnostics
#endif
@@ -101,6 +103,7 @@ export module __test; // loud-warning {{'__test' is a reserved name for a module
export int a = 43;
//--- te__st.cppm
+module;
#ifdef NODIAGNOSTICS
// expected-no-diagnostics
#endif
@@ -108,6 +111,7 @@ export module te__st; // loud-warning {{'te__st' is a reserved name for a module
export int a = 43;
//--- std.cppm
+module;
#ifdef NODIAGNOSTICS
// expected-no-diagnostics
#endif
@@ -116,36 +120,42 @@ export module std; // loud-warning {{'std' is a reserved name for a module}}
export int a = 43;
//--- std.foo.cppm
+module;
#ifdef NODIAGNOSTICS
// expected-no-diagnostics
#endif
export module std.foo;// loud-warning {{'std' is a reserved name for a module}}
//--- std0.cppm
+module;
#ifdef NODIAGNOSTICS
// expected-no-diagnostics
#endif
export module std0; // loud-warning {{'std0' is a reserved name for a module}}
//--- std1000000.cppm
+module;
#ifdef NODIAGNOSTICS
// expected-no-diagnostics
#endif
export module std1000000; // loud-warning {{'std1000000' is a reserved name for a module}}
//--- should_diag._Test.cppm
+module;
#ifdef NODIAGNOSTICS
// expected-no-diagnostics
#endif
export module should_diag._Test; // loud-warning {{'_Test' is a reserved name for a module}}
//--- system-module.cppm
+module; // expected-error {{missing 'module' declaration at end of global module fragment introduced here}}
// Show that being in a system header doesn't save you from diagnostics about
// use of an invalid module-name identifier.
# 34 "reserved-names-1.cpp" 1 3
export module module; // expected-error {{'module' is an invalid name for a module}}
//--- system._Test.import.cppm
+module; // expected-error {{missing 'module' declaration at end of global module fragment introduced here}}
# 34 "reserved-names-1.cpp" 1 3
export module _Test.import; // expected-error {{'import' is an invalid name for a module}}
diff --git a/clang/test/Modules/reserved-names-system-header-1.cpp b/clang/test/Modules/reserved-names-system-header-1.cpp
index 2db4c08add1d9..fa869483980f6 100644
--- a/clang/test/Modules/reserved-names-system-header-1.cpp
+++ b/clang/test/Modules/reserved-names-system-header-1.cpp
@@ -1,6 +1,7 @@
// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
// expected-no-diagnostics
+module;
// Show that we suppress the reserved identifier diagnostic in a system header.
# 100 "file.cpp" 1 3 // Enter a system header
export module std;
diff --git a/clang/test/Modules/reserved-names-system-header-2.cpp b/clang/test/Modules/reserved-names-system-header-2.cpp
index 2087f487721cb..d429e58dc1714 100644
--- a/clang/test/Modules/reserved-names-system-header-2.cpp
+++ b/clang/test/Modules/reserved-names-system-header-2.cpp
@@ -1,6 +1,7 @@
// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
// expected-no-diagnostics
+module;
// Show that we suppress the reserved identifier diagnostic in a system header.
# 100 "file.cpp" 1 3 // Enter a system header
export module __test;
diff --git a/clang/test/SemaCXX/modules.cppm b/clang/test/SemaCXX/modules.cppm
index 41204be76eafa..5d0d6da44a2ed 100644
--- a/clang/test/SemaCXX/modules.cppm
+++ b/clang/test/SemaCXX/modules.cppm
@@ -1,19 +1,20 @@
-// RUN: %clang_cc1 -std=c++20 -emit-module-interface %s -o %t.0.pcm -verify -DTEST=0
-// RUN: %clang_cc1 -std=c++20 -emit-module-interface %s -o %t.1.pcm -verify -DTEST=1
-// RUN: %clang_cc1 -std=c++20 -emit-module-interface %s -fmodule-file=foo=%t.0.pcm -o %t.2.pcm -verify -DTEST=2
-// RUN: %clang_cc1 -std=c++20 -emit-module-interface %s -fmodule-file=foo=%t.0.pcm -o %t.3.pcm -verify -Dfoo=bar -DTEST=3
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
-#if TEST == 0 || TEST == 2
-// expected-no-diagnostics
-#endif
+// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/test0.cpp -o %t/test0.pcm -verify
+// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/test1.cpp -o %t/test1.pcm -verify
+// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/test2.cpp -fmodule-file=foo=%t/test0.pcm -o %t/test2.pcm -verify
+// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/test3.cpp -fmodule-file=foo=%t/test0.pcm -o %t/test3.pcm -verify
+//--- test0.cpp
+// expected-no-diagnostics
export module foo;
static int m;
int n;
-#if TEST == 0
export {
int a;
int b;
@@ -27,33 +28,18 @@ export void f() {}
export struct T {
} t;
-#elif TEST == 3
-int use_a = a; // expected-error {{use of undeclared identifier 'a'}}
-#undef foo
-import foo; // expected-error {{imports must immediately follow the module declaration}}
-
-export {}
-export {
- ; // No diagnostic after P2615R1 DR
-}
-export {
- static_assert(true); // No diagnostic after P2615R1 DR
-}
+//--- test1.cpp
+export module foo;
-int use_b = b; // expected-error{{use of undeclared identifier 'b'}}
-int use_n = n; // FIXME: this should not be visible, because it is not exported
+static int m;
-extern int n;
-static_assert(&n != p); // expected-error{{use of undeclared identifier 'p'}}
-#endif
+int n;
-#if TEST == 1
struct S {
export int n; // expected-error {{expected member name or ';'}}
export static int n; // expected-error {{expected member name or ';'}}
};
-#endif
// FIXME: Exports of declarations without external linkage are disallowed.
// Exports of declarations with non-external-linkage types are disallowed.
@@ -61,7 +47,6 @@ struct S {
// Cannot export within another export. This isn't precisely covered by the
// language rules right now, but (per personal correspondence between zygoloid
// and gdr) is the intent.
-#if TEST == 1
export { // expected-note {{export block begins here}}
extern "C++" {
namespace NestedExport {
@@ -71,4 +56,36 @@ export { // expected-note {{export block begins here}}
} // namespace NestedExport
}
}
-#endif
+
+//--- test2.cpp
+// expected-no-diagnostics
+export module foo;
+
+static int m;
+
+int n;
+
+//--- test3.cpp
+export module bar;
+
+static int m;
+
+int n;
+
+int use_a = a; // expected-error {{use of undeclared identifier 'a'}}
+
+import foo; // expected-error {{imports must immediately follow the module declaration}}
+
+export {}
+export {
+ ; // No diagnostic after P2615R1 DR
+}
+export {
+ static_assert(true); // No diagnostic after P2615R1 DR
+}
+
+int use_b = b; // expected-error{{use of undeclared identifier 'b'}}
+int use_n = n; // FIXME: this should not be visible, because it is not exported
+
+extern int n;
+static_assert(&n != p); // expected-error{{use of undeclared identifier 'p'}}
diff --git a/clang/test/SemaCXX/type-aware-new-delete-transparent-contexts.cpp b/clang/test/SemaCXX/type-aware-new-delete-transparent-contexts.cpp
index 7c0b967a3c03f..30fea464a8dc5 100644
--- a/clang/test/SemaCXX/type-aware-new-delete-transparent-contexts.cpp
+++ b/clang/test/SemaCXX/type-aware-new-delete-transparent-contexts.cpp
@@ -1,12 +1,22 @@
-// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26 -fexceptions -DTRANSPARENT_DECL=0
-// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26 -fexceptions -DTRANSPARENT_DECL=1
-// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26 -fexceptions -DTRANSPARENT_DECL=2
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: split-file %s %t
+// RUN: %clang_cc1 -fsyntax-only -verify %t/testing.cpp -std=c++26 -fexceptions -DTRANSPARENT_DECL=0
+// RUN: %clang_cc1 -fsyntax-only -verify %t/testing.cpp -std=c++26 -fexceptions -DTRANSPARENT_DECL=1
+// RUN: %clang_cc1 -fsyntax-only -verify %t/module_testing.cppm -std=c++26 -fexceptions -DTRANSPARENT_DECL=2
+
+//--- module_testing.cppm
// expected-no-diagnostics
-#if TRANSPARENT_DECL==2
export module Testing;
-#endif
+#include "testing.inc"
+
+//--- testing.cpp
+// expected-no-diagnostics
+#include "testing.inc"
+
+//--- testing.inc
namespace std {
template <class T> struct type_identity {};
using size_t = __SIZE_TYPE__;
diff --git a/clang/unittests/Lex/LexerTest.cpp b/clang/unittests/Lex/LexerTest.cpp
index 381755d4d1b6f..33c8abbec35a3 100644
--- a/clang/unittests/Lex/LexerTest.cpp
+++ b/clang/unittests/Lex/LexerTest.cpp
@@ -49,7 +49,8 @@ class LexerTest : public ::testing::Test {
}
std::unique_ptr<Preprocessor> CreatePP(StringRef Source,
- TrivialModuleLoader &ModLoader) {
+ TrivialModuleLoader &ModLoader,
+ StringRef PreDefines = {}) {
std::unique_ptr<llvm::MemoryBuffer> Buf =
llvm::MemoryBuffer::getMemBuffer(Source);
SourceMgr.setMainFileID(SourceMgr.createFileID(std::move(Buf)));
@@ -61,6 +62,8 @@ class LexerTest : public ::testing::Test {
PPOpts, Diags, LangOpts, SourceMgr, HeaderInfo, ModLoader,
/*IILookup =*/nullptr,
/*OwnsHeaderSearch =*/false);
+ if (!PreDefines.empty())
+ PP->setPredefines(PreDefines.str());
PP->Initialize(*Target);
PP->EnterMainSourceFile();
return PP;
@@ -769,4 +772,46 @@ TEST(LexerPreambleTest, PreambleBounds) {
}
}
+TEST_F(LexerTest, CheckFirstPPToken) {
+ {
+ TrivialModuleLoader ModLoader;
+ auto PP = CreatePP("// This is a comment\n"
+ "int a;",
+ ModLoader);
+ Token Tok;
+ PP->Lex(Tok);
+ EXPECT_TRUE(Tok.is(tok::kw_int));
+ EXPECT_TRUE(PP->hasSeenMainFileFirstPPToken());
+ EXPECT_TRUE(PP->getMainFileFirstPPToken().isFirstPPToken());
+ EXPECT_TRUE(PP->getMainFileFirstPPToken().is(tok::kw_int));
+ }
+ {
+ TrivialModuleLoader ModLoader;
+ auto PP = CreatePP("// This is a comment\n"
+ "#define FOO int\n"
+ "FOO a;",
+ ModLoader);
+ Token Tok;
+ PP->Lex(Tok);
+ EXPECT_TRUE(Tok.is(tok::kw_int));
+ EXPECT_TRUE(PP->hasSeenMainFileFirstPPToken());
+ EXPECT_TRUE(PP->getMainFileFirstPPToken().isFirstPPToken());
+ EXPECT_TRUE(PP->getMainFileFirstPPToken().is(tok::hash));
+ }
+
+ {
+ TrivialModuleLoader ModLoader;
+ auto PP = CreatePP("// This is a comment\n"
+ "FOO a;",
+ ModLoader, "#define FOO int\n");
+ Token Tok;
+ PP->Lex(Tok);
+ EXPECT_TRUE(Tok.is(tok::kw_int));
+ EXPECT_TRUE(PP->hasSeenMainFileFirstPPToken());
+ EXPECT_TRUE(PP->getMainFileFirstPPToken().isFirstPPToken());
+ EXPECT_TRUE(PP->getMainFileFirstPPToken().is(tok::identifier));
+ EXPECT_TRUE(
+ PP->getMainFileFirstPPToken().getIdentifierInfo()->isStr("FOO"));
+ }
+}
} // anonymous namespace
>From 77941eba7f01fc6576b3e060a3fb9cad1a64f9ea Mon Sep 17 00:00:00 2001
From: David Green <david.green at arm.com>
Date: Sat, 21 Jun 2025 12:29:29 +0100
Subject: [PATCH 05/40] [CostModel] Add a DstTy to getShuffleCost (#141634)
A shuffle will take two input vectors and a mask, to produce a new
vector of size <MaskElts x SrcEltTy>. Historically it has been assumed
that the SrcTy and the DstTy are the same for getShuffleCost, with that
being relaxed in recent years. If the Tp passed to getShuffleCost is the
SrcTy, then the DstTy can be calculated from the Mask elts and the src
elt size, but the Mask is not always provided and the Tp is not reliably
always the SrcTy. This has led to situations notably in the SLP
vectorizer but also in the generic cost routines where assumption about
how vectors will be legalized are built into the generic cost routines -
for example whether they will widen or promote, with the cost modelling
assuming they will widen but the default lowering to promote for integer
vectors.
This patch attempts to start improving that - it originally tried to
alter more of the cost model but that too quickly became too many
changes at once, so this patch just plumbs in a DstTy to getShuffleCost
so that DstTy and SrcTy can be reliably distinguished. The callers of
getShuffleCost have been updated to try and include a DstTy that is more
accurate. Otherwise it tries to be fairly non-functional, keeping the
SrcTy used as the primary type used in shuffle cost routines, only using
DstTy where it was in the past (for InsertSubVector for example).
Some asserts have been added that help to check for consistent values
when a Mask and a DstTy are provided to getShuffleCost. Some of them
took a while to get right, and some non-mask calls might still be
incorrect. Hopefully this will provide a useful base to build more
shuffles that alter size.
---
.../llvm/Analysis/TargetTransformInfo.h | 18 +-
.../llvm/Analysis/TargetTransformInfoImpl.h | 60 ++++---
llvm/include/llvm/CodeGen/BasicTTIImpl.h | 60 +++----
llvm/lib/Analysis/TargetTransformInfo.cpp | 11 +-
.../AArch64/AArch64TargetTransformInfo.cpp | 59 ++++---
.../AArch64/AArch64TargetTransformInfo.h | 6 +-
.../AMDGPU/AMDGPUTargetTransformInfo.cpp | 17 +-
.../Target/AMDGPU/AMDGPUTargetTransformInfo.h | 6 +-
.../lib/Target/ARM/ARMTargetTransformInfo.cpp | 27 +--
llvm/lib/Target/ARM/ARMTargetTransformInfo.h | 6 +-
.../Hexagon/HexagonTargetTransformInfo.cpp | 10 +-
.../Hexagon/HexagonTargetTransformInfo.h | 6 +-
.../Target/PowerPC/PPCTargetTransformInfo.cpp | 7 +-
.../Target/PowerPC/PPCTargetTransformInfo.h | 6 +-
.../Target/RISCV/RISCVTargetTransformInfo.cpp | 80 +++++----
.../Target/RISCV/RISCVTargetTransformInfo.h | 6 +-
.../SystemZ/SystemZTargetTransformInfo.cpp | 19 ++-
.../SystemZ/SystemZTargetTransformInfo.h | 6 +-
.../lib/Target/X86/X86TargetTransformInfo.cpp | 151 +++++++++--------
llvm/lib/Target/X86/X86TargetTransformInfo.h | 6 +-
.../Scalar/LowerMatrixIntrinsics.cpp | 12 +-
.../Transforms/Vectorize/LoopVectorize.cpp | 13 +-
.../Transforms/Vectorize/SLPVectorizer.cpp | 23 ++-
.../lib/Transforms/Vectorize/VPlanRecipes.cpp | 22 +--
.../Transforms/Vectorize/VectorCombine.cpp | 155 +++++++++---------
25 files changed, 444 insertions(+), 348 deletions(-)
diff --git a/llvm/include/llvm/Analysis/TargetTransformInfo.h b/llvm/include/llvm/Analysis/TargetTransformInfo.h
index ba47cef274bec..90d92e0fcf55c 100644
--- a/llvm/include/llvm/Analysis/TargetTransformInfo.h
+++ b/llvm/include/llvm/Analysis/TargetTransformInfo.h
@@ -1381,16 +1381,16 @@ class TargetTransformInfo {
const SmallBitVector &OpcodeMask,
TTI::TargetCostKind CostKind = TTI::TCK_RecipThroughput) const;
- /// \return The cost of a shuffle instruction of kind Kind and of type Tp.
- /// The exact mask may be passed as Mask, or else the array will be empty.
- /// The index and subtype parameters are used by the subvector insertion and
- /// extraction shuffle kinds to show the insert/extract point and the type of
- /// the subvector being inserted/extracted. The operands of the shuffle can be
- /// passed through \p Args, which helps improve the cost estimation in some
- /// cases, like in broadcast loads.
- /// NOTE: For subvector extractions Tp represents the source type.
+ /// \return The cost of a shuffle instruction of kind Kind with inputs of type
+ /// SrcTy, producing a vector of type DstTy. The exact mask may be passed as
+ /// Mask, or else the array will be empty. The Index and SubTp parameters
+ /// are used by the subvector insertions shuffle kinds to show the insert
+ /// point and the type of the subvector being inserted. The operands of the
+ /// shuffle can be passed through \p Args, which helps improve the cost
+ /// estimation in some cases, like in broadcast loads.
LLVM_ABI InstructionCost getShuffleCost(
- ShuffleKind Kind, VectorType *Tp, ArrayRef<int> Mask = {},
+ ShuffleKind Kind, VectorType *DstTy, VectorType *SrcTy,
+ ArrayRef<int> Mask = {},
TTI::TargetCostKind CostKind = TTI::TCK_RecipThroughput, int Index = 0,
VectorType *SubTp = nullptr, ArrayRef<const Value *> Args = {},
const Instruction *CxtI = nullptr) const;
diff --git a/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h b/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
index 640766cf8cd10..c22928c9bcd94 100644
--- a/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
+++ b/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
@@ -710,9 +710,9 @@ class TargetTransformInfoImplBase {
}
virtual InstructionCost
- getShuffleCost(TTI::ShuffleKind Kind, VectorType *Ty, ArrayRef<int> Mask,
- TTI::TargetCostKind CostKind, int Index, VectorType *SubTp,
- ArrayRef<const Value *> Args = {},
+ getShuffleCost(TTI::ShuffleKind Kind, VectorType *DstTy, VectorType *SrcTy,
+ ArrayRef<int> Mask, TTI::TargetCostKind CostKind, int Index,
+ VectorType *SubTp, ArrayRef<const Value *> Args = {},
const Instruction *CxtI = nullptr) const {
return 1;
}
@@ -1541,13 +1541,14 @@ class TargetTransformInfoImplCRTPBase : public TargetTransformInfoImplBase {
return 0;
if (Shuffle->isExtractSubvectorMask(SubIndex))
- return TargetTTI->getShuffleCost(TTI::SK_ExtractSubvector, VecSrcTy,
- Mask, CostKind, SubIndex, VecTy,
- Operands, Shuffle);
+ return TargetTTI->getShuffleCost(TTI::SK_ExtractSubvector, VecTy,
+ VecSrcTy, Mask, CostKind, SubIndex,
+ VecTy, Operands, Shuffle);
if (Shuffle->isInsertSubvectorMask(NumSubElts, SubIndex))
return TargetTTI->getShuffleCost(
- TTI::SK_InsertSubvector, VecTy, Mask, CostKind, SubIndex,
+ TTI::SK_InsertSubvector, VecTy, VecSrcTy, Mask, CostKind,
+ SubIndex,
FixedVectorType::get(VecTy->getScalarType(), NumSubElts),
Operands, Shuffle);
@@ -1576,21 +1577,24 @@ class TargetTransformInfoImplCRTPBase : public TargetTransformInfoImplBase {
return TargetTTI->getShuffleCost(
IsUnary ? TTI::SK_PermuteSingleSrc : TTI::SK_PermuteTwoSrc, VecTy,
- AdjustMask, CostKind, 0, nullptr, Operands, Shuffle);
+ VecTy, AdjustMask, CostKind, 0, nullptr, Operands, Shuffle);
}
// Narrowing shuffle - perform shuffle at original wider width and
// then extract the lower elements.
+ // FIXME: This can assume widening, which is not true of all vector
+ // architectures (and is not even the default).
AdjustMask.append(NumSubElts - Mask.size(), PoisonMaskElem);
InstructionCost ShuffleCost = TargetTTI->getShuffleCost(
IsUnary ? TTI::SK_PermuteSingleSrc : TTI::SK_PermuteTwoSrc,
- VecSrcTy, AdjustMask, CostKind, 0, nullptr, Operands, Shuffle);
+ VecSrcTy, VecSrcTy, AdjustMask, CostKind, 0, nullptr, Operands,
+ Shuffle);
SmallVector<int, 16> ExtractMask(Mask.size());
std::iota(ExtractMask.begin(), ExtractMask.end(), 0);
return ShuffleCost + TargetTTI->getShuffleCost(
- TTI::SK_ExtractSubvector, VecSrcTy,
+ TTI::SK_ExtractSubvector, VecTy, VecSrcTy,
ExtractMask, CostKind, 0, VecTy, {}, Shuffle);
}
@@ -1598,40 +1602,44 @@ class TargetTransformInfoImplCRTPBase : public TargetTransformInfoImplBase {
return 0;
if (Shuffle->isReverse())
- return TargetTTI->getShuffleCost(TTI::SK_Reverse, VecTy, Mask, CostKind,
- 0, nullptr, Operands, Shuffle);
+ return TargetTTI->getShuffleCost(TTI::SK_Reverse, VecTy, VecSrcTy, Mask,
+ CostKind, 0, nullptr, Operands,
+ Shuffle);
if (Shuffle->isSelect())
- return TargetTTI->getShuffleCost(TTI::SK_Select, VecTy, Mask, CostKind,
- 0, nullptr, Operands, Shuffle);
+ return TargetTTI->getShuffleCost(TTI::SK_Select, VecTy, VecSrcTy, Mask,
+ CostKind, 0, nullptr, Operands,
+ Shuffle);
if (Shuffle->isTranspose())
- return TargetTTI->getShuffleCost(TTI::SK_Transpose, VecTy, Mask,
- CostKind, 0, nullptr, Operands,
+ return TargetTTI->getShuffleCost(TTI::SK_Transpose, VecTy, VecSrcTy,
+ Mask, CostKind, 0, nullptr, Operands,
Shuffle);
if (Shuffle->isZeroEltSplat())
- return TargetTTI->getShuffleCost(TTI::SK_Broadcast, VecTy, Mask,
- CostKind, 0, nullptr, Operands,
+ return TargetTTI->getShuffleCost(TTI::SK_Broadcast, VecTy, VecSrcTy,
+ Mask, CostKind, 0, nullptr, Operands,
Shuffle);
if (Shuffle->isSingleSource())
- return TargetTTI->getShuffleCost(TTI::SK_PermuteSingleSrc, VecTy, Mask,
- CostKind, 0, nullptr, Operands,
- Shuffle);
+ return TargetTTI->getShuffleCost(TTI::SK_PermuteSingleSrc, VecTy,
+ VecSrcTy, Mask, CostKind, 0, nullptr,
+ Operands, Shuffle);
if (Shuffle->isInsertSubvectorMask(NumSubElts, SubIndex))
return TargetTTI->getShuffleCost(
- TTI::SK_InsertSubvector, VecTy, Mask, CostKind, SubIndex,
+ TTI::SK_InsertSubvector, VecTy, VecSrcTy, Mask, CostKind, SubIndex,
FixedVectorType::get(VecTy->getScalarType(), NumSubElts), Operands,
Shuffle);
if (Shuffle->isSplice(SubIndex))
- return TargetTTI->getShuffleCost(TTI::SK_Splice, VecTy, Mask, CostKind,
- SubIndex, nullptr, Operands, Shuffle);
+ return TargetTTI->getShuffleCost(TTI::SK_Splice, VecTy, VecSrcTy, Mask,
+ CostKind, SubIndex, nullptr, Operands,
+ Shuffle);
- return TargetTTI->getShuffleCost(TTI::SK_PermuteTwoSrc, VecTy, Mask,
- CostKind, 0, nullptr, Operands, Shuffle);
+ return TargetTTI->getShuffleCost(TTI::SK_PermuteTwoSrc, VecTy, VecSrcTy,
+ Mask, CostKind, 0, nullptr, Operands,
+ Shuffle);
}
case Instruction::ExtractElement: {
auto *EEI = dyn_cast<ExtractElementInst>(U);
diff --git a/llvm/include/llvm/CodeGen/BasicTTIImpl.h b/llvm/include/llvm/CodeGen/BasicTTIImpl.h
index 90a75c3d352e4..0477c1b6f1a6f 100644
--- a/llvm/include/llvm/CodeGen/BasicTTIImpl.h
+++ b/llvm/include/llvm/CodeGen/BasicTTIImpl.h
@@ -329,11 +329,11 @@ class BasicTTIImplBase : public TargetTransformInfoImplCRTPBase<T> {
// Cost the call + mask.
auto Cost =
thisT()->getCallInstrCost(nullptr, RetTy, ICA.getArgTypes(), CostKind);
- if (VD->isMasked())
- Cost += thisT()->getShuffleCost(
- TargetTransformInfo::SK_Broadcast,
- VectorType::get(IntegerType::getInt1Ty(Ctx), VF), {}, CostKind, 0,
- nullptr, {});
+ if (VD->isMasked()) {
+ auto VecTy = VectorType::get(IntegerType::getInt1Ty(Ctx), VF);
+ Cost += thisT()->getShuffleCost(TargetTransformInfo::SK_Broadcast, VecTy,
+ VecTy, {}, CostKind, 0, nullptr, {});
+ }
// Lowering to a library call (with output pointers) may require us to emit
// reloads for the results.
@@ -1101,11 +1101,11 @@ class BasicTTIImplBase : public TargetTransformInfoImplCRTPBase<T> {
TTI::ShuffleKind improveShuffleKindFromMask(TTI::ShuffleKind Kind,
ArrayRef<int> Mask,
- VectorType *Ty, int &Index,
+ VectorType *SrcTy, int &Index,
VectorType *&SubTy) const {
if (Mask.empty())
return Kind;
- int NumSrcElts = Ty->getElementCount().getKnownMinValue();
+ int NumSrcElts = SrcTy->getElementCount().getKnownMinValue();
switch (Kind) {
case TTI::SK_PermuteSingleSrc: {
if (ShuffleVectorInst::isReverseMask(Mask, NumSrcElts))
@@ -1116,7 +1116,7 @@ class BasicTTIImplBase : public TargetTransformInfoImplCRTPBase<T> {
return TTI::SK_Broadcast;
if (ShuffleVectorInst::isExtractSubvectorMask(Mask, NumSrcElts, Index) &&
(Index + Mask.size()) <= (size_t)NumSrcElts) {
- SubTy = FixedVectorType::get(Ty->getElementType(), Mask.size());
+ SubTy = FixedVectorType::get(SrcTy->getElementType(), Mask.size());
return TTI::SK_ExtractSubvector;
}
break;
@@ -1127,7 +1127,7 @@ class BasicTTIImplBase : public TargetTransformInfoImplCRTPBase<T> {
Mask, NumSrcElts, NumSubElts, Index)) {
if (Index + NumSubElts > NumSrcElts)
return Kind;
- SubTy = FixedVectorType::get(Ty->getElementType(), NumSubElts);
+ SubTy = FixedVectorType::get(SrcTy->getElementType(), NumSubElts);
return TTI::SK_InsertSubvector;
}
if (ShuffleVectorInst::isSelectMask(Mask, NumSrcElts))
@@ -1151,13 +1151,13 @@ class BasicTTIImplBase : public TargetTransformInfoImplCRTPBase<T> {
}
InstructionCost
- getShuffleCost(TTI::ShuffleKind Kind, VectorType *Tp, ArrayRef<int> Mask,
- TTI::TargetCostKind CostKind, int Index, VectorType *SubTp,
- ArrayRef<const Value *> Args = {},
+ getShuffleCost(TTI::ShuffleKind Kind, VectorType *DstTy, VectorType *SrcTy,
+ ArrayRef<int> Mask, TTI::TargetCostKind CostKind, int Index,
+ VectorType *SubTp, ArrayRef<const Value *> Args = {},
const Instruction *CxtI = nullptr) const override {
- switch (improveShuffleKindFromMask(Kind, Mask, Tp, Index, SubTp)) {
+ switch (improveShuffleKindFromMask(Kind, Mask, SrcTy, Index, SubTp)) {
case TTI::SK_Broadcast:
- if (auto *FVT = dyn_cast<FixedVectorType>(Tp))
+ if (auto *FVT = dyn_cast<FixedVectorType>(SrcTy))
return getBroadcastShuffleOverhead(FVT, CostKind);
return InstructionCost::getInvalid();
case TTI::SK_Select:
@@ -1166,14 +1166,14 @@ class BasicTTIImplBase : public TargetTransformInfoImplCRTPBase<T> {
case TTI::SK_Transpose:
case TTI::SK_PermuteSingleSrc:
case TTI::SK_PermuteTwoSrc:
- if (auto *FVT = dyn_cast<FixedVectorType>(Tp))
+ if (auto *FVT = dyn_cast<FixedVectorType>(SrcTy))
return getPermuteShuffleOverhead(FVT, CostKind);
return InstructionCost::getInvalid();
case TTI::SK_ExtractSubvector:
- return getExtractSubvectorOverhead(Tp, CostKind, Index,
+ return getExtractSubvectorOverhead(SrcTy, CostKind, Index,
cast<FixedVectorType>(SubTp));
case TTI::SK_InsertSubvector:
- return getInsertSubvectorOverhead(Tp, CostKind, Index,
+ return getInsertSubvectorOverhead(DstTy, CostKind, Index,
cast<FixedVectorType>(SubTp));
}
llvm_unreachable("Unknown TTI::ShuffleKind");
@@ -1910,6 +1910,7 @@ class BasicTTIImplBase : public TargetTransformInfoImplCRTPBase<T> {
return BaseT::getIntrinsicInstrCost(ICA, CostKind);
unsigned Index = cast<ConstantInt>(Args[1])->getZExtValue();
return thisT()->getShuffleCost(TTI::SK_ExtractSubvector,
+ cast<VectorType>(RetTy),
cast<VectorType>(Args[0]->getType()), {},
CostKind, Index, cast<VectorType>(RetTy));
}
@@ -1920,17 +1921,18 @@ class BasicTTIImplBase : public TargetTransformInfoImplCRTPBase<T> {
return BaseT::getIntrinsicInstrCost(ICA, CostKind);
unsigned Index = cast<ConstantInt>(Args[2])->getZExtValue();
return thisT()->getShuffleCost(
- TTI::SK_InsertSubvector, cast<VectorType>(Args[0]->getType()), {},
- CostKind, Index, cast<VectorType>(Args[1]->getType()));
+ TTI::SK_InsertSubvector, cast<VectorType>(RetTy),
+ cast<VectorType>(Args[0]->getType()), {}, CostKind, Index,
+ cast<VectorType>(Args[1]->getType()));
}
case Intrinsic::vector_reverse: {
- return thisT()->getShuffleCost(TTI::SK_Reverse,
+ return thisT()->getShuffleCost(TTI::SK_Reverse, cast<VectorType>(RetTy),
cast<VectorType>(Args[0]->getType()), {},
CostKind, 0, cast<VectorType>(RetTy));
}
case Intrinsic::vector_splice: {
unsigned Index = cast<ConstantInt>(Args[2])->getZExtValue();
- return thisT()->getShuffleCost(TTI::SK_Splice,
+ return thisT()->getShuffleCost(TTI::SK_Splice, cast<VectorType>(RetTy),
cast<VectorType>(Args[0]->getType()), {},
CostKind, Index, cast<VectorType>(RetTy));
}
@@ -2376,8 +2378,8 @@ class BasicTTIImplBase : public TargetTransformInfoImplCRTPBase<T> {
CostKind, 1, nullptr, nullptr);
Cost += thisT()->getVectorInstrCost(Instruction::InsertElement, SearchTy,
CostKind, 0, nullptr, nullptr);
- Cost += thisT()->getShuffleCost(TTI::SK_Broadcast, SearchTy, {}, CostKind,
- 0, nullptr);
+ Cost += thisT()->getShuffleCost(TTI::SK_Broadcast, SearchTy, SearchTy, {},
+ CostKind, 0, nullptr);
Cost += thisT()->getCmpSelInstrCost(BinaryOperator::ICmp, SearchTy, RetTy,
CmpInst::ICMP_EQ, CostKind);
Cost +=
@@ -2961,8 +2963,8 @@ class BasicTTIImplBase : public TargetTransformInfoImplCRTPBase<T> {
while (NumVecElts > MVTLen) {
NumVecElts /= 2;
VectorType *SubTy = FixedVectorType::get(ScalarTy, NumVecElts);
- ShuffleCost += thisT()->getShuffleCost(TTI::SK_ExtractSubvector, Ty, {},
- CostKind, NumVecElts, SubTy);
+ ShuffleCost += thisT()->getShuffleCost(
+ TTI::SK_ExtractSubvector, SubTy, Ty, {}, CostKind, NumVecElts, SubTy);
ArithCost += thisT()->getArithmeticInstrCost(Opcode, SubTy, CostKind);
Ty = SubTy;
++LongVectorCount;
@@ -2978,7 +2980,7 @@ class BasicTTIImplBase : public TargetTransformInfoImplCRTPBase<T> {
// By default reductions need one shuffle per reduction level.
ShuffleCost +=
NumReduxLevels * thisT()->getShuffleCost(TTI::SK_PermuteSingleSrc, Ty,
- {}, CostKind, 0, Ty);
+ Ty, {}, CostKind, 0, Ty);
ArithCost +=
NumReduxLevels * thisT()->getArithmeticInstrCost(Opcode, Ty, CostKind);
return ShuffleCost + ArithCost +
@@ -3052,8 +3054,8 @@ class BasicTTIImplBase : public TargetTransformInfoImplCRTPBase<T> {
NumVecElts /= 2;
auto *SubTy = FixedVectorType::get(ScalarTy, NumVecElts);
- ShuffleCost += thisT()->getShuffleCost(TTI::SK_ExtractSubvector, Ty, {},
- CostKind, NumVecElts, SubTy);
+ ShuffleCost += thisT()->getShuffleCost(
+ TTI::SK_ExtractSubvector, SubTy, Ty, {}, CostKind, NumVecElts, SubTy);
IntrinsicCostAttributes Attrs(IID, SubTy, {SubTy, SubTy}, FMF);
MinMaxCost += getIntrinsicInstrCost(Attrs, CostKind);
@@ -3069,7 +3071,7 @@ class BasicTTIImplBase : public TargetTransformInfoImplCRTPBase<T> {
// architecture-dependent length.
ShuffleCost +=
NumReduxLevels * thisT()->getShuffleCost(TTI::SK_PermuteSingleSrc, Ty,
- {}, CostKind, 0, Ty);
+ Ty, {}, CostKind, 0, Ty);
IntrinsicCostAttributes Attrs(IID, Ty, {Ty, Ty}, FMF);
MinMaxCost += NumReduxLevels * getIntrinsicInstrCost(Attrs, CostKind);
// The last min/max should be in vector registers and we counted it above.
diff --git a/llvm/lib/Analysis/TargetTransformInfo.cpp b/llvm/lib/Analysis/TargetTransformInfo.cpp
index 8cc7f8a9d2ab2..3ebd9d487ba04 100644
--- a/llvm/lib/Analysis/TargetTransformInfo.cpp
+++ b/llvm/lib/Analysis/TargetTransformInfo.cpp
@@ -985,11 +985,16 @@ InstructionCost TargetTransformInfo::getAltInstrCost(
}
InstructionCost TargetTransformInfo::getShuffleCost(
- ShuffleKind Kind, VectorType *Ty, ArrayRef<int> Mask,
+ ShuffleKind Kind, VectorType *DstTy, VectorType *SrcTy, ArrayRef<int> Mask,
TTI::TargetCostKind CostKind, int Index, VectorType *SubTp,
ArrayRef<const Value *> Args, const Instruction *CxtI) const {
- InstructionCost Cost = TTIImpl->getShuffleCost(Kind, Ty, Mask, CostKind,
- Index, SubTp, Args, CxtI);
+ assert((Mask.empty() || DstTy->isScalableTy() ||
+ Mask.size() == DstTy->getElementCount().getKnownMinValue()) &&
+ "Expected the Mask to match the return size if given");
+ assert(SrcTy->getScalarType() == DstTy->getScalarType() &&
+ "Expected the same scalar types");
+ InstructionCost Cost = TTIImpl->getShuffleCost(
+ Kind, DstTy, SrcTy, Mask, CostKind, Index, SubTp, Args, CxtI);
assert(Cost >= 0 && "TTI should not produce negative costs!");
return Cost;
}
diff --git a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
index 9d5c984fa4f16..8c6f272a8c8da 100644
--- a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
@@ -5468,19 +5468,25 @@ InstructionCost AArch64TTIImpl::getPartialReductionCost(
return Cost;
}
-InstructionCost AArch64TTIImpl::getShuffleCost(
- TTI::ShuffleKind Kind, VectorType *Tp, ArrayRef<int> Mask,
- TTI::TargetCostKind CostKind, int Index, VectorType *SubTp,
- ArrayRef<const Value *> Args, const Instruction *CxtI) const {
- std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(Tp);
+InstructionCost
+AArch64TTIImpl::getShuffleCost(TTI::ShuffleKind Kind, VectorType *DstTy,
+ VectorType *SrcTy, ArrayRef<int> Mask,
+ TTI::TargetCostKind CostKind, int Index,
+ VectorType *SubTp, ArrayRef<const Value *> Args,
+ const Instruction *CxtI) const {
+ assert((Mask.empty() || DstTy->isScalableTy() ||
+ Mask.size() == DstTy->getElementCount().getKnownMinValue()) &&
+ "Expected the Mask to match the return size if given");
+ assert(SrcTy->getScalarType() == DstTy->getScalarType() &&
+ "Expected the same scalar types");
+ std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(SrcTy);
// If we have a Mask, and the LT is being legalized somehow, split the Mask
// into smaller vectors and sum the cost of each shuffle.
- if (!Mask.empty() && isa<FixedVectorType>(Tp) && LT.second.isVector() &&
+ if (!Mask.empty() && isa<FixedVectorType>(SrcTy) && LT.second.isVector() &&
LT.second.getScalarSizeInBits() * Mask.size() > 128 &&
- Tp->getScalarSizeInBits() == LT.second.getScalarSizeInBits() &&
+ SrcTy->getScalarSizeInBits() == LT.second.getScalarSizeInBits() &&
Mask.size() > LT.second.getVectorNumElements() && !Index && !SubTp) {
-
// Check for LD3/LD4 instructions, which are represented in llvm IR as
// deinterleaving-shuffle(load). The shuffle cost could potentially be free,
// but we model it with a cost of LT.first so that LD3/LD4 have a higher
@@ -5496,16 +5502,16 @@ InstructionCost AArch64TTIImpl::getShuffleCost(
// cost than just the store.
if (CxtI && CxtI->hasOneUse() && isa<StoreInst>(*CxtI->user_begin()) &&
(ShuffleVectorInst::isInterleaveMask(
- Mask, 4, Tp->getElementCount().getKnownMinValue() * 2) ||
+ Mask, 4, SrcTy->getElementCount().getKnownMinValue() * 2) ||
ShuffleVectorInst::isInterleaveMask(
- Mask, 3, Tp->getElementCount().getKnownMinValue() * 2)))
+ Mask, 3, SrcTy->getElementCount().getKnownMinValue() * 2)))
return LT.first;
unsigned TpNumElts = Mask.size();
unsigned LTNumElts = LT.second.getVectorNumElements();
unsigned NumVecs = (TpNumElts + LTNumElts - 1) / LTNumElts;
- VectorType *NTp =
- VectorType::get(Tp->getScalarType(), LT.second.getVectorElementCount());
+ VectorType *NTp = VectorType::get(SrcTy->getScalarType(),
+ LT.second.getVectorElementCount());
InstructionCost Cost;
std::map<std::tuple<unsigned, unsigned, SmallVector<int>>, InstructionCost>
PreviousCosts;
@@ -5513,7 +5519,7 @@ InstructionCost AArch64TTIImpl::getShuffleCost(
SmallVector<int> NMask;
// Split the existing mask into chunks of size LTNumElts. Track the source
// sub-vectors to ensure the result has at most 2 inputs.
- unsigned Source1 = 0, Source2 = 0;
+ unsigned Source1 = -1U, Source2 = -1U;
unsigned NumSources = 0;
for (unsigned E = 0; E < LTNumElts; E++) {
int MaskElt = (N * LTNumElts + E < TpNumElts) ? Mask[N * LTNumElts + E]
@@ -5561,7 +5567,8 @@ InstructionCost AArch64TTIImpl::getShuffleCost(
NumSources <= 2
? getShuffleCost(NumSources <= 1 ? TTI::SK_PermuteSingleSrc
: TTI::SK_PermuteTwoSrc,
- NTp, NMask, CostKind, 0, nullptr, Args, CxtI)
+ NTp, NTp, NMask, CostKind, 0, nullptr, Args,
+ CxtI)
: LTNumElts;
Result.first->second = NCost;
Cost += NCost;
@@ -5569,7 +5576,7 @@ InstructionCost AArch64TTIImpl::getShuffleCost(
return Cost;
}
- Kind = improveShuffleKindFromMask(Kind, Mask, Tp, Index, SubTp);
+ Kind = improveShuffleKindFromMask(Kind, Mask, SrcTy, Index, SubTp);
bool IsExtractSubvector = Kind == TTI::SK_ExtractSubvector;
// A subvector extract can be implemented with an ext (or trivial extract, if
// from lane 0). This currently only handles low or high extracts to prevent
@@ -5585,6 +5592,12 @@ InstructionCost AArch64TTIImpl::getShuffleCost(
}
Kind = TTI::SK_PermuteSingleSrc;
}
+ // FIXME: This was added to keep the costs equal when adding DstTys. Update
+ // the code to handle length-changing shuffles.
+ if (Kind == TTI::SK_InsertSubvector) {
+ LT = getTypeLegalizationCost(DstTy);
+ SrcTy = DstTy;
+ }
// Check for broadcast loads, which are supported by the LD1R instruction.
// In terms of code-size, the shuffle vector is free when a load + dup get
@@ -5596,15 +5609,17 @@ InstructionCost AArch64TTIImpl::getShuffleCost(
if (CostKind == TTI::TCK_CodeSize && Kind == TTI::SK_Broadcast) {
bool IsLoad = !Args.empty() && isa<LoadInst>(Args[0]);
if (IsLoad && LT.second.isVector() &&
- isLegalBroadcastLoad(Tp->getElementType(),
+ isLegalBroadcastLoad(SrcTy->getElementType(),
LT.second.getVectorElementCount()))
return 0;
}
// If we have 4 elements for the shuffle and a Mask, get the cost straight
// from the perfect shuffle tables.
- if (Mask.size() == 4 && Tp->getElementCount() == ElementCount::getFixed(4) &&
- (Tp->getScalarSizeInBits() == 16 || Tp->getScalarSizeInBits() == 32) &&
+ if (Mask.size() == 4 &&
+ SrcTy->getElementCount() == ElementCount::getFixed(4) &&
+ (SrcTy->getScalarSizeInBits() == 16 ||
+ SrcTy->getScalarSizeInBits() == 32) &&
all_of(Mask, [](int E) { return E < 8; }))
return getPerfectShuffleCost(Mask);
@@ -5764,8 +5779,8 @@ InstructionCost AArch64TTIImpl::getShuffleCost(
return LT.first * Entry->Cost;
}
- if (Kind == TTI::SK_Splice && isa<ScalableVectorType>(Tp))
- return getSpliceCost(Tp, Index, CostKind);
+ if (Kind == TTI::SK_Splice && isa<ScalableVectorType>(SrcTy))
+ return getSpliceCost(SrcTy, Index, CostKind);
// Inserting a subvector can often be done with either a D, S or H register
// move, so long as the inserted vector is "aligned".
@@ -5783,8 +5798,8 @@ InstructionCost AArch64TTIImpl::getShuffleCost(
// Restore optimal kind.
if (IsExtractSubvector)
Kind = TTI::SK_ExtractSubvector;
- return BaseT::getShuffleCost(Kind, Tp, Mask, CostKind, Index, SubTp, Args,
- CxtI);
+ return BaseT::getShuffleCost(Kind, DstTy, SrcTy, Mask, CostKind, Index, SubTp,
+ Args, CxtI);
}
static bool containsDecreasingPointers(Loop *TheLoop,
diff --git a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.h b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.h
index 470af01be3154..9ada70bd7086a 100644
--- a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.h
+++ b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.h
@@ -451,9 +451,9 @@ class AArch64TTIImpl final : public BasicTTIImplBase<AArch64TTIImpl> {
TTI::TargetCostKind CostKind = TTI::TCK_RecipThroughput) const override;
InstructionCost
- getShuffleCost(TTI::ShuffleKind Kind, VectorType *Tp, ArrayRef<int> Mask,
- TTI::TargetCostKind CostKind, int Index, VectorType *SubTp,
- ArrayRef<const Value *> Args = {},
+ getShuffleCost(TTI::ShuffleKind Kind, VectorType *DstTy, VectorType *SrcTy,
+ ArrayRef<int> Mask, TTI::TargetCostKind CostKind, int Index,
+ VectorType *SubTp, ArrayRef<const Value *> Args = {},
const Instruction *CxtI = nullptr) const override;
InstructionCost getScalarizationOverhead(
diff --git a/llvm/lib/Target/AMDGPU/AMDGPUTargetTransformInfo.cpp b/llvm/lib/Target/AMDGPU/AMDGPUTargetTransformInfo.cpp
index d5a1aaef4ad68..5e41273556d3d 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPUTargetTransformInfo.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPUTargetTransformInfo.cpp
@@ -1183,21 +1183,23 @@ Value *GCNTTIImpl::rewriteIntrinsicWithAddressSpace(IntrinsicInst *II,
}
InstructionCost GCNTTIImpl::getShuffleCost(TTI::ShuffleKind Kind,
- VectorType *VT, ArrayRef<int> Mask,
+ VectorType *DstTy, VectorType *SrcTy,
+ ArrayRef<int> Mask,
TTI::TargetCostKind CostKind,
int Index, VectorType *SubTp,
ArrayRef<const Value *> Args,
const Instruction *CxtI) const {
- if (!isa<FixedVectorType>(VT))
- return BaseT::getShuffleCost(Kind, VT, Mask, CostKind, Index, SubTp);
+ if (!isa<FixedVectorType>(SrcTy))
+ return BaseT::getShuffleCost(Kind, DstTy, SrcTy, Mask, CostKind, Index,
+ SubTp);
- Kind = improveShuffleKindFromMask(Kind, Mask, VT, Index, SubTp);
+ Kind = improveShuffleKindFromMask(Kind, Mask, SrcTy, Index, SubTp);
// Larger vector widths may require additional instructions, but are
// typically cheaper than scalarized versions.
- unsigned NumVectorElts = cast<FixedVectorType>(VT)->getNumElements();
+ unsigned NumVectorElts = cast<FixedVectorType>(SrcTy)->getNumElements();
if (ST->getGeneration() >= AMDGPUSubtarget::VOLCANIC_ISLANDS &&
- DL.getTypeSizeInBits(VT->getElementType()) == 16) {
+ DL.getTypeSizeInBits(SrcTy->getElementType()) == 16) {
bool HasVOP3P = ST->hasVOP3PInsts();
unsigned RequestedElts =
count_if(Mask, [](int MaskElt) { return MaskElt != -1; });
@@ -1239,7 +1241,8 @@ InstructionCost GCNTTIImpl::getShuffleCost(TTI::ShuffleKind Kind,
}
}
- return BaseT::getShuffleCost(Kind, VT, Mask, CostKind, Index, SubTp);
+ return BaseT::getShuffleCost(Kind, DstTy, SrcTy, Mask, CostKind, Index,
+ SubTp);
}
/// Whether it is profitable to sink the operands of an
diff --git a/llvm/lib/Target/AMDGPU/AMDGPUTargetTransformInfo.h b/llvm/lib/Target/AMDGPU/AMDGPUTargetTransformInfo.h
index 0fae301abf532..64a244e33f18f 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPUTargetTransformInfo.h
+++ b/llvm/lib/Target/AMDGPU/AMDGPUTargetTransformInfo.h
@@ -236,9 +236,9 @@ class GCNTTIImpl final : public BasicTTIImplBase<GCNTTIImpl> {
InstructionCost getVectorSplitCost() const { return 0; }
InstructionCost
- getShuffleCost(TTI::ShuffleKind Kind, VectorType *Tp, ArrayRef<int> Mask,
- TTI::TargetCostKind CostKind, int Index, VectorType *SubTp,
- ArrayRef<const Value *> Args = {},
+ getShuffleCost(TTI::ShuffleKind Kind, VectorType *DstTy, VectorType *SrcTy,
+ ArrayRef<int> Mask, TTI::TargetCostKind CostKind, int Index,
+ VectorType *SubTp, ArrayRef<const Value *> Args = {},
const Instruction *CxtI = nullptr) const override;
bool isProfitableToSinkOperands(Instruction *I,
diff --git a/llvm/lib/Target/ARM/ARMTargetTransformInfo.cpp b/llvm/lib/Target/ARM/ARMTargetTransformInfo.cpp
index 6c3a1ae7e1775..203fb76d7be86 100644
--- a/llvm/lib/Target/ARM/ARMTargetTransformInfo.cpp
+++ b/llvm/lib/Target/ARM/ARMTargetTransformInfo.cpp
@@ -1233,12 +1233,19 @@ InstructionCost ARMTTIImpl::getMemcpyCost(const Instruction *I) const {
}
InstructionCost ARMTTIImpl::getShuffleCost(TTI::ShuffleKind Kind,
- VectorType *Tp, ArrayRef<int> Mask,
+ VectorType *DstTy, VectorType *SrcTy,
+ ArrayRef<int> Mask,
TTI::TargetCostKind CostKind,
int Index, VectorType *SubTp,
ArrayRef<const Value *> Args,
const Instruction *CxtI) const {
- Kind = improveShuffleKindFromMask(Kind, Mask, Tp, Index, SubTp);
+ assert((Mask.empty() || DstTy->isScalableTy() ||
+ Mask.size() == DstTy->getElementCount().getKnownMinValue()) &&
+ "Expected the Mask to match the return size if given");
+ assert(SrcTy->getScalarType() == DstTy->getScalarType() &&
+ "Expected the same scalar types");
+
+ Kind = improveShuffleKindFromMask(Kind, Mask, SrcTy, Index, SubTp);
// Treat extractsubvector as single op permutation.
bool IsExtractSubvector = Kind == TTI::SK_ExtractSubvector;
if (IsExtractSubvector)
@@ -1259,7 +1266,7 @@ InstructionCost ARMTTIImpl::getShuffleCost(TTI::ShuffleKind Kind,
{ISD::VECTOR_SHUFFLE, MVT::v8i16, 1},
{ISD::VECTOR_SHUFFLE, MVT::v16i8, 1}};
- std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(Tp);
+ std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(SrcTy);
if (const auto *Entry =
CostTableLookup(NEONDupTbl, ISD::VECTOR_SHUFFLE, LT.second))
return LT.first * Entry->Cost;
@@ -1280,7 +1287,7 @@ InstructionCost ARMTTIImpl::getShuffleCost(TTI::ShuffleKind Kind,
{ISD::VECTOR_SHUFFLE, MVT::v8i16, 2},
{ISD::VECTOR_SHUFFLE, MVT::v16i8, 2}};
- std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(Tp);
+ std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(SrcTy);
if (const auto *Entry =
CostTableLookup(NEONShuffleTbl, ISD::VECTOR_SHUFFLE, LT.second))
return LT.first * Entry->Cost;
@@ -1304,7 +1311,7 @@ InstructionCost ARMTTIImpl::getShuffleCost(TTI::ShuffleKind Kind,
{ISD::VECTOR_SHUFFLE, MVT::v16i8, 32}};
- std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(Tp);
+ std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(SrcTy);
if (const auto *Entry = CostTableLookup(NEONSelShuffleTbl,
ISD::VECTOR_SHUFFLE, LT.second))
return LT.first * Entry->Cost;
@@ -1320,7 +1327,7 @@ InstructionCost ARMTTIImpl::getShuffleCost(TTI::ShuffleKind Kind,
{ISD::VECTOR_SHUFFLE, MVT::v4f32, 1},
{ISD::VECTOR_SHUFFLE, MVT::v8f16, 1}};
- std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(Tp);
+ std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(SrcTy);
if (const auto *Entry = CostTableLookup(MVEDupTbl, ISD::VECTOR_SHUFFLE,
LT.second))
return LT.first * Entry->Cost *
@@ -1328,7 +1335,7 @@ InstructionCost ARMTTIImpl::getShuffleCost(TTI::ShuffleKind Kind,
}
if (!Mask.empty()) {
- std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(Tp);
+ std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(SrcTy);
if (LT.second.isVector() &&
Mask.size() <= LT.second.getVectorNumElements() &&
(isVREVMask(Mask, LT.second, 16) || isVREVMask(Mask, LT.second, 32) ||
@@ -1340,11 +1347,11 @@ InstructionCost ARMTTIImpl::getShuffleCost(TTI::ShuffleKind Kind,
// Restore optimal kind.
if (IsExtractSubvector)
Kind = TTI::SK_ExtractSubvector;
- int BaseCost = ST->hasMVEIntegerOps() && Tp->isVectorTy()
+ int BaseCost = ST->hasMVEIntegerOps() && SrcTy->isVectorTy()
? ST->getMVEVectorCostFactor(TTI::TCK_RecipThroughput)
: 1;
- return BaseCost *
- BaseT::getShuffleCost(Kind, Tp, Mask, CostKind, Index, SubTp);
+ return BaseCost * BaseT::getShuffleCost(Kind, DstTy, SrcTy, Mask, CostKind,
+ Index, SubTp);
}
InstructionCost ARMTTIImpl::getArithmeticInstrCost(
diff --git a/llvm/lib/Target/ARM/ARMTargetTransformInfo.h b/llvm/lib/Target/ARM/ARMTargetTransformInfo.h
index c1af4e3dc5da6..ca06b9e3cb661 100644
--- a/llvm/lib/Target/ARM/ARMTargetTransformInfo.h
+++ b/llvm/lib/Target/ARM/ARMTargetTransformInfo.h
@@ -223,9 +223,9 @@ class ARMTTIImpl final : public BasicTTIImplBase<ARMTTIImpl> {
int getNumMemOps(const IntrinsicInst *I) const;
InstructionCost
- getShuffleCost(TTI::ShuffleKind Kind, VectorType *Tp, ArrayRef<int> Mask,
- TTI::TargetCostKind CostKind, int Index, VectorType *SubTp,
- ArrayRef<const Value *> Args = {},
+ getShuffleCost(TTI::ShuffleKind Kind, VectorType *DstTy, VectorType *SrcTy,
+ ArrayRef<int> Mask, TTI::TargetCostKind CostKind, int Index,
+ VectorType *SubTp, ArrayRef<const Value *> Args = {},
const Instruction *CxtI = nullptr) const override;
bool preferInLoopReduction(RecurKind Kind, Type *Ty) const override;
diff --git a/llvm/lib/Target/Hexagon/HexagonTargetTransformInfo.cpp b/llvm/lib/Target/Hexagon/HexagonTargetTransformInfo.cpp
index a4cc472fdbf29..9fb7d471fd22a 100644
--- a/llvm/lib/Target/Hexagon/HexagonTargetTransformInfo.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonTargetTransformInfo.cpp
@@ -226,10 +226,12 @@ HexagonTTIImpl::getMaskedMemoryOpCost(unsigned Opcode, Type *Src,
CostKind);
}
-InstructionCost HexagonTTIImpl::getShuffleCost(
- TTI::ShuffleKind Kind, VectorType *Tp, ArrayRef<int> Mask,
- TTI::TargetCostKind CostKind, int Index, VectorType *SubTp,
- ArrayRef<const Value *> Args, const Instruction *CxtI) const {
+InstructionCost
+HexagonTTIImpl::getShuffleCost(TTI::ShuffleKind Kind, VectorType *DstTy,
+ VectorType *SrcTy, ArrayRef<int> Mask,
+ TTI::TargetCostKind CostKind, int Index,
+ VectorType *SubTp, ArrayRef<const Value *> Args,
+ const Instruction *CxtI) const {
return 1;
}
diff --git a/llvm/lib/Target/Hexagon/HexagonTargetTransformInfo.h b/llvm/lib/Target/Hexagon/HexagonTargetTransformInfo.h
index c03cad4713e40..af8dede723083 100644
--- a/llvm/lib/Target/Hexagon/HexagonTargetTransformInfo.h
+++ b/llvm/lib/Target/Hexagon/HexagonTargetTransformInfo.h
@@ -123,9 +123,9 @@ class HexagonTTIImpl final : public BasicTTIImplBase<HexagonTTIImpl> {
unsigned AddressSpace,
TTI::TargetCostKind CostKind) const override;
InstructionCost
- getShuffleCost(TTI::ShuffleKind Kind, VectorType *Tp, ArrayRef<int> Mask,
- TTI::TargetCostKind CostKind, int Index, VectorType *SubTp,
- ArrayRef<const Value *> Args = {},
+ getShuffleCost(TTI::ShuffleKind Kind, VectorType *DstTy, VectorType *SrcTy,
+ ArrayRef<int> Mask, TTI::TargetCostKind CostKind, int Index,
+ VectorType *SubTp, ArrayRef<const Value *> Args = {},
const Instruction *CxtI = nullptr) const override;
InstructionCost getGatherScatterOpCost(unsigned Opcode, Type *DataTy,
const Value *Ptr, bool VariableMask,
diff --git a/llvm/lib/Target/PowerPC/PPCTargetTransformInfo.cpp b/llvm/lib/Target/PowerPC/PPCTargetTransformInfo.cpp
index cd9b226ca82dc..2fba090f2d501 100644
--- a/llvm/lib/Target/PowerPC/PPCTargetTransformInfo.cpp
+++ b/llvm/lib/Target/PowerPC/PPCTargetTransformInfo.cpp
@@ -604,19 +604,20 @@ InstructionCost PPCTTIImpl::getArithmeticInstrCost(
}
InstructionCost PPCTTIImpl::getShuffleCost(TTI::ShuffleKind Kind,
- VectorType *Tp, ArrayRef<int> Mask,
+ VectorType *DstTy, VectorType *SrcTy,
+ ArrayRef<int> Mask,
TTI::TargetCostKind CostKind,
int Index, VectorType *SubTp,
ArrayRef<const Value *> Args,
const Instruction *CxtI) const {
InstructionCost CostFactor =
- vectorCostAdjustmentFactor(Instruction::ShuffleVector, Tp, nullptr);
+ vectorCostAdjustmentFactor(Instruction::ShuffleVector, SrcTy, nullptr);
if (!CostFactor.isValid())
return InstructionCost::getMax();
// Legalize the type.
- std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(Tp);
+ std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(SrcTy);
// PPC, for both Altivec/VSX, support cheap arbitrary permutations
// (at least in the sense that there need only be one non-loop-invariant
diff --git a/llvm/lib/Target/PowerPC/PPCTargetTransformInfo.h b/llvm/lib/Target/PowerPC/PPCTargetTransformInfo.h
index bc5f7a4d06de1..475472ac3720f 100644
--- a/llvm/lib/Target/PowerPC/PPCTargetTransformInfo.h
+++ b/llvm/lib/Target/PowerPC/PPCTargetTransformInfo.h
@@ -111,9 +111,9 @@ class PPCTTIImpl final : public BasicTTIImplBase<PPCTTIImpl> {
ArrayRef<const Value *> Args = {},
const Instruction *CxtI = nullptr) const override;
InstructionCost
- getShuffleCost(TTI::ShuffleKind Kind, VectorType *Tp, ArrayRef<int> Mask,
- TTI::TargetCostKind CostKind, int Index, VectorType *SubTp,
- ArrayRef<const Value *> Args = {},
+ getShuffleCost(TTI::ShuffleKind Kind, VectorType *DstTy, VectorType *SrcTy,
+ ArrayRef<int> Mask, TTI::TargetCostKind CostKind, int Index,
+ VectorType *SubTp, ArrayRef<const Value *> Args = {},
const Instruction *CxtI = nullptr) const override;
InstructionCost
getCastInstrCost(unsigned Opcode, Type *Dst, Type *Src,
diff --git a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
index 1b80b0fcaf10a..67a51c12b508e 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
@@ -452,12 +452,16 @@ static InstructionCost costShuffleViaSplitting(const RISCVTTIImpl &TTI,
if (!ReusedSingleSrcShuffles.insert(std::make_pair(RegMask, SrcReg))
.second)
return;
- Cost += TTI.getShuffleCost(TTI::SK_PermuteSingleSrc, SingleOpTy,
- RegMask, CostKind, 0, nullptr);
+ Cost += TTI.getShuffleCost(
+ TTI::SK_PermuteSingleSrc,
+ FixedVectorType::get(SingleOpTy->getElementType(), RegMask.size()),
+ SingleOpTy, RegMask, CostKind, 0, nullptr);
},
[&](ArrayRef<int> RegMask, unsigned Idx1, unsigned Idx2, bool NewReg) {
- Cost += TTI.getShuffleCost(TTI::SK_PermuteTwoSrc, SingleOpTy, RegMask,
- CostKind, 0, nullptr);
+ Cost += TTI.getShuffleCost(
+ TTI::SK_PermuteTwoSrc,
+ FixedVectorType::get(SingleOpTy->getElementType(), RegMask.size()),
+ SingleOpTy, RegMask, CostKind, 0, nullptr);
});
return Cost;
}
@@ -526,11 +530,11 @@ costShuffleViaVRegSplitting(const RISCVTTIImpl &TTI, MVT LegalVT,
return;
++NumShuffles;
Cost += TTI.getShuffleCost(TTI::SK_PermuteSingleSrc, SingleOpTy,
- RegMask, CostKind, 0, nullptr);
+ SingleOpTy, RegMask, CostKind, 0, nullptr);
},
[&](ArrayRef<int> RegMask, unsigned Idx1, unsigned Idx2, bool NewReg) {
- Cost += TTI.getShuffleCost(TTI::SK_PermuteTwoSrc, SingleOpTy, RegMask,
- CostKind, 0, nullptr);
+ Cost += TTI.getShuffleCost(TTI::SK_PermuteTwoSrc, SingleOpTy,
+ SingleOpTy, RegMask, CostKind, 0, nullptr);
NumShuffles += 2;
});
// Note: check that we do not emit too many shuffles here to prevent code
@@ -601,22 +605,29 @@ InstructionCost RISCVTTIImpl::getSlideCost(FixedVectorType *Tp,
return FirstSlideCost + SecondSlideCost + MaskCost;
}
-InstructionCost RISCVTTIImpl::getShuffleCost(TTI::ShuffleKind Kind,
- VectorType *Tp, ArrayRef<int> Mask,
- TTI::TargetCostKind CostKind,
- int Index, VectorType *SubTp,
- ArrayRef<const Value *> Args,
- const Instruction *CxtI) const {
- Kind = improveShuffleKindFromMask(Kind, Mask, Tp, Index, SubTp);
- std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(Tp);
+InstructionCost
+RISCVTTIImpl::getShuffleCost(TTI::ShuffleKind Kind, VectorType *DstTy,
+ VectorType *SrcTy, ArrayRef<int> Mask,
+ TTI::TargetCostKind CostKind, int Index,
+ VectorType *SubTp, ArrayRef<const Value *> Args,
+ const Instruction *CxtI) const {
+ assert((Mask.empty() || DstTy->isScalableTy() ||
+ Mask.size() == DstTy->getElementCount().getKnownMinValue()) &&
+ "Expected the Mask to match the return size if given");
+ assert(SrcTy->getScalarType() == DstTy->getScalarType() &&
+ "Expected the same scalar types");
+
+ Kind = improveShuffleKindFromMask(Kind, Mask, SrcTy, Index, SubTp);
+ std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(SrcTy);
// First, handle cases where having a fixed length vector enables us to
// give a more accurate cost than falling back to generic scalable codegen.
// TODO: Each of these cases hints at a modeling gap around scalable vectors.
- if (auto *FVTp = dyn_cast<FixedVectorType>(Tp);
+ if (auto *FVTp = dyn_cast<FixedVectorType>(SrcTy);
FVTp && ST->hasVInstructions() && LT.second.isFixedLengthVector()) {
InstructionCost VRegSplittingCost = costShuffleViaVRegSplitting(
- *this, LT.second, ST->getRealVLen(), Tp, Mask, CostKind);
+ *this, LT.second, ST->getRealVLen(),
+ Kind == TTI::SK_InsertSubvector ? DstTy : SrcTy, Mask, CostKind);
if (VRegSplittingCost.isValid())
return VRegSplittingCost;
switch (Kind) {
@@ -655,7 +666,7 @@ InstructionCost RISCVTTIImpl::getShuffleCost(TTI::ShuffleKind Kind,
for (unsigned I = 0; I != NumSlides; ++I) {
unsigned InsertIndex = SubVectorSize * (1 << I);
FixedVectorType *SubTp =
- FixedVectorType::get(Tp->getElementType(), InsertIndex);
+ FixedVectorType::get(SrcTy->getElementType(), InsertIndex);
FixedVectorType *DestTp =
FixedVectorType::getDoubleElementsVectorType(SubTp);
std::pair<InstructionCost, MVT> DestLT =
@@ -664,7 +675,7 @@ InstructionCost RISCVTTIImpl::getShuffleCost(TTI::ShuffleKind Kind,
// destination vector register group for vslideup cannot overlap the
// source.
Cost += DestLT.first * TLI->getLMULCost(DestLT.second);
- Cost += getShuffleCost(TTI::SK_InsertSubvector, DestTp, {},
+ Cost += getShuffleCost(TTI::SK_InsertSubvector, DestTp, DestTp, {},
CostKind, InsertIndex, SubTp);
}
return Cost;
@@ -680,7 +691,7 @@ InstructionCost RISCVTTIImpl::getShuffleCost(TTI::ShuffleKind Kind,
if (LT.first == 1 && (LT.second.getScalarSizeInBits() != 8 ||
LT.second.getVectorNumElements() <= 256)) {
VectorType *IdxTy =
- getVRGatherIndexType(LT.second, *ST, Tp->getContext());
+ getVRGatherIndexType(LT.second, *ST, SrcTy->getContext());
InstructionCost IndexCost = getConstantPoolLoadCost(IdxTy, CostKind);
return IndexCost +
getRISCVInstructionCost(RISCV::VRGATHER_VV, LT.second, CostKind);
@@ -699,8 +710,8 @@ InstructionCost RISCVTTIImpl::getShuffleCost(TTI::ShuffleKind Kind,
// (shuffle) mask.
if (LT.first == 1 && (LT.second.getScalarSizeInBits() != 8 ||
LT.second.getVectorNumElements() <= 256)) {
- auto &C = Tp->getContext();
- auto EC = Tp->getElementCount();
+ auto &C = SrcTy->getContext();
+ auto EC = SrcTy->getElementCount();
VectorType *IdxTy = getVRGatherIndexType(LT.second, *ST, C);
VectorType *MaskTy = VectorType::get(IntegerType::getInt1Ty(C), EC);
InstructionCost IndexCost = getConstantPoolLoadCost(IdxTy, CostKind);
@@ -769,6 +780,7 @@ InstructionCost RISCVTTIImpl::getShuffleCost(TTI::ShuffleKind Kind,
// Example sequence:
// vsetivli zero, 4, e8, mf2, tu, ma (ignored)
// vslideup.vi v8, v9, 2
+ LT = getTypeLegalizationCost(DstTy);
return LT.first *
getRISCVInstructionCost(RISCV::VSLIDEUP_VI, LT.second, CostKind);
case TTI::SK_Select: {
@@ -846,14 +858,15 @@ InstructionCost RISCVTTIImpl::getShuffleCost(TTI::ShuffleKind Kind,
// TODO: Cases to improve here:
// * Illegal vector types
// * i64 on RV32
- if (Tp->getElementType()->isIntegerTy(1)) {
+ if (SrcTy->getElementType()->isIntegerTy(1)) {
VectorType *WideTy =
- VectorType::get(IntegerType::get(Tp->getContext(), 8),
- cast<VectorType>(Tp)->getElementCount());
- return getCastInstrCost(Instruction::ZExt, WideTy, Tp,
+ VectorType::get(IntegerType::get(SrcTy->getContext(), 8),
+ cast<VectorType>(SrcTy)->getElementCount());
+ return getCastInstrCost(Instruction::ZExt, WideTy, SrcTy,
TTI::CastContextHint::None, CostKind) +
- getShuffleCost(TTI::SK_Reverse, WideTy, {}, CostKind, 0, nullptr) +
- getCastInstrCost(Instruction::Trunc, Tp, WideTy,
+ getShuffleCost(TTI::SK_Reverse, WideTy, WideTy, {}, CostKind, 0,
+ nullptr) +
+ getCastInstrCost(Instruction::Trunc, SrcTy, WideTy,
TTI::CastContextHint::None, CostKind);
}
@@ -899,7 +912,8 @@ InstructionCost RISCVTTIImpl::getShuffleCost(TTI::ShuffleKind Kind,
return FixedCost + LT.first * (GatherCost + SlideCost);
}
}
- return BaseT::getShuffleCost(Kind, Tp, Mask, CostKind, Index, SubTp);
+ return BaseT::getShuffleCost(Kind, DstTy, SrcTy, Mask, CostKind, Index,
+ SubTp);
}
static unsigned isM1OrSmaller(MVT VT) {
@@ -1025,8 +1039,8 @@ InstructionCost RISCVTTIImpl::getInterleavedMemoryOpCost(
auto Mask = createStrideMask(Index, Factor, VF);
Mask.resize(VF * Factor, -1);
InstructionCost ShuffleCost =
- getShuffleCost(TTI::ShuffleKind::SK_PermuteSingleSrc, VecTy, Mask,
- CostKind, 0, nullptr, {});
+ getShuffleCost(TTI::ShuffleKind::SK_PermuteSingleSrc, VecTy, VecTy,
+ Mask, CostKind, 0, nullptr, {});
Cost += ShuffleCost;
}
return Cost;
@@ -1052,7 +1066,7 @@ InstructionCost RISCVTTIImpl::getInterleavedMemoryOpCost(
// shuffle that goes into the wide store
auto Mask = createInterleaveMask(VF, Factor);
InstructionCost ShuffleCost =
- getShuffleCost(TTI::ShuffleKind::SK_PermuteSingleSrc, FVTy, Mask,
+ getShuffleCost(TTI::ShuffleKind::SK_PermuteSingleSrc, FVTy, FVTy, Mask,
CostKind, 0, nullptr, {});
return MemCost + ShuffleCost;
}
@@ -1523,7 +1537,7 @@ RISCVTTIImpl::getIntrinsicInstrCost(const IntrinsicCostAttributes &ICA,
// To support type-based query from vectorizer, set the index to 0.
// Note that index only change the cost from vslide.vx to vslide.vi and in
// current implementations they have same costs.
- return getShuffleCost(TTI::SK_Splice,
+ return getShuffleCost(TTI::SK_Splice, cast<VectorType>(ICA.getReturnType()),
cast<VectorType>(ICA.getArgTypes()[0]), {}, CostKind,
0, cast<VectorType>(ICA.getReturnType()));
}
diff --git a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
index 83ac71ed9da69..12bf8c1b4de70 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
+++ b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
@@ -160,9 +160,9 @@ class RISCVTTIImpl final : public BasicTTIImplBase<RISCVTTIImpl> {
}
InstructionCost
- getShuffleCost(TTI::ShuffleKind Kind, VectorType *Tp, ArrayRef<int> Mask,
- TTI::TargetCostKind CostKind, int Index, VectorType *SubTp,
- ArrayRef<const Value *> Args = {},
+ getShuffleCost(TTI::ShuffleKind Kind, VectorType *DstTy, VectorType *SrcTy,
+ ArrayRef<int> Mask, TTI::TargetCostKind CostKind, int Index,
+ VectorType *SubTp, ArrayRef<const Value *> Args = {},
const Instruction *CxtI = nullptr) const override;
InstructionCost getScalarizationOverhead(
diff --git a/llvm/lib/Target/SystemZ/SystemZTargetTransformInfo.cpp b/llvm/lib/Target/SystemZ/SystemZTargetTransformInfo.cpp
index 68ba7498d586b..f32c9bd2bdea1 100644
--- a/llvm/lib/Target/SystemZ/SystemZTargetTransformInfo.cpp
+++ b/llvm/lib/Target/SystemZ/SystemZTargetTransformInfo.cpp
@@ -738,20 +738,22 @@ InstructionCost SystemZTTIImpl::getArithmeticInstrCost(
Args, CxtI);
}
-InstructionCost SystemZTTIImpl::getShuffleCost(
- TTI::ShuffleKind Kind, VectorType *Tp, ArrayRef<int> Mask,
- TTI::TargetCostKind CostKind, int Index, VectorType *SubTp,
- ArrayRef<const Value *> Args, const Instruction *CxtI) const {
- Kind = improveShuffleKindFromMask(Kind, Mask, Tp, Index, SubTp);
+InstructionCost
+SystemZTTIImpl::getShuffleCost(TTI::ShuffleKind Kind, VectorType *DstTy,
+ VectorType *SrcTy, ArrayRef<int> Mask,
+ TTI::TargetCostKind CostKind, int Index,
+ VectorType *SubTp, ArrayRef<const Value *> Args,
+ const Instruction *CxtI) const {
+ Kind = improveShuffleKindFromMask(Kind, Mask, SrcTy, Index, SubTp);
if (ST->hasVector()) {
- unsigned NumVectors = getNumVectorRegs(Tp);
+ unsigned NumVectors = getNumVectorRegs(SrcTy);
// TODO: Since fp32 is expanded, the shuffle cost should always be 0.
// FP128 values are always in scalar registers, so there is no work
// involved with a shuffle, except for broadcast. In that case register
// moves are done with a single instruction per element.
- if (Tp->getScalarType()->isFP128Ty())
+ if (SrcTy->getScalarType()->isFP128Ty())
return (Kind == TargetTransformInfo::SK_Broadcast ? NumVectors - 1 : 0);
switch (Kind) {
@@ -775,7 +777,8 @@ InstructionCost SystemZTTIImpl::getShuffleCost(
}
}
- return BaseT::getShuffleCost(Kind, Tp, Mask, CostKind, Index, SubTp);
+ return BaseT::getShuffleCost(Kind, DstTy, SrcTy, Mask, CostKind, Index,
+ SubTp);
}
// Return the log2 difference of the element sizes of the two vector types.
diff --git a/llvm/lib/Target/SystemZ/SystemZTargetTransformInfo.h b/llvm/lib/Target/SystemZ/SystemZTargetTransformInfo.h
index 368a4af768b3e..dc5736e8af009 100644
--- a/llvm/lib/Target/SystemZ/SystemZTargetTransformInfo.h
+++ b/llvm/lib/Target/SystemZ/SystemZTargetTransformInfo.h
@@ -102,9 +102,9 @@ class SystemZTTIImpl final : public BasicTTIImplBase<SystemZTTIImpl> {
ArrayRef<const Value *> Args = {},
const Instruction *CxtI = nullptr) const override;
InstructionCost
- getShuffleCost(TTI::ShuffleKind Kind, VectorType *Tp, ArrayRef<int> Mask,
- TTI::TargetCostKind CostKind, int Index, VectorType *SubTp,
- ArrayRef<const Value *> Args = {},
+ getShuffleCost(TTI::ShuffleKind Kind, VectorType *DstTy, VectorType *SrcTy,
+ ArrayRef<int> Mask, TTI::TargetCostKind CostKind, int Index,
+ VectorType *SubTp, ArrayRef<const Value *> Args = {},
const Instruction *CxtI = nullptr) const override;
unsigned getVectorTruncCost(Type *SrcTy, Type *DstTy) const;
unsigned getVectorBitmaskConversionCost(Type *SrcTy, Type *DstTy) const;
diff --git a/llvm/lib/Target/X86/X86TargetTransformInfo.cpp b/llvm/lib/Target/X86/X86TargetTransformInfo.cpp
index a1a177528eb23..6a05a1700f0cb 100644
--- a/llvm/lib/Target/X86/X86TargetTransformInfo.cpp
+++ b/llvm/lib/Target/X86/X86TargetTransformInfo.cpp
@@ -1522,15 +1522,24 @@ X86TTIImpl::getAltInstrCost(VectorType *VecTy, unsigned Opcode0,
return InstructionCost::getInvalid();
}
-InstructionCost X86TTIImpl::getShuffleCost(
- TTI::ShuffleKind Kind, VectorType *BaseTp, ArrayRef<int> Mask,
- TTI::TargetCostKind CostKind, int Index, VectorType *SubTp,
- ArrayRef<const Value *> Args, const Instruction *CxtI) const {
+InstructionCost X86TTIImpl::getShuffleCost(TTI::ShuffleKind Kind,
+ VectorType *DstTy, VectorType *SrcTy,
+ ArrayRef<int> Mask,
+ TTI::TargetCostKind CostKind,
+ int Index, VectorType *SubTp,
+ ArrayRef<const Value *> Args,
+ const Instruction *CxtI) const {
+ assert((Mask.empty() || DstTy->isScalableTy() ||
+ Mask.size() == DstTy->getElementCount().getKnownMinValue()) &&
+ "Expected the Mask to match the return size if given");
+ assert(SrcTy->getScalarType() == DstTy->getScalarType() &&
+ "Expected the same scalar types");
+
// 64-bit packed float vectors (v2f32) are widened to type v4f32.
// 64-bit packed integer vectors (v2i32) are widened to type v4i32.
- std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(BaseTp);
+ std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(SrcTy);
- Kind = improveShuffleKindFromMask(Kind, Mask, BaseTp, Index, SubTp);
+ Kind = improveShuffleKindFromMask(Kind, Mask, SrcTy, Index, SubTp);
// If all args are constant than this will be constant folded away.
if (!Args.empty() &&
@@ -1539,11 +1548,12 @@ InstructionCost X86TTIImpl::getShuffleCost(
// Recognize a basic concat_vector shuffle.
if (Kind == TTI::SK_PermuteTwoSrc &&
- Mask.size() == (2 * BaseTp->getElementCount().getKnownMinValue()) &&
+ Mask.size() == (2 * SrcTy->getElementCount().getKnownMinValue()) &&
ShuffleVectorInst::isIdentityMask(Mask, Mask.size()))
return getShuffleCost(TTI::SK_InsertSubvector,
- VectorType::getDoubleElementsVectorType(BaseTp), Mask,
- CostKind, Mask.size() / 2, BaseTp);
+ VectorType::getDoubleElementsVectorType(SrcTy),
+ VectorType::getDoubleElementsVectorType(SrcTy), Mask,
+ CostKind, Mask.size() / 2, SrcTy);
// Treat Transpose as 2-op shuffles - there's no difference in lowering.
if (Kind == TTI::SK_Transpose)
@@ -1568,11 +1578,11 @@ InstructionCost X86TTIImpl::getShuffleCost(
// Attempt to detect a shuffle mask with a single defined element.
bool IsInLaneShuffle = false;
bool IsSingleElementMask = false;
- if (BaseTp->getPrimitiveSizeInBits() > 0 &&
- (BaseTp->getPrimitiveSizeInBits() % 128) == 0 &&
- BaseTp->getScalarSizeInBits() == LT.second.getScalarSizeInBits() &&
- Mask.size() == BaseTp->getElementCount().getKnownMinValue()) {
- unsigned NumLanes = BaseTp->getPrimitiveSizeInBits() / 128;
+ if (SrcTy->getPrimitiveSizeInBits() > 0 &&
+ (SrcTy->getPrimitiveSizeInBits() % 128) == 0 &&
+ SrcTy->getScalarSizeInBits() == LT.second.getScalarSizeInBits() &&
+ Mask.size() == SrcTy->getElementCount().getKnownMinValue()) {
+ unsigned NumLanes = SrcTy->getPrimitiveSizeInBits() / 128;
unsigned NumEltsPerLane = Mask.size() / NumLanes;
if ((Mask.size() % NumLanes) == 0) {
IsInLaneShuffle = all_of(enumerate(Mask), [&](const auto &P) {
@@ -1614,16 +1624,17 @@ InstructionCost X86TTIImpl::getShuffleCost(
LT.second.getVectorElementType() ==
SubLT.second.getVectorElementType() &&
LT.second.getVectorElementType().getSizeInBits() ==
- BaseTp->getElementType()->getPrimitiveSizeInBits()) {
+ SrcTy->getElementType()->getPrimitiveSizeInBits()) {
assert(NumElts >= NumSubElts && NumElts > OrigSubElts &&
"Unexpected number of elements!");
- auto *VecTy = FixedVectorType::get(BaseTp->getElementType(),
+ auto *VecTy = FixedVectorType::get(SrcTy->getElementType(),
LT.second.getVectorNumElements());
- auto *SubTy = FixedVectorType::get(BaseTp->getElementType(),
+ auto *SubTy = FixedVectorType::get(SrcTy->getElementType(),
SubLT.second.getVectorNumElements());
int ExtractIndex = alignDown((Index % NumElts), NumSubElts);
- InstructionCost ExtractCost = getShuffleCost(
- TTI::SK_ExtractSubvector, VecTy, {}, CostKind, ExtractIndex, SubTy);
+ InstructionCost ExtractCost =
+ getShuffleCost(TTI::SK_ExtractSubvector, VecTy, VecTy, {}, CostKind,
+ ExtractIndex, SubTy);
// If the original size is 32-bits or more, we can use pshufd. Otherwise
// if we have SSSE3 we can use pshufb.
@@ -1646,7 +1657,8 @@ InstructionCost X86TTIImpl::getShuffleCost(
// but if the destination vector legalizes to the same width as the subvector
// then the insertion will simplify to a (free) register copy.
if (Kind == TTI::SK_InsertSubvector && LT.second.isVector()) {
- int NumElts = LT.second.getVectorNumElements();
+ std::pair<InstructionCost, MVT> DstLT = getTypeLegalizationCost(DstTy);
+ int NumElts = DstLT.second.getVectorNumElements();
std::pair<InstructionCost, MVT> SubLT = getTypeLegalizationCost(SubTp);
if (SubLT.second.isVector()) {
int NumSubElts = SubLT.second.getVectorNumElements();
@@ -1670,7 +1682,7 @@ InstructionCost X86TTIImpl::getShuffleCost(
// Handle some common (illegal) sub-vector types as they are often very cheap
// to shuffle even on targets without PSHUFB.
- EVT VT = TLI->getValueType(DL, BaseTp);
+ EVT VT = TLI->getValueType(DL, SrcTy);
if (VT.isSimple() && VT.isVector() && VT.getSizeInBits() < 128 &&
!ST->hasSSSE3()) {
static const CostKindTblEntry SSE2SubVectorShuffleTbl[] = {
@@ -1717,17 +1729,17 @@ InstructionCost X86TTIImpl::getShuffleCost(
MVT LegalVT = LT.second;
if (LegalVT.isVector() &&
LegalVT.getVectorElementType().getSizeInBits() ==
- BaseTp->getElementType()->getPrimitiveSizeInBits() &&
+ SrcTy->getElementType()->getPrimitiveSizeInBits() &&
LegalVT.getVectorNumElements() <
- cast<FixedVectorType>(BaseTp)->getNumElements()) {
- unsigned VecTySize = DL.getTypeStoreSize(BaseTp);
+ cast<FixedVectorType>(SrcTy)->getNumElements()) {
+ unsigned VecTySize = DL.getTypeStoreSize(SrcTy);
unsigned LegalVTSize = LegalVT.getStoreSize();
// Number of source vectors after legalization:
unsigned NumOfSrcs = (VecTySize + LegalVTSize - 1) / LegalVTSize;
// Number of destination vectors after legalization:
InstructionCost NumOfDests = LT.first;
- auto *SingleOpTy = FixedVectorType::get(BaseTp->getElementType(),
+ auto *SingleOpTy = FixedVectorType::get(SrcTy->getElementType(),
LegalVT.getVectorNumElements());
if (!Mask.empty() && NumOfDests.isValid()) {
@@ -1746,7 +1758,7 @@ InstructionCost X86TTIImpl::getShuffleCost(
// this operation is TTI::TCC_Free.
NumOfDests =
getTypeLegalizationCost(
- FixedVectorType::get(BaseTp->getElementType(), Mask.size()))
+ FixedVectorType::get(SrcTy->getElementType(), Mask.size()))
.first;
unsigned E = NumOfDests.getValue();
unsigned NormalizedVF =
@@ -1767,8 +1779,9 @@ InstructionCost X86TTIImpl::getShuffleCost(
// one.
if (PrevRegMask.empty() || PrevSrcReg != SrcReg ||
PrevRegMask != RegMask)
- Cost += getShuffleCost(TTI::SK_PermuteSingleSrc, SingleOpTy,
- RegMask, CostKind, 0, nullptr);
+ Cost +=
+ getShuffleCost(TTI::SK_PermuteSingleSrc, SingleOpTy,
+ SingleOpTy, RegMask, CostKind, 0, nullptr);
else
// Just a copy of previous destination register.
Cost += TTI::TCC_Basic;
@@ -1785,18 +1798,20 @@ InstructionCost X86TTIImpl::getShuffleCost(
[this, SingleOpTy, CostKind,
&Cost](ArrayRef<int> RegMask, unsigned /*Unused*/,
unsigned /*Unused*/, bool /*Unused*/) {
- Cost += getShuffleCost(TTI::SK_PermuteTwoSrc, SingleOpTy, RegMask,
- CostKind, 0, nullptr);
+ Cost += getShuffleCost(TTI::SK_PermuteTwoSrc, SingleOpTy,
+ SingleOpTy, RegMask, CostKind, 0, nullptr);
});
return Cost;
}
InstructionCost NumOfShuffles = (NumOfSrcs - 1) * NumOfDests;
return NumOfShuffles * getShuffleCost(TTI::SK_PermuteTwoSrc, SingleOpTy,
- {}, CostKind, 0, nullptr);
+ SingleOpTy, {}, CostKind, 0,
+ nullptr);
}
- return BaseT::getShuffleCost(Kind, BaseTp, Mask, CostKind, Index, SubTp);
+ return BaseT::getShuffleCost(Kind, DstTy, SrcTy, Mask, CostKind, Index,
+ SubTp);
}
// If we're just moving a single element around (probably as an alternative to
@@ -2229,7 +2244,7 @@ InstructionCost X86TTIImpl::getShuffleCost(
if (ST->hasSSE3() && IsLoad)
if (const auto *Entry =
CostTableLookup(SSE3BroadcastLoadTbl, Kind, LT.second)) {
- assert(isLegalBroadcastLoad(BaseTp->getElementType(),
+ assert(isLegalBroadcastLoad(SrcTy->getElementType(),
LT.second.getVectorElementCount()) &&
"Table entry missing from isLegalBroadcastLoad()");
return LT.first * Entry->Cost;
@@ -2263,7 +2278,8 @@ InstructionCost X86TTIImpl::getShuffleCost(
return LT.first * *KindCost;
}
- return BaseT::getShuffleCost(Kind, BaseTp, Mask, CostKind, Index, SubTp);
+ return BaseT::getShuffleCost(Kind, DstTy, SrcTy, Mask, CostKind, Index,
+ SubTp);
}
InstructionCost X86TTIImpl::getCastInstrCost(unsigned Opcode, Type *Dst,
@@ -4903,8 +4919,8 @@ InstructionCost X86TTIImpl::getVectorInstrCost(unsigned Opcode, Type *Val,
EVT VT = TLI->getValueType(DL, Val);
if (VT.getScalarType() != MScalarTy || VT.getSizeInBits() >= 128)
SubTy = FixedVectorType::get(ScalarType, SubNumElts);
- ShuffleCost =
- getShuffleCost(TTI::SK_PermuteTwoSrc, SubTy, {}, CostKind, 0, SubTy);
+ ShuffleCost = getShuffleCost(TTI::SK_PermuteTwoSrc, SubTy, SubTy, {},
+ CostKind, 0, SubTy);
}
int IntOrFpCost = ScalarType->isFloatingPointTy() ? 0 : 1;
return ShuffleCost + IntOrFpCost + RegisterFileMoveCost;
@@ -4999,8 +5015,8 @@ InstructionCost X86TTIImpl::getScalarizationOverhead(
// FIXME: we don't need to extract if all non-demanded elements
// are legalization-inserted padding.
if (!LaneEltMask.isAllOnes())
- Cost += getShuffleCost(TTI::SK_ExtractSubvector, Ty, {}, CostKind,
- I * NumEltsPerLane, LaneTy);
+ Cost += getShuffleCost(TTI::SK_ExtractSubvector, Ty, Ty, {},
+ CostKind, I * NumEltsPerLane, LaneTy);
Cost += BaseT::getScalarizationOverhead(LaneTy, LaneEltMask, Insert,
/*Extract*/ false, CostKind);
}
@@ -5017,8 +5033,8 @@ InstructionCost X86TTIImpl::getScalarizationOverhead(
if (!AffectedLanes[I] ||
(Lane == 0 && FullyAffectedLegalVectors[LegalVec]))
continue;
- Cost += getShuffleCost(TTI::SK_InsertSubvector, Ty, {}, CostKind,
- I * NumEltsPerLane, LaneTy);
+ Cost += getShuffleCost(TTI::SK_InsertSubvector, Ty, Ty, {},
+ CostKind, I * NumEltsPerLane, LaneTy);
}
}
}
@@ -5077,7 +5093,7 @@ InstructionCost X86TTIImpl::getScalarizationOverhead(
NumEltsPerLane, I * NumEltsPerLane);
if (LaneEltMask.isZero())
continue;
- Cost += getShuffleCost(TTI::SK_ExtractSubvector, Ty, {}, CostKind,
+ Cost += getShuffleCost(TTI::SK_ExtractSubvector, Ty, Ty, {}, CostKind,
I * NumEltsPerLane, LaneTy);
Cost += BaseT::getScalarizationOverhead(
LaneTy, LaneEltMask, /*Insert*/ false, Extract, CostKind);
@@ -5195,9 +5211,10 @@ X86TTIImpl::getReplicationShuffleCost(Type *EltTy, int ReplicationFactor,
DemandedDstElts.zext(NumDstVectors * NumEltsPerDstVec), NumDstVectors);
unsigned NumDstVectorsDemanded = DemandedDstVectors.popcount();
- InstructionCost SingleShuffleCost = getShuffleCost(
- TTI::SK_PermuteSingleSrc, SingleDstVecTy, /*Mask=*/{}, CostKind,
- /*Index=*/0, /*SubTp=*/nullptr);
+ InstructionCost SingleShuffleCost =
+ getShuffleCost(TTI::SK_PermuteSingleSrc, SingleDstVecTy, SingleDstVecTy,
+ /*Mask=*/{}, CostKind,
+ /*Index=*/0, /*SubTp=*/nullptr);
return NumDstVectorsDemanded * SingleShuffleCost;
}
@@ -5338,9 +5355,10 @@ InstructionCost X86TTIImpl::getMemoryOpCost(unsigned Opcode, Type *Src,
SubVecEltsLeft += CurrVecTy->getNumElements();
// And that's free only for the 0'th subvector of a legalized vector.
if (!Is0thSubVec)
- Cost += getShuffleCost(IsLoad ? TTI::ShuffleKind::SK_InsertSubvector
- : TTI::ShuffleKind::SK_ExtractSubvector,
- VTy, {}, CostKind, NumEltDone(), CurrVecTy);
+ Cost +=
+ getShuffleCost(IsLoad ? TTI::ShuffleKind::SK_InsertSubvector
+ : TTI::ShuffleKind::SK_ExtractSubvector,
+ VTy, VTy, {}, CostKind, NumEltDone(), CurrVecTy);
}
// While we can directly load/store ZMM, YMM, and 64-bit halves of XMM,
@@ -5416,17 +5434,17 @@ X86TTIImpl::getMaskedMemoryOpCost(unsigned Opcode, Type *SrcTy, Align Alignment,
if (VT.isSimple() && Ty != VT.getSimpleVT() &&
LT.second.getVectorNumElements() == NumElem)
// Promotion requires extend/truncate for data and a shuffle for mask.
- Cost +=
- getShuffleCost(TTI::SK_PermuteTwoSrc, SrcVTy, {}, CostKind, 0,
- nullptr) +
- getShuffleCost(TTI::SK_PermuteTwoSrc, MaskTy, {}, CostKind, 0, nullptr);
+ Cost += getShuffleCost(TTI::SK_PermuteTwoSrc, SrcVTy, SrcVTy, {}, CostKind,
+ 0, nullptr) +
+ getShuffleCost(TTI::SK_PermuteTwoSrc, MaskTy, MaskTy, {}, CostKind,
+ 0, nullptr);
else if (LT.first * Ty.getVectorNumElements() > NumElem) {
auto *NewMaskTy = FixedVectorType::get(MaskTy->getElementType(),
Ty.getVectorNumElements());
// Expanding requires fill mask with zeroes
- Cost += getShuffleCost(TTI::SK_InsertSubvector, NewMaskTy, {}, CostKind, 0,
- MaskTy);
+ Cost += getShuffleCost(TTI::SK_InsertSubvector, NewMaskTy, NewMaskTy, {},
+ CostKind, 0, MaskTy);
}
// Pre-AVX512 - each maskmov load costs 2 + store costs ~8.
@@ -5690,7 +5708,7 @@ X86TTIImpl::getArithmeticReductionCost(unsigned Opcode, VectorType *ValTy,
// If we're reducing from 256/512 bits, use an extract_subvector.
if (Size > 128) {
auto *SubTy = FixedVectorType::get(ValVTy->getElementType(), NumVecElts);
- ReductionCost += getShuffleCost(TTI::SK_ExtractSubvector, Ty, {},
+ ReductionCost += getShuffleCost(TTI::SK_ExtractSubvector, Ty, Ty, {},
CostKind, NumVecElts, SubTy);
Ty = SubTy;
} else if (Size == 128) {
@@ -5702,8 +5720,8 @@ X86TTIImpl::getArithmeticReductionCost(unsigned Opcode, VectorType *ValTy,
else
ShufTy =
FixedVectorType::get(Type::getInt64Ty(ValVTy->getContext()), 2);
- ReductionCost += getShuffleCost(TTI::SK_PermuteSingleSrc, ShufTy, {},
- CostKind, 0, nullptr);
+ ReductionCost += getShuffleCost(TTI::SK_PermuteSingleSrc, ShufTy, ShufTy,
+ {}, CostKind, 0, nullptr);
} else if (Size == 64) {
// Reducing from 64 bits is a shuffle of v4f32/v4i32.
FixedVectorType *ShufTy;
@@ -5713,8 +5731,8 @@ X86TTIImpl::getArithmeticReductionCost(unsigned Opcode, VectorType *ValTy,
else
ShufTy =
FixedVectorType::get(Type::getInt32Ty(ValVTy->getContext()), 4);
- ReductionCost += getShuffleCost(TTI::SK_PermuteSingleSrc, ShufTy, {},
- CostKind, 0, nullptr);
+ ReductionCost += getShuffleCost(TTI::SK_PermuteSingleSrc, ShufTy, ShufTy,
+ {}, CostKind, 0, nullptr);
} else {
// Reducing from smaller size is a shift by immediate.
auto *ShiftTy = FixedVectorType::get(
@@ -5872,8 +5890,8 @@ X86TTIImpl::getMinMaxReductionCost(Intrinsic::ID IID, VectorType *ValTy,
// If we're reducing from 256/512 bits, use an extract_subvector.
if (Size > 128) {
auto *SubTy = FixedVectorType::get(ValVTy->getElementType(), NumVecElts);
- MinMaxCost += getShuffleCost(TTI::SK_ExtractSubvector, Ty, {}, CostKind,
- NumVecElts, SubTy);
+ MinMaxCost += getShuffleCost(TTI::SK_ExtractSubvector, Ty, Ty, {},
+ CostKind, NumVecElts, SubTy);
Ty = SubTy;
} else if (Size == 128) {
// Reducing from 128 bits is a permute of v2f64/v2i64.
@@ -5883,7 +5901,7 @@ X86TTIImpl::getMinMaxReductionCost(Intrinsic::ID IID, VectorType *ValTy,
FixedVectorType::get(Type::getDoubleTy(ValTy->getContext()), 2);
else
ShufTy = FixedVectorType::get(Type::getInt64Ty(ValTy->getContext()), 2);
- MinMaxCost += getShuffleCost(TTI::SK_PermuteSingleSrc, ShufTy, {},
+ MinMaxCost += getShuffleCost(TTI::SK_PermuteSingleSrc, ShufTy, ShufTy, {},
CostKind, 0, nullptr);
} else if (Size == 64) {
// Reducing from 64 bits is a shuffle of v4f32/v4i32.
@@ -5892,7 +5910,7 @@ X86TTIImpl::getMinMaxReductionCost(Intrinsic::ID IID, VectorType *ValTy,
ShufTy = FixedVectorType::get(Type::getFloatTy(ValTy->getContext()), 4);
else
ShufTy = FixedVectorType::get(Type::getInt32Ty(ValTy->getContext()), 4);
- MinMaxCost += getShuffleCost(TTI::SK_PermuteSingleSrc, ShufTy, {},
+ MinMaxCost += getShuffleCost(TTI::SK_PermuteSingleSrc, ShufTy, ShufTy, {},
CostKind, 0, nullptr);
} else {
// Reducing from smaller size is a shift by immediate.
@@ -6678,8 +6696,8 @@ InstructionCost X86TTIImpl::getInterleavedMemoryOpCostAVX512(
TTI::ShuffleKind ShuffleKind =
(NumOfMemOps > 1) ? TTI::SK_PermuteTwoSrc : TTI::SK_PermuteSingleSrc;
- InstructionCost ShuffleCost =
- getShuffleCost(ShuffleKind, SingleMemOpTy, {}, CostKind, 0, nullptr);
+ InstructionCost ShuffleCost = getShuffleCost(
+ ShuffleKind, SingleMemOpTy, SingleMemOpTy, {}, CostKind, 0, nullptr);
unsigned NumOfLoadsInInterleaveGrp =
Indices.size() ? Indices.size() : Factor;
@@ -6735,8 +6753,9 @@ InstructionCost X86TTIImpl::getInterleavedMemoryOpCostAVX512(
// There is no strided stores meanwhile. And store can't be folded in
// shuffle.
unsigned NumOfSources = Factor; // The number of values to be merged.
- InstructionCost ShuffleCost = getShuffleCost(
- TTI::SK_PermuteTwoSrc, SingleMemOpTy, {}, CostKind, 0, nullptr);
+ InstructionCost ShuffleCost =
+ getShuffleCost(TTI::SK_PermuteTwoSrc, SingleMemOpTy, SingleMemOpTy, {},
+ CostKind, 0, nullptr);
unsigned NumOfShufflesPerStore = NumOfSources - 1;
// The SK_MergeTwoSrc shuffle clobbers one of src operands.
diff --git a/llvm/lib/Target/X86/X86TargetTransformInfo.h b/llvm/lib/Target/X86/X86TargetTransformInfo.h
index 8045f1b1d6637..bc06c4746c3c4 100644
--- a/llvm/lib/Target/X86/X86TargetTransformInfo.h
+++ b/llvm/lib/Target/X86/X86TargetTransformInfo.h
@@ -149,9 +149,9 @@ class X86TTIImpl final : public BasicTTIImplBase<X86TTIImpl> {
TTI::TargetCostKind CostKind) const override;
InstructionCost
- getShuffleCost(TTI::ShuffleKind Kind, VectorType *Tp, ArrayRef<int> Mask,
- TTI::TargetCostKind CostKind, int Index, VectorType *SubTp,
- ArrayRef<const Value *> Args = {},
+ getShuffleCost(TTI::ShuffleKind Kind, VectorType *DstTy, VectorType *SrcTy,
+ ArrayRef<int> Mask, TTI::TargetCostKind CostKind, int Index,
+ VectorType *SubTp, ArrayRef<const Value *> Args = {},
const Instruction *CxtI = nullptr) const override;
InstructionCost
getCastInstrCost(unsigned Opcode, Type *Dst, Type *Src,
diff --git a/llvm/lib/Transforms/Scalar/LowerMatrixIntrinsics.cpp b/llvm/lib/Transforms/Scalar/LowerMatrixIntrinsics.cpp
index ccb68700747b3..a712b4632e9a8 100644
--- a/llvm/lib/Transforms/Scalar/LowerMatrixIntrinsics.cpp
+++ b/llvm/lib/Transforms/Scalar/LowerMatrixIntrinsics.cpp
@@ -1560,9 +1560,9 @@ class LowerMatrixIntrinsics {
InstructionCost EmbedCost(0);
// Roughly estimate the cost for embedding the columns into a vector.
for (unsigned I = 1; I < N; ++I)
- EmbedCost +=
- TTI.getShuffleCost(TTI::SK_Splice, FixedVectorType::get(EltTy, 1),
- {}, TTI::TCK_RecipThroughput);
+ EmbedCost += TTI.getShuffleCost(
+ TTI::SK_Splice, FixedVectorType::get(EltTy, 1),
+ FixedVectorType::get(EltTy, 1), {}, TTI::TCK_RecipThroughput);
return EmbedCost;
}
@@ -1582,9 +1582,9 @@ class LowerMatrixIntrinsics {
// vector.
InstructionCost EmbedCost(0);
for (unsigned I = 1; I < N; ++I)
- EmbedCost -=
- TTI.getShuffleCost(TTI::SK_Splice, FixedVectorType::get(EltTy, 1),
- {}, TTI::TCK_RecipThroughput);
+ EmbedCost -= TTI.getShuffleCost(
+ TTI::SK_Splice, FixedVectorType::get(EltTy, 1),
+ FixedVectorType::get(EltTy, 1), {}, TTI::TCK_RecipThroughput);
return EmbedCost;
}
diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
index f28c2ce0acc98..f4259d3d69880 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
@@ -5343,8 +5343,8 @@ LoopVectorizationCostModel::getConsecutiveMemOpCost(Instruction *I,
bool Reverse = ConsecutiveStride < 0;
if (Reverse)
- Cost += TTI.getShuffleCost(TargetTransformInfo::SK_Reverse, VectorTy, {},
- CostKind, 0);
+ Cost += TTI.getShuffleCost(TargetTransformInfo::SK_Reverse, VectorTy,
+ VectorTy, {}, CostKind, 0);
return Cost;
}
@@ -5361,8 +5361,8 @@ LoopVectorizationCostModel::getUniformMemOpCost(Instruction *I,
return TTI.getAddressComputationCost(ValTy) +
TTI.getMemoryOpCost(Instruction::Load, ValTy, Alignment, AS,
CostKind) +
- TTI.getShuffleCost(TargetTransformInfo::SK_Broadcast, VectorTy, {},
- CostKind);
+ TTI.getShuffleCost(TargetTransformInfo::SK_Broadcast, VectorTy,
+ VectorTy, {}, CostKind);
}
StoreInst *SI = cast<StoreInst>(I);
@@ -5428,8 +5428,8 @@ LoopVectorizationCostModel::getInterleaveGroupCost(Instruction *I,
assert(!Legal->isMaskRequired(I) &&
"Reverse masked interleaved access not supported.");
Cost += Group->getNumMembers() *
- TTI.getShuffleCost(TargetTransformInfo::SK_Reverse, VectorTy, {},
- CostKind, 0);
+ TTI.getShuffleCost(TargetTransformInfo::SK_Reverse, VectorTy,
+ VectorTy, {}, CostKind, 0);
}
return Cost;
}
@@ -6171,6 +6171,7 @@ LoopVectorizationCostModel::getInstructionCost(Instruction *I,
SmallVector<int> Mask(VF.getKnownMinValue());
std::iota(Mask.begin(), Mask.end(), VF.getKnownMinValue() - 1);
return TTI.getShuffleCost(TargetTransformInfo::SK_Splice,
+ cast<VectorType>(VectorTy),
cast<VectorType>(VectorTy), Mask, CostKind,
VF.getKnownMinValue() - 1);
}
diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index 1141c1b2babbf..cb65c225dcdb6 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -5717,20 +5717,24 @@ getShuffleCost(const TargetTransformInfo &TTI, TTI::ShuffleKind Kind,
TTI::TargetCostKind CostKind = TTI::TCK_RecipThroughput,
int Index = 0, VectorType *SubTp = nullptr,
ArrayRef<const Value *> Args = {}) {
+ VectorType *DstTy = Tp;
+ if (!Mask.empty())
+ DstTy = FixedVectorType::get(Tp->getScalarType(), Mask.size());
+
if (Kind != TTI::SK_PermuteTwoSrc)
- return TTI.getShuffleCost(Kind, Tp, Mask, CostKind, Index, SubTp, Args);
+ return TTI.getShuffleCost(Kind, DstTy, Tp, Mask, CostKind, Index, SubTp,
+ Args);
int NumSrcElts = Tp->getElementCount().getKnownMinValue();
int NumSubElts;
if (Mask.size() > 2 && ShuffleVectorInst::isInsertSubvectorMask(
Mask, NumSrcElts, NumSubElts, Index)) {
if (Index + NumSubElts > NumSrcElts &&
Index + NumSrcElts <= static_cast<int>(Mask.size()))
- return TTI.getShuffleCost(
- TTI::SK_InsertSubvector,
- getWidenedType(Tp->getElementType(), Mask.size()), Mask,
- TTI::TCK_RecipThroughput, Index, Tp);
+ return TTI.getShuffleCost(TTI::SK_InsertSubvector, DstTy, Tp, Mask,
+ TTI::TCK_RecipThroughput, Index, Tp);
}
- return TTI.getShuffleCost(Kind, Tp, Mask, CostKind, Index, SubTp, Args);
+ return TTI.getShuffleCost(Kind, DstTy, Tp, Mask, CostKind, Index, SubTp,
+ Args);
}
/// This is similar to TargetTransformInfo::getScalarizationOverhead, but if
@@ -12036,7 +12040,7 @@ class BoUpSLP::ShuffleCostEstimator : public BaseShuffleAnalysis {
if (isa<FixedVectorType>(ScalarTy)) {
assert(SLPReVec && "FixedVectorType is not expected.");
return TTI.getShuffleCost(
- TTI::SK_InsertSubvector, VecTy, {}, CostKind,
+ TTI::SK_InsertSubvector, VecTy, VecTy, {}, CostKind,
std::distance(VL.begin(), It) * getNumElements(ScalarTy),
cast<FixedVectorType>(ScalarTy));
}
@@ -22995,7 +22999,10 @@ class HorizontalReduction {
unsigned ScalarTyNumElements = VecTy->getNumElements();
for (unsigned I : seq<unsigned>(ReducedVals.size())) {
VectorCost += TTI->getShuffleCost(
- TTI::SK_PermuteSingleSrc, VectorTy,
+ TTI::SK_PermuteSingleSrc,
+ FixedVectorType::get(VecTy->getScalarType(),
+ ReducedVals.size()),
+ VectorTy,
createStrideMask(I, ScalarTyNumElements, ReducedVals.size()));
VectorCost += TTI->getArithmeticReductionCost(RdxOpcode, VecTy,
FMF, CostKind);
diff --git a/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp b/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
index a48ff168efcce..3d237de5fa8de 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
@@ -822,6 +822,7 @@ InstructionCost VPInstruction::computeCost(ElementCount VF,
Type *VectorTy = toVectorTy(Ctx.Types.inferScalarType(this), VF);
return Ctx.TTI.getShuffleCost(TargetTransformInfo::SK_Splice,
+ cast<VectorType>(VectorTy),
cast<VectorType>(VectorTy), Mask,
Ctx.CostKind, VF.getKnownMinValue() - 1);
}
@@ -2869,9 +2870,9 @@ InstructionCost VPWidenMemoryRecipe::computeCost(ElementCount VF,
if (!Reverse)
return Cost;
- return Cost +=
- Ctx.TTI.getShuffleCost(TargetTransformInfo::SK_Reverse,
- cast<VectorType>(Ty), {}, Ctx.CostKind, 0);
+ return Cost += Ctx.TTI.getShuffleCost(
+ TargetTransformInfo::SK_Reverse, cast<VectorType>(Ty),
+ cast<VectorType>(Ty), {}, Ctx.CostKind, 0);
}
void VPWidenLoadRecipe::execute(VPTransformState &State) {
@@ -2985,9 +2986,9 @@ InstructionCost VPWidenLoadEVLRecipe::computeCost(ElementCount VF,
if (!Reverse)
return Cost;
- return Cost + Ctx.TTI.getShuffleCost(TargetTransformInfo::SK_Reverse,
- cast<VectorType>(Ty), {}, Ctx.CostKind,
- 0);
+ return Cost + Ctx.TTI.getShuffleCost(
+ TargetTransformInfo::SK_Reverse, cast<VectorType>(Ty),
+ cast<VectorType>(Ty), {}, Ctx.CostKind, 0);
}
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
@@ -3098,9 +3099,9 @@ InstructionCost VPWidenStoreEVLRecipe::computeCost(ElementCount VF,
if (!Reverse)
return Cost;
- return Cost + Ctx.TTI.getShuffleCost(TargetTransformInfo::SK_Reverse,
- cast<VectorType>(Ty), {}, Ctx.CostKind,
- 0);
+ return Cost + Ctx.TTI.getShuffleCost(
+ TargetTransformInfo::SK_Reverse, cast<VectorType>(Ty),
+ cast<VectorType>(Ty), {}, Ctx.CostKind, 0);
}
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
@@ -3478,7 +3479,8 @@ InstructionCost VPInterleaveRecipe::computeCost(ElementCount VF,
return Cost + IG->getNumMembers() *
Ctx.TTI.getShuffleCost(TargetTransformInfo::SK_Reverse,
- VectorTy, {}, Ctx.CostKind, 0);
+ VectorTy, VectorTy, {}, Ctx.CostKind,
+ 0);
}
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 52cb1dbb33b86..95e1f96c71b48 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -289,8 +289,8 @@ bool VectorCombine::vectorizeLoadInsert(Instruction &I) {
assert(OffsetEltIndex < MinVecNumElts && "Address offset too big");
Mask[0] = OffsetEltIndex;
if (OffsetEltIndex)
- NewCost +=
- TTI.getShuffleCost(TTI::SK_PermuteSingleSrc, MinVecTy, Mask, CostKind);
+ NewCost += TTI.getShuffleCost(TTI::SK_PermuteSingleSrc, Ty, MinVecTy, Mask,
+ CostKind);
// We can aggressively convert to the vector form because the backend can
// invert this transform if it does not result in a performance win.
@@ -510,12 +510,12 @@ bool VectorCombine::isExtractExtractCheap(ExtractElementInst *Ext0,
PoisonMaskElem);
ShuffleMask[BestInsIndex] = BestExtIndex;
NewCost += TTI.getShuffleCost(TargetTransformInfo::SK_PermuteSingleSrc,
- VecTy, ShuffleMask, CostKind, 0, nullptr,
- {ConvertToShuffle});
+ VecTy, VecTy, ShuffleMask, CostKind, 0,
+ nullptr, {ConvertToShuffle});
} else {
- NewCost +=
- TTI.getShuffleCost(TargetTransformInfo::SK_PermuteSingleSrc, VecTy,
- {}, CostKind, 0, nullptr, {ConvertToShuffle});
+ NewCost += TTI.getShuffleCost(TargetTransformInfo::SK_PermuteSingleSrc,
+ VecTy, VecTy, {}, CostKind, 0, nullptr,
+ {ConvertToShuffle});
}
}
@@ -712,8 +712,8 @@ bool VectorCombine::foldInsExtFNeg(Instruction &I) {
InstructionCost NewCost =
TTI.getArithmeticInstrCost(Instruction::FNeg, VecTy, CostKind) +
- TTI.getShuffleCost(TargetTransformInfo::SK_PermuteTwoSrc, VecTy, Mask,
- CostKind);
+ TTI.getShuffleCost(TargetTransformInfo::SK_PermuteTwoSrc, VecTy, VecTy,
+ Mask, CostKind);
bool NeedLenChg = SrcVecTy->getNumElements() != NumElts;
// If the lengths of the two vectors are not equal,
@@ -723,7 +723,7 @@ bool VectorCombine::foldInsExtFNeg(Instruction &I) {
SrcMask.assign(NumElts, PoisonMaskElem);
SrcMask[Index] = Index;
NewCost += TTI.getShuffleCost(TargetTransformInfo::SK_PermuteSingleSrc,
- SrcVecTy, SrcMask, CostKind);
+ VecTy, SrcVecTy, SrcMask, CostKind);
}
if (NewCost > OldCost)
@@ -871,12 +871,12 @@ bool VectorCombine::foldBitcastShuffle(Instruction &I) {
: TargetTransformInfo::SK_PermuteTwoSrc;
InstructionCost NewCost =
- TTI.getShuffleCost(SK, NewShuffleTy, NewMask, CostKind) +
+ TTI.getShuffleCost(SK, DestTy, NewShuffleTy, NewMask, CostKind) +
(NumOps * TTI.getCastInstrCost(Instruction::BitCast, NewShuffleTy, SrcTy,
TargetTransformInfo::CastContextHint::None,
CostKind));
InstructionCost OldCost =
- TTI.getShuffleCost(SK, SrcTy, Mask, CostKind) +
+ TTI.getShuffleCost(SK, OldShuffleTy, SrcTy, Mask, CostKind) +
TTI.getCastInstrCost(Instruction::BitCast, DestTy, OldShuffleTy,
TargetTransformInfo::CastContextHint::None,
CostKind);
@@ -943,7 +943,7 @@ bool VectorCombine::scalarizeVPIntrinsic(Instruction &I) {
Mask.resize(FVTy->getNumElements(), 0);
InstructionCost SplatCost =
TTI.getVectorInstrCost(Instruction::InsertElement, VecTy, CostKind, 0) +
- TTI.getShuffleCost(TargetTransformInfo::SK_Broadcast, VecTy, Mask,
+ TTI.getShuffleCost(TargetTransformInfo::SK_Broadcast, VecTy, VecTy, Mask,
CostKind);
// Calculate the cost of the VP Intrinsic
@@ -1260,14 +1260,13 @@ bool VectorCombine::foldExtractedCmps(Instruction &I) {
// ext (binop vNi1 vcmp, (shuffle vcmp, Index1)), Index0
int CheapIndex = ConvertToShuf == Ext0 ? Index1 : Index0;
int ExpensiveIndex = ConvertToShuf == Ext0 ? Index0 : Index1;
- auto *CmpTy = cast<FixedVectorType>(CmpInst::makeCmpResultType(X->getType()));
+ auto *CmpTy = cast<FixedVectorType>(CmpInst::makeCmpResultType(VecTy));
InstructionCost NewCost = TTI.getCmpSelInstrCost(
- CmpOpcode, X->getType(), CmpInst::makeCmpResultType(X->getType()), Pred,
- CostKind);
+ CmpOpcode, VecTy, CmpInst::makeCmpResultType(VecTy), Pred, CostKind);
SmallVector<int, 32> ShufMask(VecTy->getNumElements(), PoisonMaskElem);
ShufMask[CheapIndex] = ExpensiveIndex;
NewCost += TTI.getShuffleCost(TargetTransformInfo::SK_PermuteSingleSrc, CmpTy,
- ShufMask, CostKind);
+ CmpTy, ShufMask, CostKind);
NewCost += TTI.getArithmeticInstrCost(I.getOpcode(), CmpTy, CostKind);
NewCost += TTI.getVectorInstrCost(*Ext0, CmpTy, CostKind, CheapIndex);
NewCost += Ext0->hasOneUse() ? 0 : Ext0Cost;
@@ -1783,8 +1782,8 @@ bool VectorCombine::foldConcatOfBoolMasks(Instruction &I) {
TTI::CastContextHint::None, CostKind);
InstructionCost NewCost = 0;
- NewCost += TTI.getShuffleCost(TargetTransformInfo::SK_PermuteTwoSrc, MaskTy,
- ConcatMask, CostKind);
+ NewCost += TTI.getShuffleCost(TargetTransformInfo::SK_PermuteTwoSrc, ConcatTy,
+ MaskTy, ConcatMask, CostKind);
NewCost += TTI.getCastInstrCost(Instruction::BitCast, ConcatIntTy, ConcatTy,
TTI::CastContextHint::None, CostKind);
if (Ty != ConcatIntTy)
@@ -1889,26 +1888,28 @@ bool VectorCombine::foldPermuteOfBinops(Instruction &I) {
// Try to merge shuffles across the binop if the new shuffles are not costly.
InstructionCost OldCost =
TTI.getArithmeticInstrCost(Opcode, BinOpTy, CostKind) +
- TTI.getShuffleCost(TargetTransformInfo::SK_PermuteSingleSrc, BinOpTy,
- OuterMask, CostKind, 0, nullptr, {BinOp}, &I);
+ TTI.getShuffleCost(TargetTransformInfo::SK_PermuteSingleSrc, ShuffleDstTy,
+ BinOpTy, OuterMask, CostKind, 0, nullptr, {BinOp}, &I);
if (Match0)
- OldCost += TTI.getShuffleCost(TargetTransformInfo::SK_PermuteTwoSrc, Op0Ty,
- Mask0, CostKind, 0, nullptr, {Op00, Op01},
- cast<Instruction>(BinOp->getOperand(0)));
+ OldCost += TTI.getShuffleCost(
+ TargetTransformInfo::SK_PermuteTwoSrc, BinOpTy, Op0Ty, Mask0, CostKind,
+ 0, nullptr, {Op00, Op01}, cast<Instruction>(BinOp->getOperand(0)));
if (Match1)
- OldCost += TTI.getShuffleCost(TargetTransformInfo::SK_PermuteTwoSrc, Op1Ty,
- Mask1, CostKind, 0, nullptr, {Op10, Op11},
- cast<Instruction>(BinOp->getOperand(1)));
+ OldCost += TTI.getShuffleCost(
+ TargetTransformInfo::SK_PermuteTwoSrc, BinOpTy, Op1Ty, Mask1, CostKind,
+ 0, nullptr, {Op10, Op11}, cast<Instruction>(BinOp->getOperand(1)));
InstructionCost NewCost =
TTI.getArithmeticInstrCost(Opcode, ShuffleDstTy, CostKind);
if (!IsIdentity0)
- NewCost += TTI.getShuffleCost(TargetTransformInfo::SK_PermuteTwoSrc, Op0Ty,
- NewMask0, CostKind, 0, nullptr, {Op00, Op01});
+ NewCost +=
+ TTI.getShuffleCost(TargetTransformInfo::SK_PermuteTwoSrc, ShuffleDstTy,
+ Op0Ty, NewMask0, CostKind, 0, nullptr, {Op00, Op01});
if (!IsIdentity1)
- NewCost += TTI.getShuffleCost(TargetTransformInfo::SK_PermuteTwoSrc, Op1Ty,
- NewMask1, CostKind, 0, nullptr, {Op10, Op11});
+ NewCost +=
+ TTI.getShuffleCost(TargetTransformInfo::SK_PermuteTwoSrc, ShuffleDstTy,
+ Op1Ty, NewMask1, CostKind, 0, nullptr, {Op10, Op11});
LLVM_DEBUG(dbgs() << "Found a shuffle feeding a shuffled binop: " << I
<< "\n OldCost: " << OldCost << " vs NewCost: " << NewCost
@@ -2002,8 +2003,9 @@ bool VectorCombine::foldShuffleOfBinops(Instruction &I) {
InstructionCost OldCost =
TTI.getInstructionCost(LHS, CostKind) +
TTI.getInstructionCost(RHS, CostKind) +
- TTI.getShuffleCost(TargetTransformInfo::SK_PermuteTwoSrc, BinResTy,
- OldMask, CostKind, 0, nullptr, {LHS, RHS}, &I);
+ TTI.getShuffleCost(TargetTransformInfo::SK_PermuteTwoSrc, ShuffleDstTy,
+ BinResTy, OldMask, CostKind, 0, nullptr, {LHS, RHS},
+ &I);
// Handle shuffle(binop(shuffle(x),y),binop(z,shuffle(w))) style patterns
// where one use shuffles have gotten split across the binop/cmp. These
@@ -2035,16 +2037,18 @@ bool VectorCombine::foldShuffleOfBinops(Instruction &I) {
ReducedInstCount |= MergeInner(Z, NumSrcElts, NewMask0, CostKind);
ReducedInstCount |= MergeInner(W, NumSrcElts, NewMask1, CostKind);
+ auto *ShuffleCmpTy =
+ FixedVectorType::get(BinOpTy->getElementType(), ShuffleDstTy);
InstructionCost NewCost =
- TTI.getShuffleCost(SK0, BinOpTy, NewMask0, CostKind, 0, nullptr, {X, Z}) +
- TTI.getShuffleCost(SK1, BinOpTy, NewMask1, CostKind, 0, nullptr, {Y, W});
+ TTI.getShuffleCost(SK0, ShuffleCmpTy, BinOpTy, NewMask0, CostKind, 0,
+ nullptr, {X, Z}) +
+ TTI.getShuffleCost(SK1, ShuffleCmpTy, BinOpTy, NewMask1, CostKind, 0,
+ nullptr, {Y, W});
if (PredLHS == CmpInst::BAD_ICMP_PREDICATE) {
NewCost +=
TTI.getArithmeticInstrCost(LHS->getOpcode(), ShuffleDstTy, CostKind);
} else {
- auto *ShuffleCmpTy =
- FixedVectorType::get(BinOpTy->getElementType(), ShuffleDstTy);
NewCost += TTI.getCmpSelInstrCost(LHS->getOpcode(), ShuffleCmpTy,
ShuffleDstTy, PredLHS, CostKind);
}
@@ -2112,15 +2116,17 @@ bool VectorCombine::foldShuffleOfSelects(Instruction &I) {
SelOp, SrcVecTy, C1VecTy, CmpInst::BAD_ICMP_PREDICATE, CostKind);
OldCost += TTI.getCmpSelInstrCost(SelOp, SrcVecTy, C2VecTy,
CmpInst::BAD_ICMP_PREDICATE, CostKind);
- OldCost += TTI.getShuffleCost(SK, SrcVecTy, Mask, CostKind, 0, nullptr,
- {I.getOperand(0), I.getOperand(1)}, &I);
+ OldCost +=
+ TTI.getShuffleCost(SK, DstVecTy, SrcVecTy, Mask, CostKind, 0, nullptr,
+ {I.getOperand(0), I.getOperand(1)}, &I);
- InstructionCost NewCost =
- TTI.getShuffleCost(SK, C1VecTy, Mask, CostKind, 0, nullptr, {C1, C2});
- NewCost +=
- TTI.getShuffleCost(SK, SrcVecTy, Mask, CostKind, 0, nullptr, {T1, T2});
- NewCost +=
- TTI.getShuffleCost(SK, SrcVecTy, Mask, CostKind, 0, nullptr, {F1, F2});
+ InstructionCost NewCost = TTI.getShuffleCost(
+ SK, FixedVectorType::get(C1VecTy->getScalarType(), Mask.size()), C1VecTy,
+ Mask, CostKind, 0, nullptr, {C1, C2});
+ NewCost += TTI.getShuffleCost(SK, DstVecTy, SrcVecTy, Mask, CostKind, 0,
+ nullptr, {T1, T2});
+ NewCost += TTI.getShuffleCost(SK, DstVecTy, SrcVecTy, Mask, CostKind, 0,
+ nullptr, {F1, F2});
auto *C1C2ShuffledVecTy = cast<FixedVectorType>(
toVectorTy(Type::getInt1Ty(I.getContext()), DstVecTy->getNumElements()));
NewCost += TTI.getCmpSelInstrCost(SelOp, DstVecTy, C1C2ShuffledVecTy,
@@ -2220,11 +2226,12 @@ bool VectorCombine::foldShuffleOfCastops(Instruction &I) {
TTI::CastContextHint::None, CostKind);
InstructionCost OldCost = CostC0 + CostC1;
OldCost +=
- TTI.getShuffleCost(TargetTransformInfo::SK_PermuteTwoSrc, CastDstTy,
- OldMask, CostKind, 0, nullptr, {}, &I);
+ TTI.getShuffleCost(TargetTransformInfo::SK_PermuteTwoSrc, ShuffleDstTy,
+ CastDstTy, OldMask, CostKind, 0, nullptr, {}, &I);
- InstructionCost NewCost = TTI.getShuffleCost(
- TargetTransformInfo::SK_PermuteTwoSrc, CastSrcTy, NewMask, CostKind);
+ InstructionCost NewCost =
+ TTI.getShuffleCost(TargetTransformInfo::SK_PermuteTwoSrc, NewShuffleDstTy,
+ CastSrcTy, NewMask, CostKind);
NewCost += TTI.getCastInstrCost(Opcode, ShuffleDstTy, NewShuffleDstTy,
TTI::CastContextHint::None, CostKind);
if (!C0->hasOneUse())
@@ -2363,8 +2370,9 @@ bool VectorCombine::foldShuffleOfShuffles(Instruction &I) {
TargetTransformInfo::ShuffleKind SK =
IsUnary ? TargetTransformInfo::SK_PermuteSingleSrc
: TargetTransformInfo::SK_PermuteTwoSrc;
- InstructionCost NewCost = TTI.getShuffleCost(
- SK, ShuffleSrcTy, NewMask, CostKind, 0, nullptr, {NewX, NewY});
+ InstructionCost NewCost =
+ TTI.getShuffleCost(SK, ShuffleDstTy, ShuffleSrcTy, NewMask, CostKind, 0,
+ nullptr, {NewX, NewY});
if (!OuterV0->hasOneUse())
NewCost += InnerCost0;
if (!OuterV1->hasOneUse())
@@ -2415,21 +2423,23 @@ bool VectorCombine::foldShuffleOfIntrinsics(Instruction &I) {
InstructionCost OldCost =
TTI.getIntrinsicInstrCost(IntrinsicCostAttributes(IID, *II0), CostKind) +
TTI.getIntrinsicInstrCost(IntrinsicCostAttributes(IID, *II1), CostKind) +
- TTI.getShuffleCost(TargetTransformInfo::SK_PermuteTwoSrc, II0Ty, OldMask,
- CostKind, 0, nullptr, {II0, II1}, &I);
+ TTI.getShuffleCost(TargetTransformInfo::SK_PermuteTwoSrc, ShuffleDstTy,
+ II0Ty, OldMask, CostKind, 0, nullptr, {II0, II1}, &I);
SmallVector<Type *> NewArgsTy;
InstructionCost NewCost = 0;
- for (unsigned I = 0, E = II0->arg_size(); I != E; ++I)
+ for (unsigned I = 0, E = II0->arg_size(); I != E; ++I) {
if (isVectorIntrinsicWithScalarOpAtArg(IID, I, &TTI)) {
NewArgsTy.push_back(II0->getArgOperand(I)->getType());
} else {
auto *VecTy = cast<FixedVectorType>(II0->getArgOperand(I)->getType());
- NewArgsTy.push_back(FixedVectorType::get(VecTy->getElementType(),
- ShuffleDstTy->getNumElements()));
+ auto *ArgTy = FixedVectorType::get(VecTy->getElementType(),
+ ShuffleDstTy->getNumElements());
+ NewArgsTy.push_back(ArgTy);
NewCost += TTI.getShuffleCost(TargetTransformInfo::SK_PermuteTwoSrc,
- VecTy, OldMask, CostKind);
+ ArgTy, VecTy, OldMask, CostKind);
}
+ }
IntrinsicCostAttributes NewAttr(IID, ShuffleDstTy, NewArgsTy);
NewCost += TTI.getIntrinsicInstrCost(NewAttr, CostKind);
@@ -2508,7 +2518,9 @@ static bool isFreeConcat(ArrayRef<InstLane> Item, TTI::TargetCostKind CostKind,
// during legalization.
SmallVector<int, 16> ConcatMask(NumElts * 2);
std::iota(ConcatMask.begin(), ConcatMask.end(), 0);
- if (TTI.getShuffleCost(TTI::SK_PermuteTwoSrc, Ty, ConcatMask, CostKind) != 0)
+ if (TTI.getShuffleCost(TTI::SK_PermuteTwoSrc,
+ FixedVectorType::get(Ty->getScalarType(), NumElts * 2),
+ Ty, ConcatMask, CostKind) != 0)
return false;
unsigned NumSlices = Item.size() / NumElts;
@@ -2877,21 +2889,15 @@ bool VectorCombine::foldShuffleFromReductions(Instruction &I) {
SmallVector<int> ConcatMask;
Shuffle->getShuffleMask(ConcatMask);
sort(ConcatMask, [](int X, int Y) { return (unsigned)X < (unsigned)Y; });
- // In the case of a truncating shuffle it's possible for the mask
- // to have an index greater than the size of the resulting vector.
- // This requires special handling.
- bool IsTruncatingShuffle = VecType->getNumElements() < NumInputElts;
bool UsesSecondVec =
any_of(ConcatMask, [&](int M) { return M >= (int)NumInputElts; });
- FixedVectorType *VecTyForCost =
- (UsesSecondVec && !IsTruncatingShuffle) ? VecType : ShuffleInputType;
InstructionCost OldCost = TTI.getShuffleCost(
- UsesSecondVec ? TTI::SK_PermuteTwoSrc : TTI::SK_PermuteSingleSrc,
- VecTyForCost, Shuffle->getShuffleMask(), CostKind);
+ UsesSecondVec ? TTI::SK_PermuteTwoSrc : TTI::SK_PermuteSingleSrc, VecType,
+ ShuffleInputType, Shuffle->getShuffleMask(), CostKind);
InstructionCost NewCost = TTI.getShuffleCost(
- UsesSecondVec ? TTI::SK_PermuteTwoSrc : TTI::SK_PermuteSingleSrc,
- VecTyForCost, ConcatMask, CostKind);
+ UsesSecondVec ? TTI::SK_PermuteTwoSrc : TTI::SK_PermuteSingleSrc, VecType,
+ ShuffleInputType, ConcatMask, CostKind);
LLVM_DEBUG(dbgs() << "Found a reduction feeding from a shuffle: " << *Shuffle
<< "\n");
@@ -3205,10 +3211,11 @@ bool VectorCombine::foldSelectShuffle(Instruction &I, bool FromReduction) {
return C + TTI.getShuffleCost(isa<UndefValue>(SV->getOperand(1))
? TTI::SK_PermuteSingleSrc
: TTI::SK_PermuteTwoSrc,
- VT, SV->getShuffleMask(), CostKind);
+ VT, VT, SV->getShuffleMask(), CostKind);
};
auto AddShuffleMaskCost = [&](InstructionCost C, ArrayRef<int> Mask) {
- return C + TTI.getShuffleCost(TTI::SK_PermuteTwoSrc, VT, Mask, CostKind);
+ return C +
+ TTI.getShuffleCost(TTI::SK_PermuteTwoSrc, VT, VT, Mask, CostKind);
};
// Get the costs of the shuffles + binops before and after with the new
@@ -3446,8 +3453,8 @@ bool VectorCombine::foldInsExtVectorToShuffle(Instruction &I) {
// Ignore 'free' identity insertion shuffle.
// TODO: getShuffleCost should return TCC_Free for Identity shuffles.
if (!ShuffleVectorInst::isIdentityMask(Mask, NumSrcElts))
- NewCost += TTI.getShuffleCost(SK, DstVecTy, Mask, CostKind, 0, nullptr,
- {DstVec, SrcVec});
+ NewCost += TTI.getShuffleCost(SK, DstVecTy, DstVecTy, Mask, CostKind, 0,
+ nullptr, {DstVec, SrcVec});
} else {
// When creating length-changing-vector, always create with a Mask whose
// first element has an ExtIdx, so that the first element of the vector
@@ -3459,8 +3466,8 @@ bool VectorCombine::foldInsExtVectorToShuffle(Instruction &I) {
ExtToVecMask[0] = ExtIdx;
// Add cost for expanding or narrowing
NewCost = TTI.getShuffleCost(TargetTransformInfo::SK_PermuteSingleSrc,
- SrcVecTy, ExtToVecMask, CostKind);
- NewCost += TTI.getShuffleCost(SK, DstVecTy, Mask, CostKind);
+ DstVecTy, SrcVecTy, ExtToVecMask, CostKind);
+ NewCost += TTI.getShuffleCost(SK, DstVecTy, DstVecTy, Mask, CostKind);
}
if (!Ext->hasOneUse())
>From 831fcb5e91a6dce6260bb177eb15a5036f6e6804 Mon Sep 17 00:00:00 2001
From: Hui <hui.xie1990 at gmail.com>
Date: Sat, 21 Jun 2025 13:41:32 +0100
Subject: [PATCH 06/40] [libc++] constexpr flat_map (#137453)
Fixes #128673
---
libcxx/docs/ReleaseNotes/21.rst | 1 +
libcxx/docs/Status/Cxx2cPapers.csv | 2 +-
libcxx/include/__flat_map/flat_map.h | 391 +++++++++++-------
.../include/__flat_map/key_value_iterator.h | 58 ++-
libcxx/include/__flat_map/utils.h | 4 +-
libcxx/include/module.modulemap.in | 3 +
libcxx/test/std/containers/Emplaceable.h | 16 +-
.../flat.map/flat.map.access/at.pass.cpp | 41 +-
.../flat.map.access/at_transparent.pass.cpp | 40 +-
.../flat.map.access/index_key.pass.cpp | 24 +-
.../flat.map.access/index_rv_key.pass.cpp | 23 +-
.../index_transparent.pass.cpp | 25 +-
.../flat.map/flat.map.capacity/empty.pass.cpp | 22 +-
.../flat.map.capacity/max_size.pass.cpp | 12 +-
.../flat.map/flat.map.capacity/size.pass.cpp | 23 +-
.../flat.map/flat.map.cons/alloc.pass.cpp | 46 ++-
.../assign_initializer_list.pass.cpp | 22 +-
.../flat.map/flat.map.cons/compare.pass.cpp | 108 +++--
.../flat.map.cons/containers.pass.cpp | 153 ++++---
.../flat.map/flat.map.cons/copy.pass.cpp | 32 +-
.../flat.map.cons/copy_alloc.pass.cpp | 58 ++-
.../flat.map.cons/copy_assign.pass.cpp | 35 +-
.../flat.map/flat.map.cons/default.pass.cpp | 40 +-
.../flat.map.cons/default_noexcept.pass.cpp | 16 +-
.../flat.map.cons/dtor_noexcept.pass.cpp | 41 +-
.../flat.map.cons/initializer_list.pass.cpp | 159 +++----
.../flat.map/flat.map.cons/iter_iter.pass.cpp | 189 ++++++---
.../flat.map/flat.map.cons/move.pass.cpp | 35 +-
.../flat.map.cons/move_alloc.pass.cpp | 64 ++-
.../flat.map.cons/move_assign.pass.cpp | 31 +-
.../flat.map.cons/move_assign_clears.pass.cpp | 43 +-
... => move_assign_noexcept.compile.pass.cpp} | 4 +-
.../flat.map/flat.map.cons/range.pass.cpp | 239 +++++++----
.../flat.map.cons/sorted_container.pass.cpp | 118 +++---
.../sorted_initializer_list.pass.cpp | 164 ++++----
.../flat.map.cons/sorted_iter_iter.pass.cpp | 127 ++++--
.../flat.map.erasure/erase_if.pass.cpp | 28 +-
.../flat.map.iterators/iterator.pass.cpp | 20 +-
.../iterator_comparison.pass.cpp | 20 +-
.../reverse_iterator.pass.cpp | 100 +++--
.../flat.map.modifiers/clear.pass.cpp | 20 +-
.../flat.map.modifiers/emplace.pass.cpp | 26 +-
.../flat.map.modifiers/emplace_hint.pass.cpp | 27 +-
.../flat.map.modifiers/erase_iter.pass.cpp | 23 +-
.../erase_iter_iter.pass.cpp | 24 +-
.../flat.map.modifiers/erase_key.pass.cpp | 23 +-
.../erase_key_transparent.pass.cpp | 33 +-
.../flat.map.modifiers/extract.pass.cpp | 23 +-
.../flat.map.modifiers/insert_cv.pass.cpp | 23 +-
.../insert_initializer_list.pass.cpp | 40 +-
.../insert_iter_cv.pass.cpp | 23 +-
.../insert_iter_iter.pass.cpp | 52 ++-
.../insert_iter_rv.pass.cpp | 26 +-
.../insert_or_assign.pass.cpp | 24 +-
.../insert_or_assign_transparent.pass.cpp | 52 ++-
.../flat.map.modifiers/insert_range.pass.cpp | 27 +-
.../flat.map.modifiers/insert_rv.pass.cpp | 24 +-
.../insert_sorted_initializer_list.pass.cpp | 39 +-
.../insert_sorted_iter_iter.pass.cpp | 22 +-
.../insert_transparent.pass.cpp | 159 +++----
.../flat.map.modifiers/replace.pass.cpp | 23 +-
.../flat.map.modifiers/swap_free.pass.cpp | 20 +-
.../flat.map.modifiers/swap_member.pass.cpp | 20 +-
.../flat.map.modifiers/try_emplace.pass.cpp | 94 +++--
.../try_emplace_transparent.pass.cpp | 56 ++-
.../flat.map/flat.map.observers/comp.pass.cpp | 17 +-
.../flat.map.observers/keys_values.pass.cpp | 20 +-
.../flat.map.operations/contains.pass.cpp | 20 +-
.../contains_transparent.pass.cpp | 21 +-
.../flat.map.operations/count.pass.cpp | 20 +-
.../count_transparent.pass.cpp | 20 +-
.../flat.map.operations/equal_range.pass.cpp | 20 +-
.../equal_range_transparent.pass.cpp | 20 +-
.../flat.map.operations/find.pass.cpp | 20 +-
.../find_transparent.pass.cpp | 20 +-
.../flat.map.operations/lower_bound.pass.cpp | 20 +-
.../lower_bound_transparent.pass.cpp | 20 +-
.../flat.map.operations/upper_bound.pass.cpp | 20 +-
.../upper_bound_transparent.pass.cpp | 20 +-
.../container.adaptors/flat.map/helpers.h | 11 +-
.../flat.map/incomplete_type.pass.cpp | 16 +-
.../flat.map/op_compare.pass.cpp | 23 +-
.../container.adaptors/flat_helpers.h | 37 +-
libcxx/test/std/containers/test_compare.h | 18 +-
libcxx/test/support/MinSequenceContainer.h | 51 ++-
85 files changed, 2703 insertions(+), 1231 deletions(-)
rename libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/{move_assign_noexcept.pass.cpp => move_assign_noexcept.compile.pass.cpp} (99%)
diff --git a/libcxx/docs/ReleaseNotes/21.rst b/libcxx/docs/ReleaseNotes/21.rst
index 7e8570691200b..c52bc54f412bd 100644
--- a/libcxx/docs/ReleaseNotes/21.rst
+++ b/libcxx/docs/ReleaseNotes/21.rst
@@ -47,6 +47,7 @@ Implemented Papers
- P1222R4: A Standard ``flat_set`` (`Github <https://github.com/llvm/llvm-project/issues/105193>`__)
- P2897R7: ``aligned_accessor``: An mdspan accessor expressing pointer over-alignment (`Github <https://github.com/llvm/llvm-project/issues/118372>`__)
- P3247R2: Deprecate the notion of trivial types (`Github <https://github.com/llvm/llvm-project/issues/118387>`__)
+- P3372R3: ``constexpr`` containers and adaptors (`Github <https://github.com/llvm/llvm-project/issues/128673>`__) (Only ``constexpr flat_map`` is implemented)
- P2441R2: ``views::join_with`` (`Github <https://github.com/llvm/llvm-project/issues/105185>`__)
- P2711R1: Making multi-param constructors of ``views`` ``explicit`` (`Github <https://github.com/llvm/llvm-project/issues/105252>`__)
- P2770R0: Stashing stashing ``iterators`` for proper flattening (`Github <https://github.com/llvm/llvm-project/issues/105250>`__)
diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv
index 8a0417e120d75..2eb1921069776 100644
--- a/libcxx/docs/Status/Cxx2cPapers.csv
+++ b/libcxx/docs/Status/Cxx2cPapers.csv
@@ -104,7 +104,7 @@
"`P3137R3 <https://wg21.link/P3137R3>`__","``views::to_input``","2025-02 (Hagenberg)","","",""
"`P0472R3 <https://wg21.link/P0472R3>`__","Put ``std::monostate`` in ``<utility>``","2025-02 (Hagenberg)","|Complete|","21",""
"`P3349R1 <https://wg21.link/P3349R1>`__","Converting contiguous iterators to pointers","2025-02 (Hagenberg)","","",""
-"`P3372R3 <https://wg21.link/P3372R3>`__","constexpr containers and adaptors","2025-02 (Hagenberg)","","",""
+"`P3372R3 <https://wg21.link/P3372R3>`__","constexpr containers and adaptors","2025-02 (Hagenberg)","|In Progress|","",""
"`P3378R2 <https://wg21.link/P3378R2>`__","constexpr exception types","2025-02 (Hagenberg)","","",""
"`P3441R2 <https://wg21.link/P3441R2>`__","Rename ``simd_split`` to ``simd_chunk``","2025-02 (Hagenberg)","","",""
"`P3287R3 <https://wg21.link/P3287R3>`__","Exploration of namespaces for ``std::simd``","2025-02 (Hagenberg)","","",""
diff --git a/libcxx/include/__flat_map/flat_map.h b/libcxx/include/__flat_map/flat_map.h
index 8f01882934b7a..bf193f6d3c62f 100644
--- a/libcxx/include/__flat_map/flat_map.h
+++ b/libcxx/include/__flat_map/flat_map.h
@@ -114,11 +114,12 @@ class flat_map {
class value_compare {
private:
_LIBCPP_NO_UNIQUE_ADDRESS key_compare __comp_;
- _LIBCPP_HIDE_FROM_ABI value_compare(key_compare __c) : __comp_(__c) {}
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 value_compare(key_compare __c) : __comp_(__c) {}
friend flat_map;
public:
- _LIBCPP_HIDE_FROM_ABI bool operator()(const_reference __x, const_reference __y) const {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 bool
+ operator()(const_reference __x, const_reference __y) const {
return __comp_(__x.first, __y.first);
}
};
@@ -137,14 +138,14 @@ class flat_map {
public:
// [flat.map.cons], construct/copy/destroy
- _LIBCPP_HIDE_FROM_ABI flat_map() noexcept(
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 flat_map() noexcept(
is_nothrow_default_constructible_v<_KeyContainer> && is_nothrow_default_constructible_v<_MappedContainer> &&
is_nothrow_default_constructible_v<_Compare>)
: __containers_(), __compare_() {}
_LIBCPP_HIDE_FROM_ABI flat_map(const flat_map&) = default;
- _LIBCPP_HIDE_FROM_ABI flat_map(flat_map&& __other) noexcept(
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 flat_map(flat_map&& __other) noexcept(
is_nothrow_move_constructible_v<_KeyContainer> && is_nothrow_move_constructible_v<_MappedContainer> &&
is_nothrow_move_constructible_v<_Compare>)
# if _LIBCPP_HAS_EXCEPTIONS
@@ -165,7 +166,7 @@ class flat_map {
template <class _Allocator>
requires __allocator_ctor_constraint<_Allocator>
- _LIBCPP_HIDE_FROM_ABI flat_map(const flat_map& __other, const _Allocator& __alloc)
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 flat_map(const flat_map& __other, const _Allocator& __alloc)
: flat_map(__ctor_uses_allocator_tag{},
__alloc,
__other.__containers_.keys,
@@ -174,7 +175,7 @@ class flat_map {
template <class _Allocator>
requires __allocator_ctor_constraint<_Allocator>
- _LIBCPP_HIDE_FROM_ABI flat_map(flat_map&& __other, const _Allocator& __alloc)
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 flat_map(flat_map&& __other, const _Allocator& __alloc)
# if _LIBCPP_HAS_EXCEPTIONS
try
# endif // _LIBCPP_HAS_EXCEPTIONS
@@ -191,7 +192,7 @@ class flat_map {
# endif // _LIBCPP_HAS_EXCEPTIONS
}
- _LIBCPP_HIDE_FROM_ABI flat_map(
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 flat_map(
key_container_type __key_cont, mapped_container_type __mapped_cont, const key_compare& __comp = key_compare())
: __containers_{.keys = std::move(__key_cont), .values = std::move(__mapped_cont)}, __compare_(__comp) {
_LIBCPP_ASSERT_VALID_INPUT_RANGE(__containers_.keys.size() == __containers_.values.size(),
@@ -201,7 +202,7 @@ class flat_map {
template <class _Allocator>
requires __allocator_ctor_constraint<_Allocator>
- _LIBCPP_HIDE_FROM_ABI
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26
flat_map(const key_container_type& __key_cont, const mapped_container_type& __mapped_cont, const _Allocator& __alloc)
: flat_map(__ctor_uses_allocator_tag{}, __alloc, __key_cont, __mapped_cont) {
_LIBCPP_ASSERT_VALID_INPUT_RANGE(__containers_.keys.size() == __containers_.values.size(),
@@ -211,7 +212,7 @@ class flat_map {
template <class _Allocator>
requires __allocator_ctor_constraint<_Allocator>
- _LIBCPP_HIDE_FROM_ABI
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26
flat_map(const key_container_type& __key_cont,
const mapped_container_type& __mapped_cont,
const key_compare& __comp,
@@ -222,7 +223,7 @@ class flat_map {
__sort_and_unique();
}
- _LIBCPP_HIDE_FROM_ABI
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26
flat_map(sorted_unique_t,
key_container_type __key_cont,
mapped_container_type __mapped_cont,
@@ -236,7 +237,7 @@ class flat_map {
template <class _Allocator>
requires __allocator_ctor_constraint<_Allocator>
- _LIBCPP_HIDE_FROM_ABI
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26
flat_map(sorted_unique_t,
const key_container_type& __key_cont,
const mapped_container_type& __mapped_cont,
@@ -250,12 +251,12 @@ class flat_map {
template <class _Allocator>
requires __allocator_ctor_constraint<_Allocator>
- _LIBCPP_HIDE_FROM_ABI
- flat_map(sorted_unique_t,
- const key_container_type& __key_cont,
- const mapped_container_type& __mapped_cont,
- const key_compare& __comp,
- const _Allocator& __alloc)
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 flat_map(
+ sorted_unique_t,
+ const key_container_type& __key_cont,
+ const mapped_container_type& __mapped_cont,
+ const key_compare& __comp,
+ const _Allocator& __alloc)
: flat_map(__ctor_uses_allocator_tag{}, __alloc, __key_cont, __mapped_cont, __comp) {
_LIBCPP_ASSERT_VALID_INPUT_RANGE(__containers_.keys.size() == __containers_.values.size(),
"flat_map keys and mapped containers have different size");
@@ -263,21 +264,22 @@ class flat_map {
__is_sorted_and_unique(__containers_.keys), "Either the key container is not sorted or it contains duplicates");
}
- _LIBCPP_HIDE_FROM_ABI explicit flat_map(const key_compare& __comp) : __containers_(), __compare_(__comp) {}
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 explicit flat_map(const key_compare& __comp)
+ : __containers_(), __compare_(__comp) {}
template <class _Allocator>
requires __allocator_ctor_constraint<_Allocator>
- _LIBCPP_HIDE_FROM_ABI flat_map(const key_compare& __comp, const _Allocator& __alloc)
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 flat_map(const key_compare& __comp, const _Allocator& __alloc)
: flat_map(__ctor_uses_allocator_empty_tag{}, __alloc, __comp) {}
template <class _Allocator>
requires __allocator_ctor_constraint<_Allocator>
- _LIBCPP_HIDE_FROM_ABI explicit flat_map(const _Allocator& __alloc)
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 explicit flat_map(const _Allocator& __alloc)
: flat_map(__ctor_uses_allocator_empty_tag{}, __alloc) {}
template <class _InputIterator>
requires __has_input_iterator_category<_InputIterator>::value
- _LIBCPP_HIDE_FROM_ABI
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26
flat_map(_InputIterator __first, _InputIterator __last, const key_compare& __comp = key_compare())
: __containers_(), __compare_(__comp) {
insert(__first, __last);
@@ -285,7 +287,7 @@ class flat_map {
template <class _InputIterator, class _Allocator>
requires(__has_input_iterator_category<_InputIterator>::value && __allocator_ctor_constraint<_Allocator>)
- _LIBCPP_HIDE_FROM_ABI
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26
flat_map(_InputIterator __first, _InputIterator __last, const key_compare& __comp, const _Allocator& __alloc)
: flat_map(__ctor_uses_allocator_empty_tag{}, __alloc, __comp) {
insert(__first, __last);
@@ -293,99 +295,105 @@ class flat_map {
template <class _InputIterator, class _Allocator>
requires(__has_input_iterator_category<_InputIterator>::value && __allocator_ctor_constraint<_Allocator>)
- _LIBCPP_HIDE_FROM_ABI flat_map(_InputIterator __first, _InputIterator __last, const _Allocator& __alloc)
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26
+ flat_map(_InputIterator __first, _InputIterator __last, const _Allocator& __alloc)
: flat_map(__ctor_uses_allocator_empty_tag{}, __alloc) {
insert(__first, __last);
}
template <_ContainerCompatibleRange<value_type> _Range>
- _LIBCPP_HIDE_FROM_ABI flat_map(from_range_t __fr, _Range&& __rg)
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 flat_map(from_range_t __fr, _Range&& __rg)
: flat_map(__fr, std::forward<_Range>(__rg), key_compare()) {}
template <_ContainerCompatibleRange<value_type> _Range, class _Allocator>
requires __allocator_ctor_constraint<_Allocator>
- _LIBCPP_HIDE_FROM_ABI flat_map(from_range_t, _Range&& __rg, const _Allocator& __alloc)
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 flat_map(from_range_t, _Range&& __rg, const _Allocator& __alloc)
: flat_map(__ctor_uses_allocator_empty_tag{}, __alloc) {
insert_range(std::forward<_Range>(__rg));
}
template <_ContainerCompatibleRange<value_type> _Range>
- _LIBCPP_HIDE_FROM_ABI flat_map(from_range_t, _Range&& __rg, const key_compare& __comp) : flat_map(__comp) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 flat_map(from_range_t, _Range&& __rg, const key_compare& __comp)
+ : flat_map(__comp) {
insert_range(std::forward<_Range>(__rg));
}
template <_ContainerCompatibleRange<value_type> _Range, class _Allocator>
requires __allocator_ctor_constraint<_Allocator>
- _LIBCPP_HIDE_FROM_ABI flat_map(from_range_t, _Range&& __rg, const key_compare& __comp, const _Allocator& __alloc)
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26
+ flat_map(from_range_t, _Range&& __rg, const key_compare& __comp, const _Allocator& __alloc)
: flat_map(__ctor_uses_allocator_empty_tag{}, __alloc, __comp) {
insert_range(std::forward<_Range>(__rg));
}
template <class _InputIterator>
requires __has_input_iterator_category<_InputIterator>::value
- _LIBCPP_HIDE_FROM_ABI
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26
flat_map(sorted_unique_t, _InputIterator __first, _InputIterator __last, const key_compare& __comp = key_compare())
: __containers_(), __compare_(__comp) {
insert(sorted_unique, __first, __last);
}
template <class _InputIterator, class _Allocator>
requires(__has_input_iterator_category<_InputIterator>::value && __allocator_ctor_constraint<_Allocator>)
- _LIBCPP_HIDE_FROM_ABI
- flat_map(sorted_unique_t,
- _InputIterator __first,
- _InputIterator __last,
- const key_compare& __comp,
- const _Allocator& __alloc)
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 flat_map(
+ sorted_unique_t,
+ _InputIterator __first,
+ _InputIterator __last,
+ const key_compare& __comp,
+ const _Allocator& __alloc)
: flat_map(__ctor_uses_allocator_empty_tag{}, __alloc, __comp) {
insert(sorted_unique, __first, __last);
}
template <class _InputIterator, class _Allocator>
requires(__has_input_iterator_category<_InputIterator>::value && __allocator_ctor_constraint<_Allocator>)
- _LIBCPP_HIDE_FROM_ABI
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26
flat_map(sorted_unique_t, _InputIterator __first, _InputIterator __last, const _Allocator& __alloc)
: flat_map(__ctor_uses_allocator_empty_tag{}, __alloc) {
insert(sorted_unique, __first, __last);
}
- _LIBCPP_HIDE_FROM_ABI flat_map(initializer_list<value_type> __il, const key_compare& __comp = key_compare())
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26
+ flat_map(initializer_list<value_type> __il, const key_compare& __comp = key_compare())
: flat_map(__il.begin(), __il.end(), __comp) {}
template <class _Allocator>
requires __allocator_ctor_constraint<_Allocator>
- _LIBCPP_HIDE_FROM_ABI
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26
flat_map(initializer_list<value_type> __il, const key_compare& __comp, const _Allocator& __alloc)
: flat_map(__il.begin(), __il.end(), __comp, __alloc) {}
template <class _Allocator>
requires __allocator_ctor_constraint<_Allocator>
- _LIBCPP_HIDE_FROM_ABI flat_map(initializer_list<value_type> __il, const _Allocator& __alloc)
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26
+ flat_map(initializer_list<value_type> __il, const _Allocator& __alloc)
: flat_map(__il.begin(), __il.end(), __alloc) {}
- _LIBCPP_HIDE_FROM_ABI
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26
flat_map(sorted_unique_t, initializer_list<value_type> __il, const key_compare& __comp = key_compare())
: flat_map(sorted_unique, __il.begin(), __il.end(), __comp) {}
template <class _Allocator>
requires __allocator_ctor_constraint<_Allocator>
- _LIBCPP_HIDE_FROM_ABI
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26
flat_map(sorted_unique_t, initializer_list<value_type> __il, const key_compare& __comp, const _Allocator& __alloc)
: flat_map(sorted_unique, __il.begin(), __il.end(), __comp, __alloc) {}
template <class _Allocator>
requires __allocator_ctor_constraint<_Allocator>
- _LIBCPP_HIDE_FROM_ABI flat_map(sorted_unique_t, initializer_list<value_type> __il, const _Allocator& __alloc)
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26
+ flat_map(sorted_unique_t, initializer_list<value_type> __il, const _Allocator& __alloc)
: flat_map(sorted_unique, __il.begin(), __il.end(), __alloc) {}
- _LIBCPP_HIDE_FROM_ABI flat_map& operator=(initializer_list<value_type> __il) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 flat_map& operator=(initializer_list<value_type> __il) {
clear();
insert(__il);
return *this;
}
- _LIBCPP_HIDE_FROM_ABI flat_map& operator=(const flat_map&) = default;
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 flat_map& operator=(const flat_map&) = default;
- _LIBCPP_HIDE_FROM_ABI flat_map& operator=(flat_map&& __other) noexcept(
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 flat_map& operator=(flat_map&& __other) noexcept(
is_nothrow_move_assignable_v<_KeyContainer> && is_nothrow_move_assignable_v<_MappedContainer> &&
is_nothrow_move_assignable_v<_Compare>) {
// No matter what happens, we always want to clear the other container before returning
@@ -402,49 +410,65 @@ class flat_map {
}
// iterators
- _LIBCPP_HIDE_FROM_ABI iterator begin() noexcept {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator begin() noexcept {
return iterator(__containers_.keys.begin(), __containers_.values.begin());
}
- _LIBCPP_HIDE_FROM_ABI const_iterator begin() const noexcept {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 const_iterator begin() const noexcept {
return const_iterator(__containers_.keys.begin(), __containers_.values.begin());
}
- _LIBCPP_HIDE_FROM_ABI iterator end() noexcept {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator end() noexcept {
return iterator(__containers_.keys.end(), __containers_.values.end());
}
- _LIBCPP_HIDE_FROM_ABI const_iterator end() const noexcept {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 const_iterator end() const noexcept {
return const_iterator(__containers_.keys.end(), __containers_.values.end());
}
- _LIBCPP_HIDE_FROM_ABI reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
- _LIBCPP_HIDE_FROM_ABI const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); }
- _LIBCPP_HIDE_FROM_ABI reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
- _LIBCPP_HIDE_FROM_ABI const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 reverse_iterator rbegin() noexcept {
+ return reverse_iterator(end());
+ }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 const_reverse_iterator rbegin() const noexcept {
+ return const_reverse_iterator(end());
+ }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 reverse_iterator rend() noexcept {
+ return reverse_iterator(begin());
+ }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 const_reverse_iterator rend() const noexcept {
+ return const_reverse_iterator(begin());
+ }
- _LIBCPP_HIDE_FROM_ABI const_iterator cbegin() const noexcept { return begin(); }
- _LIBCPP_HIDE_FROM_ABI const_iterator cend() const noexcept { return end(); }
- _LIBCPP_HIDE_FROM_ABI const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(end()); }
- _LIBCPP_HIDE_FROM_ABI const_reverse_iterator crend() const noexcept { return const_reverse_iterator(begin()); }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 const_iterator cbegin() const noexcept { return begin(); }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 const_iterator cend() const noexcept { return end(); }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 const_reverse_iterator crbegin() const noexcept {
+ return const_reverse_iterator(end());
+ }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 const_reverse_iterator crend() const noexcept {
+ return const_reverse_iterator(begin());
+ }
// [flat.map.capacity], capacity
- [[nodiscard]] _LIBCPP_HIDE_FROM_ABI bool empty() const noexcept { return __containers_.keys.empty(); }
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 bool empty() const noexcept {
+ return __containers_.keys.empty();
+ }
- _LIBCPP_HIDE_FROM_ABI size_type size() const noexcept { return __containers_.keys.size(); }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 size_type size() const noexcept {
+ return __containers_.keys.size();
+ }
- _LIBCPP_HIDE_FROM_ABI size_type max_size() const noexcept {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 size_type max_size() const noexcept {
return std::min<size_type>(__containers_.keys.max_size(), __containers_.values.max_size());
}
// [flat.map.access], element access
- _LIBCPP_HIDE_FROM_ABI mapped_type& operator[](const key_type& __x)
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 mapped_type& operator[](const key_type& __x)
requires is_constructible_v<mapped_type>
{
return try_emplace(__x).first->second;
}
- _LIBCPP_HIDE_FROM_ABI mapped_type& operator[](key_type&& __x)
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 mapped_type& operator[](key_type&& __x)
requires is_constructible_v<mapped_type>
{
return try_emplace(std::move(__x)).first->second;
@@ -453,11 +477,11 @@ class flat_map {
template <class _Kp>
requires(__is_compare_transparent && is_constructible_v<key_type, _Kp> && is_constructible_v<mapped_type> &&
!is_convertible_v<_Kp &&, const_iterator> && !is_convertible_v<_Kp &&, iterator>)
- _LIBCPP_HIDE_FROM_ABI mapped_type& operator[](_Kp&& __x) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 mapped_type& operator[](_Kp&& __x) {
return try_emplace(std::forward<_Kp>(__x)).first->second;
}
- _LIBCPP_HIDE_FROM_ABI mapped_type& at(const key_type& __x) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 mapped_type& at(const key_type& __x) {
auto __it = find(__x);
if (__it == end()) {
std::__throw_out_of_range("flat_map::at(const key_type&): Key does not exist");
@@ -465,7 +489,7 @@ class flat_map {
return __it->second;
}
- _LIBCPP_HIDE_FROM_ABI const mapped_type& at(const key_type& __x) const {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 const mapped_type& at(const key_type& __x) const {
auto __it = find(__x);
if (__it == end()) {
std::__throw_out_of_range("flat_map::at(const key_type&) const: Key does not exist");
@@ -475,7 +499,7 @@ class flat_map {
template <class _Kp>
requires __is_compare_transparent
- _LIBCPP_HIDE_FROM_ABI mapped_type& at(const _Kp& __x) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 mapped_type& at(const _Kp& __x) {
auto __it = find(__x);
if (__it == end()) {
std::__throw_out_of_range("flat_map::at(const K&): Key does not exist");
@@ -485,7 +509,7 @@ class flat_map {
template <class _Kp>
requires __is_compare_transparent
- _LIBCPP_HIDE_FROM_ABI const mapped_type& at(const _Kp& __x) const {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 const mapped_type& at(const _Kp& __x) const {
auto __it = find(__x);
if (__it == end()) {
std::__throw_out_of_range("flat_map::at(const K&) const: Key does not exist");
@@ -496,45 +520,49 @@ class flat_map {
// [flat.map.modifiers], modifiers
template <class... _Args>
requires is_constructible_v<pair<key_type, mapped_type>, _Args...>
- _LIBCPP_HIDE_FROM_ABI pair<iterator, bool> emplace(_Args&&... __args) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 pair<iterator, bool> emplace(_Args&&... __args) {
std::pair<key_type, mapped_type> __pair(std::forward<_Args>(__args)...);
return __try_emplace(std::move(__pair.first), std::move(__pair.second));
}
template <class... _Args>
requires is_constructible_v<pair<key_type, mapped_type>, _Args...>
- _LIBCPP_HIDE_FROM_ABI iterator emplace_hint(const_iterator __hint, _Args&&... __args) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator emplace_hint(const_iterator __hint, _Args&&... __args) {
std::pair<key_type, mapped_type> __pair(std::forward<_Args>(__args)...);
return __try_emplace_hint(__hint, std::move(__pair.first), std::move(__pair.second)).first;
}
- _LIBCPP_HIDE_FROM_ABI pair<iterator, bool> insert(const value_type& __x) { return emplace(__x); }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 pair<iterator, bool> insert(const value_type& __x) {
+ return emplace(__x);
+ }
- _LIBCPP_HIDE_FROM_ABI pair<iterator, bool> insert(value_type&& __x) { return emplace(std::move(__x)); }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 pair<iterator, bool> insert(value_type&& __x) {
+ return emplace(std::move(__x));
+ }
- _LIBCPP_HIDE_FROM_ABI iterator insert(const_iterator __hint, const value_type& __x) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator insert(const_iterator __hint, const value_type& __x) {
return emplace_hint(__hint, __x);
}
- _LIBCPP_HIDE_FROM_ABI iterator insert(const_iterator __hint, value_type&& __x) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator insert(const_iterator __hint, value_type&& __x) {
return emplace_hint(__hint, std::move(__x));
}
template <class _PairLike>
requires is_constructible_v<pair<key_type, mapped_type>, _PairLike>
- _LIBCPP_HIDE_FROM_ABI pair<iterator, bool> insert(_PairLike&& __x) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 pair<iterator, bool> insert(_PairLike&& __x) {
return emplace(std::forward<_PairLike>(__x));
}
template <class _PairLike>
requires is_constructible_v<pair<key_type, mapped_type>, _PairLike>
- _LIBCPP_HIDE_FROM_ABI iterator insert(const_iterator __hint, _PairLike&& __x) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator insert(const_iterator __hint, _PairLike&& __x) {
return emplace_hint(__hint, std::forward<_PairLike>(__x));
}
template <class _InputIterator>
requires __has_input_iterator_category<_InputIterator>::value
- _LIBCPP_HIDE_FROM_ABI void insert(_InputIterator __first, _InputIterator __last) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void insert(_InputIterator __first, _InputIterator __last) {
if constexpr (sized_sentinel_for<_InputIterator, _InputIterator>) {
__reserve(__last - __first);
}
@@ -543,7 +571,8 @@ class flat_map {
template <class _InputIterator>
requires __has_input_iterator_category<_InputIterator>::value
- _LIBCPP_HIDE_FROM_ABI void insert(sorted_unique_t, _InputIterator __first, _InputIterator __last) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void
+ insert(sorted_unique_t, _InputIterator __first, _InputIterator __last) {
if constexpr (sized_sentinel_for<_InputIterator, _InputIterator>) {
__reserve(__last - __first);
}
@@ -552,7 +581,7 @@ class flat_map {
}
template <_ContainerCompatibleRange<value_type> _Range>
- _LIBCPP_HIDE_FROM_ABI void insert_range(_Range&& __range) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void insert_range(_Range&& __range) {
if constexpr (ranges::sized_range<_Range>) {
__reserve(ranges::size(__range));
}
@@ -560,19 +589,22 @@ class flat_map {
__append_sort_merge_unique</*WasSorted = */ false>(ranges::begin(__range), ranges::end(__range));
}
- _LIBCPP_HIDE_FROM_ABI void insert(initializer_list<value_type> __il) { insert(__il.begin(), __il.end()); }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void insert(initializer_list<value_type> __il) {
+ insert(__il.begin(), __il.end());
+ }
- _LIBCPP_HIDE_FROM_ABI void insert(sorted_unique_t, initializer_list<value_type> __il) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void insert(sorted_unique_t, initializer_list<value_type> __il) {
insert(sorted_unique, __il.begin(), __il.end());
}
- _LIBCPP_HIDE_FROM_ABI containers extract() && {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 containers extract() && {
auto __guard = std::__make_scope_guard([&]() noexcept { clear() /* noexcept */; });
auto __ret = std::move(__containers_);
return __ret;
}
- _LIBCPP_HIDE_FROM_ABI void replace(key_container_type&& __key_cont, mapped_container_type&& __mapped_cont) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void
+ replace(key_container_type&& __key_cont, mapped_container_type&& __mapped_cont) {
_LIBCPP_ASSERT_VALID_INPUT_RANGE(
__key_cont.size() == __mapped_cont.size(), "flat_map keys and mapped containers have different size");
@@ -586,13 +618,15 @@ class flat_map {
template <class... _Args>
requires is_constructible_v<mapped_type, _Args...>
- _LIBCPP_HIDE_FROM_ABI pair<iterator, bool> try_emplace(const key_type& __key, _Args&&... __args) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 pair<iterator, bool>
+ try_emplace(const key_type& __key, _Args&&... __args) {
return __try_emplace(__key, std::forward<_Args>(__args)...);
}
template <class... _Args>
requires is_constructible_v<mapped_type, _Args...>
- _LIBCPP_HIDE_FROM_ABI pair<iterator, bool> try_emplace(key_type&& __key, _Args&&... __args) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 pair<iterator, bool>
+ try_emplace(key_type&& __key, _Args&&... __args) {
return __try_emplace(std::move(__key), std::forward<_Args>(__args)...);
}
@@ -600,75 +634,84 @@ class flat_map {
requires(__is_compare_transparent && is_constructible_v<key_type, _Kp> &&
is_constructible_v<mapped_type, _Args...> && !is_convertible_v<_Kp &&, const_iterator> &&
!is_convertible_v<_Kp &&, iterator>)
- _LIBCPP_HIDE_FROM_ABI pair<iterator, bool> try_emplace(_Kp&& __key, _Args&&... __args) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 pair<iterator, bool> try_emplace(_Kp&& __key, _Args&&... __args) {
return __try_emplace(std::forward<_Kp>(__key), std::forward<_Args>(__args)...);
}
template <class... _Args>
requires is_constructible_v<mapped_type, _Args...>
- _LIBCPP_HIDE_FROM_ABI iterator try_emplace(const_iterator __hint, const key_type& __key, _Args&&... __args) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator
+ try_emplace(const_iterator __hint, const key_type& __key, _Args&&... __args) {
return __try_emplace_hint(__hint, __key, std::forward<_Args>(__args)...).first;
}
template <class... _Args>
requires is_constructible_v<mapped_type, _Args...>
- _LIBCPP_HIDE_FROM_ABI iterator try_emplace(const_iterator __hint, key_type&& __key, _Args&&... __args) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator
+ try_emplace(const_iterator __hint, key_type&& __key, _Args&&... __args) {
return __try_emplace_hint(__hint, std::move(__key), std::forward<_Args>(__args)...).first;
}
template <class _Kp, class... _Args>
requires __is_compare_transparent && is_constructible_v<key_type, _Kp> && is_constructible_v<mapped_type, _Args...>
- _LIBCPP_HIDE_FROM_ABI iterator try_emplace(const_iterator __hint, _Kp&& __key, _Args&&... __args) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator
+ try_emplace(const_iterator __hint, _Kp&& __key, _Args&&... __args) {
return __try_emplace_hint(__hint, std::forward<_Kp>(__key), std::forward<_Args>(__args)...).first;
}
template <class _Mapped>
requires is_assignable_v<mapped_type&, _Mapped> && is_constructible_v<mapped_type, _Mapped>
- _LIBCPP_HIDE_FROM_ABI pair<iterator, bool> insert_or_assign(const key_type& __key, _Mapped&& __obj) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 pair<iterator, bool>
+ insert_or_assign(const key_type& __key, _Mapped&& __obj) {
return __insert_or_assign(__key, std::forward<_Mapped>(__obj));
}
template <class _Mapped>
requires is_assignable_v<mapped_type&, _Mapped> && is_constructible_v<mapped_type, _Mapped>
- _LIBCPP_HIDE_FROM_ABI pair<iterator, bool> insert_or_assign(key_type&& __key, _Mapped&& __obj) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 pair<iterator, bool>
+ insert_or_assign(key_type&& __key, _Mapped&& __obj) {
return __insert_or_assign(std::move(__key), std::forward<_Mapped>(__obj));
}
template <class _Kp, class _Mapped>
requires __is_compare_transparent && is_constructible_v<key_type, _Kp> && is_assignable_v<mapped_type&, _Mapped> &&
is_constructible_v<mapped_type, _Mapped>
- _LIBCPP_HIDE_FROM_ABI pair<iterator, bool> insert_or_assign(_Kp&& __key, _Mapped&& __obj) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 pair<iterator, bool>
+ insert_or_assign(_Kp&& __key, _Mapped&& __obj) {
return __insert_or_assign(std::forward<_Kp>(__key), std::forward<_Mapped>(__obj));
}
template <class _Mapped>
requires is_assignable_v<mapped_type&, _Mapped> && is_constructible_v<mapped_type, _Mapped>
- _LIBCPP_HIDE_FROM_ABI iterator insert_or_assign(const_iterator __hint, const key_type& __key, _Mapped&& __obj) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator
+ insert_or_assign(const_iterator __hint, const key_type& __key, _Mapped&& __obj) {
return __insert_or_assign(__hint, __key, std::forward<_Mapped>(__obj));
}
template <class _Mapped>
requires is_assignable_v<mapped_type&, _Mapped> && is_constructible_v<mapped_type, _Mapped>
- _LIBCPP_HIDE_FROM_ABI iterator insert_or_assign(const_iterator __hint, key_type&& __key, _Mapped&& __obj) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator
+ insert_or_assign(const_iterator __hint, key_type&& __key, _Mapped&& __obj) {
return __insert_or_assign(__hint, std::move(__key), std::forward<_Mapped>(__obj));
}
template <class _Kp, class _Mapped>
requires __is_compare_transparent && is_constructible_v<key_type, _Kp> && is_assignable_v<mapped_type&, _Mapped> &&
is_constructible_v<mapped_type, _Mapped>
- _LIBCPP_HIDE_FROM_ABI iterator insert_or_assign(const_iterator __hint, _Kp&& __key, _Mapped&& __obj) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator
+ insert_or_assign(const_iterator __hint, _Kp&& __key, _Mapped&& __obj) {
return __insert_or_assign(__hint, std::forward<_Kp>(__key), std::forward<_Mapped>(__obj));
}
- _LIBCPP_HIDE_FROM_ABI iterator erase(iterator __position) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator erase(iterator __position) {
return __erase(__position.__key_iter_, __position.__mapped_iter_);
}
- _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __position) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator erase(const_iterator __position) {
return __erase(__position.__key_iter_, __position.__mapped_iter_);
}
- _LIBCPP_HIDE_FROM_ABI size_type erase(const key_type& __x) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 size_type erase(const key_type& __x) {
auto __iter = find(__x);
if (__iter != end()) {
erase(__iter);
@@ -680,14 +723,14 @@ class flat_map {
template <class _Kp>
requires(__is_compare_transparent && !is_convertible_v<_Kp &&, iterator> &&
!is_convertible_v<_Kp &&, const_iterator>)
- _LIBCPP_HIDE_FROM_ABI size_type erase(_Kp&& __x) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 size_type erase(_Kp&& __x) {
auto [__first, __last] = equal_range(__x);
auto __res = __last - __first;
erase(__first, __last);
return __res;
}
- _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __first, const_iterator __last) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator erase(const_iterator __first, const_iterator __last) {
auto __on_failure = std::__make_exception_guard([&]() noexcept { clear() /* noexcept */; });
auto __key_it = __containers_.keys.erase(__first.__key_iter_, __last.__key_iter_);
auto __mapped_it = __containers_.values.erase(__first.__mapped_iter_, __last.__mapped_iter_);
@@ -695,7 +738,7 @@ class flat_map {
return iterator(std::move(__key_it), std::move(__mapped_it));
}
- _LIBCPP_HIDE_FROM_ABI void swap(flat_map& __y) noexcept {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void swap(flat_map& __y) noexcept {
// warning: The spec has unconditional noexcept, which means that
// if any of the following functions throw an exception,
// std::terminate will be called.
@@ -705,133 +748,156 @@ class flat_map {
ranges::swap(__containers_.values, __y.__containers_.values);
}
- _LIBCPP_HIDE_FROM_ABI void clear() noexcept {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void clear() noexcept {
__containers_.keys.clear();
__containers_.values.clear();
}
// observers
- _LIBCPP_HIDE_FROM_ABI key_compare key_comp() const { return __compare_; }
- _LIBCPP_HIDE_FROM_ABI value_compare value_comp() const { return value_compare(__compare_); }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 key_compare key_comp() const { return __compare_; }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 value_compare value_comp() const {
+ return value_compare(__compare_);
+ }
- _LIBCPP_HIDE_FROM_ABI const key_container_type& keys() const noexcept { return __containers_.keys; }
- _LIBCPP_HIDE_FROM_ABI const mapped_container_type& values() const noexcept { return __containers_.values; }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 const key_container_type& keys() const noexcept {
+ return __containers_.keys;
+ }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 const mapped_container_type& values() const noexcept {
+ return __containers_.values;
+ }
// map operations
- _LIBCPP_HIDE_FROM_ABI iterator find(const key_type& __x) { return __find_impl(*this, __x); }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator find(const key_type& __x) {
+ return __find_impl(*this, __x);
+ }
- _LIBCPP_HIDE_FROM_ABI const_iterator find(const key_type& __x) const { return __find_impl(*this, __x); }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 const_iterator find(const key_type& __x) const {
+ return __find_impl(*this, __x);
+ }
template <class _Kp>
requires __is_compare_transparent
- _LIBCPP_HIDE_FROM_ABI iterator find(const _Kp& __x) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator find(const _Kp& __x) {
return __find_impl(*this, __x);
}
template <class _Kp>
requires __is_compare_transparent
- _LIBCPP_HIDE_FROM_ABI const_iterator find(const _Kp& __x) const {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 const_iterator find(const _Kp& __x) const {
return __find_impl(*this, __x);
}
- _LIBCPP_HIDE_FROM_ABI size_type count(const key_type& __x) const { return contains(__x) ? 1 : 0; }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 size_type count(const key_type& __x) const {
+ return contains(__x) ? 1 : 0;
+ }
template <class _Kp>
requires __is_compare_transparent
- _LIBCPP_HIDE_FROM_ABI size_type count(const _Kp& __x) const {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 size_type count(const _Kp& __x) const {
return contains(__x) ? 1 : 0;
}
- _LIBCPP_HIDE_FROM_ABI bool contains(const key_type& __x) const { return find(__x) != end(); }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 bool contains(const key_type& __x) const {
+ return find(__x) != end();
+ }
template <class _Kp>
requires __is_compare_transparent
- _LIBCPP_HIDE_FROM_ABI bool contains(const _Kp& __x) const {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 bool contains(const _Kp& __x) const {
return find(__x) != end();
}
- _LIBCPP_HIDE_FROM_ABI iterator lower_bound(const key_type& __x) { return __lower_bound<iterator>(*this, __x); }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator lower_bound(const key_type& __x) {
+ return __lower_bound<iterator>(*this, __x);
+ }
- _LIBCPP_HIDE_FROM_ABI const_iterator lower_bound(const key_type& __x) const {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 const_iterator lower_bound(const key_type& __x) const {
return __lower_bound<const_iterator>(*this, __x);
}
template <class _Kp>
requires __is_compare_transparent
- _LIBCPP_HIDE_FROM_ABI iterator lower_bound(const _Kp& __x) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator lower_bound(const _Kp& __x) {
return __lower_bound<iterator>(*this, __x);
}
template <class _Kp>
requires __is_compare_transparent
- _LIBCPP_HIDE_FROM_ABI const_iterator lower_bound(const _Kp& __x) const {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 const_iterator lower_bound(const _Kp& __x) const {
return __lower_bound<const_iterator>(*this, __x);
}
- _LIBCPP_HIDE_FROM_ABI iterator upper_bound(const key_type& __x) { return __upper_bound<iterator>(*this, __x); }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator upper_bound(const key_type& __x) {
+ return __upper_bound<iterator>(*this, __x);
+ }
- _LIBCPP_HIDE_FROM_ABI const_iterator upper_bound(const key_type& __x) const {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 const_iterator upper_bound(const key_type& __x) const {
return __upper_bound<const_iterator>(*this, __x);
}
template <class _Kp>
requires __is_compare_transparent
- _LIBCPP_HIDE_FROM_ABI iterator upper_bound(const _Kp& __x) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator upper_bound(const _Kp& __x) {
return __upper_bound<iterator>(*this, __x);
}
template <class _Kp>
requires __is_compare_transparent
- _LIBCPP_HIDE_FROM_ABI const_iterator upper_bound(const _Kp& __x) const {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 const_iterator upper_bound(const _Kp& __x) const {
return __upper_bound<const_iterator>(*this, __x);
}
- _LIBCPP_HIDE_FROM_ABI pair<iterator, iterator> equal_range(const key_type& __x) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 pair<iterator, iterator> equal_range(const key_type& __x) {
return __equal_range_impl(*this, __x);
}
- _LIBCPP_HIDE_FROM_ABI pair<const_iterator, const_iterator> equal_range(const key_type& __x) const {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 pair<const_iterator, const_iterator>
+ equal_range(const key_type& __x) const {
return __equal_range_impl(*this, __x);
}
template <class _Kp>
requires __is_compare_transparent
- _LIBCPP_HIDE_FROM_ABI pair<iterator, iterator> equal_range(const _Kp& __x) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 pair<iterator, iterator> equal_range(const _Kp& __x) {
return __equal_range_impl(*this, __x);
}
template <class _Kp>
requires __is_compare_transparent
- _LIBCPP_HIDE_FROM_ABI pair<const_iterator, const_iterator> equal_range(const _Kp& __x) const {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 pair<const_iterator, const_iterator>
+ equal_range(const _Kp& __x) const {
return __equal_range_impl(*this, __x);
}
- friend _LIBCPP_HIDE_FROM_ABI bool operator==(const flat_map& __x, const flat_map& __y) {
+ friend _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 bool operator==(const flat_map& __x, const flat_map& __y) {
return ranges::equal(__x, __y);
}
- friend _LIBCPP_HIDE_FROM_ABI auto operator<=>(const flat_map& __x, const flat_map& __y) {
+ friend _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 auto
+ operator<=>(const flat_map& __x, const flat_map& __y) {
return std::lexicographical_compare_three_way(
__x.begin(), __x.end(), __y.begin(), __y.end(), std::__synth_three_way);
}
- friend _LIBCPP_HIDE_FROM_ABI void swap(flat_map& __x, flat_map& __y) noexcept { __x.swap(__y); }
+ friend _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void swap(flat_map& __x, flat_map& __y) noexcept {
+ __x.swap(__y);
+ }
private:
struct __ctor_uses_allocator_tag {
- explicit _LIBCPP_HIDE_FROM_ABI __ctor_uses_allocator_tag() = default;
+ explicit _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __ctor_uses_allocator_tag() = default;
};
struct __ctor_uses_allocator_empty_tag {
- explicit _LIBCPP_HIDE_FROM_ABI __ctor_uses_allocator_empty_tag() = default;
+ explicit _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __ctor_uses_allocator_empty_tag() = default;
};
template <class _Allocator, class _KeyCont, class _MappedCont, class... _CompArg>
requires __allocator_ctor_constraint<_Allocator>
- _LIBCPP_HIDE_FROM_ABI
- flat_map(__ctor_uses_allocator_tag,
- const _Allocator& __alloc,
- _KeyCont&& __key_cont,
- _MappedCont&& __mapped_cont,
- _CompArg&&... __comp)
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 flat_map(
+ __ctor_uses_allocator_tag,
+ const _Allocator& __alloc,
+ _KeyCont&& __key_cont,
+ _MappedCont&& __mapped_cont,
+ _CompArg&&... __comp)
: __containers_{.keys = std::make_obj_using_allocator<key_container_type>(
__alloc, std::forward<_KeyCont>(__key_cont)),
.values = std::make_obj_using_allocator<mapped_container_type>(
@@ -840,12 +906,13 @@ class flat_map {
template <class _Allocator, class... _CompArg>
requires __allocator_ctor_constraint<_Allocator>
- _LIBCPP_HIDE_FROM_ABI flat_map(__ctor_uses_allocator_empty_tag, const _Allocator& __alloc, _CompArg&&... __comp)
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26
+ flat_map(__ctor_uses_allocator_empty_tag, const _Allocator& __alloc, _CompArg&&... __comp)
: __containers_{.keys = std::make_obj_using_allocator<key_container_type>(__alloc),
.values = std::make_obj_using_allocator<mapped_container_type>(__alloc)},
__compare_(std::forward<_CompArg>(__comp)...) {}
- _LIBCPP_HIDE_FROM_ABI bool __is_sorted_and_unique(auto&& __key_container) const {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 bool __is_sorted_and_unique(auto&& __key_container) const {
auto __greater_or_equal_to = [this](const auto& __x, const auto& __y) { return !__compare_(__x, __y); };
return ranges::adjacent_find(__key_container, __greater_or_equal_to) == ranges::end(__key_container);
}
@@ -853,7 +920,7 @@ class flat_map {
// This function is only used in constructors. So there is not exception handling in this function.
// If the function exits via an exception, there will be no flat_map object constructed, thus, there
// is no invariant state to preserve
- _LIBCPP_HIDE_FROM_ABI void __sort_and_unique() {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __sort_and_unique() {
auto __zv = ranges::views::zip(__containers_.keys, __containers_.values);
ranges::sort(__zv, __compare_, [](const auto& __p) -> decltype(auto) { return std::get<0>(__p); });
auto __dup_start = ranges::unique(__zv, __key_equiv(__compare_)).begin();
@@ -863,14 +930,16 @@ class flat_map {
}
template <class _Self, class _KeyIter>
- _LIBCPP_HIDE_FROM_ABI static auto __corresponding_mapped_it(_Self&& __self, _KeyIter&& __key_iter) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 static auto
+ __corresponding_mapped_it(_Self&& __self, _KeyIter&& __key_iter) {
return __self.__containers_.values.begin() +
static_cast<ranges::range_difference_t<mapped_container_type>>(
ranges::distance(__self.__containers_.keys.begin(), __key_iter));
}
template <bool _WasSorted, class _InputIterator, class _Sentinel>
- _LIBCPP_HIDE_FROM_ABI void __append_sort_merge_unique(_InputIterator __first, _Sentinel __last) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void
+ __append_sort_merge_unique(_InputIterator __first, _Sentinel __last) {
auto __on_failure = std::__make_exception_guard([&]() noexcept { clear() /* noexcept */; });
size_t __num_of_appended = __flat_map_utils::__append(*this, std::move(__first), std::move(__last));
if (__num_of_appended != 0) {
@@ -898,7 +967,7 @@ class flat_map {
}
template <class _Self, class _Kp>
- _LIBCPP_HIDE_FROM_ABI static auto __find_impl(_Self&& __self, const _Kp& __key) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 static auto __find_impl(_Self&& __self, const _Kp& __key) {
auto __it = __self.lower_bound(__key);
auto __last = __self.end();
if (__it == __last || __self.__compare_(__key, __it->first)) {
@@ -908,7 +977,7 @@ class flat_map {
}
template <class _Self, class _Kp>
- _LIBCPP_HIDE_FROM_ABI static auto __key_equal_range(_Self&& __self, const _Kp& __key) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 static auto __key_equal_range(_Self&& __self, const _Kp& __key) {
auto __it =
std::lower_bound(__self.__containers_.keys.begin(), __self.__containers_.keys.end(), __key, __self.__compare_);
auto __last = __self.__containers_.keys.end();
@@ -919,7 +988,7 @@ class flat_map {
}
template <class _Self, class _Kp>
- _LIBCPP_HIDE_FROM_ABI static auto __equal_range_impl(_Self&& __self, const _Kp& __key) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 static auto __equal_range_impl(_Self&& __self, const _Kp& __key) {
auto [__key_first, __key_last] = __key_equal_range(__self, __key);
using __iterator_type = ranges::iterator_t<decltype(__self)>;
return std::make_pair(__iterator_type(__key_first, __corresponding_mapped_it(__self, __key_first)),
@@ -927,7 +996,7 @@ class flat_map {
}
template <class _Res, class _Self, class _Kp>
- _LIBCPP_HIDE_FROM_ABI static _Res __lower_bound(_Self&& __self, _Kp& __x) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 static _Res __lower_bound(_Self&& __self, _Kp& __x) {
auto __key_iter =
std::lower_bound(__self.__containers_.keys.begin(), __self.__containers_.keys.end(), __x, __self.__compare_);
auto __mapped_iter = __corresponding_mapped_it(__self, __key_iter);
@@ -935,7 +1004,7 @@ class flat_map {
}
template <class _Res, class _Self, class _Kp>
- _LIBCPP_HIDE_FROM_ABI static _Res __upper_bound(_Self&& __self, _Kp& __x) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 static _Res __upper_bound(_Self&& __self, _Kp& __x) {
auto __key_iter =
std::upper_bound(__self.__containers_.keys.begin(), __self.__containers_.keys.end(), __x, __self.__compare_);
auto __mapped_iter = __corresponding_mapped_it(__self, __key_iter);
@@ -943,7 +1012,8 @@ class flat_map {
}
template <class _KeyArg, class... _MArgs>
- _LIBCPP_HIDE_FROM_ABI pair<iterator, bool> __try_emplace(_KeyArg&& __key, _MArgs&&... __mapped_args) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 pair<iterator, bool>
+ __try_emplace(_KeyArg&& __key, _MArgs&&... __mapped_args) {
auto __key_it = std::lower_bound(__containers_.keys.begin(), __containers_.keys.end(), __key, __compare_);
auto __mapped_it = __containers_.values.begin() + ranges::distance(__containers_.keys.begin(), __key_it);
@@ -962,7 +1032,7 @@ class flat_map {
}
template <class _Kp>
- _LIBCPP_HIDE_FROM_ABI bool __is_hint_correct(const_iterator __hint, _Kp&& __key) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 bool __is_hint_correct(const_iterator __hint, _Kp&& __key) {
if (__hint != cbegin() && !__compare_((__hint - 1)->first, __key)) {
return false;
}
@@ -973,7 +1043,8 @@ class flat_map {
}
template <class _Kp, class... _Args>
- _LIBCPP_HIDE_FROM_ABI pair<iterator, bool> __try_emplace_hint(const_iterator __hint, _Kp&& __key, _Args&&... __args) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 pair<iterator, bool>
+ __try_emplace_hint(const_iterator __hint, _Kp&& __key, _Args&&... __args) {
if (__is_hint_correct(__hint, __key)) {
if (__hint == cend() || __compare_(__key, __hint->first)) {
return {__flat_map_utils::__emplace_exact_pos(
@@ -994,7 +1065,8 @@ class flat_map {
}
template <class _Kp, class _Mapped>
- _LIBCPP_HIDE_FROM_ABI pair<iterator, bool> __insert_or_assign(_Kp&& __key, _Mapped&& __mapped) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 pair<iterator, bool>
+ __insert_or_assign(_Kp&& __key, _Mapped&& __mapped) {
auto __r = try_emplace(std::forward<_Kp>(__key), std::forward<_Mapped>(__mapped));
if (!__r.second) {
__r.first->second = std::forward<_Mapped>(__mapped);
@@ -1003,7 +1075,8 @@ class flat_map {
}
template <class _Kp, class _Mapped>
- _LIBCPP_HIDE_FROM_ABI iterator __insert_or_assign(const_iterator __hint, _Kp&& __key, _Mapped&& __mapped) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator
+ __insert_or_assign(const_iterator __hint, _Kp&& __key, _Mapped&& __mapped) {
auto __r = __try_emplace_hint(__hint, std::forward<_Kp>(__key), std::forward<_Mapped>(__mapped));
if (!__r.second) {
__r.first->second = std::forward<_Mapped>(__mapped);
@@ -1011,7 +1084,7 @@ class flat_map {
return __r.first;
}
- _LIBCPP_HIDE_FROM_ABI void __reserve(size_t __size) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 void __reserve(size_t __size) {
if constexpr (__container_traits<_KeyContainer>::__reservable) {
__containers_.keys.reserve(__size);
}
@@ -1022,7 +1095,8 @@ class flat_map {
}
template <class _KIter, class _MIter>
- _LIBCPP_HIDE_FROM_ABI iterator __erase(_KIter __key_iter_to_remove, _MIter __mapped_iter_to_remove) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 iterator
+ __erase(_KIter __key_iter_to_remove, _MIter __mapped_iter_to_remove) {
auto __on_failure = std::__make_exception_guard([&]() noexcept { clear() /* noexcept */; });
auto __key_iter = __containers_.keys.erase(__key_iter_to_remove);
auto __mapped_iter = __containers_.values.erase(__mapped_iter_to_remove);
@@ -1032,7 +1106,8 @@ class flat_map {
template <class _Key2, class _Tp2, class _Compare2, class _KeyContainer2, class _MappedContainer2, class _Predicate>
friend typename flat_map<_Key2, _Tp2, _Compare2, _KeyContainer2, _MappedContainer2>::size_type
- erase_if(flat_map<_Key2, _Tp2, _Compare2, _KeyContainer2, _MappedContainer2>&, _Predicate);
+ _LIBCPP_CONSTEXPR_SINCE_CXX26
+ erase_if(flat_map<_Key2, _Tp2, _Compare2, _KeyContainer2, _MappedContainer2>&, _Predicate);
friend __flat_map_utils;
@@ -1040,8 +1115,9 @@ class flat_map {
_LIBCPP_NO_UNIQUE_ADDRESS key_compare __compare_;
struct __key_equiv {
- _LIBCPP_HIDE_FROM_ABI __key_equiv(key_compare __c) : __comp_(__c) {}
- _LIBCPP_HIDE_FROM_ABI bool operator()(const_reference __x, const_reference __y) const {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __key_equiv(key_compare __c) : __comp_(__c) {}
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 bool
+ operator()(const_reference __x, const_reference __y) const {
return !__comp_(std::get<0>(__x), std::get<0>(__y)) && !__comp_(std::get<0>(__y), std::get<0>(__x));
}
key_compare __comp_;
@@ -1164,8 +1240,9 @@ struct uses_allocator<flat_map<_Key, _Tp, _Compare, _KeyContainer, _MappedContai
: bool_constant<uses_allocator_v<_KeyContainer, _Allocator> && uses_allocator_v<_MappedContainer, _Allocator>> {};
template <class _Key, class _Tp, class _Compare, class _KeyContainer, class _MappedContainer, class _Predicate>
-_LIBCPP_HIDE_FROM_ABI typename flat_map<_Key, _Tp, _Compare, _KeyContainer, _MappedContainer>::size_type
-erase_if(flat_map<_Key, _Tp, _Compare, _KeyContainer, _MappedContainer>& __flat_map, _Predicate __pred) {
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26
+ typename flat_map<_Key, _Tp, _Compare, _KeyContainer, _MappedContainer>::size_type
+ erase_if(flat_map<_Key, _Tp, _Compare, _KeyContainer, _MappedContainer>& __flat_map, _Predicate __pred) {
auto __zv = ranges::views::zip(__flat_map.__containers_.keys, __flat_map.__containers_.values);
auto __first = __zv.begin();
auto __last = __zv.end();
diff --git a/libcxx/include/__flat_map/key_value_iterator.h b/libcxx/include/__flat_map/key_value_iterator.h
index 3ebb653deb197..f163dfc28706d 100644
--- a/libcxx/include/__flat_map/key_value_iterator.h
+++ b/libcxx/include/__flat_map/key_value_iterator.h
@@ -46,7 +46,7 @@ struct __key_value_iterator {
struct __arrow_proxy {
__reference __ref_;
- _LIBCPP_HIDE_FROM_ABI __reference* operator->() { return std::addressof(__ref_); }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __reference* operator->() { return std::addressof(__ref_); }
};
__key_iterator __key_iter_;
@@ -69,99 +69,113 @@ struct __key_value_iterator {
_LIBCPP_HIDE_FROM_ABI __key_value_iterator() = default;
- _LIBCPP_HIDE_FROM_ABI __key_value_iterator(__key_value_iterator<_Owner, _KeyContainer, _MappedContainer, !_Const> __i)
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26
+ __key_value_iterator(__key_value_iterator<_Owner, _KeyContainer, _MappedContainer, !_Const> __i)
requires _Const && convertible_to<typename _KeyContainer::iterator, __key_iterator> &&
convertible_to<typename _MappedContainer::iterator, __mapped_iterator>
: __key_iter_(std::move(__i.__key_iter_)), __mapped_iter_(std::move(__i.__mapped_iter_)) {}
- _LIBCPP_HIDE_FROM_ABI __key_value_iterator(__key_iterator __key_iter, __mapped_iterator __mapped_iter)
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26
+ __key_value_iterator(__key_iterator __key_iter, __mapped_iterator __mapped_iter)
: __key_iter_(std::move(__key_iter)), __mapped_iter_(std::move(__mapped_iter)) {}
- _LIBCPP_HIDE_FROM_ABI __reference operator*() const { return __reference(*__key_iter_, *__mapped_iter_); }
- _LIBCPP_HIDE_FROM_ABI __arrow_proxy operator->() const { return __arrow_proxy{**this}; }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __reference operator*() const {
+ return __reference(*__key_iter_, *__mapped_iter_);
+ }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __arrow_proxy operator->() const { return __arrow_proxy{**this}; }
- _LIBCPP_HIDE_FROM_ABI __key_value_iterator& operator++() {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __key_value_iterator& operator++() {
++__key_iter_;
++__mapped_iter_;
return *this;
}
- _LIBCPP_HIDE_FROM_ABI __key_value_iterator operator++(int) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __key_value_iterator operator++(int) {
__key_value_iterator __tmp(*this);
++*this;
return __tmp;
}
- _LIBCPP_HIDE_FROM_ABI __key_value_iterator& operator--() {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __key_value_iterator& operator--() {
--__key_iter_;
--__mapped_iter_;
return *this;
}
- _LIBCPP_HIDE_FROM_ABI __key_value_iterator operator--(int) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __key_value_iterator operator--(int) {
__key_value_iterator __tmp(*this);
--*this;
return __tmp;
}
- _LIBCPP_HIDE_FROM_ABI __key_value_iterator& operator+=(difference_type __x) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __key_value_iterator& operator+=(difference_type __x) {
__key_iter_ += __x;
__mapped_iter_ += __x;
return *this;
}
- _LIBCPP_HIDE_FROM_ABI __key_value_iterator& operator-=(difference_type __x) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __key_value_iterator& operator-=(difference_type __x) {
__key_iter_ -= __x;
__mapped_iter_ -= __x;
return *this;
}
- _LIBCPP_HIDE_FROM_ABI __reference operator[](difference_type __n) const { return *(*this + __n); }
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __reference operator[](difference_type __n) const {
+ return *(*this + __n);
+ }
- _LIBCPP_HIDE_FROM_ABI friend constexpr bool
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 friend bool
operator==(const __key_value_iterator& __x, const __key_value_iterator& __y) {
return __x.__key_iter_ == __y.__key_iter_;
}
- _LIBCPP_HIDE_FROM_ABI friend bool operator<(const __key_value_iterator& __x, const __key_value_iterator& __y) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 friend bool
+ operator<(const __key_value_iterator& __x, const __key_value_iterator& __y) {
return __x.__key_iter_ < __y.__key_iter_;
}
- _LIBCPP_HIDE_FROM_ABI friend bool operator>(const __key_value_iterator& __x, const __key_value_iterator& __y) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 friend bool
+ operator>(const __key_value_iterator& __x, const __key_value_iterator& __y) {
return __y < __x;
}
- _LIBCPP_HIDE_FROM_ABI friend bool operator<=(const __key_value_iterator& __x, const __key_value_iterator& __y) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 friend bool
+ operator<=(const __key_value_iterator& __x, const __key_value_iterator& __y) {
return !(__y < __x);
}
- _LIBCPP_HIDE_FROM_ABI friend bool operator>=(const __key_value_iterator& __x, const __key_value_iterator& __y) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 friend bool
+ operator>=(const __key_value_iterator& __x, const __key_value_iterator& __y) {
return !(__x < __y);
}
- _LIBCPP_HIDE_FROM_ABI friend auto operator<=>(const __key_value_iterator& __x, const __key_value_iterator& __y)
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 friend auto
+ operator<=>(const __key_value_iterator& __x, const __key_value_iterator& __y)
requires three_way_comparable<__key_iterator>
{
return __x.__key_iter_ <=> __y.__key_iter_;
}
- _LIBCPP_HIDE_FROM_ABI friend __key_value_iterator operator+(const __key_value_iterator& __i, difference_type __n) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 friend __key_value_iterator
+ operator+(const __key_value_iterator& __i, difference_type __n) {
auto __tmp = __i;
__tmp += __n;
return __tmp;
}
- _LIBCPP_HIDE_FROM_ABI friend __key_value_iterator operator+(difference_type __n, const __key_value_iterator& __i) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 friend __key_value_iterator
+ operator+(difference_type __n, const __key_value_iterator& __i) {
return __i + __n;
}
- _LIBCPP_HIDE_FROM_ABI friend __key_value_iterator operator-(const __key_value_iterator& __i, difference_type __n) {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 friend __key_value_iterator
+ operator-(const __key_value_iterator& __i, difference_type __n) {
auto __tmp = __i;
__tmp -= __n;
return __tmp;
}
- _LIBCPP_HIDE_FROM_ABI friend difference_type
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 friend difference_type
operator-(const __key_value_iterator& __x, const __key_value_iterator& __y) {
return difference_type(__x.__key_iter_ - __y.__key_iter_);
}
diff --git a/libcxx/include/__flat_map/utils.h b/libcxx/include/__flat_map/utils.h
index acb7dca7ffe96..27687ae8de3bc 100644
--- a/libcxx/include/__flat_map/utils.h
+++ b/libcxx/include/__flat_map/utils.h
@@ -35,7 +35,7 @@ struct __flat_map_utils {
// roll back the changes it made to the map. If it cannot roll back the changes, it will
// clear the map.
template <class _Map, class _IterK, class _IterM, class _KeyArg, class... _MArgs>
- _LIBCPP_HIDE_FROM_ABI static typename _Map::iterator __emplace_exact_pos(
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 static typename _Map::iterator __emplace_exact_pos(
_Map& __map, _IterK&& __it_key, _IterM&& __it_mapped, _KeyArg&& __key, _MArgs&&... __mapped_args) {
auto __on_key_failed = std::__make_exception_guard([&]() noexcept {
using _KeyContainer = typename _Map::key_container_type;
@@ -82,7 +82,7 @@ struct __flat_map_utils {
// TODO: We could optimize this, see
// https://github.com/llvm/llvm-project/issues/108624
template <class _Map, class _InputIterator, class _Sentinel>
- _LIBCPP_HIDE_FROM_ABI static typename _Map::size_type
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 static typename _Map::size_type
__append(_Map& __map, _InputIterator __first, _Sentinel __last) {
typename _Map::size_type __num_appended = 0;
for (; __first != __last; ++__first) {
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 4a081e65cb7f1..adf80f2ac9acb 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1298,6 +1298,9 @@ module std [system] {
header "flat_map"
export *
+ export std.algorithm.ranges_sort
+ export std.ranges.zip_view
+ export std.tuple
}
module flat_set {
diff --git a/libcxx/test/std/containers/Emplaceable.h b/libcxx/test/std/containers/Emplaceable.h
index 246d5b255d6b1..d8d3407791731 100644
--- a/libcxx/test/std/containers/Emplaceable.h
+++ b/libcxx/test/std/containers/Emplaceable.h
@@ -22,13 +22,13 @@ class Emplaceable {
double double_;
public:
- TEST_CONSTEXPR Emplaceable() : int_(0), double_(0) {}
- TEST_CONSTEXPR Emplaceable(int i, double d) : int_(i), double_(d) {}
- TEST_CONSTEXPR_CXX14 Emplaceable(Emplaceable&& x) : int_(x.int_), double_(x.double_) {
+ TEST_CONSTEXPR_CXX20 Emplaceable() : int_(0), double_(0) {}
+ TEST_CONSTEXPR_CXX20 Emplaceable(int i, double d) : int_(i), double_(d) {}
+ TEST_CONSTEXPR_CXX20 Emplaceable(Emplaceable&& x) : int_(x.int_), double_(x.double_) {
x.int_ = 0;
x.double_ = 0;
}
- TEST_CONSTEXPR_CXX14 Emplaceable& operator=(Emplaceable&& x) {
+ TEST_CONSTEXPR_CXX20 Emplaceable& operator=(Emplaceable&& x) {
int_ = x.int_;
x.int_ = 0;
double_ = x.double_;
@@ -36,12 +36,12 @@ class Emplaceable {
return *this;
}
- TEST_CONSTEXPR bool operator==(const Emplaceable& x) const { return int_ == x.int_ && double_ == x.double_; }
- TEST_CONSTEXPR bool operator<(const Emplaceable& x) const {
+ TEST_CONSTEXPR_CXX20 bool operator==(const Emplaceable& x) const { return int_ == x.int_ && double_ == x.double_; }
+ TEST_CONSTEXPR_CXX20 bool operator<(const Emplaceable& x) const {
return int_ < x.int_ || (int_ == x.int_ && double_ < x.double_);
}
- TEST_CONSTEXPR int get() const { return int_; }
+ TEST_CONSTEXPR_CXX20 int get() const { return int_; }
};
template <>
@@ -49,7 +49,7 @@ struct std::hash<Emplaceable> {
typedef Emplaceable argument_type;
typedef std::size_t result_type;
- TEST_CONSTEXPR std::size_t operator()(const Emplaceable& x) const { return static_cast<std::size_t>(x.get()); }
+ TEST_CONSTEXPR_CXX20 std::size_t operator()(const Emplaceable& x) const { return static_cast<std::size_t>(x.get()); }
};
#endif // TEST_STD_VER >= 11
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.access/at.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.access/at.pass.cpp
index d30055bf1701c..083a9dd9f76c4 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.access/at.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.access/at.pass.cpp
@@ -18,6 +18,7 @@
#include <flat_map>
#include <functional>
#include <stdexcept>
+#include <type_traits>
#include <vector>
#include "MinSequenceContainer.h"
@@ -25,7 +26,7 @@
#include "test_macros.h"
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using P = std::pair<int, double>;
P ar[] = {
P(1, 1.5),
@@ -49,10 +50,12 @@ void test() {
assert(m.at(4) == 4.5);
assert(m.at(5) == 5.5);
#ifndef TEST_HAS_NO_EXCEPTIONS
- try {
- TEST_IGNORE_NODISCARD m.at(6);
- assert(false);
- } catch (std::out_of_range&) {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
+ try {
+ TEST_IGNORE_NODISCARD m.at(6);
+ assert(false);
+ } catch (std::out_of_range&) {
+ }
}
#endif
assert(m.at(7) == 7.5);
@@ -70,10 +73,12 @@ void test() {
assert(m.at(4) == 4.5);
assert(m.at(5) == 5.5);
#ifndef TEST_HAS_NO_EXCEPTIONS
- try {
- TEST_IGNORE_NODISCARD m.at(6);
- assert(false);
- } catch (std::out_of_range&) {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
+ try {
+ TEST_IGNORE_NODISCARD m.at(6);
+ assert(false);
+ } catch (std::out_of_range&) {
+ }
}
#endif
assert(m.at(7) == 7.5);
@@ -82,11 +87,25 @@ void test() {
}
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<double>>();
- test<std::deque<int>, std::vector<double>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.access/at_transparent.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.access/at_transparent.pass.cpp
index bc3fbfca5762b..7be6fd7cba0d5 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.access/at_transparent.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.access/at_transparent.pass.cpp
@@ -35,7 +35,7 @@ static_assert(!CanAt<NonTransparentMap>);
static_assert(!CanAt<const NonTransparentMap>);
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using P = std::pair<int, double>;
P ar[] = {
P(1, 1.5),
@@ -60,10 +60,12 @@ void test() {
assert(m.at(Transparent<int>{4}) == 4.5);
assert(m.at(Transparent<int>{5}) == 5.5);
#ifndef TEST_HAS_NO_EXCEPTIONS
- try {
- TEST_IGNORE_NODISCARD m.at(Transparent<int>{6});
- assert(false);
- } catch (std::out_of_range&) {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
+ try {
+ TEST_IGNORE_NODISCARD m.at(Transparent<int>{6});
+ assert(false);
+ } catch (std::out_of_range&) {
+ }
}
#endif
assert(m.at(Transparent<int>{7}) == 7.5);
@@ -81,10 +83,12 @@ void test() {
assert(m.at(Transparent<int>{4}) == 4.5);
assert(m.at(Transparent<int>{5}) == 5.5);
#ifndef TEST_HAS_NO_EXCEPTIONS
- try {
- TEST_IGNORE_NODISCARD m.at(Transparent<int>{6});
- assert(false);
- } catch (std::out_of_range&) {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
+ try {
+ TEST_IGNORE_NODISCARD m.at(Transparent<int>{6});
+ assert(false);
+ } catch (std::out_of_range&) {
+ }
}
#endif
assert(m.at(Transparent<int>{7}) == 7.5);
@@ -93,9 +97,14 @@ void test() {
}
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<double>>();
- test<std::deque<int>, std::vector<double>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
{
@@ -114,5 +123,14 @@ int main(int, char**) {
assert(x == 1);
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.access/index_key.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.access/index_key.pass.cpp
index ea2f5d800878a..a83aa4f0d6b1f 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.access/index_key.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.access/index_key.pass.cpp
@@ -16,6 +16,7 @@
#include <deque>
#include <flat_map>
#include <functional>
+#include <type_traits>
#include <vector>
#include "MinSequenceContainer.h"
@@ -31,7 +32,7 @@ static_assert(CanIndex<std::flat_map<int, double>, const int&>);
static_assert(!CanIndex<std::flat_map<int, NoDefaultCtr>, const int&>);
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using P = std::pair<int, double>;
P ar[] = {
P(1, 1.5),
@@ -58,13 +59,18 @@ void test() {
assert(m.size() == 8);
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<double>>();
- test<std::deque<int>, std::vector<double>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
auto index_func = [](auto& m, auto key_arg, auto value_arg) {
using FlatMap = std::decay_t<decltype(m)>;
const typename FlatMap::key_type key = key_arg;
@@ -73,5 +79,15 @@ int main(int, char**) {
};
test_emplace_exception_guarantee(index_func);
}
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.access/index_rv_key.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.access/index_rv_key.pass.cpp
index faacc3cfe8f96..778288fd13d21 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.access/index_rv_key.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.access/index_rv_key.pass.cpp
@@ -16,6 +16,7 @@
#include <deque>
#include <functional>
#include <cassert>
+#include <type_traits>
#include "MinSequenceContainer.h"
#include "../helpers.h"
@@ -31,7 +32,7 @@ static_assert(CanIndex<std::flat_map<int, double>, int&&>);
static_assert(!CanIndex<std::flat_map<int, NoDefaultCtr>, int&&>);
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
{
std::flat_map<MoveOnly, double, std::less<MoveOnly>, KeyContainer, ValueContainer> m;
ASSERT_SAME_TYPE(decltype(m[MoveOnly{}]), double&);
@@ -49,13 +50,18 @@ void test() {
}
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<MoveOnly>, std::vector<double>>();
- test<std::deque<MoveOnly>, std::vector<double>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<MoveOnly>, std::vector<double>>();
+ }
test<MinSequenceContainer<MoveOnly>, MinSequenceContainer<double>>();
test<std::vector<MoveOnly, min_allocator<MoveOnly>>, std::vector<double, min_allocator<double>>>();
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
auto index_func = [](auto& m, auto key_arg, auto value_arg) {
using FlatMap = std::decay_t<decltype(m)>;
typename FlatMap::key_type key = key_arg;
@@ -64,5 +70,14 @@ int main(int, char**) {
};
test_emplace_exception_guarantee(index_func);
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.access/index_transparent.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.access/index_transparent.pass.cpp
index 760ec69ae878d..e8ea20b345e34 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.access/index_transparent.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.access/index_transparent.pass.cpp
@@ -50,7 +50,7 @@ static_assert(!CanIndex<TransparentMap, TransparentMap::iterator>);
static_assert(!CanIndex<TransparentMap, TransparentMap::const_iterator>);
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using P = std::pair<int, double>;
P ar[] = {
P(1, 1.5),
@@ -81,11 +81,17 @@ void test() {
}
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<double>>();
- test<std::deque<int>, std::vector<double>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
+
{
bool transparent_used = false;
TransparentComparator c(transparent_used);
@@ -101,7 +107,8 @@ int main(int, char**) {
int& x = m["alpha"];
assert(x == 1);
}
- {
+
+ if (!TEST_IS_CONSTANT_EVALUATED) {
auto index_func = [](auto& m, auto key_arg, auto value_arg) {
using FlatMap = std::decay_t<decltype(m)>;
using Key = typename FlatMap::key_type;
@@ -110,5 +117,15 @@ int main(int, char**) {
};
test_emplace_exception_guarantee(index_func);
}
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.capacity/empty.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.capacity/empty.pass.cpp
index 05efe063c1e17..c1f7b7c5cb1f0 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.capacity/empty.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.capacity/empty.pass.cpp
@@ -24,10 +24,10 @@
#include "min_allocator.h"
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
- using M = std::flat_multimap<Key, Value, std::less<int>, KeyContainer, ValueContainer>;
+ using M = std::flat_map<Key, Value, std::less<int>, KeyContainer, ValueContainer>;
M m;
ASSERT_SAME_TYPE(decltype(m.empty()), bool);
ASSERT_NOEXCEPT(m.empty());
@@ -39,11 +39,25 @@ void test() {
assert(m.empty());
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<double>>();
- test<std::deque<int>, std::vector<double>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.capacity/max_size.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.capacity/max_size.pass.cpp
index 87acdfd2cf625..ee01f8eb1dfe4 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.capacity/max_size.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.capacity/max_size.pass.cpp
@@ -24,7 +24,7 @@
#include "test_allocator.h"
#include "test_macros.h"
-int main(int, char**) {
+constexpr bool test() {
{
using A1 = limited_allocator<int, 10>;
using A2 = limited_allocator<int, 20>;
@@ -72,5 +72,15 @@ int main(int, char**) {
assert(c.max_size() <= max_dist);
assert(c.max_size() <= alloc_max_size(std::allocator<char>()));
}
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.capacity/size.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.capacity/size.pass.cpp
index 957a860450091..94f2793293f1f 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.capacity/size.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.capacity/size.pass.cpp
@@ -16,6 +16,7 @@
#include <deque>
#include <flat_map>
#include <functional>
+#include <type_traits>
#include <vector>
#include "MinSequenceContainer.h"
@@ -23,7 +24,7 @@
#include "min_allocator.h"
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using M = std::flat_map<int, char, std::less<int>, KeyContainer, ValueContainer>;
{
const M m = {{1, 'a'}, {1, 'b'}, {4, 'd'}, {5, 'e'}, {5, 'h'}};
@@ -45,7 +46,7 @@ void test() {
}
{
M m;
- std::size_t s = 1000000;
+ std::size_t s = TEST_IS_CONSTANT_EVALUATED ? 100 : 1000000;
for (auto i = 0u; i < s; ++i) {
m.emplace(i, 'a');
}
@@ -55,11 +56,25 @@ void test() {
}
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<char>>();
- test<std::deque<int>, std::vector<char>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<char>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<char>>();
test<std::vector<int, min_allocator<int>>, std::vector<char, min_allocator<char>>>();
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/alloc.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/alloc.pass.cpp
index 3f8d2ed332d6b..b7a033454a354 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/alloc.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/alloc.pass.cpp
@@ -14,6 +14,7 @@
// explicit flat_map(const Allocator& a);
#include <cassert>
+#include <deque>
#include <flat_map>
#include <functional>
#include <vector>
@@ -22,7 +23,23 @@
#include "test_allocator.h"
#include "../../../test_compare.h"
-int main(int, char**) {
+template <template <class...> class KeyContainer, template <class...> class ValueContainer>
+constexpr void test() {
+ using A = test_allocator<short>;
+ using M =
+ std::flat_map<int,
+ long,
+ std::less<int>,
+ KeyContainer<int, test_allocator<int>>,
+ ValueContainer<long, test_allocator<long>>>;
+ M m(A(0, 5));
+ assert(m.empty());
+ assert(m.begin() == m.end());
+ assert(m.keys().get_allocator().get_id() == 5);
+ assert(m.values().get_allocator().get_id() == 5);
+}
+
+constexpr bool test() {
{
// The constructors in this subclause shall not participate in overload
// resolution unless uses_allocator_v<key_container_type, Alloc> is true
@@ -53,20 +70,23 @@ int main(int, char**) {
static_assert(std::is_constructible_v<M, test_allocator<int>>);
static_assert(!std::is_convertible_v<test_allocator<int>, M>);
}
+
+ test<std::vector, std::vector>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
{
- using A = test_allocator<short>;
- using M =
- std::flat_map<int,
- long,
- std::less<int>,
- std::vector<int, test_allocator<int>>,
- std::vector<long, test_allocator<long>>>;
- M m(A(0, 5));
- assert(m.empty());
- assert(m.begin() == m.end());
- assert(m.keys().get_allocator().get_id() == 5);
- assert(m.values().get_allocator().get_id() == 5);
+ test<std::deque, std::deque>();
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/assign_initializer_list.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/assign_initializer_list.pass.cpp
index 06bde71e79941..8e30793027196 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/assign_initializer_list.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/assign_initializer_list.pass.cpp
@@ -26,7 +26,7 @@
#include "test_allocator.h"
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -36,7 +36,6 @@ void test() {
m = {{3, 0}, {1, 0}, {2, 0}, {2, 1}, {3, 1}, {4, 0}, {3, 2}, {5, 0}, {6, 0}, {5, 1}};
std::pair<int, int> expected[] = {{1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}};
assert(std::ranges::equal(m.keys(), expected | std::views::elements<0>));
- LIBCPP_ASSERT(std::ranges::equal(m, expected));
}
{
M m = {{10, 1}, {8, 1}};
@@ -47,13 +46,28 @@ void test() {
}
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<int>>();
test<std::vector<int>, std::vector<double>>();
- test<std::deque<int>, std::vector<double>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ }
+
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
test<std::vector<int, min_allocator<int>>, std::vector<int, min_allocator<int>>>();
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/compare.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/compare.pass.cpp
index 40a1710f55e42..aa57c8f648bb2 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/compare.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/compare.pass.cpp
@@ -20,48 +20,52 @@
#include <type_traits>
#include <vector>
+#include "MinSequenceContainer.h"
+#include "min_allocator.h"
#include "test_macros.h"
#include "../../../test_compare.h"
#include "test_allocator.h"
-int main(int, char**) {
+// explicit flat_map(const key_compare& comp);
+template <class KeyContainer, class ValueContainer>
+constexpr void test_compare() {
+ using Key = typename KeyContainer::value_type;
+ using Value = typename ValueContainer::value_type;
{
- // The constructors in this subclause shall not participate in overload
- // resolution unless uses_allocator_v<key_container_type, Alloc> is true
- // and uses_allocator_v<mapped_container_type, Alloc> is true.
+ // The one-argument ctor is explicit.
+ using C = test_less<Key>;
+ static_assert(std::is_constructible_v<std::flat_map<Key, Value, C>, C>);
+ static_assert(!std::is_convertible_v<C, std::flat_map<Key, Value, C>>);
- using C = test_less<int>;
- using A1 = test_allocator<int>;
- using A2 = other_allocator<int>;
- using M1 = std::flat_map<int, int, C, std::vector<int, A1>, std::vector<int, A1>>;
- using M2 = std::flat_map<int, int, C, std::vector<int, A1>, std::vector<int, A2>>;
- using M3 = std::flat_map<int, int, C, std::vector<int, A2>, std::vector<int, A1>>;
- static_assert(std::is_constructible_v<M1, const C&, const A1&>);
- static_assert(!std::is_constructible_v<M1, const C&, const A2&>);
- static_assert(!std::is_constructible_v<M2, const C&, const A2&>);
- static_assert(!std::is_constructible_v<M3, const C&, const A2&>);
+ static_assert(std::is_constructible_v<std::flat_map<Key, Value>, std::less<Key>>);
+ static_assert(!std::is_convertible_v<std::less<Key>, std::flat_map<Key, Value>>);
}
{
- using C = test_less<int>;
- auto m = std::flat_map<int, char*, C>(C(3));
+ using C = test_less<Key>;
+ auto m = std::flat_map<Key, Value, C>(C(3));
assert(m.empty());
assert(m.begin() == m.end());
assert(m.key_comp() == C(3));
}
- {
- // The one-argument ctor is explicit.
- using C = test_less<int>;
- static_assert(std::is_constructible_v<std::flat_map<int, char*, C>, C>);
- static_assert(!std::is_convertible_v<C, std::flat_map<int, char*, C>>);
+}
- static_assert(std::is_constructible_v<std::flat_map<int, char*>, std::less<int>>);
- static_assert(!std::is_convertible_v<std::less<int>, std::flat_map<int, char*>>);
+// template <class Alloc>
+// flat_map(const key_compare& comp, const Alloc& a);
+template <template <class...> class KeyContainer, template <class...> class ValueContainer>
+constexpr void test_compare_alloc() {
+ {
+ // If an allocator is given, it must be usable by both containers.
+ using A = test_allocator<int>;
+ using M = std::flat_map<int, int, std::less<>, KeyContainer<int>, ValueContainer<int, A>>;
+ static_assert(std::is_constructible_v<M, std::less<>>);
+ static_assert(!std::is_constructible_v<M, std::less<>, std::allocator<int>>);
+ static_assert(!std::is_constructible_v<M, std::less<>, A>);
}
{
using C = test_less<int>;
using A1 = test_allocator<int>;
using A2 = test_allocator<short>;
- auto m = std::flat_map<int, short, C, std::vector<int, A1>, std::vector<short, A2>>(C(4), A1(5));
+ auto m = std::flat_map<int, short, C, KeyContainer<int, A1>, ValueContainer<short, A2>>(C(4), A1(5));
assert(m.empty());
assert(m.begin() == m.end());
assert(m.key_comp() == C(4));
@@ -70,24 +74,60 @@ int main(int, char**) {
}
{
// explicit(false)
- using C = test_less<int>;
- using A1 = test_allocator<int>;
- using A2 = test_allocator<short>;
- std::flat_map<int, short, C, std::deque<int, A1>, std::deque<short, A2>> m = {C(4), A1(5)};
+ using C = test_less<int>;
+ using A1 = test_allocator<int>;
+ using A2 = test_allocator<short>;
+ std::flat_map<int, short, C, KeyContainer<int, A1>, ValueContainer<short, A2>> m = {C(4), A1(5)};
assert(m.empty());
assert(m.begin() == m.end());
assert(m.key_comp() == C(4));
assert(m.keys().get_allocator() == A1(5));
assert(m.values().get_allocator() == A2(5));
}
+}
+
+constexpr bool test() {
{
- // If an allocator is given, it must be usable by both containers.
- using A = test_allocator<int>;
- using M = std::flat_map<int, int, std::less<>, std::vector<int>, std::vector<int, A>>;
- static_assert(std::is_constructible_v<M, std::less<>>);
- static_assert(!std::is_constructible_v<M, std::less<>, std::allocator<int>>);
- static_assert(!std::is_constructible_v<M, std::less<>, A>);
+ // The constructors in this subclause shall not participate in overload
+ // resolution unless uses_allocator_v<key_container_type, Alloc> is true
+ // and uses_allocator_v<mapped_container_type, Alloc> is true.
+
+ using C = test_less<int>;
+ using A1 = test_allocator<int>;
+ using A2 = other_allocator<int>;
+ using M1 = std::flat_map<int, int, C, std::vector<int, A1>, std::vector<int, A1>>;
+ using M2 = std::flat_map<int, int, C, std::vector<int, A1>, std::vector<int, A2>>;
+ using M3 = std::flat_map<int, int, C, std::vector<int, A2>, std::vector<int, A1>>;
+ static_assert(std::is_constructible_v<M1, const C&, const A1&>);
+ static_assert(!std::is_constructible_v<M1, const C&, const A2&>);
+ static_assert(!std::is_constructible_v<M2, const C&, const A2&>);
+ static_assert(!std::is_constructible_v<M3, const C&, const A2&>);
+ }
+
+ test_compare<std::vector<int>, std::vector<int>>();
+ test_compare<std::vector<int>, std::vector<double>>();
+ test_compare<MinSequenceContainer<int>, MinSequenceContainer<double>>();
+ test_compare<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
+ test_compare<std::vector<int, min_allocator<int>>, std::vector<int, min_allocator<int>>>();
+
+ test_compare_alloc<std::vector, std::vector>();
+
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test_compare<std::deque<int>, std::vector<double>>();
+ test_compare_alloc<std::deque, std::deque>();
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/containers.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/containers.pass.cpp
index 812e2c3e4f02a..c7503bb8aa774 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/containers.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/containers.pass.cpp
@@ -30,49 +30,34 @@
#include "test_allocator.h"
#include "test_iterators.h"
#include "test_macros.h"
+#include "../helpers.h"
#include "../../../test_compare.h"
struct P {
int first;
int second;
template <class T, class U>
- bool operator==(const std::pair<T, U>& rhs) const {
+ constexpr bool operator==(const std::pair<T, U>& rhs) const {
return MoveOnly(first) == rhs.first && MoveOnly(second) == rhs.second;
}
};
-int main(int, char**) {
- {
- // The constructors in this subclause shall not participate in overload
- // resolution unless uses_allocator_v<key_container_type, Alloc> is true
- // and uses_allocator_v<mapped_container_type, Alloc> is true.
-
- using C = test_less<int>;
- using A1 = test_allocator<int>;
- using A2 = other_allocator<int>;
- using V1 = std::vector<int, A1>;
- using V2 = std::vector<int, A2>;
- using M1 = std::flat_map<int, int, C, V1, V1>;
- using M2 = std::flat_map<int, int, C, V1, V2>;
- using M3 = std::flat_map<int, int, C, V2, V1>;
- static_assert(std::is_constructible_v<M1, const V1&, const V1&, const A1&>);
- static_assert(!std::is_constructible_v<M1, const V1&, const V1&, const A2&>);
- static_assert(!std::is_constructible_v<M2, const V1&, const V2&, const A2&>);
- static_assert(!std::is_constructible_v<M3, const V2&, const V1&, const A2&>);
-
- static_assert(std::is_constructible_v<M1, const V1&, const V1&, const C&, const A1&>);
- static_assert(!std::is_constructible_v<M1, const V1&, const V1&, const C&, const A2&>);
- static_assert(!std::is_constructible_v<M2, const V1&, const V2&, const C&, const A2&>);
- static_assert(!std::is_constructible_v<M3, const V2&, const V1&, const C&, const A2&>);
- }
+template <template <class...> class KeyContainer, template <class...> class ValueContainer>
+constexpr void test() {
{
// flat_map(key_container_type , mapped_container_type)
- using M = std::flat_map<int, char>;
- std::vector<int> ks = {1, 1, 1, 2, 2, 3, 2, 3, 3};
- std::vector<char> vs = {1, 2, 3, 4, 5, 6, 7, 8, 9};
- auto m = M(ks, vs);
- assert((m.keys() == std::vector<int>{1, 2, 3}));
- LIBCPP_ASSERT((m.values() == std::vector<char>{1, 4, 6}));
+ using M = std::flat_map<int, short, std::less<int>, KeyContainer<int>, ValueContainer<short>>;
+ KeyContainer<int> ks = {1, 1, 1, 2, 2, 3, 2, 3, 3};
+ ValueContainer<short> vs = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+ auto m = M(ks, vs);
+ assert((m.keys() == KeyContainer<int>{1, 2, 3}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<short>>{
+ {1, 2, 3},
+ {4, 5, 7},
+ {6, 8, 9},
+ });
// explicit(false)
M m2 = {ks, vs};
@@ -81,15 +66,21 @@ int main(int, char**) {
m = M(std::move(ks), std::move(vs));
assert(ks.empty()); // it was moved-from
assert(vs.empty()); // it was moved-from
- assert((m.keys() == std::vector<int>{1, 2, 3}));
- LIBCPP_ASSERT((m.values() == std::vector<char>{1, 4, 6}));
+ assert((m.keys() == KeyContainer<int>{1, 2, 3}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<short>>{
+ {1, 2, 3},
+ {4, 5, 7},
+ {6, 8, 9},
+ });
}
{
// flat_map(key_container_type , mapped_container_type)
// move-only
P expected[] = {{3, 2}, {2, 1}, {1, 3}};
- using Ks = std::deque<int, min_allocator<int>>;
- using Vs = std::vector<MoveOnly, min_allocator<MoveOnly>>;
+ using Ks = KeyContainer<int, min_allocator<int>>;
+ using Vs = ValueContainer<MoveOnly, min_allocator<MoveOnly>>;
using M = std::flat_map<int, MoveOnly, std::greater<int>, Ks, Vs>;
Ks ks = {1, 3, 2};
Vs vs;
@@ -105,9 +96,9 @@ int main(int, char**) {
// flat_map(key_container_type , mapped_container_type)
// container's allocators are used
using A = test_allocator<int>;
- using M = std::flat_map<int, int, std::less<int>, std::vector<int, A>, std::deque<int, A>>;
- auto ks = std::vector<int, A>({1, 1, 1, 2, 2, 3, 2, 3, 3}, A(5));
- auto vs = std::deque<int, A>({1, 1, 1, 2, 2, 3, 2, 3, 3}, A(6));
+ using M = std::flat_map<int, int, std::less<int>, KeyContainer<int, A>, ValueContainer<int, A>>;
+ auto ks = KeyContainer<int, A>({1, 1, 1, 2, 2, 3, 2, 3, 3}, A(5));
+ auto vs = ValueContainer<int, A>({1, 1, 1, 2, 2, 3, 2, 3, 3}, A(6));
auto m = M(std::move(ks), std::move(vs));
assert(ks.empty()); // it was moved-from
assert(vs.empty()); // it was moved-from
@@ -117,13 +108,19 @@ int main(int, char**) {
}
{
// flat_map(key_container_type , mapped_container_type, key_compare)
- using C = test_less<int>;
- using M = std::flat_map<int, char, C>;
- std::vector<int> ks = {1, 1, 1, 2, 2, 3, 2, 3, 3};
- std::vector<char> vs = {1, 2, 3, 4, 5, 6, 7, 8, 9};
- auto m = M(ks, vs, C(4));
- assert((m.keys() == std::vector<int>{1, 2, 3}));
- LIBCPP_ASSERT((m.values() == std::vector<char>{1, 4, 6}));
+ using C = test_less<int>;
+ using M = std::flat_map<int, char, C, KeyContainer<int>, ValueContainer<char>>;
+ KeyContainer<int> ks = {1, 1, 1, 2, 2, 3, 2, 3, 3};
+ ValueContainer<char> vs = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+ auto m = M(ks, vs, C(4));
+ assert((m.keys() == KeyContainer<int>{1, 2, 3}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<char>>{
+ {1, 2, 3},
+ {4, 5, 7},
+ {6, 8, 9},
+ });
assert(m.key_comp() == C(4));
// explicit(false)
@@ -134,9 +131,9 @@ int main(int, char**) {
{
// flat_map(key_container_type , mapped_container_type, const Allocator&)
using A = test_allocator<int>;
- using M = std::flat_map<int, int, std::less<int>, std::vector<int, A>, std::deque<int, A>>;
- auto ks = std::vector<int, A>({1, 1, 1, 2, 2, 3, 2, 3, 3}, A(5));
- auto vs = std::deque<int, A>({1, 1, 1, 2, 2, 3, 2, 3, 3}, A(6));
+ using M = std::flat_map<int, int, std::less<int>, KeyContainer<int, A>, ValueContainer<int, A>>;
+ auto ks = KeyContainer<int, A>({1, 1, 1, 2, 2, 3, 2, 3, 3}, A(5));
+ auto vs = ValueContainer<int, A>({1, 1, 1, 2, 2, 3, 2, 3, 3}, A(6));
auto m = M(ks, vs, A(4)); // replaces the allocators
assert(!ks.empty()); // it was an lvalue above
assert(!vs.empty()); // it was an lvalue above
@@ -148,9 +145,9 @@ int main(int, char**) {
// flat_map(key_container_type , mapped_container_type, const Allocator&)
// explicit(false)
using A = test_allocator<int>;
- using M = std::flat_map<int, int, std::less<int>, std::vector<int, A>, std::deque<int, A>>;
- auto ks = std::vector<int, A>({1, 1, 1, 2, 2, 3, 2, 3, 3}, A(5));
- auto vs = std::deque<int, A>({1, 1, 1, 2, 2, 3, 2, 3, 3}, A(6));
+ using M = std::flat_map<int, int, std::less<int>, KeyContainer<int, A>, ValueContainer<int, A>>;
+ auto ks = KeyContainer<int, A>({1, 1, 1, 2, 2, 3, 2, 3, 3}, A(5));
+ auto vs = ValueContainer<int, A>({1, 1, 1, 2, 2, 3, 2, 3, 3}, A(6));
M m = {ks, vs, A(4)}; // implicit ctor
assert(!ks.empty()); // it was an lvalue above
assert(!vs.empty()); // it was an lvalue above
@@ -158,6 +155,7 @@ int main(int, char**) {
assert(m.keys().get_allocator() == A(4));
assert(m.values().get_allocator() == A(4));
}
+
{
// flat_map(key_container_type , mapped_container_type, key_compare, const Allocator&)
using C = test_less<int>;
@@ -167,7 +165,13 @@ int main(int, char**) {
std::vector<int, A> vs = {1, 2, 3, 4, 5, 6, 7, 8, 9};
auto m = M(ks, vs, C(4), A(5));
assert((m.keys() == std::vector<int, A>{1, 2, 3}));
- LIBCPP_ASSERT((m.values() == std::vector<int, A>{1, 4, 6}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<int>>{
+ {1, 2, 3},
+ {4, 5, 7},
+ {6, 8, 9},
+ });
assert(m.key_comp() == C(4));
assert(m.keys().get_allocator() == A(5));
assert(m.values().get_allocator() == A(5));
@@ -179,6 +183,51 @@ int main(int, char**) {
assert(m2.keys().get_allocator() == A(5));
assert(m2.values().get_allocator() == A(5));
}
+}
+
+bool constexpr test() {
+ {
+ // The constructors in this subclause shall not participate in overload
+ // resolution unless uses_allocator_v<key_container_type, Alloc> is true
+ // and uses_allocator_v<mapped_container_type, Alloc> is true.
+
+ using C = test_less<int>;
+ using A1 = test_allocator<int>;
+ using A2 = other_allocator<int>;
+ using V1 = std::vector<int, A1>;
+ using V2 = std::vector<int, A2>;
+ using M1 = std::flat_map<int, int, C, V1, V1>;
+ using M2 = std::flat_map<int, int, C, V1, V2>;
+ using M3 = std::flat_map<int, int, C, V2, V1>;
+ static_assert(std::is_constructible_v<M1, const V1&, const V1&, const A1&>);
+ static_assert(!std::is_constructible_v<M1, const V1&, const V1&, const A2&>);
+ static_assert(!std::is_constructible_v<M2, const V1&, const V2&, const A2&>);
+ static_assert(!std::is_constructible_v<M3, const V2&, const V1&, const A2&>);
+
+ static_assert(std::is_constructible_v<M1, const V1&, const V1&, const C&, const A1&>);
+ static_assert(!std::is_constructible_v<M1, const V1&, const V1&, const C&, const A2&>);
+ static_assert(!std::is_constructible_v<M2, const V1&, const V2&, const C&, const A2&>);
+ static_assert(!std::is_constructible_v<M3, const V2&, const V1&, const C&, const A2&>);
+ }
+
+ test<std::vector, std::vector>();
+
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque, std::vector>();
+ test<std::deque, std::deque>();
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/copy.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/copy.pass.cpp
index fcd0415088c1c..856886718999a 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/copy.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/copy.pass.cpp
@@ -13,6 +13,7 @@
// flat_map(const flat_map& m);
#include <cassert>
+#include <deque>
#include <flat_map>
#include <vector>
@@ -20,11 +21,12 @@
#include "../../../test_compare.h"
#include "test_allocator.h"
-int main(int, char**) {
+template <template <class...> class KeyContainer, template <class...> class ValueContainer>
+constexpr void test() {
{
using C = test_less<int>;
- std::vector<int, test_allocator<int>> ks({1, 3, 5}, test_allocator<int>(6));
- std::vector<char, test_allocator<char>> vs({2, 2, 1}, test_allocator<char>(7));
+ KeyContainer<int, test_allocator<int>> ks({1, 3, 5}, test_allocator<int>(6));
+ ValueContainer<char, test_allocator<char>> vs({2, 2, 1}, test_allocator<char>(7));
using M = std::flat_map<int, char, C, decltype(ks), decltype(vs)>;
auto mo = M(ks, vs, C(5));
auto m = mo;
@@ -44,8 +46,8 @@ int main(int, char**) {
}
{
using C = test_less<int>;
- using Ks = std::vector<int, other_allocator<int>>;
- using Vs = std::vector<char, other_allocator<char>>;
+ using Ks = KeyContainer<int, other_allocator<int>>;
+ using Vs = ValueContainer<char, other_allocator<char>>;
auto ks = Ks({1, 3, 5}, other_allocator<int>(6));
auto vs = Vs({2, 2, 1}, other_allocator<char>(7));
using M = std::flat_map<int, char, C, Ks, Vs>;
@@ -65,6 +67,26 @@ int main(int, char**) {
assert(mo.keys().get_allocator() == other_allocator<int>(6));
assert(mo.values().get_allocator() == other_allocator<char>(7));
}
+}
+
+constexpr bool test() {
+ test<std::vector, std::vector>();
+
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque, std::deque>();
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/copy_alloc.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/copy_alloc.pass.cpp
index cbda6ea853268..f8b5774f86193 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/copy_alloc.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/copy_alloc.pass.cpp
@@ -22,7 +22,30 @@
#include "../../../test_compare.h"
#include "test_allocator.h"
-int main(int, char**) {
+template <template <class...> class KeyContainer, template <class...> class ValueContainer>
+constexpr void test() {
+ using C = test_less<int>;
+ KeyContainer<int, test_allocator<int>> ks({1, 3, 5}, test_allocator<int>(6));
+ ValueContainer<char, test_allocator<char>> vs({2, 2, 1}, test_allocator<char>(7));
+ using M = std::flat_map<int, char, C, decltype(ks), decltype(vs)>;
+ auto mo = M(ks, vs, C(5));
+ auto m = M(mo, test_allocator<int>(3));
+
+ assert(m.key_comp() == C(5));
+ assert(m.keys() == ks);
+ assert(m.values() == vs);
+ assert(m.keys().get_allocator() == test_allocator<int>(3));
+ assert(m.values().get_allocator() == test_allocator<char>(3));
+
+ // mo is unchanged
+ assert(mo.key_comp() == C(5));
+ assert(mo.keys() == ks);
+ assert(mo.values() == vs);
+ assert(mo.keys().get_allocator() == test_allocator<int>(6));
+ assert(mo.values().get_allocator() == test_allocator<char>(7));
+}
+
+constexpr bool test() {
{
// The constructors in this subclause shall not participate in overload
// resolution unless uses_allocator_v<key_container_type, Alloc> is true
@@ -41,27 +64,24 @@ int main(int, char**) {
static_assert(!std::is_constructible_v<M2, const M2&, const A2&>);
static_assert(!std::is_constructible_v<M3, const M3&, const A2&>);
}
- {
- using C = test_less<int>;
- std::vector<int, test_allocator<int>> ks({1, 3, 5}, test_allocator<int>(6));
- std::vector<char, test_allocator<char>> vs({2, 2, 1}, test_allocator<char>(7));
- using M = std::flat_map<int, char, C, decltype(ks), decltype(vs)>;
- auto mo = M(ks, vs, C(5));
- auto m = M(mo, test_allocator<int>(3));
- assert(m.key_comp() == C(5));
- assert(m.keys() == ks);
- assert(m.values() == vs);
- assert(m.keys().get_allocator() == test_allocator<int>(3));
- assert(m.values().get_allocator() == test_allocator<char>(3));
+ test<std::vector, std::vector>();
- // mo is unchanged
- assert(mo.key_comp() == C(5));
- assert(mo.keys() == ks);
- assert(mo.values() == vs);
- assert(mo.keys().get_allocator() == test_allocator<int>(6));
- assert(mo.values().get_allocator() == test_allocator<char>(7));
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque, std::deque>();
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/copy_assign.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/copy_assign.pass.cpp
index 4f9797d5bf810..8aa2e7bc539fd 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/copy_assign.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/copy_assign.pass.cpp
@@ -15,18 +15,20 @@
#include <deque>
#include <flat_map>
#include <functional>
+#include <type_traits>
#include <vector>
#include "test_macros.h"
#include "../../../test_compare.h"
#include "test_allocator.h"
-int main(int, char**) {
+template <template <class...> class KeyContainer, template <class...> class ValueContainer>
+constexpr void test() {
{
// test_allocator is not propagated
using C = test_less<int>;
- std::vector<int, test_allocator<int>> ks({1, 3, 5}, test_allocator<int>(6));
- std::vector<char, test_allocator<char>> vs({2, 2, 1}, test_allocator<char>(7));
+ KeyContainer<int, test_allocator<int>> ks({1, 3, 5}, test_allocator<int>(6));
+ ValueContainer<char, test_allocator<char>> vs({2, 2, 1}, test_allocator<char>(7));
using M = std::flat_map<int, char, C, decltype(ks), decltype(vs)>;
auto mo = M(ks, vs, C(5));
auto m = M({{3, 3}, {4, 4}, {5, 5}}, C(3), test_allocator<int>(2));
@@ -48,8 +50,8 @@ int main(int, char**) {
{
// other_allocator is propagated
using C = test_less<int>;
- using Ks = std::vector<int, other_allocator<int>>;
- using Vs = std::vector<char, other_allocator<char>>;
+ using Ks = KeyContainer<int, other_allocator<int>>;
+ using Vs = ValueContainer<char, other_allocator<char>>;
auto ks = Ks({1, 3, 5}, other_allocator<int>(6));
auto vs = Vs({2, 2, 1}, other_allocator<char>(7));
using M = std::flat_map<int, char, C, Ks, Vs>;
@@ -70,7 +72,7 @@ int main(int, char**) {
assert(mo.keys().get_allocator() == other_allocator<int>(6));
assert(mo.values().get_allocator() == other_allocator<char>(7));
}
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
// comparator is copied and invariant is preserved
using M = std::flat_map<int, int, std::function<bool(int, int)>>;
M mo = M({{1, 2}, {3, 4}}, std::less<int>());
@@ -88,5 +90,26 @@ int main(int, char**) {
m = static_cast<const M&>(m);
assert((m == M{{1, 2}, {3, 4}}));
}
+}
+
+constexpr bool test() {
+ test<std::vector, std::vector>();
+
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque, std::deque>();
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/default.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/default.pass.cpp
index c5b94896b9293..5ef31ef82384b 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/default.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/default.pass.cpp
@@ -24,23 +24,24 @@
#include "test_allocator.h"
struct DefaultCtableComp {
- explicit DefaultCtableComp() { default_constructed_ = true; }
- bool operator()(int, int) const { return false; }
+ constexpr explicit DefaultCtableComp() { default_constructed_ = true; }
+ constexpr bool operator()(int, int) const { return false; }
bool default_constructed_ = false;
};
-int main(int, char**) {
+template <template <class...> class KeyContainer, template <class...> class ValueContainer>
+constexpr void test() {
{
- std::flat_map<int, char*> m;
+ std::flat_map<int, char*, std::less<int>, KeyContainer<int>, ValueContainer<char*>> m;
assert(m.empty());
}
{
// explicit(false)
- std::flat_map<int, char*> m = {};
+ std::flat_map<int, char*, std::less<int>, KeyContainer<int>, ValueContainer<char*>> m = {};
assert(m.empty());
}
{
- std::flat_map<int, char*, DefaultCtableComp, std::deque<int, min_allocator<int>>> m;
+ std::flat_map<int, char*, DefaultCtableComp, KeyContainer<int, min_allocator<int>>> m;
assert(m.empty());
assert(m.begin() == m.end());
assert(m.key_comp().default_constructed_);
@@ -49,13 +50,13 @@ int main(int, char**) {
using A1 = explicit_allocator<int>;
using A2 = explicit_allocator<char*>;
{
- std::flat_map<int, char*, DefaultCtableComp, std::vector<int, A1>, std::vector<char*, A2>> m;
+ std::flat_map<int, char*, DefaultCtableComp, KeyContainer<int, A1>, ValueContainer<char*, A2>> m;
assert(m.empty());
assert(m.key_comp().default_constructed_);
}
{
A1 a1;
- std::flat_map<int, int, DefaultCtableComp, std::vector<int, A1>, std::vector<int, A1>> m(a1);
+ std::flat_map<int, int, DefaultCtableComp, KeyContainer<int, A1>, ValueContainer<int, A1>> m(a1);
assert(m.empty());
assert(m.key_comp().default_constructed_);
}
@@ -63,10 +64,31 @@ int main(int, char**) {
{
// If an allocator is given, it must be usable by both containers.
using A = test_allocator<int>;
- using M = std::flat_map<int, int, std::less<>, std::vector<int>, std::vector<int, A>>;
+ using M = std::flat_map<int, int, std::less<>, KeyContainer<int>, ValueContainer<int, A>>;
static_assert(std::is_constructible_v<M>);
static_assert(!std::is_constructible_v<M, std::allocator<int>>);
static_assert(!std::is_constructible_v<M, A>);
}
+}
+
+constexpr bool test() {
+ test<std::vector, std::vector>();
+
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque, std::deque>();
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/default_noexcept.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/default_noexcept.pass.cpp
index 790dfa4a02ed5..9fb6785a7cf1a 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/default_noexcept.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/default_noexcept.pass.cpp
@@ -28,11 +28,11 @@
#include "test_allocator.h"
struct ThrowingCtorComp {
- ThrowingCtorComp() noexcept(false) {}
- bool operator()(const auto&, const auto&) const { return false; }
+ constexpr ThrowingCtorComp() noexcept(false) {}
+ constexpr bool operator()(const auto&, const auto&) const { return false; }
};
-int main(int, char**) {
+constexpr bool test() {
#if defined(_LIBCPP_VERSION)
{
using C = std::flat_map<MoveOnly, MoveOnly>;
@@ -55,5 +55,15 @@ int main(int, char**) {
static_assert(!std::is_nothrow_default_constructible_v<C>);
C c;
}
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/dtor_noexcept.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/dtor_noexcept.pass.cpp
index 1570b0fa14888..4562b01bc8c42 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/dtor_noexcept.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/dtor_noexcept.pass.cpp
@@ -23,35 +23,58 @@
#include "test_allocator.h"
struct ThrowingDtorComp {
- bool operator()(const auto&, const auto&) const;
- ~ThrowingDtorComp() noexcept(false) {}
+ constexpr bool operator()(const auto&, const auto&) const;
+ constexpr ~ThrowingDtorComp() noexcept(false) {}
};
-int main(int, char**) {
+template <template <class...> class KeyContainer, template <class...> class ValueContainer>
+constexpr void test() {
{
- using C = std::flat_map<MoveOnly, MoveOnly>;
+ using C = std::flat_map<MoveOnly, MoveOnly, std::less<MoveOnly>, KeyContainer<MoveOnly>, ValueContainer<MoveOnly>>;
static_assert(std::is_nothrow_destructible_v<C>);
C c;
}
{
- using V = std::vector<MoveOnly, test_allocator<MoveOnly>>;
- using C = std::flat_map<MoveOnly, MoveOnly, std::less<MoveOnly>, V, V>;
+ using V = KeyContainer<MoveOnly, test_allocator<MoveOnly>>;
+ using V2 = ValueContainer<MoveOnly, test_allocator<MoveOnly>>;
+ using C = std::flat_map<MoveOnly, MoveOnly, std::less<MoveOnly>, V, V2>;
static_assert(std::is_nothrow_destructible_v<C>);
C c;
}
{
- using V = std::deque<MoveOnly, other_allocator<MoveOnly>>;
- using C = std::flat_map<MoveOnly, MoveOnly, std::greater<MoveOnly>, V, V>;
+ using V = KeyContainer<MoveOnly, test_allocator<MoveOnly>>;
+ using V2 = ValueContainer<MoveOnly, test_allocator<MoveOnly>>;
+ using C = std::flat_map<MoveOnly, MoveOnly, std::greater<MoveOnly>, V, V2>;
static_assert(std::is_nothrow_destructible_v<C>);
C c;
}
#if defined(_LIBCPP_VERSION)
{
- using C = std::flat_map<MoveOnly, MoveOnly, ThrowingDtorComp>;
+ using C = std::flat_map<MoveOnly, MoveOnly, ThrowingDtorComp, KeyContainer<MoveOnly>, ValueContainer<MoveOnly>>;
static_assert(!std::is_nothrow_destructible_v<C>);
C c;
}
#endif // _LIBCPP_VERSION
+}
+
+constexpr bool test() {
+ test<std::vector, std::vector>();
+
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque, std::deque>();
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/initializer_list.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/initializer_list.pass.cpp
index 7a22746845d00..aea2002ba8d9c 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/initializer_list.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/initializer_list.pass.cpp
@@ -30,58 +30,13 @@
#include "../../../test_compare.h"
struct DefaultCtableComp {
- explicit DefaultCtableComp() { default_constructed_ = true; }
- bool operator()(int, int) const { return false; }
+ constexpr explicit DefaultCtableComp() { default_constructed_ = true; }
+ constexpr bool operator()(int, int) const { return false; }
bool default_constructed_ = false;
};
-int main(int, char**) {
- {
- // The constructors in this subclause shall not participate in overload
- // resolution unless uses_allocator_v<key_container_type, Alloc> is true
- // and uses_allocator_v<mapped_container_type, Alloc> is true.
-
- using C = test_less<int>;
- using A1 = test_allocator<int>;
- using A2 = other_allocator<int>;
- using V1 = std::vector<int, A1>;
- using V2 = std::vector<int, A2>;
- using M1 = std::flat_map<int, int, C, V1, V1>;
- using M2 = std::flat_map<int, int, C, V1, V2>;
- using M3 = std::flat_map<int, int, C, V2, V1>;
- using IL = std::initializer_list<std::pair<int, int>>;
- static_assert(std::is_constructible_v<M1, IL, const A1&>);
- static_assert(!std::is_constructible_v<M1, IL, const A2&>);
- static_assert(!std::is_constructible_v<M2, IL, const A2&>);
- static_assert(!std::is_constructible_v<M3, IL, const A2&>);
-
- static_assert(std::is_constructible_v<M1, IL, const C&, const A1&>);
- static_assert(!std::is_constructible_v<M1, IL, const C&, const A2&>);
- static_assert(!std::is_constructible_v<M2, IL, const C&, const A2&>);
- static_assert(!std::is_constructible_v<M3, IL, const C&, const A2&>);
- }
-
- {
- // initializer_list<value_type> needs to match exactly
- using M = std::flat_map<int, short>;
- using C = typename M::key_compare;
- static_assert(std::is_constructible_v<M, std::initializer_list<std::pair<int, short>>>);
- static_assert(std::is_constructible_v<M, std::initializer_list<std::pair<int, short>>, C>);
- static_assert(std::is_constructible_v<M, std::initializer_list<std::pair<int, short>>, C, std::allocator<int>>);
- static_assert(std::is_constructible_v<M, std::initializer_list<std::pair<int, short>>, std::allocator<int>>);
- static_assert(!std::is_constructible_v<M, std::initializer_list<std::pair<const int, short>>>);
- static_assert(!std::is_constructible_v<M, std::initializer_list<std::pair<const int, short>>, C>);
- static_assert(
- !std::is_constructible_v<M, std::initializer_list<std::pair<const int, short>>, C, std::allocator<int>>);
- static_assert(!std::is_constructible_v<M, std::initializer_list<std::pair<const int, short>>, std::allocator<int>>);
- static_assert(!std::is_constructible_v<M, std::initializer_list<std::pair<const int, const short>>>);
- static_assert(!std::is_constructible_v<M, std::initializer_list<std::pair<const int, const short>>, C>);
- static_assert(
- !std::is_constructible_v<M, std::initializer_list<std::pair<const int, const short>>, C, std::allocator<int>>);
- static_assert(
- !std::is_constructible_v<M, std::initializer_list<std::pair<const int, const short>>, std::allocator<int>>);
- }
-
+template <template <class...> class KeyContainer, template <class...> class ValueContainer>
+constexpr void test() {
std::pair<int, short> expected[] = {{1, 1}, {2, 2}, {3, 3}, {5, 2}};
{
// flat_map(initializer_list<value_type>);
@@ -99,29 +54,27 @@ int main(int, char**) {
}
{
// flat_map(initializer_list<value_type>);
- using M = std::flat_map<int, short, std::greater<int>, std::deque<int, min_allocator<int>>>;
+ using M = std::flat_map<int, short, std::greater<int>, KeyContainer<int, min_allocator<int>>>;
M m = {{5, 2}, {2, 2}, {2, 2}, {3, 3}, {1, 1}, {3, 3}};
assert(std::equal(m.rbegin(), m.rend(), expected, expected + 4));
}
{
+ // flat_map(initializer_list<value_type>);
+ // different comparator
using A = explicit_allocator<int>;
- {
- // flat_map(initializer_list<value_type>);
- // different comparator
- using M = std::flat_map<int, int, DefaultCtableComp, std::vector<int, A>, std::deque<int, A>>;
- M m = {{1, 1}, {2, 2}, {3, 3}};
- assert(m.size() == 1);
- assert(m.begin()->first == m.begin()->second);
- LIBCPP_ASSERT(*m.begin() == std::make_pair(1, 1));
- assert(m.key_comp().default_constructed_);
- }
- {
- // flat_map(initializer_list<value_type>, const Allocator&);
- using M = std::flat_map<int, int, std::greater<int>, std::deque<int, A>, std::vector<int, A>>;
- A a;
- M m({{5, 2}, {2, 2}, {2, 2}, {3, 3}, {1, 1}, {3, 3}}, a);
- assert(std::equal(m.rbegin(), m.rend(), expected, expected + 4));
- }
+ using M = std::flat_map<int, int, DefaultCtableComp, KeyContainer<int, A>, ValueContainer<int, A>>;
+ M m = {{1, 1}, {2, 2}, {3, 3}};
+ assert(m.size() == 1);
+ assert(m.begin()->first == m.begin()->second);
+ assert(m.key_comp().default_constructed_);
+ }
+ {
+ // flat_map(initializer_list<value_type>, const Allocator&);
+ using A = explicit_allocator<int>;
+ using M = std::flat_map<int, int, std::greater<int>, KeyContainer<int, A>, ValueContainer<int, A>>;
+ A a;
+ M m({{5, 2}, {2, 2}, {2, 2}, {3, 3}, {1, 1}, {3, 3}}, a);
+ assert(std::equal(m.rbegin(), m.rend(), expected, expected + 4));
}
{
// flat_map(initializer_list<value_type>, const key_compare&);
@@ -136,10 +89,10 @@ int main(int, char**) {
assert(m2 == m);
assert(m2.key_comp() == C(10));
}
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
// flat_map(initializer_list<value_type>, const key_compare&);
// Sorting uses the comparator that was passed in
- using M = std::flat_map<int, short, std::function<bool(int, int)>, std::deque<int, min_allocator<int>>>;
+ using M = std::flat_map<int, short, std::function<bool(int, int)>, KeyContainer<int, min_allocator<int>>>;
auto m = M({{5, 2}, {2, 2}, {2, 2}, {3, 3}, {1, 1}, {3, 3}}, std::greater<int>());
assert(std::equal(m.rbegin(), m.rend(), expected, expected + 4));
assert(m.key_comp()(2, 1) == true);
@@ -147,11 +100,77 @@ int main(int, char**) {
{
// flat_map(initializer_list<value_type> il, const key_compare& comp, const Alloc& a);
using A = explicit_allocator<int>;
- using M = std::flat_map<int, int, std::greater<int>, std::deque<int, A>, std::vector<int, A>>;
+ using M = std::flat_map<int, int, std::greater<int>, KeyContainer<int, A>, ValueContainer<int, A>>;
A a;
M m({{5, 2}, {2, 2}, {2, 2}, {3, 3}, {1, 1}, {3, 3}}, {}, a);
assert(std::equal(m.rbegin(), m.rend(), expected, expected + 4));
}
+}
+
+constexpr bool test() {
+ {
+ // The constructors in this subclause shall not participate in overload
+ // resolution unless uses_allocator_v<key_container_type, Alloc> is true
+ // and uses_allocator_v<mapped_container_type, Alloc> is true.
+
+ using C = test_less<int>;
+ using A1 = test_allocator<int>;
+ using A2 = other_allocator<int>;
+ using V1 = std::vector<int, A1>;
+ using V2 = std::vector<int, A2>;
+ using M1 = std::flat_map<int, int, C, V1, V1>;
+ using M2 = std::flat_map<int, int, C, V1, V2>;
+ using M3 = std::flat_map<int, int, C, V2, V1>;
+ using IL = std::initializer_list<std::pair<int, int>>;
+ static_assert(std::is_constructible_v<M1, IL, const A1&>);
+ static_assert(!std::is_constructible_v<M1, IL, const A2&>);
+ static_assert(!std::is_constructible_v<M2, IL, const A2&>);
+ static_assert(!std::is_constructible_v<M3, IL, const A2&>);
+
+ static_assert(std::is_constructible_v<M1, IL, const C&, const A1&>);
+ static_assert(!std::is_constructible_v<M1, IL, const C&, const A2&>);
+ static_assert(!std::is_constructible_v<M2, IL, const C&, const A2&>);
+ static_assert(!std::is_constructible_v<M3, IL, const C&, const A2&>);
+ }
+
+ {
+ // initializer_list<value_type> needs to match exactly
+ using M = std::flat_map<int, short>;
+ using C = typename M::key_compare;
+ static_assert(std::is_constructible_v<M, std::initializer_list<std::pair<int, short>>>);
+ static_assert(std::is_constructible_v<M, std::initializer_list<std::pair<int, short>>, C>);
+ static_assert(std::is_constructible_v<M, std::initializer_list<std::pair<int, short>>, C, std::allocator<int>>);
+ static_assert(std::is_constructible_v<M, std::initializer_list<std::pair<int, short>>, std::allocator<int>>);
+ static_assert(!std::is_constructible_v<M, std::initializer_list<std::pair<const int, short>>>);
+ static_assert(!std::is_constructible_v<M, std::initializer_list<std::pair<const int, short>>, C>);
+ static_assert(
+ !std::is_constructible_v<M, std::initializer_list<std::pair<const int, short>>, C, std::allocator<int>>);
+ static_assert(!std::is_constructible_v<M, std::initializer_list<std::pair<const int, short>>, std::allocator<int>>);
+ static_assert(!std::is_constructible_v<M, std::initializer_list<std::pair<const int, const short>>>);
+ static_assert(!std::is_constructible_v<M, std::initializer_list<std::pair<const int, const short>>, C>);
+ static_assert(
+ !std::is_constructible_v<M, std::initializer_list<std::pair<const int, const short>>, C, std::allocator<int>>);
+ static_assert(
+ !std::is_constructible_v<M, std::initializer_list<std::pair<const int, const short>>, std::allocator<int>>);
+ }
+
+ test<std::vector, std::vector>();
+
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque, std::deque>();
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/iter_iter.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/iter_iter.pass.cpp
index 7c0c487969943..0dce4f1993c66 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/iter_iter.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/iter_iter.pass.cpp
@@ -22,51 +22,35 @@
#include <flat_map>
#include <functional>
#include <vector>
+#include <ranges>
+#include "MinSequenceContainer.h"
#include "min_allocator.h"
#include "test_allocator.h"
#include "test_iterators.h"
#include "test_macros.h"
+#include "../helpers.h"
#include "../../../test_compare.h"
-int main(int, char**) {
- {
- // The constructors in this subclause shall not participate in overload
- // resolution unless uses_allocator_v<key_container_type, Alloc> is true
- // and uses_allocator_v<mapped_container_type, Alloc> is true.
-
- using C = test_less<int>;
- using A1 = test_allocator<int>;
- using A2 = other_allocator<int>;
- using V1 = std::vector<int, A1>;
- using V2 = std::vector<int, A2>;
- using M1 = std::flat_map<int, int, C, V1, V1>;
- using M2 = std::flat_map<int, int, C, V1, V2>;
- using M3 = std::flat_map<int, int, C, V2, V1>;
- using Iter1 = typename M1::iterator;
- using Iter2 = typename M2::iterator;
- using Iter3 = typename M3::iterator;
- static_assert(std::is_constructible_v<M1, Iter1, Iter1, const A1&>);
- static_assert(!std::is_constructible_v<M1, Iter1, Iter1, const A2&>);
- static_assert(!std::is_constructible_v<M2, Iter2, Iter2, const A2&>);
- static_assert(!std::is_constructible_v<M3, Iter3, Iter3, const A2&>);
-
- static_assert(std::is_constructible_v<M1, Iter1, Iter1, const C&, const A1&>);
- static_assert(!std::is_constructible_v<M1, Iter1, Iter1, const C&, const A2&>);
- static_assert(!std::is_constructible_v<M2, Iter2, Iter2, const C&, const A2&>);
- static_assert(!std::is_constructible_v<M3, Iter3, Iter3, const C&, const A2&>);
- }
-
- using P = std::pair<int, short>;
- P ar[] = {{1, 1}, {1, 2}, {1, 3}, {2, 4}, {2, 5}, {3, 6}, {2, 7}, {3, 8}, {3, 9}};
- P expected[] = {{1, 1}, {2, 4}, {3, 6}};
+template <class KeyContainer, class ValueContainer>
+constexpr void test() {
+ using Key = typename KeyContainer::value_type;
+ using Value = typename ValueContainer::value_type;
+ using P = std::pair<Key, Value>;
+ P ar[] = {{1, 1}, {1, 2}, {1, 3}, {2, 4}, {2, 5}, {3, 6}, {2, 7}, {3, 8}, {3, 9}};
{
// flat_map(InputIterator , InputIterator)
// cpp17_input_iterator
- using M = std::flat_map<int, short>;
+ using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
auto m = M(cpp17_input_iterator<const P*>(ar), cpp17_input_iterator<const P*>(ar + 9));
- assert(std::ranges::equal(m.keys(), expected | std::views::elements<0>));
- LIBCPP_ASSERT(std::ranges::equal(m, expected));
+ assert(std::ranges::equal(m.keys(), KeyContainer{1, 2, 3}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<Value>>{
+ {1, 2, 3},
+ {4, 5, 7},
+ {6, 8, 9},
+ });
// explicit(false)
M m2 = {cpp17_input_iterator<const P*>(ar), cpp17_input_iterator<const P*>(ar + 9)};
@@ -75,25 +59,37 @@ int main(int, char**) {
{
// flat_map(InputIterator , InputIterator)
// greater
- using M = std::flat_map<int, short, std::greater<int>, std::deque<int, min_allocator<int>>, std::deque<short>>;
+ using M = std::flat_map<Key, Value, std::greater<Key>, KeyContainer, ValueContainer>;
auto m = M(cpp17_input_iterator<const P*>(ar), cpp17_input_iterator<const P*>(ar + 9));
- assert((m.keys() == std::deque<int, min_allocator<int>>{3, 2, 1}));
- LIBCPP_ASSERT((m.values() == std::deque<short>{6, 4, 1}));
+ assert(std::ranges::equal(m.keys(), KeyContainer{3, 2, 1}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<Value>>{
+ {6, 8, 9},
+ {4, 5, 7},
+ {1, 2, 3},
+ });
}
{
// flat_map(InputIterator , InputIterator)
// Test when the operands are of array type (also contiguous iterator type)
- using M = std::flat_map<int, short, std::greater<int>, std::vector<int, min_allocator<int>>>;
+ using M = std::flat_map<Key, Value, std::greater<Key>, KeyContainer, ValueContainer>;
auto m = M(ar, ar);
assert(m.empty());
}
{
// flat_map(InputIterator , InputIterator, const key_compare&)
- using C = test_less<int>;
- using M = std::flat_map<int, short, C, std::vector<int>, std::deque<short>>;
+ using C = test_less<Key>;
+ using M = std::flat_map<Key, Value, C, KeyContainer, ValueContainer>;
auto m = M(ar, ar + 9, C(3));
- assert(std::ranges::equal(m.keys(), expected | std::views::elements<0>));
- LIBCPP_ASSERT(std::ranges::equal(m, expected));
+ assert(std::ranges::equal(m.keys(), KeyContainer{1, 2, 3}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<Value>>{
+ {1, 2, 3},
+ {4, 5, 7},
+ {6, 8, 9},
+ });
assert(m.key_comp() == C(3));
// explicit(false)
@@ -101,14 +97,27 @@ int main(int, char**) {
assert(m2 == m);
assert(m2.key_comp() == C(3));
}
+}
+
+template <template <class...> class KeyContainer, template <class...> class ValueContainer>
+constexpr void test_alloc() {
+ using P = std::pair<int, short>;
+ P ar[] = {{1, 1}, {1, 2}, {1, 3}, {2, 4}, {2, 5}, {3, 6}, {2, 7}, {3, 8}, {3, 9}};
+
{
// flat_map(InputIterator , InputIterator, const Allocator&)
using A1 = test_allocator<int>;
using A2 = test_allocator<short>;
- using M = std::flat_map<int, short, std::less<int>, std::vector<int, A1>, std::deque<short, A2>>;
+ using M = std::flat_map<int, short, std::less<int>, KeyContainer<int, A1>, ValueContainer<short, A2>>;
auto m = M(ar, ar + 9, A1(5));
- assert(std::ranges::equal(m.keys(), expected | std::views::elements<0>));
- LIBCPP_ASSERT(std::ranges::equal(m, expected));
+ assert(std::ranges::equal(m.keys(), KeyContainer<int, A1>{1, 2, 3}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<short>>{
+ {1, 2, 3},
+ {4, 5, 7},
+ {6, 8, 9},
+ });
assert(m.keys().get_allocator() == A1(5));
assert(m.values().get_allocator() == A2(5));
}
@@ -117,10 +126,16 @@ int main(int, char**) {
// explicit(false)
using A1 = test_allocator<int>;
using A2 = test_allocator<short>;
- using M = std::flat_map<int, short, std::less<int>, std::vector<int, A1>, std::deque<short, A2>>;
+ using M = std::flat_map<int, short, std::less<int>, KeyContainer<int, A1>, ValueContainer<short, A2>>;
M m = {ar, ar + 9, A1(5)}; // implicit ctor
- assert(std::ranges::equal(m.keys(), expected | std::views::elements<0>));
- LIBCPP_ASSERT(std::ranges::equal(m, expected));
+ assert(std::ranges::equal(m.keys(), KeyContainer<int, A1>{1, 2, 3}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<short>>{
+ {1, 2, 3},
+ {4, 5, 7},
+ {6, 8, 9},
+ });
assert(m.keys().get_allocator() == A1(5));
assert(m.values().get_allocator() == A2(5));
}
@@ -129,10 +144,16 @@ int main(int, char**) {
using C = test_less<int>;
using A1 = test_allocator<int>;
using A2 = test_allocator<short>;
- using M = std::flat_map<int, short, C, std::vector<int, A1>, std::deque<short, A2>>;
+ using M = std::flat_map<int, short, C, KeyContainer<int, A1>, ValueContainer<short, A2>>;
auto m = M(ar, ar + 9, C(3), A1(5));
- assert(std::ranges::equal(m.keys(), expected | std::views::elements<0>));
- LIBCPP_ASSERT(std::ranges::equal(m, expected));
+ assert(std::ranges::equal(m.keys(), KeyContainer<int, A1>{1, 2, 3}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<short>>{
+ {1, 2, 3},
+ {4, 5, 7},
+ {6, 8, 9},
+ });
assert(m.key_comp() == C(3));
assert(m.keys().get_allocator() == A1(5));
assert(m.values().get_allocator() == A2(5));
@@ -142,13 +163,73 @@ int main(int, char**) {
// explicit(false)
using A1 = test_allocator<int>;
using A2 = test_allocator<short>;
- using M = std::flat_map<int, short, std::less<int>, std::deque<int, A1>, std::vector<short, A2>>;
+ using M = std::flat_map<int, short, std::less<int>, KeyContainer<int, A1>, ValueContainer<short, A2>>;
M m = {ar, ar + 9, {}, A2(5)}; // implicit ctor
- assert(std::ranges::equal(m.keys(), expected | std::views::elements<0>));
- LIBCPP_ASSERT(std::ranges::equal(m, expected));
+ assert(std::ranges::equal(m.keys(), KeyContainer<int, A1>{1, 2, 3}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<short>>{
+ {1, 2, 3},
+ {4, 5, 7},
+ {6, 8, 9},
+ });
assert(m.keys().get_allocator() == A1(5));
assert(m.values().get_allocator() == A2(5));
}
+}
+
+constexpr bool test() {
+ {
+ // The constructors in this subclause shall not participate in overload
+ // resolution unless uses_allocator_v<key_container_type, Alloc> is true
+ // and uses_allocator_v<mapped_container_type, Alloc> is true.
+
+ using C = test_less<int>;
+ using A1 = test_allocator<int>;
+ using A2 = other_allocator<int>;
+ using V1 = std::vector<int, A1>;
+ using V2 = std::vector<int, A2>;
+ using M1 = std::flat_map<int, int, C, V1, V1>;
+ using M2 = std::flat_map<int, int, C, V1, V2>;
+ using M3 = std::flat_map<int, int, C, V2, V1>;
+ using Iter1 = typename M1::iterator;
+ using Iter2 = typename M2::iterator;
+ using Iter3 = typename M3::iterator;
+ static_assert(std::is_constructible_v<M1, Iter1, Iter1, const A1&>);
+ static_assert(!std::is_constructible_v<M1, Iter1, Iter1, const A2&>);
+ static_assert(!std::is_constructible_v<M2, Iter2, Iter2, const A2&>);
+ static_assert(!std::is_constructible_v<M3, Iter3, Iter3, const A2&>);
+
+ static_assert(std::is_constructible_v<M1, Iter1, Iter1, const C&, const A1&>);
+ static_assert(!std::is_constructible_v<M1, Iter1, Iter1, const C&, const A2&>);
+ static_assert(!std::is_constructible_v<M2, Iter2, Iter2, const C&, const A2&>);
+ static_assert(!std::is_constructible_v<M3, Iter3, Iter3, const C&, const A2&>);
+ }
+
+ test<std::vector<int>, std::vector<int>>();
+ test<std::vector<int>, std::vector<double>>();
+ test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
+ test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
+ test<std::vector<int, min_allocator<int>>, std::vector<int, min_allocator<int>>>();
+
+ test_alloc<std::vector, std::vector>();
+
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ test_alloc<std::deque, std::deque>();
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move.pass.cpp
index 955d3156064aa..de4cef1409ffb 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move.pass.cpp
@@ -16,6 +16,7 @@
#include <deque>
#include <flat_map>
#include <functional>
+#include <type_traits>
#include <utility>
#include <vector>
@@ -25,11 +26,12 @@
#include "test_allocator.h"
#include "min_allocator.h"
-int main(int, char**) {
+template <template <class...> class KeyContainer, template <class...> class ValueContainer>
+constexpr void test() {
{
using C = test_less<int>;
using A = test_allocator<int>;
- using M = std::flat_map<int, int, C, std::vector<int, A>, std::deque<int, A>>;
+ using M = std::flat_map<int, int, C, KeyContainer<int, A>, ValueContainer<int, A>>;
M mo = M({{1, 1}, {2, 2}, {3, 1}}, C(5), A(7));
M m = std::move(mo);
assert((m == M{{1, 1}, {2, 2}, {3, 1}}));
@@ -45,7 +47,7 @@ int main(int, char**) {
{
using C = test_less<int>;
using A = min_allocator<int>;
- using M = std::flat_map<int, int, C, std::vector<int, A>, std::deque<int, A>>;
+ using M = std::flat_map<int, int, C, KeyContainer<int, A>, ValueContainer<int, A>>;
M mo = M({{1, 1}, {2, 2}, {3, 1}}, C(5), A());
M m = std::move(mo);
assert((m == M{{1, 1}, {2, 2}, {3, 1}}));
@@ -58,9 +60,9 @@ int main(int, char**) {
assert(m.keys().get_allocator() == A());
assert(m.values().get_allocator() == A());
}
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
// A moved-from flat_map maintains its class invariant in the presence of moved-from comparators.
- using M = std::flat_map<int, int, std::function<bool(int, int)>>;
+ using M = std::flat_map<int, int, std::function<bool(int, int)>, KeyContainer<int>, ValueContainer<int>>;
M mo = M({{1, 1}, {2, 2}, {3, 1}}, std::less<int>());
M m = std::move(mo);
assert(m.size() == 3);
@@ -75,7 +77,7 @@ int main(int, char**) {
}
{
// moved-from object maintains invariant if one of underlying container does not clear after move
- using M = std::flat_map<int, int, std::less<>, std::vector<int>, CopyOnlyVector<int>>;
+ using M = std::flat_map<int, int, std::less<>, KeyContainer<int>, CopyOnlyVector<int>>;
M m1 = M({1, 2, 3}, {1, 2, 3});
M m2 = std::move(m1);
assert(m2.size() == 3);
@@ -84,5 +86,26 @@ int main(int, char**) {
LIBCPP_ASSERT(m1.keys().size() == 0);
LIBCPP_ASSERT(m1.values().size() == 0);
}
+}
+
+constexpr bool test() {
+ test<std::vector, std::vector>();
+
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque, std::deque>();
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move_alloc.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move_alloc.pass.cpp
index 93a3976422520..94121123b6a31 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move_alloc.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move_alloc.pass.cpp
@@ -24,30 +24,13 @@
#include "../../../test_compare.h"
#include "test_allocator.h"
-int main(int, char**) {
- {
- // The constructors in this subclause shall not participate in overload
- // resolution unless uses_allocator_v<key_container_type, Alloc> is true
- // and uses_allocator_v<mapped_container_type, Alloc> is true.
-
- using C = test_less<int>;
- using A1 = test_allocator<int>;
- using A2 = other_allocator<int>;
- using V1 = std::vector<int, A1>;
- using V2 = std::vector<int, A2>;
- using M1 = std::flat_map<int, int, C, V1, V1>;
- using M2 = std::flat_map<int, int, C, V1, V2>;
- using M3 = std::flat_map<int, int, C, V2, V1>;
- static_assert(std::is_constructible_v<M1, M1&&, const A1&>);
- static_assert(!std::is_constructible_v<M1, M1&&, const A2&>);
- static_assert(!std::is_constructible_v<M2, M2&&, const A2&>);
- static_assert(!std::is_constructible_v<M3, M3&&, const A2&>);
- }
+template <template <class...> class KeyContainer, template <class...> class ValueContainer>
+constexpr void test() {
{
std::pair<int, int> expected[] = {{1, 1}, {2, 2}, {3, 1}};
using C = test_less<int>;
using A = test_allocator<int>;
- using M = std::flat_map<int, int, C, std::vector<int, A>, std::deque<int, A>>;
+ using M = std::flat_map<int, int, C, KeyContainer<int, A>, ValueContainer<int, A>>;
auto mo = M(expected, expected + 3, C(5), A(7));
auto m = M(std::move(mo), A(3));
@@ -68,7 +51,7 @@ int main(int, char**) {
}
{
// moved-from object maintains invariant if one of underlying container does not clear after move
- using M = std::flat_map<int, int, std::less<>, std::vector<int>, CopyOnlyVector<int>>;
+ using M = std::flat_map<int, int, std::less<>, KeyContainer<int>, CopyOnlyVector<int>>;
M m1 = M({1, 2, 3}, {1, 2, 3});
M m2(std::move(m1), std::allocator<int>{});
assert(m2.size() == 3);
@@ -77,6 +60,45 @@ int main(int, char**) {
LIBCPP_ASSERT(m1.keys().size() == 0);
LIBCPP_ASSERT(m1.values().size() == 0);
}
+}
+
+constexpr bool test() {
+ {
+ // The constructors in this subclause shall not participate in overload
+ // resolution unless uses_allocator_v<key_container_type, Alloc> is true
+ // and uses_allocator_v<mapped_container_type, Alloc> is true.
+
+ using C = test_less<int>;
+ using A1 = test_allocator<int>;
+ using A2 = other_allocator<int>;
+ using V1 = std::vector<int, A1>;
+ using V2 = std::vector<int, A2>;
+ using M1 = std::flat_map<int, int, C, V1, V1>;
+ using M2 = std::flat_map<int, int, C, V1, V2>;
+ using M3 = std::flat_map<int, int, C, V2, V1>;
+ static_assert(std::is_constructible_v<M1, M1&&, const A1&>);
+ static_assert(!std::is_constructible_v<M1, M1&&, const A2&>);
+ static_assert(!std::is_constructible_v<M2, M2&&, const A2&>);
+ static_assert(!std::is_constructible_v<M3, M3&&, const A2&>);
+ }
+
+ test<std::vector, std::vector>();
+
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque, std::deque>();
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move_assign.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move_assign.pass.cpp
index a94c442c695dd..633c73e167a88 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move_assign.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move_assign.pass.cpp
@@ -26,12 +26,13 @@
#include "test_allocator.h"
#include "min_allocator.h"
-int main(int, char**) {
+template <template <class...> class KeyContainer, template <class...> class ValueContainer>
+constexpr void test() {
{
using C = test_less<int>;
using A1 = test_allocator<int>;
using A2 = test_allocator<char>;
- using M = std::flat_map<int, char, C, std::vector<int, A1>, std::vector<char, A2>>;
+ using M = std::flat_map<int, char, C, KeyContainer<int, A1>, ValueContainer<char, A2>>;
M mo = M({{1, 1}, {2, 3}, {3, 2}}, C(5), A1(7));
M m = M({}, C(3), A1(7));
m = std::move(mo);
@@ -46,7 +47,7 @@ int main(int, char**) {
using C = test_less<int>;
using A1 = other_allocator<int>;
using A2 = other_allocator<char>;
- using M = std::flat_map<int, char, C, std::deque<int, A1>, std::deque<char, A2>>;
+ using M = std::flat_map<int, char, C, KeyContainer<int, A1>, ValueContainer<char, A2>>;
M mo = M({{4, 5}, {5, 4}}, C(5), A1(7));
M m = M({{1, 1}, {2, 2}, {3, 3}, {4, 4}}, C(3), A1(7));
m = std::move(mo);
@@ -59,7 +60,7 @@ int main(int, char**) {
}
{
using A = min_allocator<int>;
- using M = std::flat_map<int, int, std::greater<int>, std::vector<int, A>, std::vector<int, A>>;
+ using M = std::flat_map<int, int, std::greater<int>, KeyContainer<int, A>, ValueContainer<int, A>>;
M mo = M({{5, 1}, {4, 2}, {3, 3}}, A());
M m = M({{4, 4}, {3, 3}, {2, 2}, {1, 1}}, A());
m = std::move(mo);
@@ -69,6 +70,26 @@ int main(int, char**) {
assert(vs.get_allocator() == A());
assert(mo.empty());
}
+}
- return 0;
+constexpr bool test() {
+ test<std::vector, std::vector>();
+
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque, std::deque>();
+ }
+
+ return true;
}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
+ return 0;
+}
\ No newline at end of file
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move_assign_clears.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move_assign_clears.pass.cpp
index f28d52dd4e463..0b52d67b0a9b6 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move_assign_clears.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move_assign_clears.pass.cpp
@@ -16,6 +16,7 @@
#include <algorithm>
#include <cassert>
#include <compare>
+#include <deque>
#include <flat_map>
#include <functional>
#include <utility>
@@ -27,9 +28,9 @@
struct MoveNegates {
int value_ = 0;
MoveNegates() = default;
- MoveNegates(int v) : value_(v) {}
- MoveNegates(MoveNegates&& rhs) : value_(rhs.value_) { rhs.value_ = -rhs.value_; }
- MoveNegates& operator=(MoveNegates&& rhs) {
+ constexpr MoveNegates(int v) : value_(v) {}
+ constexpr MoveNegates(MoveNegates&& rhs) : value_(rhs.value_) { rhs.value_ = -rhs.value_; }
+ constexpr MoveNegates& operator=(MoveNegates&& rhs) {
value_ = rhs.value_;
rhs.value_ = -rhs.value_;
return *this;
@@ -41,9 +42,9 @@ struct MoveNegates {
struct MoveClears {
int value_ = 0;
MoveClears() = default;
- MoveClears(int v) : value_(v) {}
- MoveClears(MoveClears&& rhs) : value_(rhs.value_) { rhs.value_ = 0; }
- MoveClears& operator=(MoveClears&& rhs) {
+ constexpr MoveClears(int v) : value_(v) {}
+ constexpr MoveClears(MoveClears&& rhs) : value_(rhs.value_) { rhs.value_ = 0; }
+ constexpr MoveClears& operator=(MoveClears&& rhs) {
value_ = rhs.value_;
rhs.value_ = 0;
return *this;
@@ -52,11 +53,12 @@ struct MoveClears {
auto operator<=>(const MoveClears&) const = default;
};
-int main(int, char**) {
+template <template <class...> class KeyContainer, template <class...> class ValueContainer>
+constexpr void test() {
auto value_eq = [](auto&& p, auto&& q) { return p.first == q.first; };
{
const std::pair<int, int> expected[] = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {7, 7}, {8, 8}};
- using M = std::flat_map<MoveNegates, int, std::less<MoveNegates>, std::vector<MoveNegates>>;
+ using M = std::flat_map<MoveNegates, int, std::less<MoveNegates>, KeyContainer<MoveNegates>, ValueContainer<int>>;
M m = M(expected, expected + 8);
M m2 = M(expected, expected + 3);
@@ -73,7 +75,7 @@ int main(int, char**) {
}
{
const std::pair<int, int> expected[] = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {7, 7}, {8, 8}};
- using M = std::flat_map<MoveClears, int, std::less<MoveClears>, std::vector<MoveClears>>;
+ using M = std::flat_map<MoveClears, int, std::less<MoveClears>, KeyContainer<MoveClears>, ValueContainer<int>>;
M m = M(expected, expected + 8);
M m2 = M(expected, expected + 3);
@@ -90,7 +92,7 @@ int main(int, char**) {
}
{
// moved-from object maintains invariant if one of underlying container does not clear after move
- using M = std::flat_map<int, int, std::less<>, std::vector<int>, CopyOnlyVector<int>>;
+ using M = std::flat_map<int, int, std::less<>, KeyContainer<int>, CopyOnlyVector<int>>;
M m1 = M({1, 2, 3}, {1, 2, 3});
M m2 = M({1, 2}, {1, 2});
m2 = std::move(m1);
@@ -100,5 +102,26 @@ int main(int, char**) {
LIBCPP_ASSERT(m1.keys().size() == 0);
LIBCPP_ASSERT(m1.values().size() == 0);
}
+}
+
+constexpr bool test() {
+ test<std::vector, std::vector>();
+
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque, std::deque>();
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move_assign_noexcept.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move_assign_noexcept.compile.pass.cpp
similarity index 99%
rename from libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move_assign_noexcept.pass.cpp
rename to libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move_assign_noexcept.compile.pass.cpp
index 665b763e6c4f7..18d332cf27b5b 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move_assign_noexcept.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move_assign_noexcept.compile.pass.cpp
@@ -49,7 +49,7 @@ struct MoveThrowsComp {
bool operator()(const auto&, const auto&) const;
};
-int main(int, char**) {
+void test() {
{
using C = std::flat_map<int, int>;
LIBCPP_STATIC_ASSERT(std::is_nothrow_move_assignable_v<C>);
@@ -105,6 +105,4 @@ int main(int, char**) {
using C = std::flat_map<int, int, std::less<int>, std::vector<int>, std::pmr::vector<int>>;
static_assert(!std::is_nothrow_move_assignable_v<C>);
}
-
- return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/range.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/range.pass.cpp
index 282cc71f31994..9f748738a545b 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/range.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/range.pass.cpp
@@ -27,9 +27,11 @@
#include <vector>
#include "min_allocator.h"
+#include "MinSequenceContainer.h"
#include "test_allocator.h"
#include "test_iterators.h"
#include "test_macros.h"
+#include "../helpers.h"
#include "../../../test_compare.h"
// test constraint container-compatible-range
@@ -66,70 +68,28 @@ static_assert(std::is_constructible_v<Map,
static_assert(!std::is_constructible_v<Map, std::from_range_t, RangeOf<int>, std::less<int>, std::allocator<int>>);
static_assert(!std::is_constructible_v<Map, std::from_range_t, RangeOf<double>, std::less<int>, std::allocator<int>>);
-int main(int, char**) {
- {
- // The constructors in this subclause shall not participate in overload
- // resolution unless uses_allocator_v<key_container_type, Alloc> is true
- // and uses_allocator_v<mapped_container_type, Alloc> is true.
-
- using C = test_less<int>;
- using A1 = test_allocator<int>;
- using A2 = other_allocator<int>;
- using V1 = std::vector<int, A1>;
- using V2 = std::vector<int, A2>;
- using M1 = std::flat_map<int, int, C, V1, V1>;
- using M2 = std::flat_map<int, int, C, V1, V2>;
- using M3 = std::flat_map<int, int, C, V2, V1>;
- static_assert(std::is_constructible_v<M1, std::from_range_t, M1, const A1&>);
- static_assert(!std::is_constructible_v<M1, std::from_range_t, M1, const A2&>);
- static_assert(!std::is_constructible_v<M2, std::from_range_t, M2, const A2&>);
- static_assert(!std::is_constructible_v<M3, std::from_range_t, M3, const A2&>);
-
- static_assert(std::is_constructible_v<M1, std::from_range_t, M1, const C&, const A1&>);
- static_assert(!std::is_constructible_v<M1, std::from_range_t, M1, const C&, const A2&>);
- static_assert(!std::is_constructible_v<M2, std::from_range_t, M2, const C&, const A2&>);
- static_assert(!std::is_constructible_v<M3, std::from_range_t, M3, const C&, const A2&>);
- }
- {
- // container-compatible-range
- using C = test_less<int>;
- using A1 = test_allocator<int>;
- using A2 = test_allocator<std::string>;
- using M = std::flat_map<int, std::string, C, std::vector<int, A1>, std::vector<std::string, A2>>;
- using Pair = std::pair<int, std::string>;
- using PairLike = std::tuple<int, std::string>;
- using NonPairLike = int;
-
- static_assert(std::is_constructible_v<M, std::from_range_t, std::vector<Pair>&>);
- static_assert(std::is_constructible_v<M, std::from_range_t, std::vector<PairLike>&>);
- static_assert(!std::is_constructible_v<M, std::from_range_t, std::vector<NonPairLike>&>);
-
- static_assert(std::is_constructible_v<M, std::from_range_t, std::vector<Pair>&, const C&>);
- static_assert(std::is_constructible_v<M, std::from_range_t, std::vector<PairLike>&, const C&>);
- static_assert(!std::is_constructible_v<M, std::from_range_t, std::vector<NonPairLike>&, const C&>);
-
- static_assert(std::is_constructible_v<M, std::from_range_t, std::vector<Pair>&, const A1&>);
- static_assert(std::is_constructible_v<M, std::from_range_t, std::vector<PairLike>&, const A1&>);
- static_assert(!std::is_constructible_v<M, std::from_range_t, std::vector<NonPairLike>&, const A1&>);
-
- static_assert(std::is_constructible_v<M, std::from_range_t, std::vector<Pair>&, const C&, const A1&>);
- static_assert(std::is_constructible_v<M, std::from_range_t, std::vector<PairLike>&, const C&, const A1&>);
- static_assert(!std::is_constructible_v<M, std::from_range_t, std::vector<NonPairLike>&, const C&, const A1&>);
- }
-
- using P = std::pair<int, short>;
- P ar[] = {{1, 1}, {1, 2}, {1, 3}, {2, 4}, {2, 5}, {3, 6}, {2, 7}, {3, 8}, {3, 9}};
- P expected[] = {{1, 1}, {2, 4}, {3, 6}};
+template <class KeyContainer, class ValueContainer>
+constexpr void test() {
+ using Key = typename KeyContainer::value_type;
+ using Value = typename ValueContainer::value_type;
+ using P = std::pair<Key, Value>;
+ P ar[] = {{1, 1}, {1, 2}, {1, 3}, {2, 4}, {2, 5}, {3, 6}, {2, 7}, {3, 8}, {3, 9}};
{
// flat_map(from_range_t, R&&)
// input_range && !common
- using M = std::flat_map<int, short>;
+ using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
using Iter = cpp20_input_iterator<const P*>;
using Sent = sentinel_wrapper<Iter>;
using R = std::ranges::subrange<Iter, Sent>;
auto m = M(std::from_range, R(Iter(ar), Sent(Iter(ar + 9))));
- assert(std::ranges::equal(m.keys(), expected | std::views::elements<0>));
- LIBCPP_ASSERT(std::ranges::equal(m, expected));
+ assert(std::ranges::equal(m.keys(), KeyContainer{1, 2, 3}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<Value>>{
+ {1, 2, 3},
+ {4, 5, 7},
+ {6, 8, 9},
+ });
// explicit(false)
M m2 = {std::from_range, R(Iter(ar), Sent(Iter(ar + 9)))};
@@ -138,31 +98,49 @@ int main(int, char**) {
{
// flat_map(from_range_t, R&&)
// greater
- using M = std::flat_map<int, short, std::greater<int>, std::deque<int, min_allocator<int>>, std::deque<short>>;
+ using M = std::flat_map<Key, Value, std::greater<int>, KeyContainer, ValueContainer>;
using Iter = cpp20_input_iterator<const P*>;
using Sent = sentinel_wrapper<Iter>;
using R = std::ranges::subrange<Iter, Sent>;
auto m = M(std::from_range, R(Iter(ar), Sent(Iter(ar + 9))));
- assert((m.keys() == std::deque<int, min_allocator<int>>{3, 2, 1}));
- LIBCPP_ASSERT((m.values() == std::deque<short>{6, 4, 1}));
+ assert(std::ranges::equal(m.keys(), KeyContainer{3, 2, 1}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<Value>>{
+ {6, 8, 9},
+ {4, 5, 7},
+ {1, 2, 3},
+ });
}
{
// flat_map(from_range_t, R&&)
// contiguous range
- using M = std::flat_map<int, short>;
+ using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
using R = std::ranges::subrange<const P*>;
auto m = M(std::from_range, R(ar, ar + 9));
- assert(std::ranges::equal(m.keys(), expected | std::views::elements<0>));
- LIBCPP_ASSERT(std::ranges::equal(m, expected));
+ assert(std::ranges::equal(m.keys(), KeyContainer{1, 2, 3}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<Value>>{
+ {1, 2, 3},
+ {4, 5, 7},
+ {6, 8, 9},
+ });
}
{
// flat_map(from_range_t, R&&, const key_compare&)
using C = test_less<int>;
- using M = std::flat_map<int, short, C, std::vector<int>, std::deque<short>>;
+ using M = std::flat_map<Key, Value, C, KeyContainer, ValueContainer>;
using R = std::ranges::subrange<const P*>;
auto m = M(std::from_range, R(ar, ar + 9), C(3));
- assert(std::ranges::equal(m.keys(), expected | std::views::elements<0>));
- LIBCPP_ASSERT(std::ranges::equal(m, expected));
+ assert(std::ranges::equal(m.keys(), KeyContainer{1, 2, 3}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<Value>>{
+ {1, 2, 3},
+ {4, 5, 7},
+ {6, 8, 9},
+ });
assert(m.key_comp() == C(3));
// explicit(false)
@@ -170,15 +148,27 @@ int main(int, char**) {
assert(m2 == m);
assert(m2.key_comp() == C(3));
}
+}
+
+template <template <class...> class KeyContainer, template <class...> class ValueContainer>
+constexpr void test_alloc() {
+ using P = std::pair<int, short>;
+ P ar[] = {{1, 1}, {1, 2}, {1, 3}, {2, 4}, {2, 5}, {3, 6}, {2, 7}, {3, 8}, {3, 9}};
{
// flat_map(from_range_t, R&&, const Allocator&)
using A1 = test_allocator<int>;
using A2 = test_allocator<short>;
- using M = std::flat_map<int, short, std::less<int>, std::vector<int, A1>, std::deque<short, A2>>;
+ using M = std::flat_map<int, short, std::less<int>, KeyContainer<int, A1>, ValueContainer<short, A2>>;
using R = std::ranges::subrange<const P*>;
auto m = M(std::from_range, R(ar, ar + 9), A1(5));
- assert(std::ranges::equal(m.keys(), expected | std::views::elements<0>));
- LIBCPP_ASSERT(std::ranges::equal(m, expected));
+ assert(std::ranges::equal(m.keys(), KeyContainer<int, A1>{1, 2, 3}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<short>>{
+ {1, 2, 3},
+ {4, 5, 7},
+ {6, 8, 9},
+ });
assert(m.keys().get_allocator() == A1(5));
assert(m.values().get_allocator() == A2(5));
}
@@ -187,11 +177,17 @@ int main(int, char**) {
// explicit(false)
using A1 = test_allocator<int>;
using A2 = test_allocator<short>;
- using M = std::flat_map<int, short, std::less<int>, std::vector<int, A1>, std::deque<short, A2>>;
+ using M = std::flat_map<int, short, std::less<int>, KeyContainer<int, A1>, ValueContainer<short, A2>>;
using R = std::ranges::subrange<const P*>;
M m = {std::from_range, R(ar, ar + 9), A1(5)}; // implicit ctor
- assert(std::ranges::equal(m.keys(), expected | std::views::elements<0>));
- LIBCPP_ASSERT(std::ranges::equal(m, expected));
+ assert(std::ranges::equal(m.keys(), KeyContainer<int, A1>{1, 2, 3}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<short>>{
+ {1, 2, 3},
+ {4, 5, 7},
+ {6, 8, 9},
+ });
assert(m.keys().get_allocator() == A1(5));
assert(m.values().get_allocator() == A2(5));
}
@@ -200,11 +196,17 @@ int main(int, char**) {
using C = test_less<int>;
using A1 = test_allocator<int>;
using A2 = test_allocator<short>;
- using M = std::flat_map<int, short, C, std::vector<int, A1>, std::deque<short, A2>>;
+ using M = std::flat_map<int, short, C, KeyContainer<int, A1>, ValueContainer<short, A2>>;
using R = std::ranges::subrange<const P*>;
auto m = M(std::from_range, R(ar, ar + 9), C(3), A1(5));
- assert(std::ranges::equal(m.keys(), expected | std::views::elements<0>));
- LIBCPP_ASSERT(std::ranges::equal(m, expected));
+ assert(std::ranges::equal(m.keys(), KeyContainer<int, A1>{1, 2, 3}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<short>>{
+ {1, 2, 3},
+ {4, 5, 7},
+ {6, 8, 9},
+ });
assert(m.key_comp() == C(3));
assert(m.keys().get_allocator() == A1(5));
assert(m.values().get_allocator() == A2(5));
@@ -214,14 +216,95 @@ int main(int, char**) {
// explicit(false)
using A1 = test_allocator<int>;
using A2 = test_allocator<short>;
- using M = std::flat_map<int, short, std::less<int>, std::deque<int, A1>, std::vector<short, A2>>;
+ using M = std::flat_map<int, short, std::less<int>, KeyContainer<int, A1>, ValueContainer<short, A2>>;
using R = std::ranges::subrange<const P*>;
M m = {std::from_range, R(ar, ar + 9), {}, A2(5)}; // implicit ctor
- assert(std::ranges::equal(m.keys(), expected | std::views::elements<0>));
- LIBCPP_ASSERT(std::ranges::equal(m, expected));
+ assert(std::ranges::equal(m.keys(), KeyContainer<int, A1>{1, 2, 3}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<short>>{
+ {1, 2, 3},
+ {4, 5, 7},
+ {6, 8, 9},
+ });
assert(m.keys().get_allocator() == A1(5));
assert(m.values().get_allocator() == A2(5));
}
+}
+
+constexpr bool test() {
+ {
+ // The constructors in this subclause shall not participate in overload
+ // resolution unless uses_allocator_v<key_container_type, Alloc> is true
+ // and uses_allocator_v<mapped_container_type, Alloc> is true.
+
+ using C = test_less<int>;
+ using A1 = test_allocator<int>;
+ using A2 = other_allocator<int>;
+ using V1 = std::vector<int, A1>;
+ using V2 = std::vector<int, A2>;
+ using M1 = std::flat_map<int, int, C, V1, V1>;
+ using M2 = std::flat_map<int, int, C, V1, V2>;
+ using M3 = std::flat_map<int, int, C, V2, V1>;
+ static_assert(std::is_constructible_v<M1, std::from_range_t, M1, const A1&>);
+ static_assert(!std::is_constructible_v<M1, std::from_range_t, M1, const A2&>);
+ static_assert(!std::is_constructible_v<M2, std::from_range_t, M2, const A2&>);
+ static_assert(!std::is_constructible_v<M3, std::from_range_t, M3, const A2&>);
+
+ static_assert(std::is_constructible_v<M1, std::from_range_t, M1, const C&, const A1&>);
+ static_assert(!std::is_constructible_v<M1, std::from_range_t, M1, const C&, const A2&>);
+ static_assert(!std::is_constructible_v<M2, std::from_range_t, M2, const C&, const A2&>);
+ static_assert(!std::is_constructible_v<M3, std::from_range_t, M3, const C&, const A2&>);
+ }
+ {
+ // container-compatible-range
+ using C = test_less<int>;
+ using A1 = test_allocator<int>;
+ using A2 = test_allocator<std::string>;
+ using M = std::flat_map<int, std::string, C, std::vector<int, A1>, std::vector<std::string, A2>>;
+ using Pair = std::pair<int, std::string>;
+ using PairLike = std::tuple<int, std::string>;
+ using NonPairLike = int;
+
+ static_assert(std::is_constructible_v<M, std::from_range_t, std::vector<Pair>&>);
+ static_assert(std::is_constructible_v<M, std::from_range_t, std::vector<PairLike>&>);
+ static_assert(!std::is_constructible_v<M, std::from_range_t, std::vector<NonPairLike>&>);
+
+ static_assert(std::is_constructible_v<M, std::from_range_t, std::vector<Pair>&, const C&>);
+ static_assert(std::is_constructible_v<M, std::from_range_t, std::vector<PairLike>&, const C&>);
+ static_assert(!std::is_constructible_v<M, std::from_range_t, std::vector<NonPairLike>&, const C&>);
+
+ static_assert(std::is_constructible_v<M, std::from_range_t, std::vector<Pair>&, const A1&>);
+ static_assert(std::is_constructible_v<M, std::from_range_t, std::vector<PairLike>&, const A1&>);
+ static_assert(!std::is_constructible_v<M, std::from_range_t, std::vector<NonPairLike>&, const A1&>);
+
+ static_assert(std::is_constructible_v<M, std::from_range_t, std::vector<Pair>&, const C&, const A1&>);
+ static_assert(std::is_constructible_v<M, std::from_range_t, std::vector<PairLike>&, const C&, const A1&>);
+ static_assert(!std::is_constructible_v<M, std::from_range_t, std::vector<NonPairLike>&, const C&, const A1&>);
+ }
+
+ test<std::vector<int>, std::vector<int>>();
+ test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
+ test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
+
+ test_alloc<std::vector, std::vector>();
+
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ test_alloc<std::deque, std::deque>();
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/sorted_container.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/sorted_container.pass.cpp
index 3c8868f2ff424..4c583594b2501 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/sorted_container.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/sorted_container.pass.cpp
@@ -31,39 +31,18 @@
#include "test_allocator.h"
#include "test_iterators.h"
#include "test_macros.h"
+#include "../helpers.h"
#include "../../../test_compare.h"
-int main(int, char**) {
- {
- // The constructors in this subclause shall not participate in overload
- // resolution unless uses_allocator_v<key_container_type, Alloc> is true
- // and uses_allocator_v<mapped_container_type, Alloc> is true.
-
- using C = test_less<int>;
- using A1 = test_allocator<int>;
- using A2 = other_allocator<int>;
- using V1 = std::vector<int, A1>;
- using V2 = std::vector<int, A2>;
- using M1 = std::flat_map<int, int, C, V1, V1>;
- using M2 = std::flat_map<int, int, C, V1, V2>;
- using M3 = std::flat_map<int, int, C, V2, V1>;
- static_assert(std::is_constructible_v<M1, std::sorted_unique_t, const V1&, const V1&, const A1&>);
- static_assert(!std::is_constructible_v<M1, std::sorted_unique_t, const V1&, const V1&, const A2&>);
- static_assert(!std::is_constructible_v<M2, std::sorted_unique_t, const V1&, const V2&, const A2&>);
- static_assert(!std::is_constructible_v<M3, std::sorted_unique_t, const V2&, const V1&, const A2&>);
-
- static_assert(std::is_constructible_v<M1, std::sorted_unique_t, const V1&, const V1&, const C&, const A1&>);
- static_assert(!std::is_constructible_v<M1, std::sorted_unique_t, const V1&, const V1&, const C&, const A2&>);
- static_assert(!std::is_constructible_v<M2, std::sorted_unique_t, const V1&, const V2&, const C&, const A2&>);
- static_assert(!std::is_constructible_v<M3, std::sorted_unique_t, const V2&, const V1&, const C&, const A2&>);
- }
+template <template <class...> class KeyContainer, template <class...> class ValueContainer>
+constexpr void test() {
{
// flat_map(sorted_unique_t, key_container_type , mapped_container_type)
- using M = std::flat_map<int, char>;
- std::vector<int> ks = {1, 2, 4, 10};
- std::vector<char> vs = {4, 3, 2, 1};
- auto ks2 = ks;
- auto vs2 = vs;
+ using M = std::flat_map<int, char, std::less<int>, KeyContainer<int>, ValueContainer<char>>;
+ KeyContainer<int> ks = {1, 2, 4, 10};
+ ValueContainer<char> vs = {4, 3, 2, 1};
+ auto ks2 = ks;
+ auto vs2 = vs;
auto m = M(std::sorted_unique, ks, vs);
assert((m == M{{1, 4}, {2, 3}, {4, 2}, {10, 1}}));
@@ -79,8 +58,8 @@ int main(int, char**) {
{
// flat_map(sorted_unique_t, key_container_type , mapped_container_type)
// non-default container, comparator and allocator type
- using Ks = std::deque<int, min_allocator<int>>;
- using Vs = std::deque<char, min_allocator<char>>;
+ using Ks = KeyContainer<int, min_allocator<int>>;
+ using Vs = ValueContainer<char, min_allocator<char>>;
using M = std::flat_map<int, char, std::greater<int>, Ks, Vs>;
Ks ks = {10, 4, 2, 1};
Vs vs = {1, 2, 3, 4};
@@ -95,9 +74,9 @@ int main(int, char**) {
// flat_map(sorted_unique_t, key_container_type , mapped_container_type)
// allocator copied into the containers
using A = test_allocator<int>;
- using M = std::flat_map<int, int, std::less<int>, std::vector<int, A>, std::deque<int, A>>;
- auto ks = std::vector<int, A>({1, 2, 4, 10}, A(4));
- auto vs = std::deque<int, A>({4, 3, 2, 1}, A(5));
+ using M = std::flat_map<int, int, std::less<int>, KeyContainer<int, A>, ValueContainer<int, A>>;
+ auto ks = KeyContainer<int, A>({1, 2, 4, 10}, A(4));
+ auto vs = ValueContainer<int, A>({4, 3, 2, 1}, A(5));
auto m = M(std::sorted_unique, std::move(ks), std::move(vs));
assert(ks.empty()); // it was moved-from
assert(vs.empty()); // it was moved-from
@@ -107,10 +86,10 @@ int main(int, char**) {
}
{
// flat_map(sorted_unique_t, key_container_type , mapped_container_type, key_compare)
- using C = test_less<int>;
- using M = std::flat_map<int, char, C>;
- std::vector<int> ks = {1, 2, 4, 10};
- std::vector<char> vs = {4, 3, 2, 1};
+ using C = test_less<int>;
+ using M = std::flat_map<int, char, C, KeyContainer<int>, ValueContainer<char>>;
+ KeyContainer<int> ks = {1, 2, 4, 10};
+ ValueContainer<char> vs = {4, 3, 2, 1};
auto m = M(std::sorted_unique, ks, vs, C(4));
assert((m == M{{1, 4}, {2, 3}, {4, 2}, {10, 1}}));
@@ -123,12 +102,12 @@ int main(int, char**) {
}
{
// flat_map(sorted_unique_t, key_container_type , mapped_container_type, key_compare, const Allocator&)
- using C = test_less<int>;
- using A = test_allocator<int>;
- using M = std::flat_map<int, int, C, std::vector<int, A>, std::vector<int, A>>;
- std::vector<int, A> ks = {1, 2, 4, 10};
- std::vector<int, A> vs = {4, 3, 2, 1};
- auto m = M(std::sorted_unique, ks, vs, C(4), A(5));
+ using C = test_less<int>;
+ using A = test_allocator<int>;
+ using M = std::flat_map<int, int, C, KeyContainer<int, A>, ValueContainer<int, A>>;
+ KeyContainer<int, A> ks = {1, 2, 4, 10};
+ ValueContainer<int, A> vs = {4, 3, 2, 1};
+ auto m = M(std::sorted_unique, ks, vs, C(4), A(5));
assert((m == M{{1, 4}, {2, 3}, {4, 2}, {10, 1}}));
assert(m.key_comp() == C(4));
assert(m.keys().get_allocator() == A(5));
@@ -144,9 +123,9 @@ int main(int, char**) {
{
// flat_map(sorted_unique_t, key_container_type , mapped_container_type, const Allocator&)
using A = test_allocator<int>;
- using M = std::flat_map<int, int, std::less<int>, std::vector<int, A>, std::deque<int, A>>;
- auto ks = std::vector<int, A>({1, 2, 4, 10}, A(4));
- auto vs = std::deque<int, A>({4, 3, 2, 1}, A(5));
+ using M = std::flat_map<int, int, std::less<int>, KeyContainer<int, A>, ValueContainer<int, A>>;
+ auto ks = KeyContainer<int, A>({1, 2, 4, 10}, A(4));
+ auto vs = ValueContainer<int, A>({4, 3, 2, 1}, A(5));
auto m = M(std::sorted_unique, ks, vs, A(6)); // replaces the allocators
assert(!ks.empty()); // it was an lvalue above
assert(!vs.empty()); // it was an lvalue above
@@ -160,6 +139,51 @@ int main(int, char**) {
assert(m2.keys().get_allocator() == A(6));
assert(m2.values().get_allocator() == A(6));
}
+}
+
+constexpr bool test() {
+ {
+ // The constructors in this subclause shall not participate in overload
+ // resolution unless uses_allocator_v<key_container_type, Alloc> is true
+ // and uses_allocator_v<mapped_container_type, Alloc> is true.
+
+ using C = test_less<int>;
+ using A1 = test_allocator<int>;
+ using A2 = other_allocator<int>;
+ using V1 = std::vector<int, A1>;
+ using V2 = std::vector<int, A2>;
+ using M1 = std::flat_map<int, int, C, V1, V1>;
+ using M2 = std::flat_map<int, int, C, V1, V2>;
+ using M3 = std::flat_map<int, int, C, V2, V1>;
+ static_assert(std::is_constructible_v<M1, std::sorted_unique_t, const V1&, const V1&, const A1&>);
+ static_assert(!std::is_constructible_v<M1, std::sorted_unique_t, const V1&, const V1&, const A2&>);
+ static_assert(!std::is_constructible_v<M2, std::sorted_unique_t, const V1&, const V2&, const A2&>);
+ static_assert(!std::is_constructible_v<M3, std::sorted_unique_t, const V2&, const V1&, const A2&>);
+
+ static_assert(std::is_constructible_v<M1, std::sorted_unique_t, const V1&, const V1&, const C&, const A1&>);
+ static_assert(!std::is_constructible_v<M1, std::sorted_unique_t, const V1&, const V1&, const C&, const A2&>);
+ static_assert(!std::is_constructible_v<M2, std::sorted_unique_t, const V1&, const V2&, const C&, const A2&>);
+ static_assert(!std::is_constructible_v<M3, std::sorted_unique_t, const V2&, const V1&, const C&, const A2&>);
+ }
+
+ test<std::vector, std::vector>();
+
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque, std::vector>();
+ test<std::deque, std::deque>();
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/sorted_initializer_list.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/sorted_initializer_list.pass.cpp
index 26452472ba201..e8ac5d3961f7b 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/sorted_initializer_list.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/sorted_initializer_list.pass.cpp
@@ -22,6 +22,7 @@
#include <deque>
#include <flat_map>
#include <functional>
+#include <type_traits>
#include <vector>
#include "min_allocator.h"
@@ -31,13 +32,87 @@
#include "../../../test_compare.h"
template <class T, class U>
-std::initializer_list<std::pair<T, U>> il = {{1, 1}, {2, 2}, {4, 4}, {5, 5}};
+constexpr std::initializer_list<std::pair<T, U>> il = {{1, 1}, {2, 2}, {4, 4}, {5, 5}};
-const auto il1 = il<int, int>;
-const auto il2 = il<int, short>;
-const auto il3 = il<short, int>;
+constexpr auto il1 = il<int, int>;
+constexpr auto il2 = il<int, short>;
+constexpr auto il3 = il<short, int>;
-int main(int, char**) {
+template <template <class...> class KeyContainer, template <class...> class ValueContainer>
+constexpr void test() {
+ {
+ // flat_map(sorted_unique_t, initializer_list<value_type>);
+ using M = std::flat_map<int, int, std::less<int>, KeyContainer<int>, ValueContainer<int>>;
+ auto m = M(std::sorted_unique, il1);
+ auto expected = M{{1, 1}, {2, 2}, {4, 4}, {5, 5}};
+ assert(m == expected);
+
+ // explicit(false)
+ M m2 = {std::sorted_unique, il1};
+ assert(m2 == m);
+ }
+ if (!TEST_IS_CONSTANT_EVALUATED) {
+ // flat_map(sorted_unique_t, initializer_list<value_type>, const key_compare&);
+ using M = std::flat_map<int, int, std::function<bool(int, int)>, KeyContainer<int>, ValueContainer<int>>;
+ auto m = M(std::sorted_unique, il1, std::less<int>());
+ assert(m == M({{1, 1}, {2, 2}, {4, 4}, {5, 5}}, std::less<>()));
+ assert(m.key_comp()(1, 2) == true);
+
+ // explicit(false)
+ M m2 = {std::sorted_unique, il1, std::less<int>()};
+ assert(m2 == m);
+ }
+ {
+ // flat_map(sorted_unique_t, initializer_list<value_type>, const key_compare&);
+ // greater
+ using M = std::flat_map<int, int, std::greater<int>, KeyContainer<int, min_allocator<int>>, ValueContainer<int>>;
+ std::initializer_list<std::pair<int, int>> il4{{5, 5}, {4, 4}, {2, 2}, {1, 1}};
+ auto m = M(std::sorted_unique, il4, std::greater<int>());
+ assert((m == M{{5, 5}, {4, 4}, {2, 2}, {1, 1}}));
+ }
+ {
+ // flat_map(sorted_unique_t, initializer_list<value_type>, const Allocator&)
+ using A1 = test_allocator<int>;
+ using A2 = test_allocator<short>;
+ using M = std::flat_map<int, short, std::less<int>, KeyContainer<int, A1>, ValueContainer<short, A2>>;
+ auto m = M(std::sorted_unique, il2, A1(5));
+ auto expected = M{{1, 1}, {2, 2}, {4, 4}, {5, 5}};
+ assert(m == expected);
+ assert(m.keys().get_allocator() == A1(5));
+ assert(m.values().get_allocator() == A2(5));
+
+ // explicit(false)
+ M m2 = {std::sorted_unique, il2, A1(5)};
+ assert(m2 == m);
+ assert(m2.keys().get_allocator() == A1(5));
+ assert(m2.values().get_allocator() == A2(5));
+ }
+ {
+ // flat_map(sorted_unique_t, initializer_list<value_type>, const key_compare&, const Allocator&);
+ using C = test_less<int>;
+ using A1 = test_allocator<int>;
+ using A2 = test_allocator<short>;
+ using M = std::flat_map<int, short, C, KeyContainer<int, A1>, ValueContainer<short, A2>>;
+ auto m = M(std::sorted_unique, il2, C(3), A1(5));
+ assert((m == M{{1, 1}, {2, 2}, {4, 4}, {5, 5}}));
+ assert(m.key_comp() == C(3));
+ assert(m.keys().get_allocator() == A1(5));
+ assert(m.values().get_allocator() == A2(5));
+ }
+ {
+ // flat_map(sorted_unique_t, initializer_list<value_type>, const key_compare&, const Allocator&);
+ // explicit(false)
+ using A1 = test_allocator<short>;
+ using A2 = test_allocator<int>;
+ using M = std::flat_map<short, int, std::less<int>, KeyContainer<short, A1>, ValueContainer<int, A2>>;
+ M m = {std::sorted_unique, il3, {}, A1(5)}; // implicit ctor
+ assert((m == M{{1, 1}, {2, 2}, {4, 4}, {5, 5}}));
+ assert(m.keys().get_allocator() == A1(5));
+ assert(m.values().get_allocator() == A2(5));
+ }
+}
+
+constexpr bool test() {
{
// The constructors in this subclause shall not participate in overload
// resolution unless uses_allocator_v<key_container_type, Alloc> is true
@@ -104,76 +179,23 @@ int main(int, char**) {
std::allocator<int>>);
}
- {
- // flat_map(sorted_unique_t, initializer_list<value_type>);
- using M = std::flat_map<int, int>;
- auto m = M(std::sorted_unique, il1);
- auto expected = M{{1, 1}, {2, 2}, {4, 4}, {5, 5}};
- assert(m == expected);
-
- // explicit(false)
- M m2 = {std::sorted_unique, il1};
- assert(m2 == m);
- }
- {
- // flat_map(sorted_unique_t, initializer_list<value_type>, const key_compare&);
- using M = std::flat_map<int, int, std::function<bool(int, int)>>;
- auto m = M(std::sorted_unique, il1, std::less<int>());
- assert(m == M({{1, 1}, {2, 2}, {4, 4}, {5, 5}}, std::less<>()));
- assert(m.key_comp()(1, 2) == true);
+ test<std::vector, std::vector>();
- // explicit(false)
- M m2 = {std::sorted_unique, il1, std::less<int>()};
- assert(m2 == m);
- }
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
{
- // flat_map(sorted_unique_t, initializer_list<value_type>, const key_compare&);
- // greater
- using M = std::flat_map<int, int, std::greater<int>, std::deque<int, min_allocator<int>>, std::vector<int>>;
- std::initializer_list<std::pair<int, int>> il4{{5, 5}, {4, 4}, {2, 2}, {1, 1}};
- auto m = M(std::sorted_unique, il4, std::greater<int>());
- assert((m == M{{5, 5}, {4, 4}, {2, 2}, {1, 1}}));
+ test<std::deque, std::deque>();
}
- {
- // flat_map(sorted_unique_t, initializer_list<value_type>, const Allocator&)
- using A1 = test_allocator<int>;
- using A2 = test_allocator<short>;
- using M = std::flat_map<int, short, std::less<int>, std::vector<int, A1>, std::deque<short, A2>>;
- auto m = M(std::sorted_unique, il2, A1(5));
- auto expected = M{{1, 1}, {2, 2}, {4, 4}, {5, 5}};
- assert(m == expected);
- assert(m.keys().get_allocator() == A1(5));
- assert(m.values().get_allocator() == A2(5));
- // explicit(false)
- M m2 = {std::sorted_unique, il2, A1(5)};
- assert(m2 == m);
- assert(m2.keys().get_allocator() == A1(5));
- assert(m2.values().get_allocator() == A2(5));
- }
- {
- // flat_map(sorted_unique_t, initializer_list<value_type>, const key_compare&, const Allocator&);
- using C = test_less<int>;
- using A1 = test_allocator<int>;
- using A2 = test_allocator<short>;
- using M = std::flat_map<int, short, C, std::vector<int, A1>, std::deque<short, A2>>;
- auto m = M(std::sorted_unique, il2, C(3), A1(5));
- assert((m == M{{1, 1}, {2, 2}, {4, 4}, {5, 5}}));
- assert(m.key_comp() == C(3));
- assert(m.keys().get_allocator() == A1(5));
- assert(m.values().get_allocator() == A2(5));
- }
- {
- // flat_map(sorted_unique_t, initializer_list<value_type>, const key_compare&, const Allocator&);
- // explicit(false)
- using A1 = test_allocator<short>;
- using A2 = test_allocator<int>;
- using M = std::flat_map<short, int, std::less<int>, std::deque<short, A1>, std::vector<int, A2>>;
- M m = {std::sorted_unique, il3, {}, A1(5)}; // implicit ctor
- assert((m == M{{1, 1}, {2, 2}, {4, 4}, {5, 5}}));
- assert(m.keys().get_allocator() == A1(5));
- assert(m.values().get_allocator() == A2(5));
- }
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/sorted_iter_iter.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/sorted_iter_iter.pass.cpp
index 8eb7547e917cc..f853a083e4779 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/sorted_iter_iter.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/sorted_iter_iter.pass.cpp
@@ -20,45 +20,26 @@
#include <deque>
#include <flat_map>
#include <functional>
+#include <type_traits>
#include <vector>
+#include "MinSequenceContainer.h"
#include "min_allocator.h"
#include "test_allocator.h"
#include "test_iterators.h"
#include "test_macros.h"
#include "../../../test_compare.h"
-int main(int, char**) {
- {
- // The constructors in this subclause shall not participate in overload
- // resolution unless uses_allocator_v<key_container_type, Alloc> is true
- // and uses_allocator_v<mapped_container_type, Alloc> is true.
- using C = test_less<int>;
- using A1 = test_allocator<int>;
- using A2 = other_allocator<int>;
- using V1 = std::vector<int, A1>;
- using V2 = std::vector<int, A2>;
- using M1 = std::flat_map<int, int, C, V1, V1>;
- using M2 = std::flat_map<int, int, C, V1, V2>;
- using M3 = std::flat_map<int, int, C, V2, V1>;
- using Iter1 = typename M1::iterator;
- using Iter2 = typename M2::iterator;
- using Iter3 = typename M3::iterator;
- static_assert(std::is_constructible_v<M1, std::sorted_unique_t, Iter1, Iter1, const A1&>);
- static_assert(!std::is_constructible_v<M1, std::sorted_unique_t, Iter1, Iter1, const A2&>);
- static_assert(!std::is_constructible_v<M2, std::sorted_unique_t, Iter2, Iter2, const A2&>);
- static_assert(!std::is_constructible_v<M3, std::sorted_unique_t, Iter3, Iter3, const A2&>);
+template <class KeyContainer, class ValueContainer>
+constexpr void test() {
+ using Key = typename KeyContainer::value_type;
+ using Value = typename ValueContainer::value_type;
- static_assert(std::is_constructible_v<M1, std::sorted_unique_t, Iter1, Iter1, const C&, const A1&>);
- static_assert(!std::is_constructible_v<M1, std::sorted_unique_t, Iter1, Iter1, const C&, const A2&>);
- static_assert(!std::is_constructible_v<M2, std::sorted_unique_t, Iter2, Iter2, const C&, const A2&>);
- static_assert(!std::is_constructible_v<M3, std::sorted_unique_t, Iter3, Iter3, const C&, const A2&>);
- }
{
// flat_map(sorted_unique_t, InputIterator, InputIterator);
// cpp17_input_iterator
- using M = std::flat_map<int, int>;
- using P = std::pair<int, int>;
+ using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
+ using P = std::pair<Key, Value>;
P ar[] = {{1, 1}, {2, 2}, {4, 4}, {5, 5}};
auto m = M(std::sorted_unique, cpp17_input_iterator<const P*>(ar), cpp17_input_iterator<const P*>(ar + 4));
auto expected = M{{1, 1}, {2, 2}, {4, 4}, {5, 5}};
@@ -71,18 +52,19 @@ int main(int, char**) {
{
// flat_map(sorted_unique_t, InputIterator, InputIterator);
// contiguous iterator
- using C = test_less<int>;
- using M = std::flat_map<int, int, C, std::vector<int, min_allocator<int>>, std::vector<int, min_allocator<int>>>;
- std::pair<int, int> ar[] = {{1, 1}, {2, 2}, {4, 4}, {5, 5}};
- auto m = M(std::sorted_unique, ar, ar + 4);
- auto expected = M{{1, 1}, {2, 2}, {4, 4}, {5, 5}};
+ using C = test_less<Key>;
+ using P = std::pair<Key, Value>;
+ using M = std::flat_map<Key, Value, C, KeyContainer, ValueContainer>;
+ P ar[] = {{1, 1}, {2, 2}, {4, 4}, {5, 5}};
+ auto m = M(std::sorted_unique, ar, ar + 4);
+ auto expected = M{{1, 1}, {2, 2}, {4, 4}, {5, 5}};
assert(m == expected);
}
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
// flat_map(sorted_unique_t, InputIterator, InputIterator, const key_compare&);
// cpp_17_input_iterator
- using M = std::flat_map<int, int, std::function<bool(int, int)>>;
- using P = std::pair<int, int>;
+ using M = std::flat_map<Key, Value, std::function<bool(Key, Value)>>;
+ using P = std::pair<Key, Value>;
P ar[] = {{1, 1}, {2, 2}, {4, 4}, {5, 5}};
auto m = M(std::sorted_unique,
cpp17_input_iterator<const P*>(ar),
@@ -101,8 +83,8 @@ int main(int, char**) {
{
// flat_map(sorted_unique_t, InputIterator, InputIterator, const key_compare&);
// greater
- using M = std::flat_map<int, int, std::greater<int>, std::deque<int, min_allocator<int>>, std::vector<int>>;
- using P = std::pair<int, int>;
+ using M = std::flat_map<Key, Value, std::greater<int>, KeyContainer, ValueContainer>;
+ using P = std::pair<Key, Value>;
P ar[] = {{5, 5}, {4, 4}, {2, 2}, {1, 1}};
auto m = M(std::sorted_unique,
cpp17_input_iterator<const P*>(ar),
@@ -113,18 +95,22 @@ int main(int, char**) {
{
// flat_map(sorted_unique_t, InputIterator, InputIterator, const key_compare&);
// contiguous iterator
- using C = test_less<int>;
- using M = std::flat_map<int, int, C, std::vector<int, min_allocator<int>>, std::vector<int, min_allocator<int>>>;
- std::pair<int, int> ar[1] = {{42, 42}};
- auto m = M(std::sorted_unique, ar, ar, C(5));
+ using C = test_less<Key>;
+ using M = std::flat_map<Key, Value, C, KeyContainer, ValueContainer>;
+ std::pair<Key, Value> ar[1] = {{42, 42}};
+ auto m = M(std::sorted_unique, ar, ar, C(5));
assert(m.empty());
assert(m.key_comp() == C(5));
}
+}
+
+template <template <class...> class KeyContainer, template <class...> class ValueContainer>
+constexpr void test_alloc() {
{
// flat_map(sorted_unique_t, InputIterator , InputIterator, const Allocator&)
using A1 = test_allocator<int>;
using A2 = test_allocator<short>;
- using M = std::flat_map<int, short, std::less<int>, std::vector<int, A1>, std::deque<short, A2>>;
+ using M = std::flat_map<int, short, std::less<int>, KeyContainer<int, A1>, ValueContainer<short, A2>>;
using P = std::pair<int, int>;
P ar[] = {{1, 1}, {2, 2}, {4, 4}, {5, 5}};
auto m = M(std::sorted_unique, ar, ar + 4, A1(5));
@@ -144,7 +130,7 @@ int main(int, char**) {
using C = test_less<int>;
using A1 = test_allocator<int>;
using A2 = test_allocator<short>;
- using M = std::flat_map<int, short, C, std::vector<int, A1>, std::deque<short, A2>>;
+ using M = std::flat_map<int, short, C, KeyContainer<int, A1>, ValueContainer<short, A2>>;
using P = std::pair<int, int>;
P ar[] = {{1, 1}, {2, 2}, {4, 4}, {5, 5}};
auto m = M(std::sorted_unique, ar, ar + 4, C(3), A1(5));
@@ -158,7 +144,7 @@ int main(int, char**) {
// explicit(false)
using A1 = test_allocator<short>;
using A2 = test_allocator<int>;
- using M = std::flat_map<short, int, std::less<int>, std::deque<short, A1>, std::vector<int, A2>>;
+ using M = std::flat_map<short, int, std::less<int>, KeyContainer<short, A1>, ValueContainer<int, A2>>;
using P = std::pair<int, int>;
P ar[] = {{1, 1}, {2, 2}, {4, 4}, {5, 5}};
M m = {std::sorted_unique, ar, ar + 4, {}, A1(5)}; // implicit ctor
@@ -166,6 +152,59 @@ int main(int, char**) {
assert(m.keys().get_allocator() == A1(5));
assert(m.values().get_allocator() == A2(5));
}
+}
+
+constexpr bool test() {
+ {
+ // The constructors in this subclause shall not participate in overload
+ // resolution unless uses_allocator_v<key_container_type, Alloc> is true
+ // and uses_allocator_v<mapped_container_type, Alloc> is true.
+ using C = test_less<int>;
+ using A1 = test_allocator<int>;
+ using A2 = other_allocator<int>;
+ using V1 = std::vector<int, A1>;
+ using V2 = std::vector<int, A2>;
+ using M1 = std::flat_map<int, int, C, V1, V1>;
+ using M2 = std::flat_map<int, int, C, V1, V2>;
+ using M3 = std::flat_map<int, int, C, V2, V1>;
+ using Iter1 = typename M1::iterator;
+ using Iter2 = typename M2::iterator;
+ using Iter3 = typename M3::iterator;
+ static_assert(std::is_constructible_v<M1, std::sorted_unique_t, Iter1, Iter1, const A1&>);
+ static_assert(!std::is_constructible_v<M1, std::sorted_unique_t, Iter1, Iter1, const A2&>);
+ static_assert(!std::is_constructible_v<M2, std::sorted_unique_t, Iter2, Iter2, const A2&>);
+ static_assert(!std::is_constructible_v<M3, std::sorted_unique_t, Iter3, Iter3, const A2&>);
+
+ static_assert(std::is_constructible_v<M1, std::sorted_unique_t, Iter1, Iter1, const C&, const A1&>);
+ static_assert(!std::is_constructible_v<M1, std::sorted_unique_t, Iter1, Iter1, const C&, const A2&>);
+ static_assert(!std::is_constructible_v<M2, std::sorted_unique_t, Iter2, Iter2, const C&, const A2&>);
+ static_assert(!std::is_constructible_v<M3, std::sorted_unique_t, Iter3, Iter3, const C&, const A2&>);
+ }
+
+ test<std::vector<int>, std::vector<int>>();
+ test<std::vector<int>, std::vector<double>>();
+ test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
+ test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
+ test<std::vector<int, min_allocator<int>>, std::vector<int, min_allocator<int>>>();
+
+ test_alloc<std::vector, std::vector>();
+
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ test_alloc<std::deque, std::deque>();
+ }
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.erasure/erase_if.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.erasure/erase_if.pass.cpp
index fb0563eec5376..7d5a6ecf02f63 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.erasure/erase_if.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.erasure/erase_if.pass.cpp
@@ -32,7 +32,7 @@ static_assert(HasStdErase<std::vector<int>>);
static_assert(!HasStdErase<std::flat_map<int, int>>);
template <class M>
-M make(std::initializer_list<int> vals) {
+constexpr M make(std::initializer_list<int> vals) {
M ret;
for (int v : vals)
ret[static_cast<typename M::key_type>(v)] = static_cast<typename M::mapped_type>(v + 10);
@@ -40,8 +40,8 @@ M make(std::initializer_list<int> vals) {
}
template <class M, class Pred>
-void test0(
- std::initializer_list<int> vals, Pred p, std::initializer_list<int> expected, std::size_t expected_erased_count) {
+constexpr void
+test0(std::initializer_list<int> vals, Pred p, std::initializer_list<int> expected, std::size_t expected_erased_count) {
M s = make<M>(vals);
ASSERT_SAME_TYPE(typename M::size_type, decltype(std::erase_if(s, p)));
assert(expected_erased_count == std::erase_if(s, p));
@@ -49,7 +49,7 @@ void test0(
}
template <class S>
-void test() {
+constexpr void test() {
// Test all the plausible signatures for this predicate.
auto is1 = [](typename S::const_reference v) { return v.first == 1; };
auto is2 = [](typename S::value_type v) { return v.first == 2; };
@@ -76,7 +76,7 @@ void test() {
test0<S>({1, 2, 3}, False, {1, 2, 3}, 0);
}
-int main(int, char**) {
+constexpr bool test() {
test<std::flat_map<int, char>>();
test<std::flat_map<int,
char,
@@ -84,10 +84,24 @@ int main(int, char**) {
std::vector<int, min_allocator<int>>,
std::vector<char, min_allocator<char>>>>();
test<std::flat_map<int, char, std::greater<int>, std::vector<int, test_allocator<int>>>>();
- test<std::flat_map<int, char, std::less<int>, std::deque<int, min_allocator<int>>>>();
- test<std::flat_map<int, char, std::greater<int>, std::deque<int, test_allocator<int>>>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::flat_map<int, char, std::less<int>, std::deque<int, min_allocator<int>>>>();
+ test<std::flat_map<int, char, std::greater<int>, std::deque<int, test_allocator<int>>>>();
+ }
test<std::flat_map<long, int>>();
test<std::flat_map<double, int>>();
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.iterators/iterator.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.iterators/iterator.pass.cpp
index b63ce6b19ee16..eef90f4ac6bc8 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.iterators/iterator.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.iterators/iterator.pass.cpp
@@ -30,7 +30,7 @@
#include "min_allocator.h"
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -70,9 +70,14 @@ void test() {
assert(i == m.begin());
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<char>>();
- test<std::deque<int>, std::vector<char>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<char>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<char>>();
test<std::vector<int, min_allocator<int>>, std::vector<char, min_allocator<char>>>();
@@ -92,5 +97,14 @@ int main(int, char**) {
assert(!(cii != ii1));
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.iterators/iterator_comparison.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.iterators/iterator_comparison.pass.cpp
index d31daa84924fa..e8aac7fb7c9e9 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.iterators/iterator_comparison.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.iterators/iterator_comparison.pass.cpp
@@ -24,7 +24,7 @@
#include "min_allocator.h"
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -142,11 +142,25 @@ void test() {
assert(cri2 <=> cri1 == std::strong_ordering::greater);
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<char>>();
- test<std::deque<int>, std::vector<char>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<char>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<char>>();
test<std::vector<int, min_allocator<int>>, std::vector<char, min_allocator<char>>>();
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.iterators/reverse_iterator.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.iterators/reverse_iterator.pass.cpp
index b4d76aac119a3..2de992e07ba4e 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.iterators/reverse_iterator.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.iterators/reverse_iterator.pass.cpp
@@ -25,48 +25,64 @@
#include <iterator>
+#include "MinSequenceContainer.h"
#include "test_macros.h"
+#include "min_allocator.h"
-int main(int, char**) {
+template <class KeyContainer, class ValueContainer>
+constexpr void test() {
+ using Key = typename KeyContainer::value_type;
+ using Value = typename ValueContainer::value_type;
+ using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
+ M m = {{1, 'a'}, {2, 'b'}, {3, 'c'}, {4, 'd'}};
+ const M& cm = m;
+ ASSERT_SAME_TYPE(decltype(m.rbegin()), typename M::reverse_iterator);
+ ASSERT_SAME_TYPE(decltype(m.crbegin()), typename M::const_reverse_iterator);
+ ASSERT_SAME_TYPE(decltype(cm.rbegin()), typename M::const_reverse_iterator);
+ ASSERT_SAME_TYPE(decltype(m.rend()), typename M::reverse_iterator);
+ ASSERT_SAME_TYPE(decltype(m.crend()), typename M::const_reverse_iterator);
+ ASSERT_SAME_TYPE(decltype(cm.rend()), typename M::const_reverse_iterator);
+ static_assert(noexcept(m.rbegin()));
+ static_assert(noexcept(cm.rbegin()));
+ static_assert(noexcept(m.crbegin()));
+ static_assert(noexcept(m.rend()));
+ static_assert(noexcept(cm.rend()));
+ static_assert(noexcept(m.crend()));
+ assert(m.size() == 4);
+ assert(std::distance(m.rbegin(), m.rend()) == 4);
+ assert(std::distance(cm.rbegin(), cm.rend()) == 4);
+ assert(std::distance(m.crbegin(), m.crend()) == 4);
+ assert(std::distance(cm.crbegin(), cm.crend()) == 4);
+ typename M::reverse_iterator i; // default-construct
+ ASSERT_SAME_TYPE(decltype(i->first), const int&);
+ ASSERT_SAME_TYPE(decltype(i->second), char&);
+ i = m.rbegin(); // move-assignment
+ typename M::const_reverse_iterator k = i; // converting constructor
+ assert(i == k); // comparison
+ for (int j = 4; j >= 1; --j, ++i) { // pre-increment
+ assert(i->first == j); // operator->
+ assert(i->second == 'a' + j - 1);
+ }
+ assert(i == m.rend());
+ for (int j = 1; j <= 4; ++j) {
+ --i; // pre-decrement
+ assert((*i).first == j);
+ assert((*i).second == 'a' + j - 1);
+ }
+ assert(i == m.rbegin());
+}
+
+constexpr bool test() {
+ test<std::vector<int>, std::vector<char>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
{
- using M = std::flat_map<int, char, std::less<int>, std::deque<int>, std::deque<char>>;
- M m = {{1, 'a'}, {2, 'b'}, {3, 'c'}, {4, 'd'}};
- const M& cm = m;
- ASSERT_SAME_TYPE(decltype(m.rbegin()), M::reverse_iterator);
- ASSERT_SAME_TYPE(decltype(m.crbegin()), M::const_reverse_iterator);
- ASSERT_SAME_TYPE(decltype(cm.rbegin()), M::const_reverse_iterator);
- ASSERT_SAME_TYPE(decltype(m.rend()), M::reverse_iterator);
- ASSERT_SAME_TYPE(decltype(m.crend()), M::const_reverse_iterator);
- ASSERT_SAME_TYPE(decltype(cm.rend()), M::const_reverse_iterator);
- static_assert(noexcept(m.rbegin()));
- static_assert(noexcept(cm.rbegin()));
- static_assert(noexcept(m.crbegin()));
- static_assert(noexcept(m.rend()));
- static_assert(noexcept(cm.rend()));
- static_assert(noexcept(m.crend()));
- assert(m.size() == 4);
- assert(std::distance(m.rbegin(), m.rend()) == 4);
- assert(std::distance(cm.rbegin(), cm.rend()) == 4);
- assert(std::distance(m.crbegin(), m.crend()) == 4);
- assert(std::distance(cm.crbegin(), cm.crend()) == 4);
- M::reverse_iterator i; // default-construct
- ASSERT_SAME_TYPE(decltype(i->first), const int&);
- ASSERT_SAME_TYPE(decltype(i->second), char&);
- i = m.rbegin(); // move-assignment
- M::const_reverse_iterator k = i; // converting constructor
- assert(i == k); // comparison
- for (int j = 4; j >= 1; --j, ++i) { // pre-increment
- assert(i->first == j); // operator->
- assert(i->second == 'a' + j - 1);
- }
- assert(i == m.rend());
- for (int j = 1; j <= 4; ++j) {
- --i; // pre-decrement
- assert((*i).first == j);
- assert((*i).second == 'a' + j - 1);
- }
- assert(i == m.rbegin());
+ test<std::deque<int>, std::vector<char>>();
}
+ test<MinSequenceContainer<int>, MinSequenceContainer<char>>();
+ test<std::vector<int, min_allocator<int>>, std::vector<char, min_allocator<char>>>();
+
{
// N3644 testing
using C = std::flat_map<int, char>;
@@ -82,6 +98,14 @@ int main(int, char**) {
assert(!(ii1 != cii));
assert(!(cii != ii1));
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/clear.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/clear.pass.cpp
index 30271eb55660b..85ef573f47da8 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/clear.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/clear.pass.cpp
@@ -39,7 +39,7 @@ static_assert(
#endif
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -52,13 +52,27 @@ void test() {
assert(m.size() == 0);
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<int>>();
test<std::vector<int>, std::vector<double>>();
- test<std::deque<int>, std::vector<double>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
test<std::vector<int, min_allocator<int>>, std::vector<int, min_allocator<int>>>();
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/emplace.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/emplace.pass.cpp
index a54fcad639280..03192588373a8 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/emplace.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/emplace.pass.cpp
@@ -18,6 +18,7 @@
#include <deque>
#include <tuple>
#include <functional>
+#include <type_traits>
#include <vector>
#include "MinSequenceContainer.h"
@@ -39,7 +40,7 @@ static_assert(!CanEmplace<Map, Emplaceable>);
static_assert(!CanEmplace<Map, int, double>);
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -117,7 +118,7 @@ void test() {
}
template <class KeyContainer, class ValueContainer>
-void test_emplaceable() {
+constexpr void test_emplaceable() {
using M = std::flat_map<int, Emplaceable, std::less<int>, KeyContainer, ValueContainer>;
using R = std::pair<typename M::iterator, bool>;
@@ -143,23 +144,38 @@ void test_emplaceable() {
assert(m.begin()->second == Emplaceable(2, 3.5));
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<double>>();
- test<std::deque<int>, std::vector<double>>();
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
test_emplaceable<std::vector<int>, std::vector<Emplaceable>>();
- test_emplaceable<std::deque<int>, std::vector<Emplaceable>>();
test_emplaceable<MinSequenceContainer<int>, MinSequenceContainer<Emplaceable>>();
test_emplaceable<std::vector<int, min_allocator<int>>, std::vector<Emplaceable, min_allocator<Emplaceable>>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
{
+ test<std::deque<int>, std::vector<double>>();
+ test_emplaceable<std::deque<int>, std::vector<Emplaceable>>();
+ }
+
+ if (!TEST_IS_CONSTANT_EVALUATED) {
auto emplace_func = [](auto& m, auto key_arg, auto value_arg) {
m.emplace(std::piecewise_construct, std::tuple(key_arg), std::tuple(value_arg));
};
test_emplace_exception_guarantee(emplace_func);
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/emplace_hint.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/emplace_hint.pass.cpp
index 77c022a6de92b..02af400cec9d7 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/emplace_hint.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/emplace_hint.pass.cpp
@@ -17,6 +17,7 @@
#include <cassert>
#include <deque>
#include <functional>
+#include <type_traits>
#include <vector>
#include "MinSequenceContainer.h"
@@ -42,7 +43,7 @@ static_assert(!CanEmplaceHint<Map, int, double>);
#endif
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -139,7 +140,7 @@ void test() {
}
template <class KeyContainer, class ValueContainer>
-void test_emplaceable() {
+constexpr void test_emplaceable() {
using M = std::flat_map<int, Emplaceable, std::less<int>, KeyContainer, ValueContainer>;
using R = M::iterator;
@@ -162,23 +163,37 @@ void test_emplaceable() {
assert(m.begin()->second == Emplaceable(2, 3.5));
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<double>>();
- test<std::deque<int>, std::vector<double>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ test_emplaceable<std::deque<int>, std::vector<Emplaceable>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
test_emplaceable<std::vector<int>, std::vector<Emplaceable>>();
- test_emplaceable<std::deque<int>, std::vector<Emplaceable>>();
test_emplaceable<MinSequenceContainer<int>, MinSequenceContainer<Emplaceable>>();
test_emplaceable<std::vector<int, min_allocator<int>>, std::vector<Emplaceable, min_allocator<Emplaceable>>>();
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
auto emplace_func = [](auto& m, auto key_arg, auto value_arg) {
m.emplace_hint(m.begin(), std::piecewise_construct, std::tuple(key_arg), std::tuple(value_arg));
};
test_emplace_exception_guarantee(emplace_func);
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/erase_iter.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/erase_iter.pass.cpp
index 914e8b676a656..841a7938aa26b 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/erase_iter.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/erase_iter.pass.cpp
@@ -18,6 +18,7 @@
#include <deque>
#include <flat_map>
#include <functional>
+#include <type_traits>
#include <utility>
#include <vector>
@@ -27,7 +28,7 @@
#include "min_allocator.h"
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -136,16 +137,30 @@ void test() {
assert(i8 == m.end());
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<double>>();
- test<std::deque<int>, std::vector<double>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
auto erase_function = [](auto& m, auto) { m.erase(m.begin() + 2); };
test_erase_exception_guarantee(erase_function);
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/erase_iter_iter.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/erase_iter_iter.pass.cpp
index 0bc9208294029..76f184c013723 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/erase_iter_iter.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/erase_iter_iter.pass.cpp
@@ -17,6 +17,7 @@
#include <deque>
#include <flat_map>
#include <functional>
+#include <type_traits>
#include <utility>
#include <vector>
@@ -26,7 +27,7 @@
#include "min_allocator.h"
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -95,15 +96,30 @@ void test() {
assert(i4 == m.end());
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<double>>();
- test<std::deque<int>, std::vector<double>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
auto erase_function = [](auto& m, auto) { m.erase(m.begin(), m.begin() + 2); };
test_erase_exception_guarantee(erase_function);
}
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/erase_key.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/erase_key.pass.cpp
index ef57b1cb5512d..e4cce76b90919 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/erase_key.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/erase_key.pass.cpp
@@ -26,7 +26,7 @@
#include "min_allocator.h"
template <class KeyContainer, class ValueContainer, class Compare = std::less<>>
-void test() {
+constexpr void test() {
using M = std::flat_map<int, char, Compare, KeyContainer, ValueContainer>;
auto make = [](std::initializer_list<int> il) {
@@ -70,14 +70,20 @@ void test() {
assert(m.empty());
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<char>>();
test<std::vector<int>, std::vector<char>, std::greater<>>();
- test<std::deque<int>, std::vector<char>>();
+
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<char>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<char>>();
test<std::vector<int, min_allocator<int>>, std::vector<char, min_allocator<char>>>();
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
auto erase_function = [](auto& m, auto key_arg) {
using Map = std::decay_t<decltype(m)>;
using Key = typename Map::key_type;
@@ -87,5 +93,14 @@ int main(int, char**) {
test_erase_exception_guarantee(erase_function);
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/erase_key_transparent.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/erase_key_transparent.pass.cpp
index ecc71a65caa83..be660b75101dc 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/erase_key_transparent.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/erase_key_transparent.pass.cpp
@@ -38,9 +38,9 @@ static_assert(!CanErase<const NonTransparentMap>);
template <class Key, class It>
struct HeterogeneousKey {
- explicit HeterogeneousKey(Key key, It it) : key_(key), it_(it) {}
- operator It() && { return it_; }
- auto operator<=>(Key key) const { return key_ <=> key; }
+ constexpr explicit HeterogeneousKey(Key key, It it) : key_(key), it_(it) {}
+ constexpr operator It() && { return it_; }
+ constexpr auto operator<=>(Key key) const { return key_ <=> key; }
friend bool operator<(const HeterogeneousKey&, const HeterogeneousKey&) {
assert(false);
return false;
@@ -50,7 +50,7 @@ struct HeterogeneousKey {
};
template <class KeyContainer, class ValueContainer>
-void test_simple() {
+constexpr void test_simple() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -71,7 +71,7 @@ void test_simple() {
}
template <class KeyContainer, class ValueContainer>
-void test_transparent_comparator() {
+constexpr void test_transparent_comparator() {
using M = std::flat_map<std::string, int, TransparentComparator, KeyContainer, ValueContainer>;
M m = {{"alpha", 1}, {"beta", 2}, {"epsilon", 3}, {"eta", 4}, {"gamma", 5}};
ASSERT_SAME_TYPE(decltype(m.erase(Transparent<std::string>{"abc"})), typename M::size_type);
@@ -87,18 +87,24 @@ void test_transparent_comparator() {
assert(m == expected);
}
-int main(int, char**) {
+constexpr bool test() {
test_simple<std::vector<int>, std::vector<double>>();
- test_simple<std::deque<int>, std::vector<double>>();
test_simple<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test_simple<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
test_transparent_comparator<std::vector<std::string>, std::vector<int>>();
- test_transparent_comparator<std::deque<std::string>, std::vector<int>>();
test_transparent_comparator<MinSequenceContainer<std::string>, MinSequenceContainer<int>>();
test_transparent_comparator<std::vector<std::string, min_allocator<std::string>>,
std::vector<int, min_allocator<int>>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test_simple<std::deque<int>, std::vector<double>>();
+ test_transparent_comparator<std::deque<std::string>, std::vector<int>>();
+ }
+
{
// P2077's HeterogeneousKey example
using M = std::flat_map<int, int, std::less<>>;
@@ -131,7 +137,7 @@ int main(int, char**) {
assert(n == 1);
assert(transparent_used);
}
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
auto erase_transparent = [](auto& m, auto key_arg) {
using Map = std::decay_t<decltype(m)>;
using Key = typename Map::key_type;
@@ -147,5 +153,14 @@ int main(int, char**) {
assert(n == 1);
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/extract.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/extract.pass.cpp
index d8e4ce94efb9e..996e16aee4a6c 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/extract.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/extract.pass.cpp
@@ -33,7 +33,7 @@ static_assert(!CanExtract<std::flat_map<int, int> const&>);
static_assert(!CanExtract<std::flat_map<int, int> const&&>);
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using M = std::flat_map<int, int, std::less<int>, KeyContainer, ValueContainer>;
M m = M({1, 2, 3}, {4, 5, 6});
@@ -49,9 +49,14 @@ void test() {
LIBCPP_ASSERT(m.values().size() == 0);
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<int>>();
- test<std::deque<int>, std::vector<int>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<int>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<int>>();
test<std::vector<int, min_allocator<int>>, std::vector<int, min_allocator<int>>>();
{
@@ -67,7 +72,7 @@ int main(int, char**) {
LIBCPP_ASSERT(m.values().size() == 0);
}
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
#ifndef TEST_HAS_NO_EXCEPTIONS
using KeyContainer = std::vector<int>;
using ValueContainer = ThrowOnMoveContainer<int>;
@@ -87,5 +92,15 @@ int main(int, char**) {
}
#endif
}
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_cv.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_cv.pass.cpp
index 7e667c4e4877b..fd4b93d3d223d 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_cv.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_cv.pass.cpp
@@ -23,7 +23,7 @@
#include "min_allocator.h"
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -64,13 +64,18 @@ void test() {
assert(r.first->second == 3.5);
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<double>>();
- test<std::deque<int>, std::vector<double>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
auto insert_func = [](auto& m, auto key_arg, auto value_arg) {
using FlatMap = std::decay_t<decltype(m)>;
using value_type = typename FlatMap::value_type;
@@ -79,5 +84,15 @@ int main(int, char**) {
};
test_emplace_exception_guarantee(insert_func);
}
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_initializer_list.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_initializer_list.pass.cpp
index 32be3ab8a95b3..a111837d8d6ef 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_initializer_list.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_initializer_list.pass.cpp
@@ -12,10 +12,12 @@
// void insert(initializer_list<value_type> il);
+#include <algorithm>
#include <flat_map>
#include <cassert>
#include <functional>
#include <deque>
+#include <type_traits>
#include "MinSequenceContainer.h"
#include "../helpers.h"
@@ -23,11 +25,10 @@
#include "min_allocator.h"
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
- using V = std::pair<const int, double>;
M m = {{1, 1}, {1, 1.5}, {1, 2}, {3, 1}, {3, 1.5}, {3, 2}};
m.insert({
@@ -42,20 +43,29 @@ void test() {
{2, 2},
});
assert(m.size() == 4);
- assert(std::distance(m.begin(), m.end()) == 4);
- assert(*m.begin() == V(1, 1));
- assert(*std::next(m.begin()) == V(2, 1));
- assert(*std::next(m.begin(), 2) == V(3, 1));
- assert(*std::next(m.begin(), 3) == V(4, 1));
+ assert(std::ranges::equal(m.keys(), KeyContainer{1, 2, 3, 4}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<Value>>{
+ {1, 1.5, 2},
+ {1, 1.5, 2},
+ {1, 1.5, 2},
+ {1, 1.5, 2},
+ });
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<double>>();
- test<std::deque<int>, std::vector<double>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
auto insert_func = [](auto& m, const auto& newValues) {
using FlatMap = std::decay_t<decltype(m)>;
using value_type = typename FlatMap::value_type;
@@ -64,5 +74,15 @@ int main(int, char**) {
};
test_insert_range_exception_guarantee(insert_func);
}
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_iter_cv.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_iter_cv.pass.cpp
index 4bbe0628317dc..a65c5cde68866 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_iter_cv.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_iter_cv.pass.cpp
@@ -23,7 +23,7 @@
#include "min_allocator.h"
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -60,13 +60,18 @@ void test() {
assert(r->second == 3.5);
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<double>>();
- test<std::deque<int>, std::vector<double>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
auto insert_func = [](auto& m, auto key_arg, auto value_arg) {
using FlatMap = std::decay_t<decltype(m)>;
using value_type = typename FlatMap::value_type;
@@ -75,5 +80,15 @@ int main(int, char**) {
};
test_emplace_exception_guarantee(insert_func);
}
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_iter_iter.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_iter_iter.pass.cpp
index ccce117c90fca..66bd4022f321a 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_iter_iter.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_iter_iter.pass.cpp
@@ -38,7 +38,7 @@ static_assert(!CanInsert<Map, int, int>);
static_assert(!CanInsert<Map, cpp20_input_iterator<Pair*>, cpp20_input_iterator<Pair*>>);
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using P = std::pair<int, double>;
using M = std::flat_map<int, double, std::less<int>, KeyContainer, ValueContainer>;
@@ -68,22 +68,45 @@ void test() {
M m;
m.insert(cpp17_input_iterator<P*>(ar1), cpp17_input_iterator<P*>(ar1 + sizeof(ar1) / sizeof(ar1[0])));
assert(m.size() == 3);
- M expected{{1, 1}, {2, 1}, {3, 1}};
- assert(m == expected);
- m.insert(cpp17_input_iterator<P*>(ar2), cpp17_input_iterator<P*>(ar2 + sizeof(ar2) / sizeof(ar2[0])));
- assert(m.size() == 5);
- M expected2{{0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}};
- assert(m == expected2);
+ assert(std::ranges::equal(m.keys(), KeyContainer{1, 2, 3}));
+ check_possible_values(
+ m.values(),
+ std::vector<std::vector<double>>{
+ {1, 1.5, 2},
+ {1, 1.5, 2},
+ {1, 1.5, 2},
+ });
+
+ auto m2 = m;
+
+ m2.insert(cpp17_input_iterator<P*>(ar2), cpp17_input_iterator<P*>(ar2 + sizeof(ar2) / sizeof(ar2[0])));
+ assert(m2.size() == 5);
+
+ assert(std::ranges::equal(m2.keys(), KeyContainer{0, 1, 2, 3, 4}));
+ check_possible_values(
+ m2.values(),
+ std::vector<std::vector<double>>{
+ {1, 1.5, 2},
+ {m[1]},
+ {m[2]},
+ {m[3]},
+ {1, 1.5, 2},
+ });
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<double>>();
- test<std::deque<int>, std::vector<double>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
auto insert_func = [](auto& m, const auto& newValues) { m.insert(newValues.begin(), newValues.end()); };
test_insert_range_exception_guarantee(insert_func);
}
@@ -94,5 +117,14 @@ int main(int, char**) {
assert(std::ranges::equal(m, std::vector<std::pair<int, int>>{{1, 1}, {2, 2}, {3, 3}, {4, 4}}));
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_iter_rv.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_iter_rv.pass.cpp
index 034941b55eb80..8a5d6481bd0d6 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_iter_rv.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_iter_rv.pass.cpp
@@ -22,7 +22,7 @@
#include "test_macros.h"
template <class Container, class Pair>
-void do_insert_iter_rv_test() {
+constexpr void do_insert_iter_rv_test() {
using M = Container;
using P = Pair;
using R = typename M::iterator;
@@ -53,7 +53,7 @@ void do_insert_iter_rv_test() {
}
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -64,17 +64,22 @@ void test() {
do_insert_iter_rv_test<M, CP>();
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<double>>();
test<std::vector<int>, std::vector<MoveOnly>>();
- test<std::deque<int>, std::deque<double>>();
- test<std::deque<int>, std::deque<MoveOnly>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::deque<double>>();
+ test<std::deque<int>, std::deque<MoveOnly>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<MinSequenceContainer<int>, MinSequenceContainer<MoveOnly>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
test<std::vector<int, min_allocator<int>>, std::vector<MoveOnly, min_allocator<MoveOnly>>>();
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
auto insert_func = [](auto& m, auto key_arg, auto value_arg) {
using FlatMap = std::decay_t<decltype(m)>;
using value_type = typename FlatMap::value_type;
@@ -84,5 +89,14 @@ int main(int, char**) {
test_emplace_exception_guarantee(insert_func);
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_or_assign.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_or_assign.pass.cpp
index 398a7a1a4052e..daa4674db6c1d 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_or_assign.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_or_assign.pass.cpp
@@ -78,7 +78,7 @@ static_assert(!CanInsertOrAssignIter<std::flat_map<int, ConstructFrom<V>>, int&&
static_assert(!CanInsertOrAssignIter<std::flat_map<int, AssignFrom<V>>, int&&, V>);
template <class KeyContainer, class ValueContainer>
-void test_cv_key() {
+constexpr void test_cv_key() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, TransparentComparator, KeyContainer, ValueContainer>;
@@ -194,7 +194,7 @@ void test_cv_key() {
}
template <class KeyContainer, class ValueContainer>
-void test_rv_key() {
+constexpr void test_rv_key() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, TransparentComparator, KeyContainer, ValueContainer>;
@@ -311,16 +311,30 @@ void test_rv_key() {
}
}
-int main(int, char**) {
+constexpr bool test() {
test_cv_key<std::vector<int>, std::vector<Moveable>>();
- test_cv_key<std::deque<int>, std::vector<Moveable>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test_cv_key<std::deque<int>, std::vector<Moveable>>();
+ test_rv_key<std::deque<Moveable>, std::vector<Moveable>>();
+ }
test_cv_key<MinSequenceContainer<int>, MinSequenceContainer<Moveable>>();
test_cv_key<std::vector<int, min_allocator<int>>, std::vector<Moveable, min_allocator<Moveable>>>();
test_rv_key<std::vector<Moveable>, std::vector<Moveable>>();
- test_rv_key<std::deque<Moveable>, std::vector<Moveable>>();
test_rv_key<MinSequenceContainer<Moveable>, MinSequenceContainer<Moveable>>();
test_rv_key<std::vector<Moveable, min_allocator<Moveable>>, std::vector<Moveable, min_allocator<Moveable>>>();
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_or_assign_transparent.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_or_assign_transparent.pass.cpp
index bd6d176e26e23..d036b45b958ac 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_or_assign_transparent.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_or_assign_transparent.pass.cpp
@@ -94,7 +94,7 @@ static_assert(
!CanInsertOrAssignIter<std::flat_map<int, AssignFrom<V>, TransparentComparator>, ConvertibleTransparent<int>, V>);
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, TransparentComparator, KeyContainer, ValueContainer>;
@@ -212,9 +212,14 @@ void test() {
}
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<Moveable>>();
- test<std::deque<int>, std::vector<Moveable>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<Moveable>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<Moveable>>();
test<std::vector<int, min_allocator<int>>, std::vector<Moveable, min_allocator<Moveable>>>();
@@ -237,22 +242,24 @@ int main(int, char**) {
assert(transparent_used);
}
- {
- auto insert_or_assign = [](auto& m, auto key_arg, auto value_arg) {
- using M = std::decay_t<decltype(m)>;
- using Key = typename M::key_type;
- m.insert_or_assign(ConvertibleTransparent<Key>{key_arg}, value_arg);
- };
- test_emplace_exception_guarantee(insert_or_assign);
- }
+ if (!TEST_IS_CONSTANT_EVALUATED) {
+ {
+ auto insert_or_assign = [](auto& m, auto key_arg, auto value_arg) {
+ using M = std::decay_t<decltype(m)>;
+ using Key = typename M::key_type;
+ m.insert_or_assign(ConvertibleTransparent<Key>{key_arg}, value_arg);
+ };
+ test_emplace_exception_guarantee(insert_or_assign);
+ }
- {
- auto insert_or_assign_iter = [](auto& m, auto key_arg, auto value_arg) {
- using M = std::decay_t<decltype(m)>;
- using Key = typename M::key_type;
- m.insert_or_assign(m.begin(), ConvertibleTransparent<Key>{key_arg}, value_arg);
- };
- test_emplace_exception_guarantee(insert_or_assign_iter);
+ {
+ auto insert_or_assign_iter = [](auto& m, auto key_arg, auto value_arg) {
+ using M = std::decay_t<decltype(m)>;
+ using Key = typename M::key_type;
+ m.insert_or_assign(m.begin(), ConvertibleTransparent<Key>{key_arg}, value_arg);
+ };
+ test_emplace_exception_guarantee(insert_or_assign_iter);
+ }
}
{
// LWG4239 std::string and C string literal
@@ -267,5 +274,14 @@ int main(int, char**) {
assert(it2->second == 2);
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_range.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_range.pass.cpp
index a2e64431a3c25..88be747df6796 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_range.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_range.pass.cpp
@@ -39,7 +39,7 @@ static_assert(!CanInsertRange<Map, std::ranges::subrange<int*>>);
static_assert(!CanInsertRange<Map, std::ranges::subrange<double*>>);
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
@@ -68,7 +68,7 @@ void test() {
{
// The "uniquing" part uses the comparator, not operator==.
struct ModTen {
- bool operator()(int a, int b) const { return (a % 10) < (b % 10); }
+ constexpr bool operator()(int a, int b) const { return (a % 10) < (b % 10); }
};
using P = std::pair<int, int>;
using M = std::flat_map<Key, Value, ModTen, KeyContainer, ValueContainer>;
@@ -79,9 +79,14 @@ void test() {
}
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<int>>();
- test<std::deque<int>, std::vector<int>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<int>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<int>>();
test<std::vector<int, min_allocator<int>>, std::vector<int, min_allocator<int>>>();
{
@@ -95,15 +100,25 @@ int main(int, char**) {
{
// The element type of the range doesn't need to be std::pair (P2767).
std::pair<int, int> pa[] = {{3, 3}, {1, 1}, {4, 4}, {1, 1}, {5, 5}};
- std::deque<std::reference_wrapper<std::pair<int, int>>> a(pa, pa + 5);
+ std::vector<std::reference_wrapper<std::pair<int, int>>> a(pa, pa + 5);
std::flat_map<int, int> m;
m.insert_range(a);
std::pair<int, int> expected[] = {{1, 1}, {3, 3}, {4, 4}, {5, 5}};
assert(std::ranges::equal(m, expected));
}
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
auto insert_func = [](auto& m, const auto& newValues) { m.insert_range(newValues); };
test_insert_range_exception_guarantee(insert_func);
}
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_rv.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_rv.pass.cpp
index 9ea7a6a636666..76deaa43b3e4e 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_rv.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_rv.pass.cpp
@@ -25,7 +25,7 @@
#include "../helpers.h"
template <class Container, class Pair>
-void do_insert_rv_test() {
+constexpr void do_insert_rv_test() {
using M = Container;
using P = Pair;
using R = std::pair<typename M::iterator, bool>;
@@ -60,7 +60,7 @@ void do_insert_rv_test() {
}
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, TransparentComparator, KeyContainer, ValueContainer>;
@@ -72,9 +72,14 @@ void test() {
do_insert_rv_test<M, CP>();
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<MoveOnly>>();
- test<std::deque<int>, std::vector<MoveOnly>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<MoveOnly>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<MoveOnly>>();
test<std::vector<int, min_allocator<int>>, std::vector<MoveOnly, min_allocator<MoveOnly>>>();
@@ -110,7 +115,7 @@ int main(int, char**) {
assert(r.first->first == 3);
assert(r.first->second == 3);
}
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
auto insert_func = [](auto& m, auto key_arg, auto value_arg) {
using FlatMap = std::decay_t<decltype(m)>;
using value_type = typename FlatMap::value_type;
@@ -120,5 +125,14 @@ int main(int, char**) {
test_emplace_exception_guarantee(insert_func);
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_sorted_initializer_list.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_sorted_initializer_list.pass.cpp
index 08d2caf349879..f4469cd46892e 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_sorted_initializer_list.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_sorted_initializer_list.pass.cpp
@@ -23,7 +23,7 @@
#include "min_allocator.h"
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -39,20 +39,34 @@ void test() {
});
assert(m.size() == 5);
assert(std::distance(m.begin(), m.end()) == 5);
+
assert(*m.begin() == V(0, 1));
- assert(*std::next(m.begin()) == V(1, 1));
- assert(*std::next(m.begin(), 2) == V(2, 1));
- assert(*std::next(m.begin(), 3) == V(3, 1));
- assert(*std::next(m.begin(), 4) == V(4, 1));
+ auto v1 = *std::next(m.begin());
+ assert(v1.first == 1);
+ assert(v1.second == 1 || v1.second == 1.5 || v1.second == 2);
+ auto v2 = *std::next(m.begin(), 2);
+ assert(v2.first == 2);
+ assert(v2.second == 1);
+ auto v3 = *std::next(m.begin(), 3);
+ assert(v3.first == 3);
+ assert(v3.second == 1 || v3.second == 1.5 || v3.second == 2);
+ auto v4 = *std::next(m.begin(), 4);
+ assert(v4.first == 4);
+ assert(v4.second == 1);
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<double>>();
- test<std::deque<int>, std::vector<double>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
auto insert_func = [](auto& m, const auto& newValues) {
using FlatMap = std::decay_t<decltype(m)>;
using value_type = typename FlatMap::value_type;
@@ -62,5 +76,14 @@ int main(int, char**) {
test_insert_range_exception_guarantee(insert_func);
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_sorted_iter_iter.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_sorted_iter_iter.pass.cpp
index 18a3b571a4199..e943dc6408e04 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_sorted_iter_iter.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_sorted_iter_iter.pass.cpp
@@ -37,7 +37,7 @@ static_assert(!CanInsert<Map, std::sorted_unique_t, int, int>);
static_assert(!CanInsert<Map, std::sorted_unique_t, cpp20_input_iterator<Pair*>, cpp20_input_iterator<Pair*>>);
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -69,18 +69,32 @@ void test() {
assert(m == expected2);
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<double>>();
- test<std::deque<int>, std::vector<double>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
auto insert_func = [](auto& m, const auto& newValues) {
m.insert(std::sorted_unique, newValues.begin(), newValues.end());
};
test_insert_range_exception_guarantee(insert_func);
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_transparent.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_transparent.pass.cpp
index c24c8373f51e9..75ddc49c13806 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_transparent.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/insert_transparent.pass.cpp
@@ -41,98 +41,42 @@ static_assert(CanInsert<Map, Iter, std::tuple<short, double>&&>);
static_assert(!CanInsert<Map, int>);
static_assert(!CanInsert<Map, Iter, int>);
-static int expensive_comparisons = 0;
-static int cheap_comparisons = 0;
-
-struct CompareCounter {
- int i_ = 0;
- CompareCounter(int i) : i_(i) {}
- friend auto operator<=>(const CompareCounter& x, const CompareCounter& y) {
- expensive_comparisons += 1;
- return x.i_ <=> y.i_;
- }
- bool operator==(const CompareCounter&) const = default;
- friend auto operator<=>(const CompareCounter& x, int y) {
- cheap_comparisons += 1;
- return x.i_ <=> y;
- }
-};
+constexpr bool test() {
+ {
+ // template<class K> pair<iterator, bool> insert(P&& x);
+ bool transparent_used = false;
+ TransparentComparator c(transparent_used);
+ using M = std::flat_map<int, int, TransparentComparator>;
+ M m(std::sorted_unique, {{1, 1}, {2, 2}, {4, 4}}, c);
+ assert(!transparent_used);
-template <class KeyContainer, class ValueContainer>
-void test() {
- using Key = typename KeyContainer::value_type;
- using Value = typename ValueContainer::value_type;
- using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
+ std::same_as<std::pair<typename M::iterator, bool>> decltype(auto) res =
+ m.insert(std::pair(ConvertibleTransparent<int>{3}, 3));
- const std::pair<int, int> expected[] = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}};
- {
- // insert(P&&)
+ assert(res.second);
+ assert(res.first->first == 3);
+ assert(res.first->second == 3);
// Unlike flat_set, here we can't use key_compare to compare value_type versus P,
// so we must eagerly convert to value_type.
- M m = {{1, 1}, {2, 2}, {4, 4}, {5, 5}};
- expensive_comparisons = 0;
- cheap_comparisons = 0;
- std::same_as<std::pair<typename M::iterator, bool>> auto p =
- m.insert(std::make_pair(3, 3)); // conversion happens first
- assert(expensive_comparisons >= 2);
- assert(cheap_comparisons == 0);
- assert(p == std::make_pair(m.begin() + 2, true));
- assert(std::ranges::equal(m, expected));
- }
- {
- // insert(const_iterator, P&&)
- M m = {{1, 1}, {2, 2}, {4, 4}, {5, 5}};
- expensive_comparisons = 0;
- cheap_comparisons = 0;
- std::same_as<typename M::iterator> auto it = m.insert(m.begin(), std::make_pair(3, 3));
- assert(expensive_comparisons >= 2);
- assert(cheap_comparisons == 0);
- assert(it == m.begin() + 2);
- assert(std::ranges::equal(m, expected));
- }
- {
- // insert(value_type&&)
- M m = {{1, 1}, {2, 2}, {4, 4}, {5, 5}};
- expensive_comparisons = 0;
- cheap_comparisons = 0;
- std::same_as<std::pair<typename M::iterator, bool>> auto p =
- m.insert(std::make_pair(3, 3)); // conversion happens last
- assert(expensive_comparisons >= 2);
- assert(cheap_comparisons == 0);
- assert(p == std::make_pair(m.begin() + 2, true));
- assert(std::ranges::equal(m, expected));
- }
- {
- // insert(const_iterator, value_type&&)
- M m = {{1, 1}, {2, 2}, {4, 4}, {5, 5}};
- expensive_comparisons = 0;
- cheap_comparisons = 0;
- std::same_as<typename M::iterator> auto it = m.insert(m.begin(), std::make_pair(3, 3));
- assert(expensive_comparisons >= 2);
- assert(cheap_comparisons == 0);
- assert(it == m.begin() + 2);
- assert(std::ranges::equal(m, expected));
+ assert(!transparent_used);
}
{
- // emplace(Args&&...)
- M m = {{1, 1}, {2, 2}, {4, 4}, {5, 5}};
- expensive_comparisons = 0;
- cheap_comparisons = 0;
- std::same_as<std::pair<typename M::iterator, bool>> auto p =
- m.emplace(std::make_pair(3, 3)); // conversion happens first
- assert(expensive_comparisons >= 2);
- assert(cheap_comparisons == 0);
- assert(p == std::make_pair(m.begin() + 2, true));
- assert(std::ranges::equal(m, expected));
- }
-}
+ // template<class K> iterator insert(const_iterator hint, P&& x);
+ bool transparent_used = false;
+ TransparentComparator c(transparent_used);
+ using M = std::flat_map<int, int, TransparentComparator>;
+ M m(std::sorted_unique, {{1, 1}, {2, 2}, {4, 4}}, c);
+ assert(!transparent_used);
-int main(int, char**) {
- test<std::vector<CompareCounter>, std::vector<double>>();
- test<std::deque<CompareCounter>, std::vector<double>>();
- test<MinSequenceContainer<CompareCounter>, MinSequenceContainer<double>>();
- test<std::vector<CompareCounter, min_allocator<CompareCounter>>, std::vector<double, min_allocator<double>>>();
+ std::same_as<typename M::iterator> decltype(auto) res =
+ m.insert(m.begin(), std::pair(ConvertibleTransparent<int>{3}, 3));
+ assert(res->first == 3);
+ assert(res->second == 3);
+ // Unlike flat_set, here we can't use key_compare to compare value_type versus P,
+ // so we must eagerly convert to value_type.
+ assert(!transparent_used);
+ }
{
// no ambiguity between insert(pos, P&&) and insert(first, last)
using M = std::flat_map<int, int>;
@@ -145,23 +89,26 @@ int main(int, char**) {
ASSERT_SAME_TYPE(decltype(m.insert(m.begin(), Evil())), M::iterator);
ASSERT_SAME_TYPE(decltype(m.insert(m.begin(), m.end())), void);
}
- {
- auto insert_func = [](auto& m, auto key_arg, auto value_arg) {
- using FlatMap = std::decay_t<decltype(m)>;
- using tuple_type = std::tuple<typename FlatMap::key_type, typename FlatMap::mapped_type>;
- tuple_type t(key_arg, value_arg);
- m.insert(t);
- };
- test_emplace_exception_guarantee(insert_func);
- }
- {
- auto insert_func_iter = [](auto& m, auto key_arg, auto value_arg) {
- using FlatMap = std::decay_t<decltype(m)>;
- using tuple_type = std::tuple<typename FlatMap::key_type, typename FlatMap::mapped_type>;
- tuple_type t(key_arg, value_arg);
- m.insert(m.begin(), t);
- };
- test_emplace_exception_guarantee(insert_func_iter);
+
+ if (!TEST_IS_CONSTANT_EVALUATED) {
+ {
+ auto insert_func = [](auto& m, auto key_arg, auto value_arg) {
+ using FlatMap = std::decay_t<decltype(m)>;
+ using tuple_type = std::tuple<typename FlatMap::key_type, typename FlatMap::mapped_type>;
+ tuple_type t(key_arg, value_arg);
+ m.insert(t);
+ };
+ test_emplace_exception_guarantee(insert_func);
+ }
+ {
+ auto insert_func_iter = [](auto& m, auto key_arg, auto value_arg) {
+ using FlatMap = std::decay_t<decltype(m)>;
+ using tuple_type = std::tuple<typename FlatMap::key_type, typename FlatMap::mapped_type>;
+ tuple_type t(key_arg, value_arg);
+ m.insert(m.begin(), t);
+ };
+ test_emplace_exception_guarantee(insert_func_iter);
+ }
}
{
// LWG4239 std::string and C string literal
@@ -173,5 +120,15 @@ int main(int, char**) {
auto it2 = m.insert(m.begin(), {"beta2", 2});
assert(it2 == m.begin() + 2);
}
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/replace.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/replace.pass.cpp
index 5ca811d761520..6582d475708fe 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/replace.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/replace.pass.cpp
@@ -33,7 +33,7 @@ static_assert(!CanReplace<Map, std::vector<int>, const std::vector<int>&>);
static_assert(!CanReplace<Map, const std::vector<int>&, const std::vector<int>&>);
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -49,13 +49,18 @@ void test() {
assert(std::ranges::equal(m.values(), expected_values));
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<double>>();
- test<std::deque<int>, std::vector<double>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
#ifndef TEST_HAS_NO_EXCEPTIONS
using KeyContainer = std::vector<int>;
using ValueContainer = ThrowOnMoveContainer<int>;
@@ -76,5 +81,15 @@ int main(int, char**) {
}
#endif
}
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/swap_free.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/swap_free.pass.cpp
index 98c60c1488cf5..37f2914ebfdb2 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/swap_free.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/swap_free.pass.cpp
@@ -39,7 +39,7 @@ static_assert(
#endif
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -87,11 +87,25 @@ void test() {
}
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<double>>();
- test<std::deque<int>, std::vector<double>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/swap_member.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/swap_member.pass.cpp
index d2d8f5673edeb..771bcd3de2c09 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/swap_member.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/swap_member.pass.cpp
@@ -38,7 +38,7 @@ static_assert(
#endif
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -85,11 +85,25 @@ void test() {
}
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<double>>();
- test<std::deque<int>, std::vector<double>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<double>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<double>>();
test<std::vector<int, min_allocator<int>>, std::vector<double, min_allocator<double>>>();
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/try_emplace.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/try_emplace.pass.cpp
index 4be2fe1c4333e..7fdddc4d19dcd 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/try_emplace.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/try_emplace.pass.cpp
@@ -63,7 +63,7 @@ static_assert(!CanTryEmplace<Map, Iter, Emplaceable, const Emplaceable&>);
static_assert(!CanTryEmplace<Map, Iter, Emplaceable, int>);
template <class KeyContainer, class ValueContainer>
-void test_ck() {
+constexpr void test_ck() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -101,7 +101,7 @@ void test_ck() {
assert(r3.first->second.get() == 5); // value
Moveable mv3(-1, 3.0);
- std::same_as<R> decltype(auto) r4 = m.try_emplace(117, std::move(mv2));
+ std::same_as<R> decltype(auto) r4 = m.try_emplace(117, std::move(mv3));
assert(m.size() == 13);
assert(r4.second); // was inserted
assert(mv2.moved()); // was moved from
@@ -135,7 +135,7 @@ void test_ck() {
}
template <class KeyContainer, class ValueContainer>
-void test_rk() {
+constexpr void test_rk() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -193,54 +193,70 @@ void test_rk() {
}
}
-int main(int, char**) {
+constexpr bool test() {
test_ck<std::vector<int>, std::vector<Moveable>>();
- test_ck<std::deque<int>, std::vector<Moveable>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test_ck<std::deque<int>, std::vector<Moveable>>();
+ test_rk<std::deque<Moveable>, std::vector<Moveable>>();
+ }
test_ck<MinSequenceContainer<int>, MinSequenceContainer<Moveable>>();
test_ck<std::vector<int, min_allocator<int>>, std::vector<Moveable, min_allocator<Moveable>>>();
test_rk<std::vector<Moveable>, std::vector<Moveable>>();
- test_rk<std::deque<Moveable>, std::vector<Moveable>>();
test_rk<MinSequenceContainer<Moveable>, MinSequenceContainer<Moveable>>();
test_rk<std::vector<Moveable, min_allocator<Moveable>>, std::vector<Moveable, min_allocator<Moveable>>>();
- {
- auto try_emplace_ck = [](auto& m, auto key_arg, auto value_arg) {
- using M = std::decay_t<decltype(m)>;
- using Key = typename M::key_type;
- const Key key{key_arg};
- m.try_emplace(key, value_arg);
- };
- test_emplace_exception_guarantee(try_emplace_ck);
- }
+ if (!TEST_IS_CONSTANT_EVALUATED) {
+ {
+ auto try_emplace_ck = [](auto& m, auto key_arg, auto value_arg) {
+ using M = std::decay_t<decltype(m)>;
+ using Key = typename M::key_type;
+ const Key key{key_arg};
+ m.try_emplace(key, value_arg);
+ };
+ test_emplace_exception_guarantee(try_emplace_ck);
+ }
- {
- auto try_emplace_rk = [](auto& m, auto key_arg, auto value_arg) {
- using M = std::decay_t<decltype(m)>;
- using Key = typename M::key_type;
- m.try_emplace(Key{key_arg}, value_arg);
- };
- test_emplace_exception_guarantee(try_emplace_rk);
- }
+ {
+ auto try_emplace_rk = [](auto& m, auto key_arg, auto value_arg) {
+ using M = std::decay_t<decltype(m)>;
+ using Key = typename M::key_type;
+ m.try_emplace(Key{key_arg}, value_arg);
+ };
+ test_emplace_exception_guarantee(try_emplace_rk);
+ }
- {
- auto try_emplace_iter_ck = [](auto& m, auto key_arg, auto value_arg) {
- using M = std::decay_t<decltype(m)>;
- using Key = typename M::key_type;
- const Key key{key_arg};
- m.try_emplace(m.begin(), key, value_arg);
- };
- test_emplace_exception_guarantee(try_emplace_iter_ck);
- }
+ {
+ auto try_emplace_iter_ck = [](auto& m, auto key_arg, auto value_arg) {
+ using M = std::decay_t<decltype(m)>;
+ using Key = typename M::key_type;
+ const Key key{key_arg};
+ m.try_emplace(m.begin(), key, value_arg);
+ };
+ test_emplace_exception_guarantee(try_emplace_iter_ck);
+ }
- {
- auto try_emplace_iter_rk = [](auto& m, auto key_arg, auto value_arg) {
- using M = std::decay_t<decltype(m)>;
- using Key = typename M::key_type;
- m.try_emplace(m.begin(), Key{key_arg}, value_arg);
- };
- test_emplace_exception_guarantee(try_emplace_iter_rk);
+ {
+ auto try_emplace_iter_rk = [](auto& m, auto key_arg, auto value_arg) {
+ using M = std::decay_t<decltype(m)>;
+ using Key = typename M::key_type;
+ m.try_emplace(m.begin(), Key{key_arg}, value_arg);
+ };
+ test_emplace_exception_guarantee(try_emplace_iter_rk);
+ }
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/try_emplace_transparent.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/try_emplace_transparent.pass.cpp
index c301f07937ea7..6a536d4d8282c 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/try_emplace_transparent.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/try_emplace_transparent.pass.cpp
@@ -7,6 +7,8 @@
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// gcc 15 ICE on this test
+// UNSUPPORTED: gcc
// <flat_map>
@@ -65,7 +67,7 @@ static_assert(!CanTryEmplace<NonTransparentMap, TransparentMapConstIter, NonConv
static_assert(!CanTryEmplace<TransparentMap, TransparentMapConstIter, ConvertibleTransparent<int>, int>);
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, TransparentComparator, KeyContainer, ValueContainer>;
@@ -103,7 +105,7 @@ void test() {
assert(r3.first->second.get() == 5); // value
Moveable mv3(-1, 3.0);
- std::same_as<R> decltype(auto) r4 = m.try_emplace(ConvertibleTransparent<int>{117}, std::move(mv2));
+ std::same_as<R> decltype(auto) r4 = m.try_emplace(ConvertibleTransparent<int>{117}, std::move(mv3));
assert(m.size() == 13);
assert(r4.second); // was inserted
assert(mv2.moved()); // was moved from
@@ -136,9 +138,14 @@ void test() {
}
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<Moveable>>();
- test<std::deque<int>, std::vector<Moveable>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<Moveable>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<Moveable>>();
test<std::vector<int, min_allocator<int>>, std::vector<Moveable, min_allocator<Moveable>>>();
@@ -171,23 +178,34 @@ int main(int, char**) {
auto it2 = m.try_emplace(m.begin(), "beta2", 2);
assert(it2 == m.begin() + 2);
}
- {
- auto try_emplace = [](auto& m, auto key_arg, auto value_arg) {
- using M = std::decay_t<decltype(m)>;
- using Key = typename M::key_type;
- m.try_emplace(ConvertibleTransparent<Key>{key_arg}, value_arg);
- };
- test_emplace_exception_guarantee(try_emplace);
- }
+ if (!TEST_IS_CONSTANT_EVALUATED) {
+ {
+ auto try_emplace = [](auto& m, auto key_arg, auto value_arg) {
+ using M = std::decay_t<decltype(m)>;
+ using Key = typename M::key_type;
+ m.try_emplace(ConvertibleTransparent<Key>{key_arg}, value_arg);
+ };
+ test_emplace_exception_guarantee(try_emplace);
+ }
- {
- auto try_emplace_iter = [](auto& m, auto key_arg, auto value_arg) {
- using M = std::decay_t<decltype(m)>;
- using Key = typename M::key_type;
- m.try_emplace(m.begin(), ConvertibleTransparent<Key>{key_arg}, value_arg);
- };
- test_emplace_exception_guarantee(try_emplace_iter);
+ {
+ auto try_emplace_iter = [](auto& m, auto key_arg, auto value_arg) {
+ using M = std::decay_t<decltype(m)>;
+ using Key = typename M::key_type;
+ m.try_emplace(m.begin(), ConvertibleTransparent<Key>{key_arg}, value_arg);
+ };
+ test_emplace_exception_guarantee(try_emplace_iter);
+ }
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.observers/comp.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.observers/comp.pass.cpp
index d86224952dee4..5712493740bc8 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.observers/comp.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.observers/comp.pass.cpp
@@ -16,12 +16,13 @@
#include <cassert>
#include <flat_map>
#include <functional>
+#include <type_traits>
#include <utility>
#include <vector>
#include "test_macros.h"
-int main(int, char**) {
+constexpr bool test() {
{
using M = std::flat_map<int, char>;
using Comp = std::less<int>; // the default
@@ -38,7 +39,7 @@ int main(int, char**) {
assert(vc({1, '2'}, {2, '1'}));
assert(!vc({2, '1'}, {1, '2'}));
}
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
using Comp = std::function<bool(int, int)>;
using M = std::flat_map<int, int, Comp>;
Comp comp = std::greater<int>();
@@ -72,7 +73,7 @@ int main(int, char**) {
assert(vc({1, 2}, {2, 1}));
assert(!vc({2, 1}, {1, 2}));
}
- {
+ if (!TEST_IS_CONSTANT_EVALUATED) {
using Comp = std::function<bool(const std::vector<int>&, const std::vector<int>&)>;
using M = std::flat_map<std::vector<int>, int, Comp>;
Comp comp = [i = 1](const auto& x, const auto& y) { return x[i] < y[i]; };
@@ -92,5 +93,15 @@ int main(int, char**) {
assert(!vc(b, a));
assert(!vc(c, b));
}
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.observers/keys_values.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.observers/keys_values.pass.cpp
index 84d8f8344aaa6..6b98c116f2875 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.observers/keys_values.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.observers/keys_values.pass.cpp
@@ -28,7 +28,7 @@
#include "min_allocator.h"
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -47,11 +47,25 @@ void test() {
assert(std::ranges::equal(values, expected_values));
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<char>>();
- test<std::deque<int>, std::vector<char>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<char>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<char>>();
test<std::vector<int, min_allocator<int>>, std::vector<char, min_allocator<char>>>();
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/contains.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/contains.pass.cpp
index 208d6138fa683..051c8952bd1dc 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/contains.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/contains.pass.cpp
@@ -23,7 +23,7 @@
#include "min_allocator.h"
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
{
@@ -60,11 +60,25 @@ void test() {
}
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<int>>();
- test<std::deque<int>, std::vector<int>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<int>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<int>>();
test<std::vector<int, min_allocator<int>>, std::vector<int, min_allocator<int>>>();
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/contains_transparent.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/contains_transparent.pass.cpp
index e28e94c3d8fb9..ecbd42366dadd 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/contains_transparent.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/contains_transparent.pass.cpp
@@ -35,7 +35,7 @@ static_assert(!CanContains<NonTransparentMap>);
static_assert(!CanContains<const NonTransparentMap>);
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, TransparentComparator, KeyContainer, ValueContainer>;
@@ -53,9 +53,14 @@ void test() {
assert(m.contains(Transparent<std::string>{"g"}) == false);
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<std::string>, std::vector<int>>();
- test<std::deque<std::string>, std::vector<int>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<std::string>, std::vector<int>>();
+ }
test<MinSequenceContainer<std::string>, MinSequenceContainer<int>>();
test<std::vector<std::string, min_allocator<std::string>>, std::vector<int, min_allocator<int>>>();
@@ -75,5 +80,15 @@ int main(int, char**) {
assert(m.contains("beta") == true);
assert(m.contains("charlie") == false);
}
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/count.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/count.pass.cpp
index db675854d5e98..acfc8acb2746d 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/count.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/count.pass.cpp
@@ -23,7 +23,7 @@
#include "min_allocator.h"
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
@@ -59,11 +59,25 @@ void test() {
}
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<int>>();
- test<std::deque<int>, std::vector<int>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<int>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<int>>();
test<std::vector<int, min_allocator<int>>, std::vector<int, min_allocator<int>>>();
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/count_transparent.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/count_transparent.pass.cpp
index 37d815b2ce988..660b2249471cb 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/count_transparent.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/count_transparent.pass.cpp
@@ -35,7 +35,7 @@ static_assert(!CanCount<NonTransparentMap>);
static_assert(!CanCount<const NonTransparentMap>);
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, TransparentComparator, KeyContainer, ValueContainer>;
@@ -53,9 +53,14 @@ void test() {
assert(m.count(Transparent<std::string>{"g"}) == 0);
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<std::string>, std::vector<int>>();
- test<std::deque<std::string>, std::vector<int>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<std::string>, std::vector<int>>();
+ }
test<MinSequenceContainer<std::string>, MinSequenceContainer<int>>();
test<std::vector<std::string, min_allocator<std::string>>, std::vector<int, min_allocator<int>>>();
@@ -76,5 +81,14 @@ int main(int, char**) {
assert(m.count("charlie") == 0);
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/equal_range.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/equal_range.pass.cpp
index 8fa73d2a2eb51..fabce9cdd5a84 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/equal_range.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/equal_range.pass.cpp
@@ -24,7 +24,7 @@
#include "min_allocator.h"
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
{
@@ -68,11 +68,25 @@ void test() {
}
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<char>>();
- test<std::deque<int>, std::vector<char>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<char>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<char>>();
test<std::vector<int, min_allocator<int>>, std::vector<char, min_allocator<char>>>();
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/equal_range_transparent.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/equal_range_transparent.pass.cpp
index bff70a320928a..fbaf81417b344 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/equal_range_transparent.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/equal_range_transparent.pass.cpp
@@ -36,7 +36,7 @@ static_assert(!CanEqualRange<NonTransparentMap>);
static_assert(!CanEqualRange<const NonTransparentMap>);
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, TransparentComparator, KeyContainer, ValueContainer>;
@@ -81,9 +81,14 @@ void test() {
test_not_found(cm, "zzz", 5);
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<std::string>, std::vector<int>>();
- test<std::deque<std::string>, std::vector<int>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<std::string>, std::vector<int>>();
+ }
test<MinSequenceContainer<std::string>, MinSequenceContainer<int>>();
test<std::vector<std::string, min_allocator<std::string>>, std::vector<int, min_allocator<int>>>();
@@ -105,5 +110,14 @@ int main(int, char**) {
assert(last == m.begin() + 2);
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/find.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/find.pass.cpp
index 9fae407c7d8f7..c5bf7478d001a 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/find.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/find.pass.cpp
@@ -25,7 +25,7 @@
#include "min_allocator.h"
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
@@ -45,11 +45,25 @@ void test() {
assert(std::as_const(m).find(9) == m.end());
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<char>>();
- test<std::deque<int>, std::vector<char>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<char>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<char>>();
test<std::vector<int, min_allocator<int>>, std::vector<char, min_allocator<char>>>();
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/find_transparent.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/find_transparent.pass.cpp
index b1d1301fce63a..a0f9a15924b4d 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/find_transparent.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/find_transparent.pass.cpp
@@ -36,7 +36,7 @@ static_assert(!CanFind<NonTransparentMap>);
static_assert(!CanFind<const NonTransparentMap>);
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, TransparentComparator, KeyContainer, ValueContainer>;
@@ -69,9 +69,14 @@ void test() {
test_find(cm, "zzz", 5);
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<std::string>, std::vector<int>>();
- test<std::deque<std::string>, std::vector<int>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<std::string>, std::vector<int>>();
+ }
test<MinSequenceContainer<std::string>, MinSequenceContainer<int>>();
test<std::vector<std::string, min_allocator<std::string>>, std::vector<int, min_allocator<int>>>();
@@ -92,5 +97,14 @@ int main(int, char**) {
assert(it == m.begin() + 1);
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/lower_bound.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/lower_bound.pass.cpp
index b5491f3b22674..36f7c56e70f36 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/lower_bound.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/lower_bound.pass.cpp
@@ -24,7 +24,7 @@
#include "min_allocator.h"
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
{
@@ -61,11 +61,25 @@ void test() {
}
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<char>>();
- test<std::deque<int>, std::vector<char>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<char>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<char>>();
test<std::vector<int, min_allocator<int>>, std::vector<char, min_allocator<char>>>();
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/lower_bound_transparent.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/lower_bound_transparent.pass.cpp
index 9e25616311963..e3dfa47eb2b0b 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/lower_bound_transparent.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/lower_bound_transparent.pass.cpp
@@ -36,7 +36,7 @@ static_assert(!CanLowerBound<NonTransparentMap>);
static_assert(!CanLowerBound<const NonTransparentMap>);
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, TransparentComparator, KeyContainer, ValueContainer>;
@@ -76,9 +76,14 @@ void test() {
test_lower_bound(cm, "zzz", 5);
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<std::string>, std::vector<int>>();
- test<std::deque<std::string>, std::vector<int>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<std::string>, std::vector<int>>();
+ }
test<MinSequenceContainer<std::string>, MinSequenceContainer<int>>();
test<std::vector<std::string, min_allocator<std::string>>, std::vector<int, min_allocator<int>>>();
@@ -99,5 +104,14 @@ int main(int, char**) {
assert(it == m.begin() + 2);
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/upper_bound.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/upper_bound.pass.cpp
index 775e53286d629..45a8b339026e9 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/upper_bound.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/upper_bound.pass.cpp
@@ -24,7 +24,7 @@
#include "min_allocator.h"
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
{
@@ -62,11 +62,25 @@ void test() {
}
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<char>>();
- test<std::deque<int>, std::vector<char>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::vector<char>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<char>>();
test<std::vector<int, min_allocator<int>>, std::vector<char, min_allocator<char>>>();
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/upper_bound_transparent.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/upper_bound_transparent.pass.cpp
index c87113f670184..54223777c4dfc 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/upper_bound_transparent.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.operations/upper_bound_transparent.pass.cpp
@@ -36,7 +36,7 @@ static_assert(!CanUpperBound<NonTransparentMap>);
static_assert(!CanUpperBound<const NonTransparentMap>);
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
using M = std::flat_map<Key, Value, TransparentComparator, KeyContainer, ValueContainer>;
@@ -76,9 +76,14 @@ void test() {
test_upper_bound(cm, "zzz", 5);
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<std::string>, std::vector<int>>();
- test<std::deque<std::string>, std::vector<int>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<std::string>, std::vector<int>>();
+ }
test<MinSequenceContainer<std::string>, MinSequenceContainer<int>>();
test<std::vector<std::string, min_allocator<std::string>>, std::vector<int, min_allocator<int>>>();
{
@@ -98,5 +103,14 @@ int main(int, char**) {
assert(it == m.begin() + 2);
}
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/helpers.h b/libcxx/test/std/containers/container.adaptors/flat.map/helpers.h
index b6b8fa061c840..932f330db829e 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/helpers.h
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/helpers.h
@@ -14,13 +14,14 @@
#include <string>
#include <vector>
#include <flat_map>
+#include <ranges>
#include "../flat_helpers.h"
#include "test_allocator.h"
#include "test_macros.h"
template <class... Args>
-void check_invariant(const std::flat_map<Args...>& m) {
+constexpr void check_invariant(const std::flat_map<Args...>& m) {
assert(m.keys().size() == m.values().size());
const auto& keys = m.keys();
assert(std::is_sorted(keys.begin(), keys.end(), m.key_comp()));
@@ -31,6 +32,14 @@ void check_invariant(const std::flat_map<Args...>& m) {
assert(std::adjacent_find(keys.begin(), keys.end(), key_equal) == keys.end());
}
+constexpr void check_possible_values(const auto& actual, const auto& expected) {
+ assert(std::ranges::size(actual) == std::ranges::size(expected));
+
+ for (const auto& [actual_value, possible_values] : std::views::zip(actual, expected)) {
+ assert(std::ranges::find(possible_values, actual_value) != std::ranges::end(possible_values));
+ }
+}
+
template <class F>
void test_emplace_exception_guarantee([[maybe_unused]] F&& emplace_function) {
#ifndef TEST_HAS_NO_EXCEPTIONS
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/incomplete_type.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/incomplete_type.pass.cpp
index 76461951f0d3d..d89e549b5fc23 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/incomplete_type.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/incomplete_type.pass.cpp
@@ -16,6 +16,8 @@
#include <flat_map>
#include <vector>
+#include "test_macros.h"
+
struct A {
using Map = std::flat_map<A, A>;
int data;
@@ -25,9 +27,19 @@ struct A {
};
// Implement the operator< required in order to instantiate flat_map<A, X>
-bool operator<(A const& L, A const& R) { return L.data < R.data; }
+constexpr bool operator<(A const& L, A const& R) { return L.data < R.data; }
-int main(int, char**) {
+constexpr bool test() {
A a;
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/op_compare.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/op_compare.pass.cpp
index fffe711580704..07f2486cc41ee 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/op_compare.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/op_compare.pass.cpp
@@ -31,7 +31,7 @@
#include "test_container_comparisons.h"
template <class KeyContainer, class ValueContainer>
-void test() {
+constexpr void test() {
using Key = typename KeyContainer::value_type;
using Value = typename ValueContainer::value_type;
@@ -70,9 +70,14 @@ void test() {
}
}
-int main(int, char**) {
+constexpr bool test() {
test<std::vector<int>, std::vector<int>>();
- test<std::deque<int>, std::deque<int>>();
+#ifndef __cpp_lib_constexpr_deque
+ if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
+ {
+ test<std::deque<int>, std::deque<int>>();
+ }
test<MinSequenceContainer<int>, MinSequenceContainer<int>>();
test<std::vector<int, min_allocator<int>>, std::vector<int, min_allocator<int>>>();
test<std::vector<int, min_allocator<int>>, std::vector<int, min_allocator<int>>>();
@@ -98,7 +103,7 @@ int main(int, char**) {
{
// Comparisons use value_type's native operators, not the comparator
struct StrongComp {
- bool operator()(double a, double b) const { return std::strong_order(a, b) < 0; }
+ constexpr bool operator()(double a, double b) const { return std::strong_order(a, b) < 0; }
};
using C = std::flat_map<double, double, StrongComp>;
C s1 = {{1, 1}};
@@ -114,5 +119,15 @@ int main(int, char**) {
assert(s1 != s2);
assert((s1 <=> s2) == std::partial_ordering::unordered);
}
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+#if TEST_STD_VER >= 26
+ static_assert(test());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/containers/container.adaptors/flat_helpers.h b/libcxx/test/std/containers/container.adaptors/flat_helpers.h
index 19d6370522037..57c4595ccfeb0 100644
--- a/libcxx/test/std/containers/container.adaptors/flat_helpers.h
+++ b/libcxx/test/std/containers/container.adaptors/flat_helpers.h
@@ -18,11 +18,12 @@ struct CopyOnlyVector : std::vector<T> {
using std::vector<T>::vector;
CopyOnlyVector(const CopyOnlyVector&) = default;
- CopyOnlyVector(CopyOnlyVector&& other) : CopyOnlyVector(other) {}
- CopyOnlyVector(CopyOnlyVector&& other, std::vector<T>::allocator_type alloc) : CopyOnlyVector(other, alloc) {}
+ constexpr CopyOnlyVector(CopyOnlyVector&& other) : CopyOnlyVector(other) {}
+ constexpr CopyOnlyVector(CopyOnlyVector&& other, std::vector<T>::allocator_type alloc)
+ : CopyOnlyVector(other, alloc) {}
- CopyOnlyVector& operator=(const CopyOnlyVector&) = default;
- CopyOnlyVector& operator=(CopyOnlyVector& other) { return this->operator=(other); }
+ constexpr CopyOnlyVector& operator=(const CopyOnlyVector&) = default;
+ constexpr CopyOnlyVector& operator=(CopyOnlyVector& other) { return this->operator=(other); }
};
template <class T>
@@ -36,7 +37,7 @@ template <class T, bool ConvertibleToT = false>
struct Transparent {
T t;
- explicit operator T() const
+ TEST_CONSTEXPR explicit operator T() const
requires ConvertibleToT
{
return t;
@@ -57,10 +58,10 @@ struct TransparentComparator {
bool* transparent_used = nullptr;
TransparentComparator() = default;
- TransparentComparator(bool& used) : transparent_used(&used) {}
+ TEST_CONSTEXPR TransparentComparator(bool& used) : transparent_used(&used) {}
template <class T, bool Convertible>
- bool operator()(const T& t, const Transparent<T, Convertible>& transparent) const {
+ TEST_CONSTEXPR bool operator()(const T& t, const Transparent<T, Convertible>& transparent) const {
if (transparent_used != nullptr) {
*transparent_used = true;
}
@@ -68,7 +69,7 @@ struct TransparentComparator {
}
template <class T, bool Convertible>
- bool operator()(const Transparent<T, Convertible>& transparent, const T& t) const {
+ TEST_CONSTEXPR bool operator()(const Transparent<T, Convertible>& transparent, const T& t) const {
if (transparent_used != nullptr) {
*transparent_used = true;
}
@@ -76,7 +77,7 @@ struct TransparentComparator {
}
template <class T>
- bool operator()(const T& t1, const T& t2) const {
+ TEST_CONSTEXPR bool operator()(const T& t1, const T& t2) const {
return t1 < t2;
}
};
@@ -101,13 +102,13 @@ class Moveable {
double double_;
public:
- Moveable() : int_(0), double_(0) {}
- Moveable(int i, double d) : int_(i), double_(d) {}
- Moveable(Moveable&& x) : int_(x.int_), double_(x.double_) {
+ TEST_CONSTEXPR Moveable() : int_(0), double_(0) {}
+ TEST_CONSTEXPR Moveable(int i, double d) : int_(i), double_(d) {}
+ TEST_CONSTEXPR Moveable(Moveable&& x) : int_(x.int_), double_(x.double_) {
x.int_ = -1;
x.double_ = -1;
}
- Moveable& operator=(Moveable&& x) {
+ TEST_CONSTEXPR Moveable& operator=(Moveable&& x) {
int_ = x.int_;
x.int_ = -1;
double_ = x.double_;
@@ -117,11 +118,13 @@ class Moveable {
Moveable(const Moveable&) = delete;
Moveable& operator=(const Moveable&) = delete;
- bool operator==(const Moveable& x) const { return int_ == x.int_ && double_ == x.double_; }
- bool operator<(const Moveable& x) const { return int_ < x.int_ || (int_ == x.int_ && double_ < x.double_); }
+ TEST_CONSTEXPR bool operator==(const Moveable& x) const { return int_ == x.int_ && double_ == x.double_; }
+ TEST_CONSTEXPR bool operator<(const Moveable& x) const {
+ return int_ < x.int_ || (int_ == x.int_ && double_ < x.double_);
+ }
- int get() const { return int_; }
- bool moved() const { return int_ == -1; }
+ TEST_CONSTEXPR int get() const { return int_; }
+ TEST_CONSTEXPR bool moved() const { return int_ == -1; }
};
#ifndef TEST_HAS_NO_EXCEPTIONS
diff --git a/libcxx/test/std/containers/test_compare.h b/libcxx/test/std/containers/test_compare.h
index 18e0b27dc954d..070f15869a4a3 100644
--- a/libcxx/test/std/containers/test_compare.h
+++ b/libcxx/test/std/containers/test_compare.h
@@ -9,22 +9,24 @@
#ifndef TEST_COMPARE_H
#define TEST_COMPARE_H
+#include "test_macros.h"
+
template <class T>
struct test_equal_to {
int data_;
- explicit test_equal_to() : data_(0) {}
- explicit test_equal_to(int data) : data_(data) {}
- bool operator()(const T& a, const T& b) const { return a == b; }
- friend bool operator==(const test_equal_to& a, const test_equal_to& b) { return a.data_ == b.data_; }
+ TEST_CONSTEXPR explicit test_equal_to() : data_(0) {}
+ TEST_CONSTEXPR explicit test_equal_to(int data) : data_(data) {}
+ TEST_CONSTEXPR bool operator()(const T& a, const T& b) const { return a == b; }
+ TEST_CONSTEXPR friend bool operator==(const test_equal_to& a, const test_equal_to& b) { return a.data_ == b.data_; }
};
template <class T>
struct test_less {
int data_;
- explicit test_less() : data_(0) {}
- explicit test_less(int data) : data_(data) {}
- bool operator()(const T& a, const T& b) const { return a < b; }
- friend bool operator==(const test_less& a, const test_less& b) { return a.data_ == b.data_; }
+ TEST_CONSTEXPR explicit test_less() : data_(0) {}
+ TEST_CONSTEXPR explicit test_less(int data) : data_(data) {}
+ TEST_CONSTEXPR bool operator()(const T& a, const T& b) const { return a < b; }
+ TEST_CONSTEXPR friend bool operator==(const test_less& a, const test_less& b) { return a.data_ == b.data_; }
};
#endif // TEST_COMPARE_H
diff --git a/libcxx/test/support/MinSequenceContainer.h b/libcxx/test/support/MinSequenceContainer.h
index ccc17b79288bc..9af5847f08871 100644
--- a/libcxx/test/support/MinSequenceContainer.h
+++ b/libcxx/test/support/MinSequenceContainer.h
@@ -13,6 +13,7 @@
#include <vector>
#include "test_iterators.h"
+#include "test_macros.h"
template <class T,
class Iterator = three_way_random_access_iterator<T*>,
@@ -26,54 +27,60 @@ struct MinSequenceContainer {
explicit MinSequenceContainer() = default;
template <class It>
- explicit MinSequenceContainer(It first, It last) : data_(first, last) {}
- MinSequenceContainer(std::initializer_list<T> il) : data_(il) {}
+ explicit TEST_CONSTEXPR_CXX20 MinSequenceContainer(It first, It last) : data_(first, last) {}
+ TEST_CONSTEXPR_CXX20 MinSequenceContainer(std::initializer_list<T> il) : data_(il) {}
template <class It>
- void assign(It first, It last) {
+ TEST_CONSTEXPR_CXX20 void assign(It first, It last) {
data_.assign(first, last);
}
- void assign(std::initializer_list<T> il) { data_.assign(il); }
- void assign(size_type n, value_type t) { data_.assign(n, t); }
- iterator begin() { return iterator(data_.data()); }
- const_iterator begin() const { return const_iterator(data_.data()); }
- const_iterator cbegin() const { return const_iterator(data_.data()); }
- iterator end() { return begin() + size(); }
- const_iterator end() const { return begin() + size(); }
- size_type size() const { return static_cast<size_type>(data_.size()); }
- bool empty() const { return data_.empty(); }
-
- void clear() { data_.clear(); }
+ TEST_CONSTEXPR_CXX20 void assign(std::initializer_list<T> il) { data_.assign(il); }
+ TEST_CONSTEXPR_CXX20 void assign(size_type n, value_type t) { data_.assign(n, t); }
+ TEST_CONSTEXPR_CXX20 iterator begin() { return iterator(data_.data()); }
+ TEST_CONSTEXPR_CXX20 const_iterator begin() const { return const_iterator(data_.data()); }
+ TEST_CONSTEXPR_CXX20 const_iterator cbegin() const { return const_iterator(data_.data()); }
+ TEST_CONSTEXPR_CXX20 iterator end() { return begin() + size(); }
+ TEST_CONSTEXPR_CXX20 const_iterator end() const { return begin() + size(); }
+ TEST_CONSTEXPR_CXX20 size_type size() const { return static_cast<size_type>(data_.size()); }
+ TEST_CONSTEXPR_CXX20 bool empty() const { return data_.empty(); }
+
+ TEST_CONSTEXPR_CXX20 void clear() { data_.clear(); }
template <class It>
- iterator insert(const_iterator p, It first, It last) {
+ TEST_CONSTEXPR_CXX20 iterator insert(const_iterator p, It first, It last) {
return from_vector_iterator(data_.insert(to_vector_iterator(p), first, last));
}
- iterator insert(const_iterator p, T value) {
+ TEST_CONSTEXPR_CXX20 iterator insert(const_iterator p, T value) {
return from_vector_iterator(data_.insert(to_vector_iterator(p), std::move(value)));
}
template <class Range>
- iterator insert_range(const_iterator p, Range&& rg) {
+ TEST_CONSTEXPR_CXX20 iterator insert_range(const_iterator p, Range&& rg) {
return from_vector_iterator(data_.insert_range(to_vector_iterator(p), std::forward<Range>(rg)));
}
- iterator erase(const_iterator first, const_iterator last) {
+ TEST_CONSTEXPR_CXX20 iterator erase(const_iterator first, const_iterator last) {
return from_vector_iterator(data_.erase(to_vector_iterator(first), to_vector_iterator(last)));
}
- iterator erase(const_iterator iter) { return from_vector_iterator(data_.erase(to_vector_iterator(iter))); }
+ TEST_CONSTEXPR_CXX20 iterator erase(const_iterator iter) {
+ return from_vector_iterator(data_.erase(to_vector_iterator(iter)));
+ }
template <class... Args>
- iterator emplace(const_iterator pos, Args&&... args) {
+ TEST_CONSTEXPR_CXX20 iterator emplace(const_iterator pos, Args&&... args) {
return from_vector_iterator(data_.emplace(to_vector_iterator(pos), std::forward<Args>(args)...));
}
private:
- std::vector<T>::const_iterator to_vector_iterator(const_iterator cit) const { return cit - cbegin() + data_.begin(); }
+ TEST_CONSTEXPR_CXX20 std::vector<T>::const_iterator to_vector_iterator(const_iterator cit) const {
+ return cit - cbegin() + data_.begin();
+ }
- iterator from_vector_iterator(std::vector<T>::iterator it) { return it - data_.begin() + begin(); }
+ TEST_CONSTEXPR_CXX20 iterator from_vector_iterator(std::vector<T>::iterator it) {
+ return it - data_.begin() + begin();
+ }
std::vector<T> data_;
};
>From 60d1276b0e0dcd287af3ea1e48d0070a5a9c752a Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Sat, 21 Jun 2025 15:41:25 +0100
Subject: [PATCH 07/40] [VPlan] Pass operand index to canNarrowLoad. (NFC)
Explicitly pass the operand we are checking to canNarrowLoad. This
simplifies the check if the operands match across recipes and enables
future optimizations.
---
.../Transforms/Vectorize/VPlanTransforms.cpp | 30 ++++++++-----------
1 file changed, 13 insertions(+), 17 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp b/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp
index c0bdbb1f4f883..d66733cac4d66 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp
@@ -3107,28 +3107,22 @@ void VPlanTransforms::materializeBroadcasts(VPlan &Plan) {
/// that feeds a store interleave group at index \p Idx, \p WideMember0 is the
/// recipe feeding the same interleave group at index 0. A VPWidenLoadRecipe can
/// be narrowed to an index-independent load if it feeds all wide ops at all
-/// indices (checked by via the operands of the wide recipe at lane0, \p
-/// WideMember0). A VPInterleaveRecipe can be narrowed to a wide load, if \p V
-/// is defined at \p Idx of a load interleave group.
+/// indices (\p OpV must be the operand at index \p OpIdx for both the recipe at
+/// lane 0, \p WideMember0, and \p WideMember). A VPInterleaveRecipe can be
+/// narrowed to a wide load, if \p V is defined at \p Idx of a load interleave
+/// group.
static bool canNarrowLoad(VPWidenRecipe *WideMember0, VPWidenRecipe *WideMember,
- VPValue *V, unsigned Idx) {
- auto *DefR = V->getDefiningRecipe();
+ unsigned OpIdx, VPValue *OpV, unsigned Idx) {
+ auto *DefR = OpV->getDefiningRecipe();
if (!DefR)
return false;
if (auto *W = dyn_cast<VPWidenLoadRecipe>(DefR))
- return !W->getMask() &&
- all_of(zip(WideMember0->operands(), WideMember->operands()),
- [V](const auto P) {
- // V must be as at the same places in both WideMember0 and
- // WideMember.
- const auto &[WideMember0Op, WideMemberOp] = P;
- return (WideMember0Op == V) == (WideMemberOp == V);
- });
+ return !W->getMask() && WideMember0->getOperand(OpIdx) == OpV;
if (auto *IR = dyn_cast<VPInterleaveRecipe>(DefR))
return IR->getInterleaveGroup()->getFactor() ==
IR->getInterleaveGroup()->getNumMembers() &&
- IR->getVPValue(Idx) == V;
+ IR->getVPValue(Idx) == OpV;
return false;
}
@@ -3243,9 +3237,11 @@ void VPlanTransforms::narrowInterleaveGroups(VPlan &Plan, ElementCount VF,
if (!R || R->getOpcode() != WideMember0->getOpcode() ||
R->getNumOperands() > 2)
return;
- if (any_of(R->operands(), [WideMember0, Idx = I, R](VPValue *V) {
- return !canNarrowLoad(WideMember0, R, V, Idx);
- }))
+ if (any_of(enumerate(R->operands()),
+ [WideMember0, Idx = I, R](const auto &P) {
+ const auto &[OpIdx, OpV] = P;
+ return !canNarrowLoad(WideMember0, R, OpIdx, OpV, Idx);
+ }))
return;
}
StoreGroups.push_back(InterleaveR);
>From a961ba88e1d48b8df1531d778e904efb2839662c Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Sat, 21 Jun 2025 23:54:27 +0900
Subject: [PATCH 08/40] AMDGPU: Use reportFatalUsageError for LDS mixed
absolute addresses (#145135)
---
llvm/lib/Target/AMDGPU/AMDGPUMemoryUtils.cpp | 4 ++--
.../CodeGen/AMDGPU/lds-reject-mixed-absolute-addresses.ll | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/llvm/lib/Target/AMDGPU/AMDGPUMemoryUtils.cpp b/llvm/lib/Target/AMDGPU/AMDGPUMemoryUtils.cpp
index 241dbd63eb5c0..aa72c3e61f680 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPUMemoryUtils.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPUMemoryUtils.cpp
@@ -278,8 +278,8 @@ LDSUsesInfoTy getTransitiveUsesOfLDS(const CallGraph &CG, Module &M) {
}
if (HasAbsoluteGVs.has_value()) {
if (*HasAbsoluteGVs != IsAbsolute) {
- report_fatal_error(
- "Module cannot mix absolute and non-absolute LDS GVs");
+ reportFatalUsageError(
+ "module cannot mix absolute and non-absolute LDS GVs");
}
} else
HasAbsoluteGVs = IsAbsolute;
diff --git a/llvm/test/CodeGen/AMDGPU/lds-reject-mixed-absolute-addresses.ll b/llvm/test/CodeGen/AMDGPU/lds-reject-mixed-absolute-addresses.ll
index e1ee9b2df9fa8..eacf6759b039a 100644
--- a/llvm/test/CodeGen/AMDGPU/lds-reject-mixed-absolute-addresses.ll
+++ b/llvm/test/CodeGen/AMDGPU/lds-reject-mixed-absolute-addresses.ll
@@ -1,10 +1,10 @@
-; RUN: not --crash opt -S -mtriple=amdgcn-- -amdgpu-lower-module-lds < %s 2>&1 | FileCheck %s
-; RUN: not --crash opt -S -mtriple=amdgcn-- -passes=amdgpu-lower-module-lds < %s 2>&1 | FileCheck %s
+; RUN: not opt -S -mtriple=amdgcn-- -amdgpu-lower-module-lds < %s 2>&1 | FileCheck %s
+; RUN: not opt -S -mtriple=amdgcn-- -passes=amdgpu-lower-module-lds < %s 2>&1 | FileCheck %s
@var1 = addrspace(3) global i32 poison, !absolute_symbol !0
@var2 = addrspace(3) global i32 poison
-; CHECK: Module cannot mix absolute and non-absolute LDS GVs
+; CHECK: LLVM ERROR: module cannot mix absolute and non-absolute LDS GVs
define amdgpu_kernel void @kern() {
%val0 = load i32, ptr addrspace(3) @var1
%val1 = add i32 %val0, 4
>From 99af99c6657db0ad76bc348fe075c873a10da031 Mon Sep 17 00:00:00 2001
From: Kunqiu Chen <camsyn at foxmail.com>
Date: Sat, 21 Jun 2025 23:02:41 +0800
Subject: [PATCH 09/40] [TSan] Fix p == end == ShadowMem::end in ShadowSet
(#144994)
In `ShadowSet`, when `p == end == ShadowMem::end`, it triggered an
assertion fail previously.
Now we do not allow `p == end` anymore in `ShadowSet`.
---
compiler-rt/lib/tsan/rtl/tsan_rtl_access.cpp | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/compiler-rt/lib/tsan/rtl/tsan_rtl_access.cpp b/compiler-rt/lib/tsan/rtl/tsan_rtl_access.cpp
index dbdc6359d92aa..bd8deefefa1bc 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_rtl_access.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_rtl_access.cpp
@@ -523,9 +523,9 @@ ALWAYS_INLINE USED void UnalignedMemoryAccess(ThreadState* thr, uptr pc,
}
void ShadowSet(RawShadow* p, RawShadow* end, RawShadow v) {
- DCHECK_LE(p, end);
+ DCHECK_LT(p, end);
DCHECK(IsShadowMem(p));
- DCHECK(p == end || IsShadowMem(end - 1));
+ DCHECK(IsShadowMem(end - 1));
UNUSED const uptr kAlign = kShadowCnt * kShadowSize;
DCHECK_EQ(reinterpret_cast<uptr>(p) % kAlign, 0);
DCHECK_EQ(reinterpret_cast<uptr>(end) % kAlign, 0);
@@ -569,6 +569,7 @@ static void MemoryRangeSet(uptr addr, uptr size, RawShadow val) {
RawShadow* mid1 =
Min(end, reinterpret_cast<RawShadow*>(RoundUp(
reinterpret_cast<uptr>(begin) + kPageSize / 2, kPageSize)));
+ // begin must < mid1
ShadowSet(begin, mid1, val);
// Reset middle part.
RawShadow* mid2 = RoundDown(end, kPageSize);
@@ -577,7 +578,10 @@ static void MemoryRangeSet(uptr addr, uptr size, RawShadow val) {
Die();
}
// Set the ending.
- ShadowSet(mid2, end, val);
+ if (mid2 < end)
+ ShadowSet(mid2, end, val);
+ else
+ DCHECK_EQ(mid2, end);
}
void MemoryResetRange(ThreadState* thr, uptr pc, uptr addr, uptr size) {
>From e6ebf8f99ba27d60026c7473b0cd5e24c855b018 Mon Sep 17 00:00:00 2001
From: Kazu Hirata <kazu at google.com>
Date: Sat, 21 Jun 2025 08:20:49 -0700
Subject: [PATCH 10/40] [mlir] Migrate away from ArrayRef(std::nullopt) (NFC)
(#145140)
ArrayRef has a constructor that accepts std::nullopt. This
constructor dates back to the days when we still had llvm::Optional.
Since the use of std::nullopt outside the context of std::optional is
kind of abuse and not intuitive to new comers, I would like to move
away from the constructor and eventually remove it.
This patch takes care of the mlir side of the migration, starting with
straightforward places like "return std::nullopt;" and ternally
expressions involving std::nullopt.
---
mlir/include/mlir/CAPI/Wrap.h | 2 +-
mlir/include/mlir/Dialect/PDL/IR/PDLOps.td | 13 +++++++------
mlir/include/mlir/IR/BuiltinAttributes.td | 5 ++---
mlir/include/mlir/Support/StorageUniquer.h | 2 +-
mlir/lib/Interfaces/FunctionInterfaces.cpp | 4 ++--
mlir/lib/Tools/PDLL/Parser/Parser.cpp | 9 ++++++---
mlir/lib/Tools/mlir-pdll-lsp-server/PDLLServer.cpp | 6 ++++--
mlir/test/lib/Dialect/Test/TestAttributes.cpp | 2 +-
8 files changed, 24 insertions(+), 19 deletions(-)
diff --git a/mlir/include/mlir/CAPI/Wrap.h b/mlir/include/mlir/CAPI/Wrap.h
index 5b68f417a3df4..fd5b6e18d4952 100644
--- a/mlir/include/mlir/CAPI/Wrap.h
+++ b/mlir/include/mlir/CAPI/Wrap.h
@@ -44,7 +44,7 @@ static llvm::ArrayRef<CppTy> unwrapList(size_t size, CTy *first,
"incompatible C and C++ types");
if (size == 0)
- return std::nullopt;
+ return {};
assert(storage.empty() && "expected to populate storage");
storage.reserve(size);
diff --git a/mlir/include/mlir/Dialect/PDL/IR/PDLOps.td b/mlir/include/mlir/Dialect/PDL/IR/PDLOps.td
index 1e108c3d8ac77..6ee638c19d1ad 100644
--- a/mlir/include/mlir/Dialect/PDL/IR/PDLOps.td
+++ b/mlir/include/mlir/Dialect/PDL/IR/PDLOps.td
@@ -360,12 +360,13 @@ def PDL_OperationOp : PDL_Op<"operation", [AttrSizedOperandSegments]> {
(`->` `(` $typeValues^ `:` type($typeValues) `)`)? attr-dict
}];
- let builders = [
- OpBuilder<(ins CArg<"std::optional<StringRef>", "std::nullopt">:$name,
- CArg<"ValueRange", "std::nullopt">:$operandValues,
- CArg<"ArrayRef<StringRef>", "std::nullopt">:$attrNames,
- CArg<"ValueRange", "std::nullopt">:$attrValues,
- CArg<"ValueRange", "std::nullopt">:$resultTypes), [{
+ let builders =
+ [OpBuilder<(ins CArg<"std::optional<StringRef>", "std::nullopt">:$name,
+ CArg<"ValueRange", "{}">:$operandValues,
+ CArg<"ArrayRef<StringRef>", "{}">:$attrNames,
+ CArg<"ValueRange", "{}">:$attrValues,
+ CArg<"ValueRange", "{}">:$resultTypes),
+ [{
auto nameAttr = name ? $_builder.getStringAttr(*name) : StringAttr();
build($_builder, $_state, $_builder.getType<OperationType>(), nameAttr,
operandValues, attrValues, $_builder.getStrArrayAttr(attrNames),
diff --git a/mlir/include/mlir/IR/BuiltinAttributes.td b/mlir/include/mlir/IR/BuiltinAttributes.td
index 8855908276500..b67b8f939b9ee 100644
--- a/mlir/include/mlir/IR/BuiltinAttributes.td
+++ b/mlir/include/mlir/IR/BuiltinAttributes.td
@@ -535,9 +535,8 @@ def Builtin_DictionaryAttr : Builtin_Attr<"Dictionary", "dictionary"> {
```
}];
let parameters = (ins ArrayRefParameter<"NamedAttribute", "">:$value);
- let builders = [
- AttrBuilder<(ins CArg<"ArrayRef<NamedAttribute>", "std::nullopt">:$value)>
- ];
+ let builders = [AttrBuilder<(
+ ins CArg<"ArrayRef<NamedAttribute>", "{}">:$value)>];
let extraClassDeclaration = [{
using ValueType = ArrayRef<NamedAttribute>;
diff --git a/mlir/include/mlir/Support/StorageUniquer.h b/mlir/include/mlir/Support/StorageUniquer.h
index 6756c4390276f..b5f4df680ac69 100644
--- a/mlir/include/mlir/Support/StorageUniquer.h
+++ b/mlir/include/mlir/Support/StorageUniquer.h
@@ -97,7 +97,7 @@ class StorageUniquer {
template <typename T>
ArrayRef<T> copyInto(ArrayRef<T> elements) {
if (elements.empty())
- return std::nullopt;
+ return {};
auto result = allocator.Allocate<T>(elements.size());
llvm::uninitialized_copy(elements, result);
return ArrayRef<T>(result, elements.size());
diff --git a/mlir/lib/Interfaces/FunctionInterfaces.cpp b/mlir/lib/Interfaces/FunctionInterfaces.cpp
index 57a8668117c68..e0f1135e992ac 100644
--- a/mlir/lib/Interfaces/FunctionInterfaces.cpp
+++ b/mlir/lib/Interfaces/FunctionInterfaces.cpp
@@ -44,14 +44,14 @@ function_interface_impl::getResultAttrDict(FunctionOpInterface op,
ArrayRef<NamedAttribute>
function_interface_impl::getArgAttrs(FunctionOpInterface op, unsigned index) {
auto argDict = getArgAttrDict(op, index);
- return argDict ? argDict.getValue() : std::nullopt;
+ return argDict ? argDict.getValue() : ArrayRef<NamedAttribute>();
}
ArrayRef<NamedAttribute>
function_interface_impl::getResultAttrs(FunctionOpInterface op,
unsigned index) {
auto resultDict = getResultAttrDict(op, index);
- return resultDict ? resultDict.getValue() : std::nullopt;
+ return resultDict ? resultDict.getValue() : ArrayRef<NamedAttribute>();
}
/// Get either the argument or result attributes array.
diff --git a/mlir/lib/Tools/PDLL/Parser/Parser.cpp b/mlir/lib/Tools/PDLL/Parser/Parser.cpp
index c0e2252bdebc5..ad7d71b0bdd2f 100644
--- a/mlir/lib/Tools/PDLL/Parser/Parser.cpp
+++ b/mlir/lib/Tools/PDLL/Parser/Parser.cpp
@@ -2883,8 +2883,9 @@ Parser::validateOperationOperands(SMRange loc, std::optional<StringRef> name,
SmallVectorImpl<ast::Expr *> &operands) {
return validateOperationOperandsOrResults(
"operand", loc, odsOp ? odsOp->getLoc() : std::optional<SMRange>(), name,
- operands, odsOp ? odsOp->getOperands() : std::nullopt, valueTy,
- valueRangeTy);
+ operands,
+ odsOp ? odsOp->getOperands() : ArrayRef<pdll::ods::OperandOrResult>(),
+ valueTy, valueRangeTy);
}
LogicalResult
@@ -2893,7 +2894,9 @@ Parser::validateOperationResults(SMRange loc, std::optional<StringRef> name,
SmallVectorImpl<ast::Expr *> &results) {
return validateOperationOperandsOrResults(
"result", loc, odsOp ? odsOp->getLoc() : std::optional<SMRange>(), name,
- results, odsOp ? odsOp->getResults() : std::nullopt, typeTy, typeRangeTy);
+ results,
+ odsOp ? odsOp->getResults() : ArrayRef<pdll::ods::OperandOrResult>(),
+ typeTy, typeRangeTy);
}
void Parser::checkOperationResultTypeInferrence(SMRange loc, StringRef opName,
diff --git a/mlir/lib/Tools/mlir-pdll-lsp-server/PDLLServer.cpp b/mlir/lib/Tools/mlir-pdll-lsp-server/PDLLServer.cpp
index c39540da396b6..84f529ae16401 100644
--- a/mlir/lib/Tools/mlir-pdll-lsp-server/PDLLServer.cpp
+++ b/mlir/lib/Tools/mlir-pdll-lsp-server/PDLLServer.cpp
@@ -1044,7 +1044,8 @@ class LSPSignatureHelpContext : public CodeCompleteContext {
const ods::Operation *odsOp =
opName ? odsContext.lookupOperation(*opName) : nullptr;
codeCompleteOperationOperandOrResultSignature(
- opName, odsOp, odsOp ? odsOp->getOperands() : std::nullopt,
+ opName, odsOp,
+ odsOp ? odsOp->getOperands() : ArrayRef<ods::OperandOrResult>(),
currentNumOperands, "operand", "Value");
}
@@ -1053,7 +1054,8 @@ class LSPSignatureHelpContext : public CodeCompleteContext {
const ods::Operation *odsOp =
opName ? odsContext.lookupOperation(*opName) : nullptr;
codeCompleteOperationOperandOrResultSignature(
- opName, odsOp, odsOp ? odsOp->getResults() : std::nullopt,
+ opName, odsOp,
+ odsOp ? odsOp->getResults() : ArrayRef<ods::OperandOrResult>(),
currentNumResults, "result", "Type");
}
diff --git a/mlir/test/lib/Dialect/Test/TestAttributes.cpp b/mlir/test/lib/Dialect/Test/TestAttributes.cpp
index 80661e68754ce..4f6655d0b2978 100644
--- a/mlir/test/lib/Dialect/Test/TestAttributes.cpp
+++ b/mlir/test/lib/Dialect/Test/TestAttributes.cpp
@@ -194,7 +194,7 @@ void TestSubElementsAccessAttr::print(::mlir::AsmPrinter &printer) const {
ArrayRef<uint64_t> TestExtern1DI64ElementsAttr::getElements() const {
if (auto *blob = getHandle().getBlob())
return blob->getDataAs<uint64_t>();
- return std::nullopt;
+ return {};
}
//===----------------------------------------------------------------------===//
>From ae372bfca890cc7a67553b3cb19134359b66c0e1 Mon Sep 17 00:00:00 2001
From: Kazu Hirata <kazu at google.com>
Date: Sat, 21 Jun 2025 08:20:57 -0700
Subject: [PATCH 11/40] [CodeGen] Use range-based for loops (NFC) (#145142)
---
clang/include/clang/CodeGen/CGFunctionInfo.h | 6 +-
clang/lib/CodeGen/CGOpenMPRuntime.cpp | 26 +++----
clang/lib/CodeGen/CGRecordLayoutBuilder.cpp | 74 +++++++++-----------
clang/lib/CodeGen/CGStmt.cpp | 11 ++-
clang/lib/CodeGen/MicrosoftCXXABI.cpp | 4 +-
clang/lib/CodeGen/ModuleBuilder.cpp | 4 +-
clang/lib/CodeGen/TargetBuiltins/ARM.cpp | 16 ++---
clang/lib/CodeGen/Targets/X86.cpp | 5 +-
8 files changed, 63 insertions(+), 83 deletions(-)
diff --git a/clang/include/clang/CodeGen/CGFunctionInfo.h b/clang/include/clang/CodeGen/CGFunctionInfo.h
index 040ee025afaa8..50be51769f1a8 100644
--- a/clang/include/clang/CodeGen/CGFunctionInfo.h
+++ b/clang/include/clang/CodeGen/CGFunctionInfo.h
@@ -828,10 +828,8 @@ class CGFunctionInfo final
ID.AddInteger(paramInfo.getOpaqueValue());
}
resultType.Profile(ID);
- for (ArrayRef<CanQualType>::iterator
- i = argTypes.begin(), e = argTypes.end(); i != e; ++i) {
- i->Profile(ID);
- }
+ for (const CanQualType &argType : argTypes)
+ argType.Profile(ID);
}
};
diff --git a/clang/lib/CodeGen/CGOpenMPRuntime.cpp b/clang/lib/CodeGen/CGOpenMPRuntime.cpp
index 4173355491fd4..8ccc37ef98a74 100644
--- a/clang/lib/CodeGen/CGOpenMPRuntime.cpp
+++ b/clang/lib/CodeGen/CGOpenMPRuntime.cpp
@@ -4168,8 +4168,7 @@ void CGOpenMPRuntime::emitDepobjElements(CodeGenFunction &CGF,
CGF, cast_or_null<OMPIteratorExpr>(
Data.IteratorExpr ? Data.IteratorExpr->IgnoreParenImpCasts()
: nullptr));
- for (unsigned I = 0, End = Data.DepExprs.size(); I < End; ++I) {
- const Expr *E = Data.DepExprs[I];
+ for (const Expr *E : Data.DepExprs) {
llvm::Value *NumDeps;
LValue Base;
LValue DepobjLVal = CGF.EmitLValue(E->IgnoreParenImpCasts());
@@ -4289,31 +4288,26 @@ std::pair<llvm::Value *, Address> CGOpenMPRuntime::emitDependClause(
/*isSigned=*/false);
}
unsigned Pos = 0;
- for (unsigned I = 0, End = Dependencies.size(); I < End; ++I) {
- if (Dependencies[I].DepKind == OMPC_DEPEND_depobj ||
- Dependencies[I].IteratorExpr)
+ for (const OMPTaskDataTy::DependData &Dep : Dependencies) {
+ if (Dep.DepKind == OMPC_DEPEND_depobj || Dep.IteratorExpr)
continue;
- emitDependData(CGF, KmpDependInfoTy, &Pos, Dependencies[I],
- DependenciesArray);
+ emitDependData(CGF, KmpDependInfoTy, &Pos, Dep, DependenciesArray);
}
// Copy regular dependencies with iterators.
LValue PosLVal = CGF.MakeAddrLValue(
CGF.CreateMemTemp(C.getSizeType(), "dep.counter.addr"), C.getSizeType());
CGF.EmitStoreOfScalar(llvm::ConstantInt::get(CGF.SizeTy, Pos), PosLVal);
- for (unsigned I = 0, End = Dependencies.size(); I < End; ++I) {
- if (Dependencies[I].DepKind == OMPC_DEPEND_depobj ||
- !Dependencies[I].IteratorExpr)
+ for (const OMPTaskDataTy::DependData &Dep : Dependencies) {
+ if (Dep.DepKind == OMPC_DEPEND_depobj || !Dep.IteratorExpr)
continue;
- emitDependData(CGF, KmpDependInfoTy, &PosLVal, Dependencies[I],
- DependenciesArray);
+ emitDependData(CGF, KmpDependInfoTy, &PosLVal, Dep, DependenciesArray);
}
// Copy final depobj arrays without iterators.
if (HasDepobjDeps) {
- for (unsigned I = 0, End = Dependencies.size(); I < End; ++I) {
- if (Dependencies[I].DepKind != OMPC_DEPEND_depobj)
+ for (const OMPTaskDataTy::DependData &Dep : Dependencies) {
+ if (Dep.DepKind != OMPC_DEPEND_depobj)
continue;
- emitDepobjElements(CGF, KmpDependInfoTy, PosLVal, Dependencies[I],
- DependenciesArray);
+ emitDepobjElements(CGF, KmpDependInfoTy, PosLVal, Dep, DependenciesArray);
}
}
DependenciesArray = CGF.Builder.CreatePointerBitCastOrAddrSpaceCast(
diff --git a/clang/lib/CodeGen/CGRecordLayoutBuilder.cpp b/clang/lib/CodeGen/CGRecordLayoutBuilder.cpp
index 232e2d8b43ca1..e1310aed818ad 100644
--- a/clang/lib/CodeGen/CGRecordLayoutBuilder.cpp
+++ b/clang/lib/CodeGen/CGRecordLayoutBuilder.cpp
@@ -972,18 +972,16 @@ void CGRecordLowering::determinePacked(bool NVBaseType) {
CharUnits NVAlignment = CharUnits::One();
CharUnits NVSize =
!NVBaseType && RD ? Layout.getNonVirtualSize() : CharUnits::Zero();
- for (std::vector<MemberInfo>::const_iterator Member = Members.begin(),
- MemberEnd = Members.end();
- Member != MemberEnd; ++Member) {
- if (!Member->Data)
+ for (const MemberInfo &Member : Members) {
+ if (!Member.Data)
continue;
// If any member falls at an offset that it not a multiple of its alignment,
// then the entire record must be packed.
- if (Member->Offset % getAlignment(Member->Data))
+ if (Member.Offset % getAlignment(Member.Data))
Packed = true;
- if (Member->Offset < NVSize)
- NVAlignment = std::max(NVAlignment, getAlignment(Member->Data));
- Alignment = std::max(Alignment, getAlignment(Member->Data));
+ if (Member.Offset < NVSize)
+ NVAlignment = std::max(NVAlignment, getAlignment(Member.Data));
+ Alignment = std::max(Alignment, getAlignment(Member.Data));
}
// If the size of the record (the capstone's offset) is not a multiple of the
// record's alignment, it must be packed.
@@ -1002,45 +1000,39 @@ void CGRecordLowering::determinePacked(bool NVBaseType) {
void CGRecordLowering::insertPadding() {
std::vector<std::pair<CharUnits, CharUnits> > Padding;
CharUnits Size = CharUnits::Zero();
- for (std::vector<MemberInfo>::const_iterator Member = Members.begin(),
- MemberEnd = Members.end();
- Member != MemberEnd; ++Member) {
- if (!Member->Data)
+ for (const MemberInfo &Member : Members) {
+ if (!Member.Data)
continue;
- CharUnits Offset = Member->Offset;
+ CharUnits Offset = Member.Offset;
assert(Offset >= Size);
// Insert padding if we need to.
if (Offset !=
- Size.alignTo(Packed ? CharUnits::One() : getAlignment(Member->Data)))
+ Size.alignTo(Packed ? CharUnits::One() : getAlignment(Member.Data)))
Padding.push_back(std::make_pair(Size, Offset - Size));
- Size = Offset + getSize(Member->Data);
+ Size = Offset + getSize(Member.Data);
}
if (Padding.empty())
return;
// Add the padding to the Members list and sort it.
- for (std::vector<std::pair<CharUnits, CharUnits> >::const_iterator
- Pad = Padding.begin(), PadEnd = Padding.end();
- Pad != PadEnd; ++Pad)
- Members.push_back(StorageInfo(Pad->first, getByteArrayType(Pad->second)));
+ for (const auto &Pad : Padding)
+ Members.push_back(StorageInfo(Pad.first, getByteArrayType(Pad.second)));
llvm::stable_sort(Members);
}
void CGRecordLowering::fillOutputFields() {
- for (std::vector<MemberInfo>::const_iterator Member = Members.begin(),
- MemberEnd = Members.end();
- Member != MemberEnd; ++Member) {
- if (Member->Data)
- FieldTypes.push_back(Member->Data);
- if (Member->Kind == MemberInfo::Field) {
- if (Member->FD)
- Fields[Member->FD->getCanonicalDecl()] = FieldTypes.size() - 1;
+ for (const MemberInfo &Member : Members) {
+ if (Member.Data)
+ FieldTypes.push_back(Member.Data);
+ if (Member.Kind == MemberInfo::Field) {
+ if (Member.FD)
+ Fields[Member.FD->getCanonicalDecl()] = FieldTypes.size() - 1;
// A field without storage must be a bitfield.
- if (!Member->Data)
- setBitFieldInfo(Member->FD, Member->Offset, FieldTypes.back());
- } else if (Member->Kind == MemberInfo::Base)
- NonVirtualBases[Member->RD] = FieldTypes.size() - 1;
- else if (Member->Kind == MemberInfo::VBase)
- VirtualBases[Member->RD] = FieldTypes.size() - 1;
+ if (!Member.Data)
+ setBitFieldInfo(Member.FD, Member.Offset, FieldTypes.back());
+ } else if (Member.Kind == MemberInfo::Base)
+ NonVirtualBases[Member.RD] = FieldTypes.size() - 1;
+ else if (Member.Kind == MemberInfo::VBase)
+ VirtualBases[Member.RD] = FieldTypes.size() - 1;
}
}
@@ -1224,20 +1216,18 @@ void CGRecordLayout::print(raw_ostream &OS) const {
// Print bit-field infos in declaration order.
std::vector<std::pair<unsigned, const CGBitFieldInfo*> > BFIs;
- for (llvm::DenseMap<const FieldDecl*, CGBitFieldInfo>::const_iterator
- it = BitFields.begin(), ie = BitFields.end();
- it != ie; ++it) {
- const RecordDecl *RD = it->first->getParent();
+ for (const auto &BitField : BitFields) {
+ const RecordDecl *RD = BitField.first->getParent();
unsigned Index = 0;
- for (RecordDecl::field_iterator
- it2 = RD->field_begin(); *it2 != it->first; ++it2)
+ for (RecordDecl::field_iterator it2 = RD->field_begin();
+ *it2 != BitField.first; ++it2)
++Index;
- BFIs.push_back(std::make_pair(Index, &it->second));
+ BFIs.push_back(std::make_pair(Index, &BitField.second));
}
llvm::array_pod_sort(BFIs.begin(), BFIs.end());
- for (unsigned i = 0, e = BFIs.size(); i != e; ++i) {
+ for (auto &BFI : BFIs) {
OS.indent(4);
- BFIs[i].second->print(OS);
+ BFI.second->print(OS);
OS << "\n";
}
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index 8742f8e0fc04a..e0650067b9547 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -757,10 +757,9 @@ void CodeGenFunction::LexicalScope::rescopeLabels() {
= CGF.EHStack.getInnermostNormalCleanup();
// Change the scope depth of all the labels.
- for (SmallVectorImpl<const LabelDecl*>::const_iterator
- i = Labels.begin(), e = Labels.end(); i != e; ++i) {
- assert(CGF.LabelMap.count(*i));
- JumpDest &dest = CGF.LabelMap.find(*i)->second;
+ for (const LabelDecl *Label : Labels) {
+ assert(CGF.LabelMap.count(Label));
+ JumpDest &dest = CGF.LabelMap.find(Label)->second;
assert(dest.getScopeDepth().isValid());
assert(innermostScope.encloses(dest.getScopeDepth()));
dest.setScopeDepth(innermostScope);
@@ -2302,8 +2301,8 @@ void CodeGenFunction::EmitSwitchStmt(const SwitchStmt &S) {
// Okay, we can dead code eliminate everything except this case. Emit the
// specified series of statements and we're good.
- for (unsigned i = 0, e = CaseStmts.size(); i != e; ++i)
- EmitStmt(CaseStmts[i]);
+ for (const Stmt *CaseStmt : CaseStmts)
+ EmitStmt(CaseStmt);
incrementProfileCounter(&S);
PGO->markStmtMaybeUsed(S.getBody());
diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
index 514cc1d9015ee..a181559834296 100644
--- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp
+++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
@@ -1836,9 +1836,9 @@ llvm::GlobalVariable *MicrosoftCXXABI::getAddrOfVTable(const CXXRecordDecl *RD,
// Create all the vftables at once in order to make sure each vftable has
// a unique mangled name.
llvm::StringSet<> ObservedMangledNames;
- for (size_t J = 0, F = VFPtrs.size(); J != F; ++J) {
+ for (const auto &VFPtr : VFPtrs) {
SmallString<256> Name;
- mangleVFTableName(getMangleContext(), RD, *VFPtrs[J], Name);
+ mangleVFTableName(getMangleContext(), RD, *VFPtr, Name);
if (!ObservedMangledNames.insert(Name.str()).second)
llvm_unreachable("Already saw this mangling before?");
}
diff --git a/clang/lib/CodeGen/ModuleBuilder.cpp b/clang/lib/CodeGen/ModuleBuilder.cpp
index 09a7d79ae4afb..8c1fee8c974f1 100644
--- a/clang/lib/CodeGen/ModuleBuilder.cpp
+++ b/clang/lib/CodeGen/ModuleBuilder.cpp
@@ -186,8 +186,8 @@ namespace {
HandlingTopLevelDeclRAII HandlingDecl(*this);
// Make sure to emit all elements of a Decl.
- for (DeclGroupRef::iterator I = DG.begin(), E = DG.end(); I != E; ++I)
- Builder->EmitTopLevelDecl(*I);
+ for (auto &I : DG)
+ Builder->EmitTopLevelDecl(I);
return true;
}
diff --git a/clang/lib/CodeGen/TargetBuiltins/ARM.cpp b/clang/lib/CodeGen/TargetBuiltins/ARM.cpp
index dab311903f6dd..6738d4be6dd21 100644
--- a/clang/lib/CodeGen/TargetBuiltins/ARM.cpp
+++ b/clang/lib/CodeGen/TargetBuiltins/ARM.cpp
@@ -4560,10 +4560,10 @@ Value *CodeGenFunction::EmitAArch64SVEBuiltinExpr(unsigned BuiltinID,
Ops.insert(&Ops[1], Builder.getInt32(/*SV_ALL*/ 31));
// Predicates must match the main datatype.
- for (unsigned i = 0, e = Ops.size(); i != e; ++i)
- if (auto PredTy = dyn_cast<llvm::VectorType>(Ops[i]->getType()))
+ for (Value *&Op : Ops)
+ if (auto PredTy = dyn_cast<llvm::VectorType>(Op->getType()))
if (PredTy->getElementType()->isIntegerTy(1))
- Ops[i] = EmitSVEPredicateCast(Ops[i], getSVEType(TypeFlags));
+ Op = EmitSVEPredicateCast(Op, getSVEType(TypeFlags));
// Splat scalar operand to vector (intrinsics with _n infix)
if (TypeFlags.hasSplatOperand()) {
@@ -4936,10 +4936,10 @@ Value *CodeGenFunction::EmitAArch64SMEBuiltinExpr(unsigned BuiltinID,
}
// Predicates must match the main datatype.
- for (unsigned i = 0, e = Ops.size(); i != e; ++i)
- if (auto PredTy = dyn_cast<llvm::VectorType>(Ops[i]->getType()))
+ for (Value *&Op : Ops)
+ if (auto PredTy = dyn_cast<llvm::VectorType>(Op->getType()))
if (PredTy->getElementType()->isIntegerTy(1))
- Ops[i] = EmitSVEPredicateCast(Ops[i], getSVEType(TypeFlags));
+ Op = EmitSVEPredicateCast(Op, getSVEType(TypeFlags));
Function *F =
TypeFlags.isOverloadNone()
@@ -8036,8 +8036,8 @@ BuildVector(ArrayRef<llvm::Value*> Ops) {
// If this is a constant vector, create a ConstantVector.
if (AllConstants) {
SmallVector<llvm::Constant*, 16> CstOps;
- for (unsigned i = 0, e = Ops.size(); i != e; ++i)
- CstOps.push_back(cast<Constant>(Ops[i]));
+ for (llvm::Value *Op : Ops)
+ CstOps.push_back(cast<Constant>(Op));
return llvm::ConstantVector::get(CstOps);
}
diff --git a/clang/lib/CodeGen/Targets/X86.cpp b/clang/lib/CodeGen/Targets/X86.cpp
index b36a6e1396653..0f59caac2323e 100644
--- a/clang/lib/CodeGen/Targets/X86.cpp
+++ b/clang/lib/CodeGen/Targets/X86.cpp
@@ -1462,9 +1462,8 @@ class X86_64TargetCodeGenInfo : public TargetCodeGenInfo {
// defines varargs anyway.
if (fnType->getCallConv() == CC_C) {
bool HasAVXType = false;
- for (CallArgList::const_iterator
- it = args.begin(), ie = args.end(); it != ie; ++it) {
- if (getABIInfo<X86_64ABIInfo>().isPassedUsingAVXType(it->Ty)) {
+ for (const CallArg &arg : args) {
+ if (getABIInfo<X86_64ABIInfo>().isPassedUsingAVXType(arg.Ty)) {
HasAVXType = true;
break;
}
>From 463ce0131047457fdd320c852f187fd70532de9a Mon Sep 17 00:00:00 2001
From: Kazu Hirata <kazu at google.com>
Date: Sat, 21 Jun 2025 08:21:04 -0700
Subject: [PATCH 12/40] [CodeGen] Remove extraneous casts to VariableID (NFC)
(#145144)
We do not need these casts because values being cast here are already
of type VariableID.
---
llvm/lib/CodeGen/AssignmentTrackingAnalysis.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/llvm/lib/CodeGen/AssignmentTrackingAnalysis.cpp b/llvm/lib/CodeGen/AssignmentTrackingAnalysis.cpp
index ffdf08eec9963..7da01e331b5a6 100644
--- a/llvm/lib/CodeGen/AssignmentTrackingAnalysis.cpp
+++ b/llvm/lib/CodeGen/AssignmentTrackingAnalysis.cpp
@@ -1312,7 +1312,7 @@ class AssignmentTrackingLowering {
DenseSet<DebugAggregate> NotAlwaysStackHomed;
VariableID getVariableID(const DebugVariable &Var) {
- return static_cast<VariableID>(FnVarLocs->insertVariable(Var));
+ return FnVarLocs->insertVariable(Var);
}
/// Join the LiveOut values of preds that are contained in \p Visited into
@@ -1556,7 +1556,7 @@ void AssignmentTrackingLowering::emitDbgValue(
VariableID Var = getVariableID(DebugVariable(Source));
VarLocInfo VarLoc;
- VarLoc.VariableID = static_cast<VariableID>(Var);
+ VarLoc.VariableID = Var;
VarLoc.Expr = Expr;
VarLoc.Values = RawLocationWrapper(Val);
VarLoc.DL = DL;
@@ -1642,7 +1642,7 @@ void AssignmentTrackingLowering::processUnknownStoreToVariable(
Fn.getContext(), 0, 0, V.getVariable()->getScope(), InlinedAt);
VarLocInfo VarLoc;
- VarLoc.VariableID = static_cast<VariableID>(Var);
+ VarLoc.VariableID = Var;
VarLoc.Expr = DIExpression::get(I.getContext(), {});
VarLoc.Values = RawLocationWrapper(
ValueAsMetadata::get(PoisonValue::get(Type::getInt1Ty(I.getContext()))));
>From 4c1a1009ad8a346068ad9428966b008ac793c170 Mon Sep 17 00:00:00 2001
From: Abhishek Kaushik <abhishek.kaushik at intel.com>
Date: Sat, 21 Jun 2025 09:34:41 -0700
Subject: [PATCH 13/40] [X86][NFC] Use std::move to avoid copy (#141455)
---
llvm/lib/Target/X86/X86ISelLowering.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp
index 33083c0eba695..53c0da45f2f66 100644
--- a/llvm/lib/Target/X86/X86ISelLowering.cpp
+++ b/llvm/lib/Target/X86/X86ISelLowering.cpp
@@ -41266,7 +41266,7 @@ static SDValue combineX86ShufflesRecursively(
resolveTargetShuffleFromZeroables(OpMask, OpUndef, OpZero,
ResolveKnownZeros);
- Mask = OpMask;
+ Mask = std::move(OpMask);
Ops.append(OpInputs.begin(), OpInputs.end());
} else {
resolveTargetShuffleFromZeroables(OpMask, OpUndef, OpZero);
>From 9f7a15539441cc589c4116de43e752c9c62cafef Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Sat, 21 Jun 2025 18:01:32 +0100
Subject: [PATCH 14/40] [VPlan] Update packScalarIntoVector to take and return
wide value (NFC)
Make the function more flexible in preparation for new users.
---
llvm/lib/Transforms/Vectorize/VPlan.cpp | 17 ++++++++---------
llvm/lib/Transforms/Vectorize/VPlanHelpers.h | 7 ++++---
llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp | 10 ++++++----
3 files changed, 18 insertions(+), 16 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VPlan.cpp b/llvm/lib/Transforms/Vectorize/VPlan.cpp
index 773a5a4a829c7..a3f39f5ad7a29 100644
--- a/llvm/lib/Transforms/Vectorize/VPlan.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlan.cpp
@@ -364,13 +364,12 @@ Value *VPTransformState::get(const VPValue *Def, bool NeedsScalar) {
VectorValue = GetBroadcastInstrs(ScalarValue);
set(Def, VectorValue);
} else {
- // Initialize packing with insertelements to start from undef.
assert(!VF.isScalable() && "VF is assumed to be non scalable.");
- Value *Undef = PoisonValue::get(toVectorizedTy(LastInst->getType(), VF));
- set(Def, Undef);
+ // Initialize packing with insertelements to start from poison.
+ VectorValue = PoisonValue::get(toVectorizedTy(LastInst->getType(), VF));
for (unsigned Lane = 0; Lane < VF.getFixedValue(); ++Lane)
- packScalarIntoVectorizedValue(Def, Lane);
- VectorValue = get(Def);
+ VectorValue = packScalarIntoVectorizedValue(Def, VectorValue, Lane);
+ set(Def, VectorValue);
}
Builder.restoreIP(OldIP);
return VectorValue;
@@ -398,10 +397,10 @@ void VPTransformState::setDebugLocFrom(DebugLoc DL) {
Builder.SetCurrentDebugLocation(DL);
}
-void VPTransformState::packScalarIntoVectorizedValue(const VPValue *Def,
- const VPLane &Lane) {
+Value *VPTransformState::packScalarIntoVectorizedValue(const VPValue *Def,
+ Value *WideValue,
+ const VPLane &Lane) {
Value *ScalarInst = get(Def, Lane);
- Value *WideValue = get(Def);
Value *LaneExpr = Lane.getAsRuntimeExpr(Builder, VF);
if (auto *StructTy = dyn_cast<StructType>(WideValue->getType())) {
// We must handle each element of a vectorized struct type.
@@ -415,7 +414,7 @@ void VPTransformState::packScalarIntoVectorizedValue(const VPValue *Def,
} else {
WideValue = Builder.CreateInsertElement(WideValue, ScalarInst, LaneExpr);
}
- set(Def, WideValue);
+ return WideValue;
}
BasicBlock *VPBasicBlock::createEmptyBasicBlock(VPTransformState &State) {
diff --git a/llvm/lib/Transforms/Vectorize/VPlanHelpers.h b/llvm/lib/Transforms/Vectorize/VPlanHelpers.h
index 0446991ebfff3..f33f94b7162c6 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanHelpers.h
+++ b/llvm/lib/Transforms/Vectorize/VPlanHelpers.h
@@ -286,9 +286,10 @@ struct VPTransformState {
/// Set the debug location in the builder using the debug location \p DL.
void setDebugLocFrom(DebugLoc DL);
- /// Construct the vectorized value of a scalarized value \p V one lane at a
- /// time.
- void packScalarIntoVectorizedValue(const VPValue *Def, const VPLane &Lane);
+ /// Insert the scalar value of \p Def at \p Lane into \p Lane of \p WideValue
+ /// and return the resulting value.
+ Value *packScalarIntoVectorizedValue(const VPValue *Def, Value *WideValue,
+ const VPLane &Lane);
/// Hold state information used when constructing the CFG of the output IR,
/// traversing the VPBasicBlocks and generating corresponding IR BasicBlocks.
diff --git a/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp b/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
index 3d237de5fa8de..3e12fdf9163eb 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
@@ -2634,14 +2634,16 @@ void VPReplicateRecipe::execute(VPTransformState &State) {
scalarizeInstruction(UI, this, *State.Lane, State);
// Insert scalar instance packing it into a vector.
if (State.VF.isVector() && shouldPack()) {
+ Value *WideValue;
// If we're constructing lane 0, initialize to start from poison.
if (State.Lane->isFirstLane()) {
assert(!State.VF.isScalable() && "VF is assumed to be non scalable.");
- Value *Poison =
- PoisonValue::get(VectorType::get(UI->getType(), State.VF));
- State.set(this, Poison);
+ WideValue = PoisonValue::get(VectorType::get(UI->getType(), State.VF));
+ } else {
+ WideValue = State.get(this);
}
- State.packScalarIntoVectorizedValue(this, *State.Lane);
+ State.set(this, State.packScalarIntoVectorizedValue(this, WideValue,
+ *State.Lane));
}
return;
}
>From 1da864b574f699d5c9be68dca9b3969ad50f4803 Mon Sep 17 00:00:00 2001
From: Craig Topper <craig.topper at sifive.com>
Date: Sat, 21 Jun 2025 10:17:30 -0700
Subject: [PATCH 15/40] [RISCV] Properly support RISCVISD::LLA in
getTargetConstantFromLoad. (#145112)
We need to pass the operand of LLA to GetSupportedConstantPool.
This replaces #142292 with test from there added as a pre-commit
for both medlow and pic.
---
llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 2 +-
.../CodeGen/RISCV/constpool-known-bits.ll | 69 +++++++++++++++++++
2 files changed, 70 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/CodeGen/RISCV/constpool-known-bits.ll
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 0c54101a11568..9e568052079ce 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -21062,7 +21062,7 @@ RISCVTargetLowering::getTargetConstantFromLoad(LoadSDNode *Ld) const {
// Simple case, LLA.
if (Ptr.getOpcode() == RISCVISD::LLA) {
- auto *CNode = GetSupportedConstantPool(Ptr);
+ auto *CNode = GetSupportedConstantPool(Ptr.getOperand(0));
if (!CNode || CNode->getTargetFlags() != 0)
return nullptr;
diff --git a/llvm/test/CodeGen/RISCV/constpool-known-bits.ll b/llvm/test/CodeGen/RISCV/constpool-known-bits.ll
new file mode 100644
index 0000000000000..85a6de1095a03
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/constpool-known-bits.ll
@@ -0,0 +1,69 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; RUN: llc < %s -O0 -mtriple=riscv64 -mattr=+m | FileCheck %s --check-prefix=NOPIC
+; RUN: llc < %s -O0 -mtriple=riscv64 -mattr=+m -relocation-model=pic | FileCheck %s --check-prefix=PIC
+
+define i64 @test(i32 noundef signext %c, i32 noundef signext %d) {
+; NOPIC-LABEL: test:
+; NOPIC: # %bb.0: # %entry
+; NOPIC-NEXT: # kill: def $x11 killed $x10
+; NOPIC-NEXT: slli a0, a0, 32
+; NOPIC-NEXT: srli a1, a0, 32
+; NOPIC-NEXT: lui a0, %hi(.LCPI0_0)
+; NOPIC-NEXT: ld a0, %lo(.LCPI0_0)(a0)
+; NOPIC-NEXT: mul a0, a1, a0
+; NOPIC-NEXT: addi a0, a0, 127
+; NOPIC-NEXT: mul a0, a1, a0
+; NOPIC-NEXT: lui a2, %hi(.LCPI0_1)
+; NOPIC-NEXT: ld a2, %lo(.LCPI0_1)(a2)
+; NOPIC-NEXT: mul a0, a0, a2
+; NOPIC-NEXT: add a0, a0, a1
+; NOPIC-NEXT: lui a1, 1015920
+; NOPIC-NEXT: addi a1, a1, 1541
+; NOPIC-NEXT: slli a1, a1, 16
+; NOPIC-NEXT: addi a1, a1, 1027
+; NOPIC-NEXT: slli a1, a1, 20
+; NOPIC-NEXT: add a0, a0, a1
+; NOPIC-NEXT: ret
+;
+; PIC-LABEL: test:
+; PIC: # %bb.0: # %entry
+; PIC-NEXT: # kill: def $x11 killed $x10
+; PIC-NEXT: slli a0, a0, 32
+; PIC-NEXT: srli a1, a0, 32
+; PIC-NEXT: .Lpcrel_hi0:
+; PIC-NEXT: auipc a0, %pcrel_hi(.LCPI0_0)
+; PIC-NEXT: addi a0, a0, %pcrel_lo(.Lpcrel_hi0)
+; PIC-NEXT: ld a0, 0(a0)
+; PIC-NEXT: mul a0, a1, a0
+; PIC-NEXT: addi a0, a0, 127
+; PIC-NEXT: mul a0, a1, a0
+; PIC-NEXT: .Lpcrel_hi1:
+; PIC-NEXT: auipc a2, %pcrel_hi(.LCPI0_1)
+; PIC-NEXT: addi a2, a2, %pcrel_lo(.Lpcrel_hi1)
+; PIC-NEXT: ld a2, 0(a2)
+; PIC-NEXT: mul a0, a0, a2
+; PIC-NEXT: add a0, a0, a1
+; PIC-NEXT: lui a1, 1015920
+; PIC-NEXT: addi a1, a1, 1541
+; PIC-NEXT: slli a1, a1, 16
+; PIC-NEXT: addi a1, a1, 1027
+; PIC-NEXT: slli a1, a1, 20
+; PIC-NEXT: add a0, a0, a1
+; PIC-NEXT: ret
+entry:
+ %or1 = or i64 -9191740941672644608, 4096
+ %or2 = or i64 -9191740941672644608, 8192
+ %or3 = or i64 -9191740941672644608, 16384
+ %conv = zext i32 %c to i64
+ %donv = zext i32 %d to i64
+ %3 = mul i64 %or1, %conv
+ %4 = mul i64 %or2, %donv
+ %5 = mul i64 %or3, %conv
+ %6 = add i64 %3, %4
+ %7 = add i64 %6, %5
+ %8 = or i64 %5, 127
+ %9 = mul i64 %3, %8
+ %add = add i64 -9191740941672644608, %9
+ %add2 = add i64 %add, %conv
+ ret i64 %add2
+}
>From fc36e47a495941910d48f29c7d305e8bbd9115fa Mon Sep 17 00:00:00 2001
From: Craig Topper <craig.topper at sifive.com>
Date: Sat, 21 Jun 2025 10:18:22 -0700
Subject: [PATCH 16/40] Revert "[RISCV] Properly support RISCVISD::LLA in
getTargetConstantFromLoad. (#145112)"
I missed the Co-authored-by that I tried to add.
This reverts commit 1da864b574f699d5c9be68dca9b3969ad50f4803.
---
llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 2 +-
.../CodeGen/RISCV/constpool-known-bits.ll | 69 -------------------
2 files changed, 1 insertion(+), 70 deletions(-)
delete mode 100644 llvm/test/CodeGen/RISCV/constpool-known-bits.ll
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 9e568052079ce..0c54101a11568 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -21062,7 +21062,7 @@ RISCVTargetLowering::getTargetConstantFromLoad(LoadSDNode *Ld) const {
// Simple case, LLA.
if (Ptr.getOpcode() == RISCVISD::LLA) {
- auto *CNode = GetSupportedConstantPool(Ptr.getOperand(0));
+ auto *CNode = GetSupportedConstantPool(Ptr);
if (!CNode || CNode->getTargetFlags() != 0)
return nullptr;
diff --git a/llvm/test/CodeGen/RISCV/constpool-known-bits.ll b/llvm/test/CodeGen/RISCV/constpool-known-bits.ll
deleted file mode 100644
index 85a6de1095a03..0000000000000
--- a/llvm/test/CodeGen/RISCV/constpool-known-bits.ll
+++ /dev/null
@@ -1,69 +0,0 @@
-; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
-; RUN: llc < %s -O0 -mtriple=riscv64 -mattr=+m | FileCheck %s --check-prefix=NOPIC
-; RUN: llc < %s -O0 -mtriple=riscv64 -mattr=+m -relocation-model=pic | FileCheck %s --check-prefix=PIC
-
-define i64 @test(i32 noundef signext %c, i32 noundef signext %d) {
-; NOPIC-LABEL: test:
-; NOPIC: # %bb.0: # %entry
-; NOPIC-NEXT: # kill: def $x11 killed $x10
-; NOPIC-NEXT: slli a0, a0, 32
-; NOPIC-NEXT: srli a1, a0, 32
-; NOPIC-NEXT: lui a0, %hi(.LCPI0_0)
-; NOPIC-NEXT: ld a0, %lo(.LCPI0_0)(a0)
-; NOPIC-NEXT: mul a0, a1, a0
-; NOPIC-NEXT: addi a0, a0, 127
-; NOPIC-NEXT: mul a0, a1, a0
-; NOPIC-NEXT: lui a2, %hi(.LCPI0_1)
-; NOPIC-NEXT: ld a2, %lo(.LCPI0_1)(a2)
-; NOPIC-NEXT: mul a0, a0, a2
-; NOPIC-NEXT: add a0, a0, a1
-; NOPIC-NEXT: lui a1, 1015920
-; NOPIC-NEXT: addi a1, a1, 1541
-; NOPIC-NEXT: slli a1, a1, 16
-; NOPIC-NEXT: addi a1, a1, 1027
-; NOPIC-NEXT: slli a1, a1, 20
-; NOPIC-NEXT: add a0, a0, a1
-; NOPIC-NEXT: ret
-;
-; PIC-LABEL: test:
-; PIC: # %bb.0: # %entry
-; PIC-NEXT: # kill: def $x11 killed $x10
-; PIC-NEXT: slli a0, a0, 32
-; PIC-NEXT: srli a1, a0, 32
-; PIC-NEXT: .Lpcrel_hi0:
-; PIC-NEXT: auipc a0, %pcrel_hi(.LCPI0_0)
-; PIC-NEXT: addi a0, a0, %pcrel_lo(.Lpcrel_hi0)
-; PIC-NEXT: ld a0, 0(a0)
-; PIC-NEXT: mul a0, a1, a0
-; PIC-NEXT: addi a0, a0, 127
-; PIC-NEXT: mul a0, a1, a0
-; PIC-NEXT: .Lpcrel_hi1:
-; PIC-NEXT: auipc a2, %pcrel_hi(.LCPI0_1)
-; PIC-NEXT: addi a2, a2, %pcrel_lo(.Lpcrel_hi1)
-; PIC-NEXT: ld a2, 0(a2)
-; PIC-NEXT: mul a0, a0, a2
-; PIC-NEXT: add a0, a0, a1
-; PIC-NEXT: lui a1, 1015920
-; PIC-NEXT: addi a1, a1, 1541
-; PIC-NEXT: slli a1, a1, 16
-; PIC-NEXT: addi a1, a1, 1027
-; PIC-NEXT: slli a1, a1, 20
-; PIC-NEXT: add a0, a0, a1
-; PIC-NEXT: ret
-entry:
- %or1 = or i64 -9191740941672644608, 4096
- %or2 = or i64 -9191740941672644608, 8192
- %or3 = or i64 -9191740941672644608, 16384
- %conv = zext i32 %c to i64
- %donv = zext i32 %d to i64
- %3 = mul i64 %or1, %conv
- %4 = mul i64 %or2, %donv
- %5 = mul i64 %or3, %conv
- %6 = add i64 %3, %4
- %7 = add i64 %6, %5
- %8 = or i64 %5, 127
- %9 = mul i64 %3, %8
- %add = add i64 -9191740941672644608, %9
- %add2 = add i64 %add, %conv
- ret i64 %add2
-}
>From 0c47628515dc80bd50599f936614da07943572a4 Mon Sep 17 00:00:00 2001
From: Craig Topper <craig.topper at sifive.com>
Date: Sat, 21 Jun 2025 10:18:49 -0700
Subject: [PATCH 17/40] Re-commit "[RISCV] Properly support RISCVISD::LLA in
getTargetConstantFromLoad. (#145112)"
With proper co-author.
Original message:
We need to pass the operand of LLA to GetSupportedConstantPool.
This replaces #142292 with test from there added as a pre-commit
for both medlow and pic.
Co-authored-by: Carl Nettelblad carl.nettelblad at rapidity-space.com
---
llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 2 +-
.../CodeGen/RISCV/constpool-known-bits.ll | 69 +++++++++++++++++++
2 files changed, 70 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/CodeGen/RISCV/constpool-known-bits.ll
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 0c54101a11568..9e568052079ce 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -21062,7 +21062,7 @@ RISCVTargetLowering::getTargetConstantFromLoad(LoadSDNode *Ld) const {
// Simple case, LLA.
if (Ptr.getOpcode() == RISCVISD::LLA) {
- auto *CNode = GetSupportedConstantPool(Ptr);
+ auto *CNode = GetSupportedConstantPool(Ptr.getOperand(0));
if (!CNode || CNode->getTargetFlags() != 0)
return nullptr;
diff --git a/llvm/test/CodeGen/RISCV/constpool-known-bits.ll b/llvm/test/CodeGen/RISCV/constpool-known-bits.ll
new file mode 100644
index 0000000000000..85a6de1095a03
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/constpool-known-bits.ll
@@ -0,0 +1,69 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; RUN: llc < %s -O0 -mtriple=riscv64 -mattr=+m | FileCheck %s --check-prefix=NOPIC
+; RUN: llc < %s -O0 -mtriple=riscv64 -mattr=+m -relocation-model=pic | FileCheck %s --check-prefix=PIC
+
+define i64 @test(i32 noundef signext %c, i32 noundef signext %d) {
+; NOPIC-LABEL: test:
+; NOPIC: # %bb.0: # %entry
+; NOPIC-NEXT: # kill: def $x11 killed $x10
+; NOPIC-NEXT: slli a0, a0, 32
+; NOPIC-NEXT: srli a1, a0, 32
+; NOPIC-NEXT: lui a0, %hi(.LCPI0_0)
+; NOPIC-NEXT: ld a0, %lo(.LCPI0_0)(a0)
+; NOPIC-NEXT: mul a0, a1, a0
+; NOPIC-NEXT: addi a0, a0, 127
+; NOPIC-NEXT: mul a0, a1, a0
+; NOPIC-NEXT: lui a2, %hi(.LCPI0_1)
+; NOPIC-NEXT: ld a2, %lo(.LCPI0_1)(a2)
+; NOPIC-NEXT: mul a0, a0, a2
+; NOPIC-NEXT: add a0, a0, a1
+; NOPIC-NEXT: lui a1, 1015920
+; NOPIC-NEXT: addi a1, a1, 1541
+; NOPIC-NEXT: slli a1, a1, 16
+; NOPIC-NEXT: addi a1, a1, 1027
+; NOPIC-NEXT: slli a1, a1, 20
+; NOPIC-NEXT: add a0, a0, a1
+; NOPIC-NEXT: ret
+;
+; PIC-LABEL: test:
+; PIC: # %bb.0: # %entry
+; PIC-NEXT: # kill: def $x11 killed $x10
+; PIC-NEXT: slli a0, a0, 32
+; PIC-NEXT: srli a1, a0, 32
+; PIC-NEXT: .Lpcrel_hi0:
+; PIC-NEXT: auipc a0, %pcrel_hi(.LCPI0_0)
+; PIC-NEXT: addi a0, a0, %pcrel_lo(.Lpcrel_hi0)
+; PIC-NEXT: ld a0, 0(a0)
+; PIC-NEXT: mul a0, a1, a0
+; PIC-NEXT: addi a0, a0, 127
+; PIC-NEXT: mul a0, a1, a0
+; PIC-NEXT: .Lpcrel_hi1:
+; PIC-NEXT: auipc a2, %pcrel_hi(.LCPI0_1)
+; PIC-NEXT: addi a2, a2, %pcrel_lo(.Lpcrel_hi1)
+; PIC-NEXT: ld a2, 0(a2)
+; PIC-NEXT: mul a0, a0, a2
+; PIC-NEXT: add a0, a0, a1
+; PIC-NEXT: lui a1, 1015920
+; PIC-NEXT: addi a1, a1, 1541
+; PIC-NEXT: slli a1, a1, 16
+; PIC-NEXT: addi a1, a1, 1027
+; PIC-NEXT: slli a1, a1, 20
+; PIC-NEXT: add a0, a0, a1
+; PIC-NEXT: ret
+entry:
+ %or1 = or i64 -9191740941672644608, 4096
+ %or2 = or i64 -9191740941672644608, 8192
+ %or3 = or i64 -9191740941672644608, 16384
+ %conv = zext i32 %c to i64
+ %donv = zext i32 %d to i64
+ %3 = mul i64 %or1, %conv
+ %4 = mul i64 %or2, %donv
+ %5 = mul i64 %or3, %conv
+ %6 = add i64 %3, %4
+ %7 = add i64 %6, %5
+ %8 = or i64 %5, 127
+ %9 = mul i64 %3, %8
+ %add = add i64 -9191740941672644608, %9
+ %add2 = add i64 %add, %conv
+ ret i64 %add2
+}
>From 6c8c816b175b3b14f47f35619cade4eced1586a2 Mon Sep 17 00:00:00 2001
From: "Mikhail R. Gadelha" <mikhail at igalia.com>
Date: Sat, 21 Jun 2025 14:42:45 -0300
Subject: [PATCH 18/40] [libc] Fix feature check for riscv (#145169)
This PR fixes the feature detection for RISC-V floating-point support in
LLVM's libc implementation.
The `__riscv_flen` macro represents the floating-point register width in
bits (32, 64, or 128). Since Extension D is specifically documented as
implying F, we can use simple >= comparisons to detect them.
For half-precision support, the implementation checks for the Zfhmin
extension as RVA22 and RVA23 profiles only require Zfhmin rather than
the full Zfh extension. Zfh also implies Zfhmin, so checking for Zfhmin
should cover all cases.
---
libc/src/__support/macros/properties/cpu_features.h | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/libc/src/__support/macros/properties/cpu_features.h b/libc/src/__support/macros/properties/cpu_features.h
index 3677e1fc3275c..457a2b7869d40 100644
--- a/libc/src/__support/macros/properties/cpu_features.h
+++ b/libc/src/__support/macros/properties/cpu_features.h
@@ -61,15 +61,15 @@
#if defined(__riscv_flen)
// https://github.com/riscv-non-isa/riscv-c-api-doc/blob/main/src/c-api.adoc
-#if (__riscv_flen & 0x10)
+#if (__riscv_arch_test && __riscv_zfhmin)
#define LIBC_TARGET_CPU_HAS_RISCV_FPU_HALF
#define LIBC_TARGET_CPU_HAS_FPU_HALF
#endif // LIBC_TARGET_CPU_HAS_RISCV_FPU_HALF
-#if (__riscv_flen & 0x20)
+#if (__riscv_flen >= 32)
#define LIBC_TARGET_CPU_HAS_RISCV_FPU_FLOAT
#define LIBC_TARGET_CPU_HAS_FPU_FLOAT
#endif // LIBC_TARGET_CPU_HAS_RISCV_FPU_FLOAT
-#if (__riscv_flen & 0x40)
+#if (__riscv_flen >= 64)
#define LIBC_TARGET_CPU_HAS_RISCV_FPU_DOUBLE
#define LIBC_TARGET_CPU_HAS_FPU_DOUBLE
#endif // LIBC_TARGET_CPU_HAS_RISCV_FPU_DOUBLE
>From 2ed089fb18b92ad668509076b9830f55d96d27fe Mon Sep 17 00:00:00 2001
From: Tal Kedar <tal.kedar at k2dq.com>
Date: Sat, 21 Jun 2025 14:00:22 -0400
Subject: [PATCH 19/40] [clang/docs] add a missing brace in
ClangFormatStyleOptions.rst (#145145)
... in the example for `WrapNamespaceBodyWithEmptyLines: Never`
---
clang/docs/ClangFormatStyleOptions.rst | 2 +-
clang/include/clang/Format/Format.h | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index 83716cc049ee3..548c73af65872 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -6992,7 +6992,7 @@ the configuration (without a prefix: ``Auto``).
.. code-block:: c++
namespace N1 {
- namespace N2
+ namespace N2 {
function();
}
}
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 127b1d08919de..2a5cf5fb50db1 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -5275,7 +5275,7 @@ struct FormatStyle {
/// Remove all empty lines at the beginning and the end of namespace body.
/// \code
/// namespace N1 {
- /// namespace N2
+ /// namespace N2 {
/// function();
/// }
/// }
>From e7dd223ec451d4e8e522aa4f2c2baaa3d027f347 Mon Sep 17 00:00:00 2001
From: Katherine Whitlock <kate at skylinesynths.nyc>
Date: Sat, 21 Jun 2025 14:10:20 -0400
Subject: [PATCH 20/40] [clang-tidy] Add new check
`readability-use-numeric-limits` (#127430)
The adds a check that replaces specific numeric literals like `32767`
with the equivalent call to `std::numeric_limits` (such as
`std::numeric_limits<int16_t>::max())`.
Partially addresses #34434, but notably does not handle cases listed in
the title post such as `~0` and `-1`.
---
.../clang-tidy/readability/CMakeLists.txt | 1 +
.../readability/ReadabilityTidyModule.cpp | 3 +
.../readability/UseNumericLimitsCheck.cpp | 160 ++++++++++++++++++
.../readability/UseNumericLimitsCheck.h | 38 +++++
clang-tools-extra/docs/ReleaseNotes.rst | 6 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../checks/readability/use-numeric-limits.rst | 31 ++++
.../readability/use-numeric-limits.cpp | 100 +++++++++++
8 files changed, 340 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/readability/UseNumericLimitsCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/readability/UseNumericLimitsCheck.h
create mode 100644 clang-tools-extra/docs/clang-tidy/checks/readability/use-numeric-limits.rst
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/readability/use-numeric-limits.cpp
diff --git a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
index 4be1a8f831339..2c40a863c5b7d 100644
--- a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
@@ -58,6 +58,7 @@ add_clang_library(clangTidyReadabilityModule STATIC
UniqueptrDeleteReleaseCheck.cpp
UppercaseLiteralSuffixCheck.cpp
UseAnyOfAllOfCheck.cpp
+ UseNumericLimitsCheck.cpp
UseStdMinMaxCheck.cpp
LINK_LIBS
diff --git a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
index d59b0312673b9..dc47c2fb31937 100644
--- a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
@@ -61,6 +61,7 @@
#include "UniqueptrDeleteReleaseCheck.h"
#include "UppercaseLiteralSuffixCheck.h"
#include "UseAnyOfAllOfCheck.h"
+#include "UseNumericLimitsCheck.h"
#include "UseStdMinMaxCheck.h"
namespace clang::tidy {
@@ -173,6 +174,8 @@ class ReadabilityModule : public ClangTidyModule {
"readability-uppercase-literal-suffix");
CheckFactories.registerCheck<UseAnyOfAllOfCheck>(
"readability-use-anyofallof");
+ CheckFactories.registerCheck<UseNumericLimitsCheck>(
+ "readability-use-numeric-limits");
CheckFactories.registerCheck<UseStdMinMaxCheck>(
"readability-use-std-min-max");
}
diff --git a/clang-tools-extra/clang-tidy/readability/UseNumericLimitsCheck.cpp b/clang-tools-extra/clang-tidy/readability/UseNumericLimitsCheck.cpp
new file mode 100644
index 0000000000000..334b69755db29
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/readability/UseNumericLimitsCheck.cpp
@@ -0,0 +1,160 @@
+//===--- UseNumericLimitsCheck.cpp - clang-tidy ---------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "UseNumericLimitsCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Preprocessor.h"
+#include <cmath>
+#include <limits>
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::readability {
+
+UseNumericLimitsCheck::UseNumericLimitsCheck(StringRef Name,
+ ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ SignedConstants{
+ {std::numeric_limits<int8_t>::min(),
+ "std::numeric_limits<int8_t>::min()"},
+ {std::numeric_limits<int8_t>::max(),
+ "std::numeric_limits<int8_t>::max()"},
+ {std::numeric_limits<int16_t>::min(),
+ "std::numeric_limits<int16_t>::min()"},
+ {std::numeric_limits<int16_t>::max(),
+ "std::numeric_limits<int16_t>::max()"},
+ {std::numeric_limits<int32_t>::min(),
+ "std::numeric_limits<int32_t>::min()"},
+ {std::numeric_limits<int32_t>::max(),
+ "std::numeric_limits<int32_t>::max()"},
+ {std::numeric_limits<int64_t>::min(),
+ "std::numeric_limits<int64_t>::min()"},
+ {std::numeric_limits<int64_t>::max(),
+ "std::numeric_limits<int64_t>::max()"},
+ },
+ UnsignedConstants{
+ {std::numeric_limits<uint8_t>::max(),
+ "std::numeric_limits<uint8_t>::max()"},
+ {std::numeric_limits<uint16_t>::max(),
+ "std::numeric_limits<uint16_t>::max()"},
+ {std::numeric_limits<uint32_t>::max(),
+ "std::numeric_limits<uint32_t>::max()"},
+ {std::numeric_limits<uint64_t>::max(),
+ "std::numeric_limits<uint64_t>::max()"},
+ },
+ Inserter(Options.getLocalOrGlobal("IncludeStyle",
+ utils::IncludeSorter::IS_LLVM),
+ areDiagsSelfContained()) {}
+
+void UseNumericLimitsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "IncludeStyle", Inserter.getStyle());
+}
+
+void UseNumericLimitsCheck::registerMatchers(MatchFinder *Finder) {
+ auto PositiveIntegerMatcher = [](auto Value) {
+ return unaryOperator(hasOperatorName("+"),
+ hasUnaryOperand(integerLiteral(equals(Value))
+ .bind("positive-integer-literal")))
+ .bind("unary-op");
+ };
+
+ auto NegativeIntegerMatcher = [](auto Value) {
+ return unaryOperator(hasOperatorName("-"),
+ hasUnaryOperand(integerLiteral(equals(-Value))
+ .bind("negative-integer-literal")))
+ .bind("unary-op");
+ };
+
+ auto BareIntegerMatcher = [](auto Value) {
+ return integerLiteral(allOf(unless(hasParent(unaryOperator(
+ hasAnyOperatorName("-", "+")))),
+ equals(Value)))
+ .bind("bare-integer-literal");
+ };
+
+ for (const auto &[Value, _] : SignedConstants) {
+ if (Value < 0) {
+ Finder->addMatcher(NegativeIntegerMatcher(Value), this);
+ } else {
+ Finder->addMatcher(
+ expr(anyOf(PositiveIntegerMatcher(Value), BareIntegerMatcher(Value))),
+ this);
+ }
+ }
+
+ for (const auto &[Value, _] : UnsignedConstants) {
+ Finder->addMatcher(
+ expr(anyOf(PositiveIntegerMatcher(Value), BareIntegerMatcher(Value))),
+ this);
+ }
+}
+
+void UseNumericLimitsCheck::registerPPCallbacks(
+ const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
+ Inserter.registerPreprocessor(PP);
+}
+
+void UseNumericLimitsCheck::check(const MatchFinder::MatchResult &Result) {
+ const IntegerLiteral *MatchedDecl = nullptr;
+
+ const IntegerLiteral *NegativeMatchedDecl =
+ Result.Nodes.getNodeAs<IntegerLiteral>("negative-integer-literal");
+ const IntegerLiteral *PositiveMatchedDecl =
+ Result.Nodes.getNodeAs<IntegerLiteral>("positive-integer-literal");
+ const IntegerLiteral *BareMatchedDecl =
+ Result.Nodes.getNodeAs<IntegerLiteral>("bare-integer-literal");
+
+ if (NegativeMatchedDecl != nullptr)
+ MatchedDecl = NegativeMatchedDecl;
+ else if (PositiveMatchedDecl != nullptr)
+ MatchedDecl = PositiveMatchedDecl;
+ else if (BareMatchedDecl != nullptr)
+ MatchedDecl = BareMatchedDecl;
+
+ const llvm::APInt MatchedIntegerConstant = MatchedDecl->getValue();
+
+ auto Fixer = [&](auto SourceValue, auto Value,
+ const std::string &Replacement) {
+ static_assert(std::is_same_v<decltype(SourceValue), decltype(Value)>,
+ "The types of SourceValue and Value must match");
+
+ SourceLocation Location = MatchedDecl->getExprLoc();
+ SourceRange Range{MatchedDecl->getBeginLoc(), MatchedDecl->getEndLoc()};
+
+ // Only valid if unary operator is present
+ const UnaryOperator *UnaryOpExpr =
+ Result.Nodes.getNodeAs<UnaryOperator>("unary-op");
+
+ if (MatchedDecl == NegativeMatchedDecl && -SourceValue == Value) {
+ Range = SourceRange(UnaryOpExpr->getBeginLoc(), UnaryOpExpr->getEndLoc());
+ Location = UnaryOpExpr->getExprLoc();
+ SourceValue = -SourceValue;
+ } else if (MatchedDecl == PositiveMatchedDecl && SourceValue == Value) {
+ Range = SourceRange(UnaryOpExpr->getBeginLoc(), UnaryOpExpr->getEndLoc());
+ Location = UnaryOpExpr->getExprLoc();
+ } else if (MatchedDecl != BareMatchedDecl || SourceValue != Value) {
+ return;
+ }
+
+ diag(Location,
+ "the constant '%0' is being utilized; consider using '%1' instead")
+ << SourceValue << Replacement
+ << FixItHint::CreateReplacement(Range, Replacement)
+ << Inserter.createIncludeInsertion(
+ Result.SourceManager->getFileID(Location), "<limits>");
+ };
+
+ for (const auto &[Value, Replacement] : SignedConstants)
+ Fixer(MatchedIntegerConstant.getSExtValue(), Value, Replacement);
+
+ for (const auto &[Value, Replacement] : UnsignedConstants)
+ Fixer(MatchedIntegerConstant.getZExtValue(), Value, Replacement);
+}
+
+} // namespace clang::tidy::readability
diff --git a/clang-tools-extra/clang-tidy/readability/UseNumericLimitsCheck.h b/clang-tools-extra/clang-tidy/readability/UseNumericLimitsCheck.h
new file mode 100644
index 0000000000000..0e7e9abb8562c
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/readability/UseNumericLimitsCheck.h
@@ -0,0 +1,38 @@
+//===--- UseNumericLimitsCheck.h - clang-tidy -------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USENUMERICLIMITSCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USENUMERICLIMITSCHECK_H
+
+#include "../ClangTidyCheck.h"
+#include "../utils/IncludeInserter.h"
+
+namespace clang::tidy::readability {
+
+/// Finds certain integer literals and suggests replacing them with equivalent
+/// ``std::numeric_limits`` calls.
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/readability/use-numeric-limits.html
+class UseNumericLimitsCheck : public ClangTidyCheck {
+public:
+ UseNumericLimitsCheck(StringRef Name, ClangTidyContext *Context);
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
+ Preprocessor *ModuleExpanderPP) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+ const llvm::SmallVector<std::pair<int64_t, std::string>> SignedConstants;
+ const llvm::SmallVector<std::pair<uint64_t, std::string>> UnsignedConstants;
+ utils::IncludeInserter Inserter;
+};
+
+} // namespace clang::tidy::readability
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USENUMERICLIMITSCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 4801dab8c1bd5..9dede347b8c97 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -154,6 +154,12 @@ New checks
Finds potentially erroneous calls to ``reset`` method on smart pointers when
the pointee type also has a ``reset`` method.
+- New :doc:`readability-use-numeric-limits
+ <clang-tidy/checks/readability/use-numeric-limits>` check.
+
+ Finds certain integer literals and suggests replacing them with equivalent
+ ``std::numeric_limits`` calls.
+
New check aliases
^^^^^^^^^^^^^^^^^
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index ccb78ee45e9c4..57ae7d330a3ce 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -409,6 +409,7 @@ Clang-Tidy Checks
:doc:`readability-uniqueptr-delete-release <readability/uniqueptr-delete-release>`, "Yes"
:doc:`readability-uppercase-literal-suffix <readability/uppercase-literal-suffix>`, "Yes"
:doc:`readability-use-anyofallof <readability/use-anyofallof>`,
+ :doc:`readability-use-numeric-limits <readability/use-numeric-limits>`, "Yes"
:doc:`readability-use-std-min-max <readability/use-std-min-max>`, "Yes"
:doc:`zircon-temporary-objects <zircon/temporary-objects>`,
diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/use-numeric-limits.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/use-numeric-limits.rst
new file mode 100644
index 0000000000000..0f6ca9f0cf2c0
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/readability/use-numeric-limits.rst
@@ -0,0 +1,31 @@
+.. title:: clang-tidy - readability-use-numeric-limits
+
+readability-use-numeric-limits
+==============================
+
+Finds certain integer literals and suggests replacing them with equivalent
+``std::numeric_limits`` calls.
+
+Before:
+
+.. code-block:: c++
+
+ void foo() {
+ int32_t a = 2147483647;
+ }
+
+After:
+
+.. code-block:: c++
+
+ void foo() {
+ int32_t a = std::numeric_limits<int32_t>::max();
+ }
+
+Options
+-------
+
+.. option:: IncludeStyle
+
+ A string specifying which include-style is used, `llvm` or `google`. Default
+ is `llvm`.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/use-numeric-limits.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/use-numeric-limits.cpp
new file mode 100644
index 0000000000000..e02d6f1b7126d
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/use-numeric-limits.cpp
@@ -0,0 +1,100 @@
+// RUN: %check_clang_tidy %s readability-use-numeric-limits %t
+// CHECK-FIXES: #include <limits>
+
+using int8_t = signed char;
+using int16_t = short;
+using int32_t = int;
+using int64_t = long long;
+using uint8_t = unsigned char;
+using uint16_t = unsigned short;
+using uint32_t = unsigned int;
+using uint64_t = unsigned long long;
+
+
+void Invalid() {
+ // CHECK-MESSAGES: :[[@LINE+2]]:14: warning: the constant '-128' is being utilized; consider using 'std::numeric_limits<int8_t>::min()' instead [readability-use-numeric-limits]
+ // CHECK-FIXES: int8_t a = std::numeric_limits<int8_t>::min();
+ int8_t a = -128;
+
+ // CHECK-MESSAGES: :[[@LINE+2]]:14: warning: the constant '127' is being utilized; consider using 'std::numeric_limits<int8_t>::max()' instead [readability-use-numeric-limits]
+ // CHECK-FIXES: int8_t b = std::numeric_limits<int8_t>::max();
+ int8_t b = +127;
+
+ // CHECK-MESSAGES: :[[@LINE+2]]:14: warning: the constant '127' is being utilized; consider using 'std::numeric_limits<int8_t>::max()' instead [readability-use-numeric-limits]
+ // CHECK-FIXES: int8_t c = std::numeric_limits<int8_t>::max();
+ int8_t c = 127;
+
+ // CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '-32768' is being utilized; consider using 'std::numeric_limits<int16_t>::min()' instead [readability-use-numeric-limits]
+ // CHECK-FIXES: int16_t d = std::numeric_limits<int16_t>::min();
+ int16_t d = -32768;
+
+ // CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '32767' is being utilized; consider using 'std::numeric_limits<int16_t>::max()' instead [readability-use-numeric-limits]
+ // CHECK-FIXES: int16_t e = std::numeric_limits<int16_t>::max();
+ int16_t e = +32767;
+
+ // CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '32767' is being utilized; consider using 'std::numeric_limits<int16_t>::max()' instead [readability-use-numeric-limits]
+ // CHECK-FIXES: int16_t f = std::numeric_limits<int16_t>::max();
+ int16_t f = 32767;
+
+ // CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '-2147483648' is being utilized; consider using 'std::numeric_limits<int32_t>::min()' instead [readability-use-numeric-limits]
+ // CHECK-FIXES: int32_t g = std::numeric_limits<int32_t>::min();
+ int32_t g = -2147483648;
+
+ // CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '2147483647' is being utilized; consider using 'std::numeric_limits<int32_t>::max()' instead [readability-use-numeric-limits]
+ // CHECK-FIXES: int32_t h = std::numeric_limits<int32_t>::max();
+ int32_t h = +2147483647;
+
+ // CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '2147483647' is being utilized; consider using 'std::numeric_limits<int32_t>::max()' instead [readability-use-numeric-limits]
+ // CHECK-FIXES: int32_t i = std::numeric_limits<int32_t>::max();
+ int32_t i = 2147483647;
+
+ // CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '-9223372036854775808' is being utilized; consider using 'std::numeric_limits<int64_t>::min()' instead [readability-use-numeric-limits]
+ // CHECK-FIXES: int64_t j = std::numeric_limits<int64_t>::min();
+ int64_t j = -9223372036854775808;
+
+ // CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '9223372036854775807' is being utilized; consider using 'std::numeric_limits<int64_t>::max()' instead [readability-use-numeric-limits]
+ // CHECK-FIXES: int64_t k = std::numeric_limits<int64_t>::max();
+ int64_t k = +9223372036854775807;
+
+ // CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '9223372036854775807' is being utilized; consider using 'std::numeric_limits<int64_t>::max()' instead [readability-use-numeric-limits]
+ // CHECK-FIXES: int64_t l = std::numeric_limits<int64_t>::max();
+ int64_t l = 9223372036854775807;
+
+ // CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '255' is being utilized; consider using 'std::numeric_limits<uint8_t>::max()' instead [readability-use-numeric-limits]
+ // CHECK-FIXES: uint8_t m = std::numeric_limits<uint8_t>::max();
+ uint8_t m = 255;
+
+ // CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '255' is being utilized; consider using 'std::numeric_limits<uint8_t>::max()' instead [readability-use-numeric-limits]
+ // CHECK-FIXES: uint8_t n = std::numeric_limits<uint8_t>::max();
+ uint8_t n = +255;
+
+ // CHECK-MESSAGES: :[[@LINE+2]]:16: warning: the constant '65535' is being utilized; consider using 'std::numeric_limits<uint16_t>::max()' instead [readability-use-numeric-limits]
+ // CHECK-FIXES: uint16_t o = std::numeric_limits<uint16_t>::max();
+ uint16_t o = 65535;
+
+ // CHECK-MESSAGES: :[[@LINE+2]]:16: warning: the constant '65535' is being utilized; consider using 'std::numeric_limits<uint16_t>::max()' instead [readability-use-numeric-limits]
+ // CHECK-FIXES: uint16_t p = std::numeric_limits<uint16_t>::max();
+ uint16_t p = +65535;
+
+ // CHECK-MESSAGES: :[[@LINE+2]]:16: warning: the constant '4294967295' is being utilized; consider using 'std::numeric_limits<uint32_t>::max()' instead [readability-use-numeric-limits]
+ // CHECK-FIXES: uint32_t q = std::numeric_limits<uint32_t>::max();
+ uint32_t q = 4294967295;
+
+ // CHECK-MESSAGES: :[[@LINE+2]]:16: warning: the constant '4294967295' is being utilized; consider using 'std::numeric_limits<uint32_t>::max()' instead [readability-use-numeric-limits]
+ // CHECK-FIXES: uint32_t r = std::numeric_limits<uint32_t>::max();
+ uint32_t r = +4294967295;
+
+ // CHECK-MESSAGES: :[[@LINE+2]]:16: warning: the constant '18446744073709551615' is being utilized; consider using 'std::numeric_limits<uint64_t>::max()' instead [readability-use-numeric-limits]
+ // CHECK-FIXES: uint64_t s = std::numeric_limits<uint64_t>::max();
+ uint64_t s = 18446744073709551615;
+
+ // CHECK-MESSAGES: :[[@LINE+2]]:16: warning: the constant '18446744073709551615' is being utilized; consider using 'std::numeric_limits<uint64_t>::max()' instead [readability-use-numeric-limits]
+ // CHECK-FIXES: uint64_t t = std::numeric_limits<uint64_t>::max();
+ uint64_t t = +18446744073709551615;
+}
+
+void Valid(){
+ int16_t a = +128;
+
+ int16_t b = -127;
+}
>From 550ed9ef198e530fb66e22198165503cc9c9de80 Mon Sep 17 00:00:00 2001
From: LLVM GN Syncbot <llvmgnsyncbot at gmail.com>
Date: Sat, 21 Jun 2025 18:10:42 +0000
Subject: [PATCH 21/40] [gn build] Port e7dd223ec451
---
.../secondary/clang-tools-extra/clang-tidy/readability/BUILD.gn | 1 +
1 file changed, 1 insertion(+)
diff --git a/llvm/utils/gn/secondary/clang-tools-extra/clang-tidy/readability/BUILD.gn b/llvm/utils/gn/secondary/clang-tools-extra/clang-tidy/readability/BUILD.gn
index cad2c4912b2cc..f6dd75008f19b 100644
--- a/llvm/utils/gn/secondary/clang-tools-extra/clang-tidy/readability/BUILD.gn
+++ b/llvm/utils/gn/secondary/clang-tools-extra/clang-tidy/readability/BUILD.gn
@@ -66,6 +66,7 @@ static_library("readability") {
"UniqueptrDeleteReleaseCheck.cpp",
"UppercaseLiteralSuffixCheck.cpp",
"UseAnyOfAllOfCheck.cpp",
+ "UseNumericLimitsCheck.cpp",
"UseStdMinMaxCheck.cpp",
]
}
>From 056b52df344f688fd3831a07bc477f77f883a696 Mon Sep 17 00:00:00 2001
From: Erick Velez <erickvelez7 at gmail.com>
Date: Sat, 21 Jun 2025 11:56:35 -0700
Subject: [PATCH 22/40] [clang-doc] Precommit test for global variables
(#145069)
---
.../test/clang-doc/json/namespace.cpp | 20 ++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/clang-tools-extra/test/clang-doc/json/namespace.cpp b/clang-tools-extra/test/clang-doc/json/namespace.cpp
index 928864be1feb0..248d47351bd38 100644
--- a/clang-tools-extra/test/clang-doc/json/namespace.cpp
+++ b/clang-tools-extra/test/clang-doc/json/namespace.cpp
@@ -103,5 +103,23 @@ typedef int MyTypedef;
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "USR": "0000000000000000000000000000000000000000"
-// CHECK-NOT: "Variables": [
+// CHECK-NOT: "Variables": [
+// CHECK-NOT: {
+// CHECK-NOT: "IsStatic": true,
+// CHECK-NOT: "Location": {
+// CHECK-NOT: "Filename": "{{.*}}namespace.cpp",
+// CHECK-NOT: "LineNumber": 13
+// CHECK-NOT: },
+// CHECK-NOT: "Name": "Global",
+// CHECK-NOT: "Type": {
+// COM: FIXME: IsBuiltIn emits as its default value
+// CHECK-NOT: "IsBuiltIn": false,
+// CHECK-NOT: "IsTemplate": false,
+// CHECK-NOT: "Name": "int",
+// CHECK-NOT: "QualName": "int",
+// CHECK-NOT: "USR": "0000000000000000000000000000000000000000"
+// CHECK-NOT: },
+// CHECK-NOT: "USR": "{{[0-9A-F]*}}"
+// CHECK-NOT: }
+// CHECK-NOT: ]
// CHECK-NEXT: }
>From e066f35c6981c720e3a7e5883efc40c861b3b7ee Mon Sep 17 00:00:00 2001
From: eleviant <56861949+eleviant at users.noreply.github.com>
Date: Sat, 21 Jun 2025 22:48:08 +0200
Subject: [PATCH 23/40] [lldb] Fix qEcho message handling (#145072)
Patch fixes the sync-on-timeout logic in lldb and switches to qEcho
based ping, instead of qC. This fixes vRun message case, when there is
no process yet and qC returns an error.
---
.../Python/lldbsuite/test/gdbclientutils.py | 10 +++
.../gdb-remote/GDBRemoteCommunication.cpp | 3 +-
.../GDBRemoteCommunicationClient.cpp | 2 +-
.../gdb_remote_client/TestGDBRemoteClient.py | 72 +++++++++++++++++++
4 files changed, 85 insertions(+), 2 deletions(-)
diff --git a/lldb/packages/Python/lldbsuite/test/gdbclientutils.py b/lldb/packages/Python/lldbsuite/test/gdbclientutils.py
index 753de22b9cfee..b603c35c8df09 100644
--- a/lldb/packages/Python/lldbsuite/test/gdbclientutils.py
+++ b/lldb/packages/Python/lldbsuite/test/gdbclientutils.py
@@ -92,6 +92,9 @@ class MockGDBServerResponder:
class RESPONSE_DISCONNECT:
pass
+ class RESPONSE_NONE:
+ pass
+
def __init__(self):
self.packetLog = []
@@ -181,6 +184,8 @@ def respond(self, packet):
return self.qQueryGDBServer()
if packet == "qHostInfo":
return self.qHostInfo()
+ if packet.startswith("qEcho"):
+ return self.qEcho(int(packet.split(":")[1]))
if packet == "qGetWorkingDir":
return self.qGetWorkingDir()
if packet == "qOffsets":
@@ -237,6 +242,9 @@ def qProcessInfo(self):
def qHostInfo(self):
return "ptrsize:8;endian:little;"
+ def qEcho(self):
+ return "E04"
+
def qQueryGDBServer(self):
return "E04"
@@ -655,6 +663,8 @@ def _handlePacket(self, packet):
if not isinstance(response, list):
response = [response]
for part in response:
+ if part is MockGDBServerResponder.RESPONSE_NONE:
+ continue
if part is MockGDBServerResponder.RESPONSE_DISCONNECT:
raise self.TerminateConnectionException()
self._sendPacket(part)
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp
index 2aea7c6b781d7..f244f7abe45e3 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp
@@ -354,8 +354,9 @@ GDBRemoteCommunication::WaitForPacketNoLock(StringExtractorGDBRemote &packet,
disconnected = true;
Disconnect();
}
+ } else {
+ timed_out = true;
}
- timed_out = true;
break;
case eConnectionStatusSuccess:
// printf ("status = success but error = %s\n",
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
index adbf06b9a19a0..d8130cae71ce6 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
@@ -406,7 +406,7 @@ void GDBRemoteCommunicationClient::GetRemoteQSupported() {
m_supports_qXfer_memory_map_read = eLazyBoolYes;
else if (x == "qXfer:siginfo:read+")
m_supports_qXfer_siginfo_read = eLazyBoolYes;
- else if (x == "qEcho")
+ else if (x == "qEcho+")
m_supports_qEcho = eLazyBoolYes;
else if (x == "QPassSignals+")
m_supports_QPassSignals = eLazyBoolYes;
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestGDBRemoteClient.py b/lldb/test/API/functionalities/gdb_remote_client/TestGDBRemoteClient.py
index 08ac9290ee85a..12b464d3397eb 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestGDBRemoteClient.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestGDBRemoteClient.py
@@ -356,6 +356,78 @@ def A(self, packet):
["vRun;%s;61726731;61726732;61726733" % (exe_hex,)]
)
+ def test_launch_lengthy_vRun(self):
+ class MyResponder(MockGDBServerResponder):
+ def __init__(self, *args, **kwargs):
+ self.started = False
+ return super().__init__(*args, **kwargs)
+
+ def qC(self):
+ if self.started:
+ return "QCp10.10"
+ else:
+ return "E42"
+
+ def qfThreadInfo(self):
+ if self.started:
+ return "mp10.10"
+ else:
+ return "E42"
+
+ def qsThreadInfo(self):
+ return "l"
+
+ def qEcho(self, num):
+ resp = "qEcho:" + str(num)
+ if num >= 2:
+ # We have launched our program
+ self.started = True
+ return [resp, "T13"]
+
+ return resp
+
+ def qSupported(self, client_supported):
+ return "PacketSize=3fff;QStartNoAckMode+;qEcho+;"
+
+ def qHostInfo(self):
+ return "default_packet_timeout:1;"
+
+ def vRun(self, packet):
+ return [self.RESPONSE_NONE]
+
+ def A(self, packet):
+ return "E28"
+
+ self.server.responder = MyResponder()
+
+ target = self.createTarget("a.yaml")
+ # NB: apparently GDB packets are using "/" on Windows too
+ exe_path = self.getBuildArtifact("a").replace(os.path.sep, "/")
+ exe_hex = binascii.b2a_hex(exe_path.encode()).decode()
+ process = self.connect(target)
+ lldbutil.expect_state_changes(
+ self, self.dbg.GetListener(), process, [lldb.eStateConnected]
+ )
+
+ process = target.Launch(
+ lldb.SBListener(),
+ ["arg1", "arg2", "arg3"], # argv
+ [], # envp
+ None, # stdin_path
+ None, # stdout_path
+ None, # stderr_path
+ None, # working_directory
+ 0, # launch_flags
+ True, # stop_at_entry
+ lldb.SBError(),
+ ) # error
+ self.assertTrue(process, PROCESS_IS_VALID)
+ self.assertEqual(process.GetProcessID(), 16)
+
+ self.assertPacketLogContains(
+ ["vRun;%s;61726731;61726732;61726733" % (exe_hex,)]
+ )
+
def test_launch_QEnvironment(self):
class MyResponder(MockGDBServerResponder):
def qC(self):
>From c455f4a32d91436c131a751fc9587d7fa3ded614 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Sat, 21 Jun 2025 22:03:18 +0100
Subject: [PATCH 24/40] [LV] Add more tests for narrowing interleave groups
with live-ins.
---
...interleave-to-widen-memory-constant-ops.ll | 314 ++++++++++++++++++
1 file changed, 314 insertions(+)
diff --git a/llvm/test/Transforms/LoopVectorize/AArch64/transform-narrow-interleave-to-widen-memory-constant-ops.ll b/llvm/test/Transforms/LoopVectorize/AArch64/transform-narrow-interleave-to-widen-memory-constant-ops.ll
index 5f6a372db2e98..7d3b3d86b90d4 100644
--- a/llvm/test/Transforms/LoopVectorize/AArch64/transform-narrow-interleave-to-widen-memory-constant-ops.ll
+++ b/llvm/test/Transforms/LoopVectorize/AArch64/transform-narrow-interleave-to-widen-memory-constant-ops.ll
@@ -183,3 +183,317 @@ loop:
exit:
ret void
}
+
+define void @test_add_double_same_var_args_1(ptr %res, ptr noalias %A, ptr noalias %B, double %x) {
+; CHECK-LABEL: define void @test_add_double_same_var_args_1(
+; CHECK-SAME: ptr [[RES:%.*]], ptr noalias [[A:%.*]], ptr noalias [[B:%.*]], double [[X:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: br i1 false, label %[[SCALAR_PH:.*]], label %[[VECTOR_PH:.*]]
+; CHECK: [[VECTOR_PH]]:
+; CHECK-NEXT: [[BROADCAST_SPLATINSERT:%.*]] = insertelement <2 x double> poison, double [[X]], i64 0
+; CHECK-NEXT: [[BROADCAST_SPLAT:%.*]] = shufflevector <2 x double> [[BROADCAST_SPLATINSERT]], <2 x double> poison, <2 x i32> zeroinitializer
+; CHECK-NEXT: br label %[[VECTOR_BODY:.*]]
+; CHECK: [[VECTOR_BODY]]:
+; CHECK-NEXT: [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
+; CHECK-NEXT: [[TMP0:%.*]] = add i64 [[INDEX]], 2
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[A]], i64 [[INDEX]]
+; CHECK-NEXT: [[TMP2:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[A]], i64 [[TMP0]]
+; CHECK-NEXT: [[WIDE_VEC:%.*]] = load <4 x double>, ptr [[TMP1]], align 4
+; CHECK-NEXT: [[STRIDED_VEC:%.*]] = shufflevector <4 x double> [[WIDE_VEC]], <4 x double> poison, <2 x i32> <i32 0, i32 2>
+; CHECK-NEXT: [[STRIDED_VEC1:%.*]] = shufflevector <4 x double> [[WIDE_VEC]], <4 x double> poison, <2 x i32> <i32 1, i32 3>
+; CHECK-NEXT: [[WIDE_VEC2:%.*]] = load <4 x double>, ptr [[TMP2]], align 4
+; CHECK-NEXT: [[STRIDED_VEC3:%.*]] = shufflevector <4 x double> [[WIDE_VEC2]], <4 x double> poison, <2 x i32> <i32 0, i32 2>
+; CHECK-NEXT: [[STRIDED_VEC4:%.*]] = shufflevector <4 x double> [[WIDE_VEC2]], <4 x double> poison, <2 x i32> <i32 1, i32 3>
+; CHECK-NEXT: [[TMP3:%.*]] = fadd <2 x double> [[STRIDED_VEC]], [[BROADCAST_SPLAT]]
+; CHECK-NEXT: [[TMP4:%.*]] = fadd <2 x double> [[STRIDED_VEC3]], [[BROADCAST_SPLAT]]
+; CHECK-NEXT: [[TMP5:%.*]] = fadd <2 x double> [[STRIDED_VEC1]], [[BROADCAST_SPLAT]]
+; CHECK-NEXT: [[TMP6:%.*]] = fadd <2 x double> [[STRIDED_VEC4]], [[BROADCAST_SPLAT]]
+; CHECK-NEXT: [[TMP7:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[RES]], i64 [[INDEX]]
+; CHECK-NEXT: [[TMP8:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[RES]], i64 [[TMP0]]
+; CHECK-NEXT: [[TMP9:%.*]] = shufflevector <2 x double> [[TMP3]], <2 x double> [[TMP5]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+; CHECK-NEXT: [[INTERLEAVED_VEC:%.*]] = shufflevector <4 x double> [[TMP9]], <4 x double> poison, <4 x i32> <i32 0, i32 2, i32 1, i32 3>
+; CHECK-NEXT: store <4 x double> [[INTERLEAVED_VEC]], ptr [[TMP7]], align 4
+; CHECK-NEXT: [[TMP10:%.*]] = shufflevector <2 x double> [[TMP4]], <2 x double> [[TMP6]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+; CHECK-NEXT: [[INTERLEAVED_VEC5:%.*]] = shufflevector <4 x double> [[TMP10]], <4 x double> poison, <4 x i32> <i32 0, i32 2, i32 1, i32 3>
+; CHECK-NEXT: store <4 x double> [[INTERLEAVED_VEC5]], ptr [[TMP8]], align 4
+; CHECK-NEXT: [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 4
+; CHECK-NEXT: [[TMP11:%.*]] = icmp eq i64 [[INDEX_NEXT]], 100
+; CHECK-NEXT: br i1 [[TMP11]], label %[[MIDDLE_BLOCK:.*]], label %[[VECTOR_BODY]], !llvm.loop [[LOOP8:![0-9]+]]
+; CHECK: [[MIDDLE_BLOCK]]:
+; CHECK-NEXT: br i1 true, [[EXIT:label %.*]], label %[[SCALAR_PH]]
+; CHECK: [[SCALAR_PH]]:
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
+ %gep.A.0 = getelementptr inbounds nuw { double, double }, ptr %A, i64 %iv
+ %l.A.0 = load double, ptr %gep.A.0, align 4
+ %gep.A.1 = getelementptr inbounds nuw i8, ptr %gep.A.0, i64 8
+ %l.A.1 = load double, ptr %gep.A.1, align 4
+ %add.0 = fadd double %l.A.0, %x
+ %add.1 = fadd double %l.A.1, %x
+ %gep.res.0 = getelementptr inbounds nuw { double, double }, ptr %res, i64 %iv
+ store double %add.0, ptr %gep.res.0, align 4
+ %gep.res.1 = getelementptr inbounds nuw i8, ptr %gep.res.0, i64 8
+ store double %add.1, ptr %gep.res.1, align 4
+ %iv.next = add nuw nsw i64 %iv, 1
+ %ec = icmp eq i64 %iv.next, 100
+ br i1 %ec, label %exit, label %loop
+
+exit:
+ ret void
+}
+
+define void @test_add_double_same_var_args_2(ptr %res, ptr noalias %A, ptr noalias %B, double %x) {
+; CHECK-LABEL: define void @test_add_double_same_var_args_2(
+; CHECK-SAME: ptr [[RES:%.*]], ptr noalias [[A:%.*]], ptr noalias [[B:%.*]], double [[X:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: br i1 false, label %[[SCALAR_PH:.*]], label %[[VECTOR_PH:.*]]
+; CHECK: [[VECTOR_PH]]:
+; CHECK-NEXT: [[BROADCAST_SPLATINSERT:%.*]] = insertelement <2 x double> poison, double [[X]], i64 0
+; CHECK-NEXT: [[BROADCAST_SPLAT:%.*]] = shufflevector <2 x double> [[BROADCAST_SPLATINSERT]], <2 x double> poison, <2 x i32> zeroinitializer
+; CHECK-NEXT: br label %[[VECTOR_BODY:.*]]
+; CHECK: [[VECTOR_BODY]]:
+; CHECK-NEXT: [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
+; CHECK-NEXT: [[TMP0:%.*]] = add i64 [[INDEX]], 2
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[A]], i64 [[INDEX]]
+; CHECK-NEXT: [[TMP2:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[A]], i64 [[TMP0]]
+; CHECK-NEXT: [[WIDE_VEC:%.*]] = load <4 x double>, ptr [[TMP1]], align 4
+; CHECK-NEXT: [[STRIDED_VEC:%.*]] = shufflevector <4 x double> [[WIDE_VEC]], <4 x double> poison, <2 x i32> <i32 0, i32 2>
+; CHECK-NEXT: [[STRIDED_VEC1:%.*]] = shufflevector <4 x double> [[WIDE_VEC]], <4 x double> poison, <2 x i32> <i32 1, i32 3>
+; CHECK-NEXT: [[WIDE_VEC2:%.*]] = load <4 x double>, ptr [[TMP2]], align 4
+; CHECK-NEXT: [[STRIDED_VEC3:%.*]] = shufflevector <4 x double> [[WIDE_VEC2]], <4 x double> poison, <2 x i32> <i32 0, i32 2>
+; CHECK-NEXT: [[STRIDED_VEC4:%.*]] = shufflevector <4 x double> [[WIDE_VEC2]], <4 x double> poison, <2 x i32> <i32 1, i32 3>
+; CHECK-NEXT: [[TMP3:%.*]] = fadd <2 x double> [[BROADCAST_SPLAT]], [[STRIDED_VEC]]
+; CHECK-NEXT: [[TMP4:%.*]] = fadd <2 x double> [[BROADCAST_SPLAT]], [[STRIDED_VEC3]]
+; CHECK-NEXT: [[TMP5:%.*]] = fadd <2 x double> [[BROADCAST_SPLAT]], [[STRIDED_VEC1]]
+; CHECK-NEXT: [[TMP6:%.*]] = fadd <2 x double> [[BROADCAST_SPLAT]], [[STRIDED_VEC4]]
+; CHECK-NEXT: [[TMP7:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[RES]], i64 [[INDEX]]
+; CHECK-NEXT: [[TMP8:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[RES]], i64 [[TMP0]]
+; CHECK-NEXT: [[TMP9:%.*]] = shufflevector <2 x double> [[TMP3]], <2 x double> [[TMP5]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+; CHECK-NEXT: [[INTERLEAVED_VEC:%.*]] = shufflevector <4 x double> [[TMP9]], <4 x double> poison, <4 x i32> <i32 0, i32 2, i32 1, i32 3>
+; CHECK-NEXT: store <4 x double> [[INTERLEAVED_VEC]], ptr [[TMP7]], align 4
+; CHECK-NEXT: [[TMP10:%.*]] = shufflevector <2 x double> [[TMP4]], <2 x double> [[TMP6]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+; CHECK-NEXT: [[INTERLEAVED_VEC5:%.*]] = shufflevector <4 x double> [[TMP10]], <4 x double> poison, <4 x i32> <i32 0, i32 2, i32 1, i32 3>
+; CHECK-NEXT: store <4 x double> [[INTERLEAVED_VEC5]], ptr [[TMP8]], align 4
+; CHECK-NEXT: [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 4
+; CHECK-NEXT: [[TMP11:%.*]] = icmp eq i64 [[INDEX_NEXT]], 100
+; CHECK-NEXT: br i1 [[TMP11]], label %[[MIDDLE_BLOCK:.*]], label %[[VECTOR_BODY]], !llvm.loop [[LOOP10:![0-9]+]]
+; CHECK: [[MIDDLE_BLOCK]]:
+; CHECK-NEXT: br i1 true, [[EXIT:label %.*]], label %[[SCALAR_PH]]
+; CHECK: [[SCALAR_PH]]:
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
+ %gep.A.0 = getelementptr inbounds nuw { double, double }, ptr %A, i64 %iv
+ %l.A.0 = load double, ptr %gep.A.0, align 4
+ %gep.A.1 = getelementptr inbounds nuw i8, ptr %gep.A.0, i64 8
+ %l.A.1 = load double, ptr %gep.A.1, align 4
+ %add.0 = fadd double %x, %l.A.0
+ %add.1 = fadd double %x, %l.A.1
+ %gep.res.0 = getelementptr inbounds nuw { double, double }, ptr %res, i64 %iv
+ store double %add.0, ptr %gep.res.0, align 4
+ %gep.res.1 = getelementptr inbounds nuw i8, ptr %gep.res.0, i64 8
+ store double %add.1, ptr %gep.res.1, align 4
+ %iv.next = add nuw nsw i64 %iv, 1
+ %ec = icmp eq i64 %iv.next, 100
+ br i1 %ec, label %exit, label %loop
+
+exit:
+ ret void
+}
+
+define void @test_add_double_same_var_args_at_different_positions(ptr %res, ptr noalias %A, ptr noalias %B, double %x) {
+; CHECK-LABEL: define void @test_add_double_same_var_args_at_different_positions(
+; CHECK-SAME: ptr [[RES:%.*]], ptr noalias [[A:%.*]], ptr noalias [[B:%.*]], double [[X:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: br i1 false, label %[[SCALAR_PH:.*]], label %[[VECTOR_PH:.*]]
+; CHECK: [[VECTOR_PH]]:
+; CHECK-NEXT: [[BROADCAST_SPLATINSERT:%.*]] = insertelement <2 x double> poison, double [[X]], i64 0
+; CHECK-NEXT: [[BROADCAST_SPLAT:%.*]] = shufflevector <2 x double> [[BROADCAST_SPLATINSERT]], <2 x double> poison, <2 x i32> zeroinitializer
+; CHECK-NEXT: br label %[[VECTOR_BODY:.*]]
+; CHECK: [[VECTOR_BODY]]:
+; CHECK-NEXT: [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
+; CHECK-NEXT: [[TMP0:%.*]] = add i64 [[INDEX]], 2
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[A]], i64 [[INDEX]]
+; CHECK-NEXT: [[TMP2:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[A]], i64 [[TMP0]]
+; CHECK-NEXT: [[WIDE_VEC:%.*]] = load <4 x double>, ptr [[TMP1]], align 4
+; CHECK-NEXT: [[STRIDED_VEC:%.*]] = shufflevector <4 x double> [[WIDE_VEC]], <4 x double> poison, <2 x i32> <i32 0, i32 2>
+; CHECK-NEXT: [[STRIDED_VEC1:%.*]] = shufflevector <4 x double> [[WIDE_VEC]], <4 x double> poison, <2 x i32> <i32 1, i32 3>
+; CHECK-NEXT: [[WIDE_VEC2:%.*]] = load <4 x double>, ptr [[TMP2]], align 4
+; CHECK-NEXT: [[STRIDED_VEC3:%.*]] = shufflevector <4 x double> [[WIDE_VEC2]], <4 x double> poison, <2 x i32> <i32 0, i32 2>
+; CHECK-NEXT: [[STRIDED_VEC4:%.*]] = shufflevector <4 x double> [[WIDE_VEC2]], <4 x double> poison, <2 x i32> <i32 1, i32 3>
+; CHECK-NEXT: [[TMP3:%.*]] = fadd <2 x double> [[STRIDED_VEC]], [[BROADCAST_SPLAT]]
+; CHECK-NEXT: [[TMP4:%.*]] = fadd <2 x double> [[STRIDED_VEC3]], [[BROADCAST_SPLAT]]
+; CHECK-NEXT: [[TMP5:%.*]] = fadd <2 x double> [[BROADCAST_SPLAT]], [[STRIDED_VEC1]]
+; CHECK-NEXT: [[TMP6:%.*]] = fadd <2 x double> [[BROADCAST_SPLAT]], [[STRIDED_VEC4]]
+; CHECK-NEXT: [[TMP7:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[RES]], i64 [[INDEX]]
+; CHECK-NEXT: [[TMP8:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[RES]], i64 [[TMP0]]
+; CHECK-NEXT: [[TMP9:%.*]] = shufflevector <2 x double> [[TMP3]], <2 x double> [[TMP5]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+; CHECK-NEXT: [[INTERLEAVED_VEC:%.*]] = shufflevector <4 x double> [[TMP9]], <4 x double> poison, <4 x i32> <i32 0, i32 2, i32 1, i32 3>
+; CHECK-NEXT: store <4 x double> [[INTERLEAVED_VEC]], ptr [[TMP7]], align 4
+; CHECK-NEXT: [[TMP10:%.*]] = shufflevector <2 x double> [[TMP4]], <2 x double> [[TMP6]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+; CHECK-NEXT: [[INTERLEAVED_VEC5:%.*]] = shufflevector <4 x double> [[TMP10]], <4 x double> poison, <4 x i32> <i32 0, i32 2, i32 1, i32 3>
+; CHECK-NEXT: store <4 x double> [[INTERLEAVED_VEC5]], ptr [[TMP8]], align 4
+; CHECK-NEXT: [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 4
+; CHECK-NEXT: [[TMP11:%.*]] = icmp eq i64 [[INDEX_NEXT]], 100
+; CHECK-NEXT: br i1 [[TMP11]], label %[[MIDDLE_BLOCK:.*]], label %[[VECTOR_BODY]], !llvm.loop [[LOOP12:![0-9]+]]
+; CHECK: [[MIDDLE_BLOCK]]:
+; CHECK-NEXT: br i1 true, [[EXIT:label %.*]], label %[[SCALAR_PH]]
+; CHECK: [[SCALAR_PH]]:
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
+ %gep.A.0 = getelementptr inbounds nuw { double, double }, ptr %A, i64 %iv
+ %l.A.0 = load double, ptr %gep.A.0, align 4
+ %gep.A.1 = getelementptr inbounds nuw i8, ptr %gep.A.0, i64 8
+ %l.A.1 = load double, ptr %gep.A.1, align 4
+ %add.0 = fadd double %l.A.0, %x
+ %add.1 = fadd double %x, %l.A.1
+ %gep.res.0 = getelementptr inbounds nuw { double, double }, ptr %res, i64 %iv
+ store double %add.0, ptr %gep.res.0, align 4
+ %gep.res.1 = getelementptr inbounds nuw i8, ptr %gep.res.0, i64 8
+ store double %add.1, ptr %gep.res.1, align 4
+ %iv.next = add nuw nsw i64 %iv, 1
+ %ec = icmp eq i64 %iv.next, 100
+ br i1 %ec, label %exit, label %loop
+
+exit:
+ ret void
+}
+
+define void @test_add_double_different_var_args_1(ptr %res, ptr noalias %A, ptr noalias %B, double %x, double %y) {
+; CHECK-LABEL: define void @test_add_double_different_var_args_1(
+; CHECK-SAME: ptr [[RES:%.*]], ptr noalias [[A:%.*]], ptr noalias [[B:%.*]], double [[X:%.*]], double [[Y:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: br i1 false, label %[[SCALAR_PH:.*]], label %[[VECTOR_PH:.*]]
+; CHECK: [[VECTOR_PH]]:
+; CHECK-NEXT: [[BROADCAST_SPLATINSERT:%.*]] = insertelement <2 x double> poison, double [[X]], i64 0
+; CHECK-NEXT: [[BROADCAST_SPLAT:%.*]] = shufflevector <2 x double> [[BROADCAST_SPLATINSERT]], <2 x double> poison, <2 x i32> zeroinitializer
+; CHECK-NEXT: [[BROADCAST_SPLATINSERT1:%.*]] = insertelement <2 x double> poison, double [[Y]], i64 0
+; CHECK-NEXT: [[BROADCAST_SPLAT2:%.*]] = shufflevector <2 x double> [[BROADCAST_SPLATINSERT1]], <2 x double> poison, <2 x i32> zeroinitializer
+; CHECK-NEXT: br label %[[VECTOR_BODY:.*]]
+; CHECK: [[VECTOR_BODY]]:
+; CHECK-NEXT: [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
+; CHECK-NEXT: [[TMP0:%.*]] = add i64 [[INDEX]], 2
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[A]], i64 [[INDEX]]
+; CHECK-NEXT: [[TMP2:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[A]], i64 [[TMP0]]
+; CHECK-NEXT: [[WIDE_VEC:%.*]] = load <4 x double>, ptr [[TMP1]], align 4
+; CHECK-NEXT: [[STRIDED_VEC:%.*]] = shufflevector <4 x double> [[WIDE_VEC]], <4 x double> poison, <2 x i32> <i32 0, i32 2>
+; CHECK-NEXT: [[STRIDED_VEC3:%.*]] = shufflevector <4 x double> [[WIDE_VEC]], <4 x double> poison, <2 x i32> <i32 1, i32 3>
+; CHECK-NEXT: [[WIDE_VEC4:%.*]] = load <4 x double>, ptr [[TMP2]], align 4
+; CHECK-NEXT: [[STRIDED_VEC5:%.*]] = shufflevector <4 x double> [[WIDE_VEC4]], <4 x double> poison, <2 x i32> <i32 0, i32 2>
+; CHECK-NEXT: [[STRIDED_VEC6:%.*]] = shufflevector <4 x double> [[WIDE_VEC4]], <4 x double> poison, <2 x i32> <i32 1, i32 3>
+; CHECK-NEXT: [[TMP3:%.*]] = fadd <2 x double> [[STRIDED_VEC]], [[BROADCAST_SPLAT]]
+; CHECK-NEXT: [[TMP4:%.*]] = fadd <2 x double> [[STRIDED_VEC5]], [[BROADCAST_SPLAT]]
+; CHECK-NEXT: [[TMP5:%.*]] = fadd <2 x double> [[STRIDED_VEC3]], [[BROADCAST_SPLAT2]]
+; CHECK-NEXT: [[TMP6:%.*]] = fadd <2 x double> [[STRIDED_VEC6]], [[BROADCAST_SPLAT2]]
+; CHECK-NEXT: [[TMP7:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[RES]], i64 [[INDEX]]
+; CHECK-NEXT: [[TMP8:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[RES]], i64 [[TMP0]]
+; CHECK-NEXT: [[TMP9:%.*]] = shufflevector <2 x double> [[TMP3]], <2 x double> [[TMP5]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+; CHECK-NEXT: [[INTERLEAVED_VEC:%.*]] = shufflevector <4 x double> [[TMP9]], <4 x double> poison, <4 x i32> <i32 0, i32 2, i32 1, i32 3>
+; CHECK-NEXT: store <4 x double> [[INTERLEAVED_VEC]], ptr [[TMP7]], align 4
+; CHECK-NEXT: [[TMP10:%.*]] = shufflevector <2 x double> [[TMP4]], <2 x double> [[TMP6]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+; CHECK-NEXT: [[INTERLEAVED_VEC7:%.*]] = shufflevector <4 x double> [[TMP10]], <4 x double> poison, <4 x i32> <i32 0, i32 2, i32 1, i32 3>
+; CHECK-NEXT: store <4 x double> [[INTERLEAVED_VEC7]], ptr [[TMP8]], align 4
+; CHECK-NEXT: [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 4
+; CHECK-NEXT: [[TMP11:%.*]] = icmp eq i64 [[INDEX_NEXT]], 100
+; CHECK-NEXT: br i1 [[TMP11]], label %[[MIDDLE_BLOCK:.*]], label %[[VECTOR_BODY]], !llvm.loop [[LOOP14:![0-9]+]]
+; CHECK: [[MIDDLE_BLOCK]]:
+; CHECK-NEXT: br i1 true, [[EXIT:label %.*]], label %[[SCALAR_PH]]
+; CHECK: [[SCALAR_PH]]:
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
+ %gep.A.0 = getelementptr inbounds nuw { double, double }, ptr %A, i64 %iv
+ %l.A.0 = load double, ptr %gep.A.0, align 4
+ %gep.A.1 = getelementptr inbounds nuw i8, ptr %gep.A.0, i64 8
+ %l.A.1 = load double, ptr %gep.A.1, align 4
+ %add.0 = fadd double %l.A.0, %x
+ %add.1 = fadd double %l.A.1, %y
+ %gep.res.0 = getelementptr inbounds nuw { double, double }, ptr %res, i64 %iv
+ store double %add.0, ptr %gep.res.0, align 4
+ %gep.res.1 = getelementptr inbounds nuw i8, ptr %gep.res.0, i64 8
+ store double %add.1, ptr %gep.res.1, align 4
+ %iv.next = add nuw nsw i64 %iv, 1
+ %ec = icmp eq i64 %iv.next, 100
+ br i1 %ec, label %exit, label %loop
+
+exit:
+ ret void
+}
+
+define void @test_add_double_different_var_args_2(ptr %res, ptr noalias %A, ptr noalias %B, double %x, double %y) {
+; CHECK-LABEL: define void @test_add_double_different_var_args_2(
+; CHECK-SAME: ptr [[RES:%.*]], ptr noalias [[A:%.*]], ptr noalias [[B:%.*]], double [[X:%.*]], double [[Y:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: br i1 false, label %[[SCALAR_PH:.*]], label %[[VECTOR_PH:.*]]
+; CHECK: [[VECTOR_PH]]:
+; CHECK-NEXT: [[BROADCAST_SPLATINSERT:%.*]] = insertelement <2 x double> poison, double [[Y]], i64 0
+; CHECK-NEXT: [[BROADCAST_SPLAT:%.*]] = shufflevector <2 x double> [[BROADCAST_SPLATINSERT]], <2 x double> poison, <2 x i32> zeroinitializer
+; CHECK-NEXT: [[BROADCAST_SPLATINSERT1:%.*]] = insertelement <2 x double> poison, double [[X]], i64 0
+; CHECK-NEXT: [[BROADCAST_SPLAT2:%.*]] = shufflevector <2 x double> [[BROADCAST_SPLATINSERT1]], <2 x double> poison, <2 x i32> zeroinitializer
+; CHECK-NEXT: br label %[[VECTOR_BODY:.*]]
+; CHECK: [[VECTOR_BODY]]:
+; CHECK-NEXT: [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
+; CHECK-NEXT: [[TMP0:%.*]] = add i64 [[INDEX]], 2
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[A]], i64 [[INDEX]]
+; CHECK-NEXT: [[TMP2:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[A]], i64 [[TMP0]]
+; CHECK-NEXT: [[WIDE_VEC:%.*]] = load <4 x double>, ptr [[TMP1]], align 4
+; CHECK-NEXT: [[STRIDED_VEC:%.*]] = shufflevector <4 x double> [[WIDE_VEC]], <4 x double> poison, <2 x i32> <i32 0, i32 2>
+; CHECK-NEXT: [[STRIDED_VEC3:%.*]] = shufflevector <4 x double> [[WIDE_VEC]], <4 x double> poison, <2 x i32> <i32 1, i32 3>
+; CHECK-NEXT: [[WIDE_VEC4:%.*]] = load <4 x double>, ptr [[TMP2]], align 4
+; CHECK-NEXT: [[STRIDED_VEC5:%.*]] = shufflevector <4 x double> [[WIDE_VEC4]], <4 x double> poison, <2 x i32> <i32 0, i32 2>
+; CHECK-NEXT: [[STRIDED_VEC6:%.*]] = shufflevector <4 x double> [[WIDE_VEC4]], <4 x double> poison, <2 x i32> <i32 1, i32 3>
+; CHECK-NEXT: [[TMP3:%.*]] = fadd <2 x double> [[BROADCAST_SPLAT]], [[STRIDED_VEC]]
+; CHECK-NEXT: [[TMP4:%.*]] = fadd <2 x double> [[BROADCAST_SPLAT]], [[STRIDED_VEC5]]
+; CHECK-NEXT: [[TMP5:%.*]] = fadd <2 x double> [[BROADCAST_SPLAT2]], [[STRIDED_VEC3]]
+; CHECK-NEXT: [[TMP6:%.*]] = fadd <2 x double> [[BROADCAST_SPLAT2]], [[STRIDED_VEC6]]
+; CHECK-NEXT: [[TMP7:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[RES]], i64 [[INDEX]]
+; CHECK-NEXT: [[TMP8:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[RES]], i64 [[TMP0]]
+; CHECK-NEXT: [[TMP9:%.*]] = shufflevector <2 x double> [[TMP3]], <2 x double> [[TMP5]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+; CHECK-NEXT: [[INTERLEAVED_VEC:%.*]] = shufflevector <4 x double> [[TMP9]], <4 x double> poison, <4 x i32> <i32 0, i32 2, i32 1, i32 3>
+; CHECK-NEXT: store <4 x double> [[INTERLEAVED_VEC]], ptr [[TMP7]], align 4
+; CHECK-NEXT: [[TMP10:%.*]] = shufflevector <2 x double> [[TMP4]], <2 x double> [[TMP6]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+; CHECK-NEXT: [[INTERLEAVED_VEC7:%.*]] = shufflevector <4 x double> [[TMP10]], <4 x double> poison, <4 x i32> <i32 0, i32 2, i32 1, i32 3>
+; CHECK-NEXT: store <4 x double> [[INTERLEAVED_VEC7]], ptr [[TMP8]], align 4
+; CHECK-NEXT: [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 4
+; CHECK-NEXT: [[TMP11:%.*]] = icmp eq i64 [[INDEX_NEXT]], 100
+; CHECK-NEXT: br i1 [[TMP11]], label %[[MIDDLE_BLOCK:.*]], label %[[VECTOR_BODY]], !llvm.loop [[LOOP16:![0-9]+]]
+; CHECK: [[MIDDLE_BLOCK]]:
+; CHECK-NEXT: br i1 true, [[EXIT:label %.*]], label %[[SCALAR_PH]]
+; CHECK: [[SCALAR_PH]]:
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
+ %gep.A.0 = getelementptr inbounds nuw { double, double }, ptr %A, i64 %iv
+ %l.A.0 = load double, ptr %gep.A.0, align 4
+ %gep.A.1 = getelementptr inbounds nuw i8, ptr %gep.A.0, i64 8
+ %l.A.1 = load double, ptr %gep.A.1, align 4
+ %add.0 = fadd double %y, %l.A.0
+ %add.1 = fadd double %x, %l.A.1
+ %gep.res.0 = getelementptr inbounds nuw { double, double }, ptr %res, i64 %iv
+ store double %add.0, ptr %gep.res.0, align 4
+ %gep.res.1 = getelementptr inbounds nuw i8, ptr %gep.res.0, i64 8
+ store double %add.1, ptr %gep.res.1, align 4
+ %iv.next = add nuw nsw i64 %iv, 1
+ %ec = icmp eq i64 %iv.next, 100
+ br i1 %ec, label %exit, label %loop
+
+exit:
+ ret void
+}
>From 757c80d88a6aeb6c4d61d76c492a033bb25ab028 Mon Sep 17 00:00:00 2001
From: Douglas Yung <douglas.yung at sony.com>
Date: Sat, 21 Jun 2025 22:37:02 +0000
Subject: [PATCH 25/40] Add `REQUIRES: x86` to test added in 141197 to skip
when x86 target is not present.
---
lld/test/COFF/strtab.s | 1 +
1 file changed, 1 insertion(+)
diff --git a/lld/test/COFF/strtab.s b/lld/test/COFF/strtab.s
index 4d8fa39f56db6..fbdd8df52d540 100644
--- a/lld/test/COFF/strtab.s
+++ b/lld/test/COFF/strtab.s
@@ -1,3 +1,4 @@
+# REQUIRES: x86
# RUN: llvm-mc -triple=x86_64-windows-msvc %s -filetype=obj -o %t.obj
# RUN: lld-link -out:%t.exe -entry:main %t.obj -debug:dwarf
# RUN: llvm-readobj --string-table %t.exe | FileCheck %s
>From 570885128351868c1308bb22e8ca351d318bc4a1 Mon Sep 17 00:00:00 2001
From: Arthur Eubanks <aeubanks at google.com>
Date: Sat, 21 Jun 2025 16:02:26 -0700
Subject: [PATCH 26/40] Revert "[ValueTracking] Improve `Bitcast` handling to
match SDAG" (#145191)
Reverts llvm/llvm-project#125935
Causes miscompiles, see comments in #125935
---
llvm/lib/Analysis/ValueTracking.cpp | 27 ++-----------------
.../InstCombine/X86/x86-vector-shifts.ll | 4 +--
.../InstCombine/bitcast-known-bits.ll | 21 ++++++++++-----
3 files changed, 18 insertions(+), 34 deletions(-)
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 73320b556f825..a17417cb5189c 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -1346,8 +1346,6 @@ static void computeKnownBitsFromOperator(const Operator *I,
isa<ScalableVectorType>(I->getType()))
break;
- unsigned NumElts = DemandedElts.getBitWidth();
- bool IsLE = Q.DL.isLittleEndian();
// Look through a cast from narrow vector elements to wider type.
// Examples: v4i32 -> v2i64, v3i8 -> v24
unsigned SubBitWidth = SrcVecTy->getScalarSizeInBits();
@@ -1366,6 +1364,7 @@ static void computeKnownBitsFromOperator(const Operator *I,
//
// The known bits of each sub-element are then inserted into place
// (dependent on endian) to form the full result of known bits.
+ unsigned NumElts = DemandedElts.getBitWidth();
unsigned SubScale = BitWidth / SubBitWidth;
APInt SubDemandedElts = APInt::getZero(NumElts * SubScale);
for (unsigned i = 0; i != NumElts; ++i) {
@@ -1377,32 +1376,10 @@ static void computeKnownBitsFromOperator(const Operator *I,
for (unsigned i = 0; i != SubScale; ++i) {
computeKnownBits(I->getOperand(0), SubDemandedElts.shl(i), KnownSrc, Q,
Depth + 1);
- unsigned ShiftElt = IsLE ? i : SubScale - 1 - i;
+ unsigned ShiftElt = Q.DL.isLittleEndian() ? i : SubScale - 1 - i;
Known.insertBits(KnownSrc, ShiftElt * SubBitWidth);
}
}
- // Look through a cast from wider vector elements to narrow type.
- // Examples: v2i64 -> v4i32
- if (SubBitWidth % BitWidth == 0) {
- unsigned SubScale = SubBitWidth / BitWidth;
- KnownBits KnownSrc(SubBitWidth);
- APInt SubDemandedElts =
- APIntOps::ScaleBitMask(DemandedElts, NumElts / SubScale);
- computeKnownBits(I->getOperand(0), SubDemandedElts, KnownSrc, Q,
- Depth + 1);
-
- Known.Zero.setAllBits();
- Known.One.setAllBits();
- for (unsigned i = 0; i != SubScale; ++i) {
- if (DemandedElts[i]) {
- unsigned Shifts = IsLE ? i : NumElts - 1 - i;
- unsigned Offset = (Shifts % SubScale) * BitWidth;
- Known = Known.intersectWith(KnownSrc.extractBits(BitWidth, Offset));
- if (Known.isUnknown())
- break;
- }
- }
- }
break;
}
case Instruction::SExt: {
diff --git a/llvm/test/Transforms/InstCombine/X86/x86-vector-shifts.ll b/llvm/test/Transforms/InstCombine/X86/x86-vector-shifts.ll
index cc252ae53803b..db56080a3ea2b 100644
--- a/llvm/test/Transforms/InstCombine/X86/x86-vector-shifts.ll
+++ b/llvm/test/Transforms/InstCombine/X86/x86-vector-shifts.ll
@@ -3732,6 +3732,7 @@ define <4 x i64> @test_avx2_psrl_0() {
ret <4 x i64> %16
}
+; FIXME: Failure to peek through bitcasts to ensure psllq shift amount is within bounds.
define <2 x i64> @PR125228(<2 x i64> %v, <2 x i64> %s) {
; CHECK-LABEL: @PR125228(
; CHECK-NEXT: [[MASK:%.*]] = and <2 x i64> [[S:%.*]], splat (i64 63)
@@ -3740,8 +3741,7 @@ define <2 x i64> @PR125228(<2 x i64> %v, <2 x i64> %s) {
; CHECK-NEXT: [[CAST:%.*]] = bitcast <2 x i64> [[MASK]] to <16 x i8>
; CHECK-NEXT: [[PSRLDQ:%.*]] = shufflevector <16 x i8> [[CAST]], <16 x i8> poison, <16 x i32> <i32 8, i32 9, i32 10, i32 11, i32 12, i32 13, i32 14, i32 15, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison>
; CHECK-NEXT: [[CAST3:%.*]] = bitcast <16 x i8> [[PSRLDQ]] to <2 x i64>
-; CHECK-NEXT: [[TMP2:%.*]] = shufflevector <2 x i64> [[CAST3]], <2 x i64> poison, <2 x i32> zeroinitializer
-; CHECK-NEXT: [[SLL1:%.*]] = shl <2 x i64> [[V]], [[TMP2]]
+; CHECK-NEXT: [[SLL1:%.*]] = call <2 x i64> @llvm.x86.sse2.psll.q(<2 x i64> [[V]], <2 x i64> [[CAST3]])
; CHECK-NEXT: [[SHUFP_UNCASTED:%.*]] = shufflevector <2 x i64> [[SLL0]], <2 x i64> [[SLL1]], <2 x i32> <i32 0, i32 3>
; CHECK-NEXT: ret <2 x i64> [[SHUFP_UNCASTED]]
;
diff --git a/llvm/test/Transforms/InstCombine/bitcast-known-bits.ll b/llvm/test/Transforms/InstCombine/bitcast-known-bits.ll
index 65b43df752f76..3e47e775e3a28 100644
--- a/llvm/test/Transforms/InstCombine/bitcast-known-bits.ll
+++ b/llvm/test/Transforms/InstCombine/bitcast-known-bits.ll
@@ -12,7 +12,8 @@ define <16 x i8> @knownbits_bitcast_masked_shift(<16 x i8> %arg1, <16 x i8> %arg
; CHECK-NEXT: [[BITCAST4:%.*]] = bitcast <16 x i8> [[OR]] to <8 x i16>
; CHECK-NEXT: [[SHL5:%.*]] = shl nuw <8 x i16> [[BITCAST4]], splat (i16 2)
; CHECK-NEXT: [[BITCAST6:%.*]] = bitcast <8 x i16> [[SHL5]] to <16 x i8>
-; CHECK-NEXT: ret <16 x i8> [[BITCAST6]]
+; CHECK-NEXT: [[AND7:%.*]] = and <16 x i8> [[BITCAST6]], splat (i8 -52)
+; CHECK-NEXT: ret <16 x i8> [[AND7]]
;
%and = and <16 x i8> %arg1, splat (i8 3)
%and3 = and <16 x i8> %arg2, splat (i8 48)
@@ -32,7 +33,8 @@ define <16 x i8> @knownbits_shuffle_masked_nibble_shift(<16 x i8> %arg) {
; CHECK-NEXT: [[BITCAST1:%.*]] = bitcast <16 x i8> [[SHUFFLEVECTOR]] to <8 x i16>
; CHECK-NEXT: [[SHL:%.*]] = shl nuw <8 x i16> [[BITCAST1]], splat (i16 4)
; CHECK-NEXT: [[BITCAST2:%.*]] = bitcast <8 x i16> [[SHL]] to <16 x i8>
-; CHECK-NEXT: ret <16 x i8> [[BITCAST2]]
+; CHECK-NEXT: [[AND3:%.*]] = and <16 x i8> [[BITCAST2]], splat (i8 -16)
+; CHECK-NEXT: ret <16 x i8> [[AND3]]
;
%and = and <16 x i8> %arg, splat (i8 15)
%shufflevector = shufflevector <16 x i8> %and, <16 x i8> poison, <16 x i32> <i32 1, i32 0, i32 3, i32 2, i32 5, i32 4, i32 7, i32 6, i32 9, i32 8, i32 11, i32 10, i32 13, i32 12, i32 15, i32 14>
@@ -51,7 +53,8 @@ define <16 x i8> @knownbits_reverse_shuffle_masked_shift(<16 x i8> %arg) {
; CHECK-NEXT: [[BITCAST1:%.*]] = bitcast <16 x i8> [[SHUFFLEVECTOR]] to <8 x i16>
; CHECK-NEXT: [[SHL:%.*]] = shl nuw <8 x i16> [[BITCAST1]], splat (i16 4)
; CHECK-NEXT: [[BITCAST2:%.*]] = bitcast <8 x i16> [[SHL]] to <16 x i8>
-; CHECK-NEXT: ret <16 x i8> [[BITCAST2]]
+; CHECK-NEXT: [[AND3:%.*]] = and <16 x i8> [[BITCAST2]], splat (i8 -16)
+; CHECK-NEXT: ret <16 x i8> [[AND3]]
;
%and = and <16 x i8> %arg, splat (i8 15)
%shufflevector = shufflevector <16 x i8> %and, <16 x i8> poison, <16 x i32> <i32 3, i32 2, i32 1, i32 0, i32 7, i32 6, i32 5, i32 4, i32 11, i32 10, i32 9, i32 8, i32 15, i32 14, i32 13, i32 12>
@@ -67,7 +70,8 @@ define <16 x i8> @knownbits_extract_bit(<8 x i16> %arg) {
; CHECK-SAME: <8 x i16> [[ARG:%.*]]) {
; CHECK-NEXT: [[LSHR:%.*]] = lshr <8 x i16> [[ARG]], splat (i16 15)
; CHECK-NEXT: [[BITCAST1:%.*]] = bitcast <8 x i16> [[LSHR]] to <16 x i8>
-; CHECK-NEXT: ret <16 x i8> [[BITCAST1]]
+; CHECK-NEXT: [[AND:%.*]] = and <16 x i8> [[BITCAST1]], splat (i8 1)
+; CHECK-NEXT: ret <16 x i8> [[AND]]
;
%lshr = lshr <8 x i16> %arg, splat (i16 15)
%bitcast1 = bitcast <8 x i16> %lshr to <16 x i8>
@@ -84,8 +88,7 @@ define { i32, i1 } @knownbits_popcount_add_with_overflow(<2 x i64> %arg1, <2 x i
; CHECK-NEXT: [[CALL9:%.*]] = tail call range(i64 0, 65) <2 x i64> @llvm.ctpop.v2i64(<2 x i64> [[ARG2]])
; CHECK-NEXT: [[BITCAST10:%.*]] = bitcast <2 x i64> [[CALL9]] to <4 x i32>
; CHECK-NEXT: [[EXTRACTELEMENT11:%.*]] = extractelement <4 x i32> [[BITCAST10]], i64 0
-; CHECK-NEXT: [[CALL12:%.*]] = add nuw nsw i32 [[EXTRACTELEMENT]], [[EXTRACTELEMENT11]]
-; CHECK-NEXT: [[TMP1:%.*]] = insertvalue { i32, i1 } { i32 poison, i1 false }, i32 [[CALL12]], 0
+; CHECK-NEXT: [[TMP1:%.*]] = tail call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 [[EXTRACTELEMENT]], i32 [[EXTRACTELEMENT11]])
; CHECK-NEXT: ret { i32, i1 } [[TMP1]]
;
%call = tail call <2 x i64> @llvm.ctpop.v2i64(<2 x i64> %arg1)
@@ -107,7 +110,11 @@ define <16 x i8> @knownbits_shuffle_add_shift_v32i8(<16 x i8> %arg1, <8 x i16> %
; CHECK-NEXT: [[BITCAST11:%.*]] = bitcast <8 x i16> [[SHL10]] to <16 x i8>
; CHECK-NEXT: [[ADD12:%.*]] = add <16 x i8> [[BITCAST11]], [[BITCAST7]]
; CHECK-NEXT: [[ADD14:%.*]] = add <16 x i8> [[ADD12]], [[ARG1]]
-; CHECK-NEXT: ret <16 x i8> [[ADD14]]
+; CHECK-NEXT: [[BITCAST14:%.*]] = bitcast <16 x i8> [[ADD12]] to <8 x i16>
+; CHECK-NEXT: [[SHL15:%.*]] = shl <8 x i16> [[BITCAST14]], splat (i16 8)
+; CHECK-NEXT: [[BITCAST16:%.*]] = bitcast <8 x i16> [[SHL15]] to <16 x i8>
+; CHECK-NEXT: [[ADD13:%.*]] = add <16 x i8> [[ADD14]], [[BITCAST16]]
+; CHECK-NEXT: ret <16 x i8> [[ADD13]]
;
%shl6 = shl <8 x i16> %arg2, splat (i16 8)
%bitcast7 = bitcast <8 x i16> %shl6 to <16 x i8>
>From f280d3b705de7f94ef9756e3ef2842b415a7c038 Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Sun, 22 Jun 2025 08:19:19 +0900
Subject: [PATCH 27/40] AMDGPU: Avoid report_fatal_error for getRegisterByName
subtarget case (#145173)
---
llvm/lib/Target/AMDGPU/SIISelLowering.cpp | 6 ++++--
llvm/test/CodeGen/AMDGPU/read-register-invalid-subtarget.ll | 4 ++--
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/llvm/lib/Target/AMDGPU/SIISelLowering.cpp b/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
index 3281eabcd4adb..b9023b6d7a3a6 100644
--- a/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
+++ b/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
@@ -4481,6 +4481,8 @@ SDValue SITargetLowering::lowerSET_FPENV(SDValue Op, SelectionDAG &DAG) const {
Register SITargetLowering::getRegisterByName(const char *RegName, LLT VT,
const MachineFunction &MF) const {
+ const Function &Fn = MF.getFunction();
+
Register Reg = StringSwitch<Register>(RegName)
.Case("m0", AMDGPU::M0)
.Case("exec", AMDGPU::EXEC)
@@ -4498,8 +4500,8 @@ Register SITargetLowering::getRegisterByName(const char *RegName, LLT VT,
if (!Subtarget->hasFlatScrRegister() &&
Subtarget->getRegisterInfo()->regsOverlap(Reg, AMDGPU::FLAT_SCR)) {
- report_fatal_error(Twine("invalid register \"" + StringRef(RegName) +
- "\" for subtarget."));
+ Fn.getContext().emitError(Twine("invalid register \"" + StringRef(RegName) +
+ "\" for subtarget."));
}
switch (Reg) {
diff --git a/llvm/test/CodeGen/AMDGPU/read-register-invalid-subtarget.ll b/llvm/test/CodeGen/AMDGPU/read-register-invalid-subtarget.ll
index 0e9ea0c341cd3..a91bba41bed4f 100644
--- a/llvm/test/CodeGen/AMDGPU/read-register-invalid-subtarget.ll
+++ b/llvm/test/CodeGen/AMDGPU/read-register-invalid-subtarget.ll
@@ -1,6 +1,6 @@
-; RUN: not --crash llc -mtriple=amdgcn -mcpu=tahiti -verify-machineinstrs < %s 2>&1 | FileCheck %s
+; RUN: not llc -mtriple=amdgcn -mcpu=tahiti -verify-machineinstrs < %s 2>&1 | FileCheck %s
-; CHECK: invalid register "flat_scratch_lo" for subtarget.
+; CHECK: error: invalid register "flat_scratch_lo" for subtarget.
declare i32 @llvm.read_register.i32(metadata) #0
>From b7d0c9b9d8e2b5c5d6677e368e3cdaf438df294e Mon Sep 17 00:00:00 2001
From: Craig Topper <craig.topper at sifive.com>
Date: Sat, 21 Jun 2025 18:56:00 -0700
Subject: [PATCH 28/40] [SelectionDAG][RISCV] Treat zext nneg as sext in
PromoteIntOp_ZERO_EXTEND if the promoted input is sign extended. (#145120)
If the zext has the nneg flag and we can prove the promoted input
is sign extended, we can avoid generating an AND that we might not
be able to remove. RISC-V emits a lot of sext_inreg operations during
i32->i64 promotion that makes this likely.
I've restricted this to the case where the promoted type is the same
as the result type so we don't need to create an additional extend.
I've also restricted it to cases where the target has stated a
preference for sext like i32->i64 on RV64. This is largely to avoid
wasting time in computeNumSignBits until we have a test case that
benefits.
---
.../SelectionDAG/LegalizeIntegerTypes.cpp | 19 ++++-
llvm/test/CodeGen/RISCV/shifts.ll | 75 +++++++++++++++++++
2 files changed, 91 insertions(+), 3 deletions(-)
diff --git a/llvm/lib/CodeGen/SelectionDAG/LegalizeIntegerTypes.cpp b/llvm/lib/CodeGen/SelectionDAG/LegalizeIntegerTypes.cpp
index dd64676222055..dd0412460f4e1 100644
--- a/llvm/lib/CodeGen/SelectionDAG/LegalizeIntegerTypes.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/LegalizeIntegerTypes.cpp
@@ -2605,9 +2605,22 @@ SDValue DAGTypeLegalizer::PromoteIntOp_STRICT_UINT_TO_FP(SDNode *N) {
SDValue DAGTypeLegalizer::PromoteIntOp_ZERO_EXTEND(SDNode *N) {
SDLoc dl(N);
- SDValue Op = GetPromotedInteger(N->getOperand(0));
- Op = DAG.getNode(ISD::ANY_EXTEND, dl, N->getValueType(0), Op);
- return DAG.getZeroExtendInReg(Op, dl, N->getOperand(0).getValueType());
+ SDValue Src = N->getOperand(0);
+ SDValue Op = GetPromotedInteger(Src);
+ EVT VT = N->getValueType(0);
+
+ // If this zext has the nneg flag and the target prefers sext, see if the
+ // promoted input is already sign extended.
+ // TODO: Should we have some way to set nneg on ISD::AND instead?
+ if (N->getFlags().hasNonNeg() && Op.getValueType() == VT &&
+ TLI.isSExtCheaperThanZExt(Src.getValueType(), VT)) {
+ unsigned OpEffectiveBits = DAG.ComputeMaxSignificantBits(Op);
+ if (OpEffectiveBits <= Src.getScalarValueSizeInBits())
+ return Op;
+ }
+
+ Op = DAG.getNode(ISD::ANY_EXTEND, dl, VT, Op);
+ return DAG.getZeroExtendInReg(Op, dl, Src.getValueType());
}
SDValue DAGTypeLegalizer::PromoteIntOp_VP_ZERO_EXTEND(SDNode *N) {
diff --git a/llvm/test/CodeGen/RISCV/shifts.ll b/llvm/test/CodeGen/RISCV/shifts.ll
index 32a037918a5a7..7ca1ee1cba2f8 100644
--- a/llvm/test/CodeGen/RISCV/shifts.ll
+++ b/llvm/test/CodeGen/RISCV/shifts.ll
@@ -779,3 +779,78 @@ define i128 @shl128_shamt32(i128 %a, i32 signext %b) nounwind {
%1 = shl i128 %a, %zext
ret i128 %1
}
+
+; Do some arithmetic on the i32 shift amount before the zext nneg. This
+; arithmetic will be promoted using a W instruction RV64. Make sure we can use
+; this to avoid an unncessary zext of the shift amount.
+define i128 @shl128_shamt32_arith(i128 %a, i32 signext %b) nounwind {
+; RV32I-LABEL: shl128_shamt32_arith:
+; RV32I: # %bb.0:
+; RV32I-NEXT: addi sp, sp, -32
+; RV32I-NEXT: lw a3, 0(a1)
+; RV32I-NEXT: lw a4, 4(a1)
+; RV32I-NEXT: lw a5, 8(a1)
+; RV32I-NEXT: lw a1, 12(a1)
+; RV32I-NEXT: addi a2, a2, 1
+; RV32I-NEXT: sw zero, 0(sp)
+; RV32I-NEXT: sw zero, 4(sp)
+; RV32I-NEXT: sw zero, 8(sp)
+; RV32I-NEXT: sw zero, 12(sp)
+; RV32I-NEXT: addi a6, sp, 16
+; RV32I-NEXT: srli a7, a2, 3
+; RV32I-NEXT: andi t0, a2, 31
+; RV32I-NEXT: andi a7, a7, 12
+; RV32I-NEXT: sub a6, a6, a7
+; RV32I-NEXT: sw a3, 16(sp)
+; RV32I-NEXT: sw a4, 20(sp)
+; RV32I-NEXT: sw a5, 24(sp)
+; RV32I-NEXT: sw a1, 28(sp)
+; RV32I-NEXT: lw a1, 0(a6)
+; RV32I-NEXT: lw a3, 4(a6)
+; RV32I-NEXT: lw a4, 8(a6)
+; RV32I-NEXT: lw a5, 12(a6)
+; RV32I-NEXT: xori a6, t0, 31
+; RV32I-NEXT: sll a7, a3, a2
+; RV32I-NEXT: srli t0, a1, 1
+; RV32I-NEXT: sll a5, a5, a2
+; RV32I-NEXT: sll a1, a1, a2
+; RV32I-NEXT: sll a2, a4, a2
+; RV32I-NEXT: srli a3, a3, 1
+; RV32I-NEXT: srli a4, a4, 1
+; RV32I-NEXT: srl t0, t0, a6
+; RV32I-NEXT: srl a3, a3, a6
+; RV32I-NEXT: srl a4, a4, a6
+; RV32I-NEXT: or a6, a7, t0
+; RV32I-NEXT: or a2, a2, a3
+; RV32I-NEXT: or a4, a5, a4
+; RV32I-NEXT: sw a1, 0(a0)
+; RV32I-NEXT: sw a6, 4(a0)
+; RV32I-NEXT: sw a2, 8(a0)
+; RV32I-NEXT: sw a4, 12(a0)
+; RV32I-NEXT: addi sp, sp, 32
+; RV32I-NEXT: ret
+;
+; RV64I-LABEL: shl128_shamt32_arith:
+; RV64I: # %bb.0:
+; RV64I-NEXT: addiw a4, a2, 1
+; RV64I-NEXT: addi a3, a4, -64
+; RV64I-NEXT: sll a2, a0, a4
+; RV64I-NEXT: bltz a3, .LBB17_2
+; RV64I-NEXT: # %bb.1:
+; RV64I-NEXT: mv a1, a2
+; RV64I-NEXT: j .LBB17_3
+; RV64I-NEXT: .LBB17_2:
+; RV64I-NEXT: sll a1, a1, a4
+; RV64I-NEXT: srli a0, a0, 1
+; RV64I-NEXT: not a4, a4
+; RV64I-NEXT: srl a0, a0, a4
+; RV64I-NEXT: or a1, a1, a0
+; RV64I-NEXT: .LBB17_3:
+; RV64I-NEXT: srai a0, a3, 63
+; RV64I-NEXT: and a0, a0, a2
+; RV64I-NEXT: ret
+ %c = add i32 %b, 1
+ %zext = zext nneg i32 %c to i128
+ %1 = shl i128 %a, %zext
+ ret i128 %1
+}
>From fa0b84f23c08994ce4e780f7c406e981d37599b1 Mon Sep 17 00:00:00 2001
From: Stanislav Mekhanoshin <rampitec at users.noreply.github.com>
Date: Sat, 21 Jun 2025 21:42:09 -0700
Subject: [PATCH 29/40] [AMDGPU] Rename call instructions from b64 to i64
(#145103)
These get renamed in gfx1250 and on from B64 to I64:
S_CALL_I64
S_GET_PC_I64
S_RFE_I64
S_SET_PC_I64
S_SWAP_PC_I64
---
llvm/lib/Target/AMDGPU/AMDGPU.td | 20 +++++++
.../Disassembler/AMDGPUDisassembler.cpp | 8 +++
.../AMDGPU/Disassembler/AMDGPUDisassembler.h | 1 +
llvm/lib/Target/AMDGPU/SIDefines.h | 1 +
llvm/lib/Target/AMDGPU/SIInstrInfo.td | 3 ++
llvm/lib/Target/AMDGPU/SOPInstructions.td | 26 +++++++++
.../Target/AMDGPU/Utils/AMDGPUBaseInfo.cpp | 4 ++
llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.h | 1 +
llvm/test/MC/AMDGPU/gfx1250_asm_sop1.s | 30 +++++++++++
llvm/test/MC/AMDGPU/gfx1250_asm_sopk.s | 9 ++++
.../Disassembler/AMDGPU/gfx12_dasm_sop1.txt | 54 ++++++++++++-------
.../Disassembler/AMDGPU/gfx12_dasm_sopk.txt | 20 ++++---
12 files changed, 152 insertions(+), 25 deletions(-)
create mode 100644 llvm/test/MC/AMDGPU/gfx1250_asm_sop1.s
create mode 100644 llvm/test/MC/AMDGPU/gfx1250_asm_sopk.s
diff --git a/llvm/lib/Target/AMDGPU/AMDGPU.td b/llvm/lib/Target/AMDGPU/AMDGPU.td
index 4b17e1c808b50..ab83cf9e7395a 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPU.td
+++ b/llvm/lib/Target/AMDGPU/AMDGPU.td
@@ -2244,10 +2244,30 @@ def isGFX12Only :
Predicate<"Subtarget->getGeneration() == AMDGPUSubtarget::GFX12">,
AssemblerPredicate<(all_of FeatureGFX12Insts)>;
+def isGFX12Not12_50 :
+ Predicate<"Subtarget->getGeneration() == AMDGPUSubtarget::GFX12 && !Subtarget->hasGFX1250Insts()">,
+ AssemblerPredicate<(all_of FeatureGFX12Insts, (not FeatureGFX1250Insts))>;
+
def isGFX12Plus :
Predicate<"Subtarget->getGeneration() >= AMDGPUSubtarget::GFX12">,
AssemblerPredicate<(all_of FeatureGFX12Insts)>;
+def isGFX12PlusNot12_50 :
+ Predicate<"Subtarget->getGeneration() >= AMDGPUSubtarget::GFX12 && !Subtarget->hasGFX1250Insts()">,
+ AssemblerPredicate<(all_of FeatureGFX12Insts, (not FeatureGFX1250Insts))>;
+
+def isGFX125xOnly :
+ Predicate<"Subtarget->hasGFX1250Insts()">,
+ AssemblerPredicate<(all_of FeatureGFX1250Insts)>;
+
+def isGFX1250Plus :
+ Predicate<"Subtarget->hasGFX1250Insts()">,
+ AssemblerPredicate<(all_of FeatureGFX1250Insts)>;
+
+def isNotGFX1250Plus :
+ Predicate<"!Subtarget->hasGFX1250Insts()">,
+ AssemblerPredicate<(all_of (not FeatureGFX1250Insts))>;
+
def HasMinimum3Maximum3F32 :
Predicate<"Subtarget->hasMinimum3Maximum3F32()">,
AssemblerPredicate<(all_of FeatureMinimum3Maximum3F32)>;
diff --git a/llvm/lib/Target/AMDGPU/Disassembler/AMDGPUDisassembler.cpp b/llvm/lib/Target/AMDGPU/Disassembler/AMDGPUDisassembler.cpp
index 349e408b79658..2e891419f0e39 100644
--- a/llvm/lib/Target/AMDGPU/Disassembler/AMDGPUDisassembler.cpp
+++ b/llvm/lib/Target/AMDGPU/Disassembler/AMDGPUDisassembler.cpp
@@ -717,6 +717,12 @@ DecodeStatus AMDGPUDisassembler::getInstruction(MCInst &MI, uint64_t &Size,
Address, CS))
break;
+ // FIXME: Should use DecoderTableGFX1250_FAKE1632, but it is not generated
+ // yet.
+ if (isGFX1250() &&
+ tryDecodeInst(DecoderTableGFX125032, MI, DW, Address, CS))
+ break;
+
if (isGFX12() &&
tryDecodeInst(DecoderTableGFX1232, DecoderTableGFX12_FAKE1632, MI, DW,
Address, CS))
@@ -2022,6 +2028,8 @@ bool AMDGPUDisassembler::isGFX12Plus() const {
return AMDGPU::isGFX12Plus(STI);
}
+bool AMDGPUDisassembler::isGFX1250() const { return AMDGPU::isGFX1250(STI); }
+
bool AMDGPUDisassembler::hasArchitectedFlatScratch() const {
return STI.hasFeature(AMDGPU::FeatureArchitectedFlatScratch);
}
diff --git a/llvm/lib/Target/AMDGPU/Disassembler/AMDGPUDisassembler.h b/llvm/lib/Target/AMDGPU/Disassembler/AMDGPUDisassembler.h
index a82dee430e01d..67156b4a3a188 100644
--- a/llvm/lib/Target/AMDGPU/Disassembler/AMDGPUDisassembler.h
+++ b/llvm/lib/Target/AMDGPU/Disassembler/AMDGPUDisassembler.h
@@ -230,6 +230,7 @@ class AMDGPUDisassembler : public MCDisassembler {
bool isGFX11Plus() const;
bool isGFX12() const;
bool isGFX12Plus() const;
+ bool isGFX1250() const;
bool hasArchitectedFlatScratch() const;
bool hasKernargPreload() const;
diff --git a/llvm/lib/Target/AMDGPU/SIDefines.h b/llvm/lib/Target/AMDGPU/SIDefines.h
index 0f603a43fd626..baf74dbdde20e 100644
--- a/llvm/lib/Target/AMDGPU/SIDefines.h
+++ b/llvm/lib/Target/AMDGPU/SIDefines.h
@@ -45,6 +45,7 @@ enum {
GFX940 = 9,
GFX11 = 10,
GFX12 = 11,
+ GFX1250 = 12,
};
}
diff --git a/llvm/lib/Target/AMDGPU/SIInstrInfo.td b/llvm/lib/Target/AMDGPU/SIInstrInfo.td
index 768f57c469d64..53c0635f02bf2 100644
--- a/llvm/lib/Target/AMDGPU/SIInstrInfo.td
+++ b/llvm/lib/Target/AMDGPU/SIInstrInfo.td
@@ -31,6 +31,7 @@ def SIEncodingFamily {
int GFX940 = 9;
int GFX11 = 10;
int GFX12 = 11;
+ int GFX1250 = 12;
}
//===----------------------------------------------------------------------===//
@@ -44,6 +45,8 @@ class GFXGen<Predicate pred, string dn, string suffix, int sub> {
int Subtarget = sub;
}
+def GFX1250Gen : GFXGen<isGFX125xOnly, "GFX1250", "_gfx1250", SIEncodingFamily.GFX1250>;
+def GFX12Not12_50Gen : GFXGen<isGFX12Not12_50, "GFX12", "_gfx12", SIEncodingFamily.GFX12>;
def GFX12Gen : GFXGen<isGFX12Only, "GFX12", "_gfx12", SIEncodingFamily.GFX12>;
def GFX11Gen : GFXGen<isGFX11Only, "GFX11", "_gfx11", SIEncodingFamily.GFX11>;
def GFX10Gen : GFXGen<isGFX10Only, "GFX10", "_gfx10", SIEncodingFamily.GFX10>;
diff --git a/llvm/lib/Target/AMDGPU/SOPInstructions.td b/llvm/lib/Target/AMDGPU/SOPInstructions.td
index 3f2e764f29268..8c739c2760b17 100644
--- a/llvm/lib/Target/AMDGPU/SOPInstructions.td
+++ b/llvm/lib/Target/AMDGPU/SOPInstructions.td
@@ -2008,6 +2008,15 @@ multiclass SOP1_IMM_Real_gfx12<bits<8> op> {
multiclass SOP1_Real_gfx11_gfx12<bits<8> op, string name = !tolower(NAME)> :
SOP1_Real_gfx11<op, name>, SOP1_Real_gfx12<op, name>;
+multiclass SOP1_Real_gfx1250<bits<8> op, string name = !tolower(NAME)> {
+ defvar ps = !cast<SOP1_Pseudo>(NAME);
+ def _gfx1250 : SOP1_Real<op, ps, name>,
+ Select<GFX1250Gen, ps.PseudoInstr>;
+ if !ne(ps.Mnemonic, name) then
+ let AssemblerPredicate = isGFX1250Plus in
+ def : AMDGPUMnemonicAlias<ps.Mnemonic, name>;
+}
+
defm S_MOV_B32 : SOP1_Real_gfx11_gfx12<0x000>;
defm S_MOV_B64 : SOP1_Real_gfx11_gfx12<0x001>;
defm S_CMOV_B32 : SOP1_Real_gfx11_gfx12<0x002>;
@@ -2066,10 +2075,16 @@ defm S_MOVRELS_B64 : SOP1_Real_gfx11_gfx12<0x041>;
defm S_MOVRELD_B32 : SOP1_Real_gfx11_gfx12<0x042>;
defm S_MOVRELD_B64 : SOP1_Real_gfx11_gfx12<0x043>;
defm S_MOVRELSD_2_B32 : SOP1_Real_gfx11_gfx12<0x044>;
+let OtherPredicates = [isNotGFX1250Plus] in {
defm S_GETPC_B64 : SOP1_Real_gfx11_gfx12<0x047>;
defm S_SETPC_B64 : SOP1_Real_gfx11_gfx12<0x048>;
defm S_SWAPPC_B64 : SOP1_Real_gfx11_gfx12<0x049>;
defm S_RFE_B64 : SOP1_Real_gfx11_gfx12<0x04a>;
+}
+defm S_GETPC_B64 : SOP1_Real_gfx1250<0x047, "s_get_pc_i64">;
+defm S_SETPC_B64 : SOP1_Real_gfx1250<0x048, "s_set_pc_i64">;
+defm S_SWAPPC_B64 : SOP1_Real_gfx1250<0x049, "s_swap_pc_i64">;
+defm S_RFE_B64 : SOP1_Real_gfx1250<0x04a, "s_rfe_i64">;
defm S_SENDMSG_RTN_B32 : SOP1_Real_gfx11_gfx12<0x04c>;
defm S_SENDMSG_RTN_B64 : SOP1_Real_gfx11_gfx12<0x04d>;
defm S_BARRIER_SIGNAL_M0 : SOP1_M0_Real_gfx12<0x04e>;
@@ -2444,10 +2459,21 @@ multiclass SOPK_Real32_gfx11_gfx12<bits<5> op> :
multiclass SOPK_Real64_gfx11_gfx12<bits<5> op> :
SOPK_Real64_gfx11<op>, SOPK_Real64_gfx12<op>;
+multiclass SOPK_Real32_gfx1250<bits<5> op, string name = !tolower(NAME)> {
+ defvar ps = !cast<SOPK_Pseudo>(NAME);
+ def _gfx1250 : SOPK_Real32<op, ps, name>,
+ Select<GFX1250Gen, ps.PseudoInstr>;
+ if !ne(ps.Mnemonic, name) then
+ let AssemblerPredicate = isGFX1250Plus in
+ def : AMDGPUMnemonicAlias<ps.Mnemonic, name>;
+}
+
defm S_GETREG_B32 : SOPK_Real32_gfx11_gfx12<0x011>;
defm S_SETREG_B32 : SOPK_Real32_gfx11_gfx12<0x012>;
defm S_SETREG_IMM32_B32 : SOPK_Real64_gfx11_gfx12<0x013>;
+let OtherPredicates = [isNotGFX1250Plus] in
defm S_CALL_B64 : SOPK_Real32_gfx11_gfx12<0x014>;
+defm S_CALL_B64 : SOPK_Real32_gfx1250<0x014, "s_call_i64">;
defm S_SUBVECTOR_LOOP_BEGIN : SOPK_Real32_gfx11<0x016>;
defm S_SUBVECTOR_LOOP_END : SOPK_Real32_gfx11<0x017>;
defm S_WAITCNT_VSCNT : SOPK_Real32_gfx11<0x018>;
diff --git a/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.cpp b/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.cpp
index c0cd43a9c35df..47d213d28ff7e 100644
--- a/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.cpp
+++ b/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.cpp
@@ -2233,6 +2233,10 @@ bool isGFX12Plus(const MCSubtargetInfo &STI) { return isGFX12(STI); }
bool isNotGFX12Plus(const MCSubtargetInfo &STI) { return !isGFX12Plus(STI); }
+bool isGFX1250(const MCSubtargetInfo &STI) {
+ return STI.getFeatureBits()[AMDGPU::FeatureGFX1250Insts];
+}
+
bool isNotGFX11Plus(const MCSubtargetInfo &STI) { return !isGFX11Plus(STI); }
bool isNotGFX10Plus(const MCSubtargetInfo &STI) {
diff --git a/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.h b/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.h
index 975a8908059c1..aa5406370d84b 100644
--- a/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.h
+++ b/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.h
@@ -1435,6 +1435,7 @@ bool isGFX11(const MCSubtargetInfo &STI);
bool isGFX11Plus(const MCSubtargetInfo &STI);
bool isGFX12(const MCSubtargetInfo &STI);
bool isGFX12Plus(const MCSubtargetInfo &STI);
+bool isGFX1250(const MCSubtargetInfo &STI);
bool isNotGFX12Plus(const MCSubtargetInfo &STI);
bool isNotGFX11Plus(const MCSubtargetInfo &STI);
bool isGCN3Encoding(const MCSubtargetInfo &STI);
diff --git a/llvm/test/MC/AMDGPU/gfx1250_asm_sop1.s b/llvm/test/MC/AMDGPU/gfx1250_asm_sop1.s
new file mode 100644
index 0000000000000..95a9268112920
--- /dev/null
+++ b/llvm/test/MC/AMDGPU/gfx1250_asm_sop1.s
@@ -0,0 +1,30 @@
+// RUN: llvm-mc -triple=amdgcn -show-encoding -mcpu=gfx1250 %s | FileCheck --check-prefix=GFX1250 %s
+// RUN: not llvm-mc -triple=amdgcn -mcpu=gfx1200 -show-encoding %s 2>&1 | FileCheck --check-prefix=GFX12-ERR --implicit-check-not=error: --strict-whitespace %s
+
+s_get_pc_i64 s[2:3]
+// GFX1250: s_get_pc_i64 s[2:3] ; encoding: [0x00,0x47,0x82,0xbe]
+// GFX12-ERR: :[[@LINE-2]]:{{[0-9]+}}: error: instruction not supported on this GPU
+
+s_getpc_b64 s[2:3]
+// GFX1250: s_get_pc_i64 s[2:3] ; encoding: [0x00,0x47,0x82,0xbe]
+
+s_set_pc_i64 s[2:3]
+// GFX1250: s_set_pc_i64 s[2:3] ; encoding: [0x02,0x48,0x80,0xbe]
+// GFX12-ERR: :[[@LINE-2]]:{{[0-9]+}}: error: instruction not supported on this GPU
+
+s_setpc_b64 s[2:3]
+// GFX1250: s_set_pc_i64 s[2:3] ; encoding: [0x02,0x48,0x80,0xbe]
+
+s_swap_pc_i64 s[2:3], 10
+// GFX1250: s_swap_pc_i64 s[2:3], 10 ; encoding: [0x8a,0x49,0x82,0xbe]
+// GFX12-ERR: :[[@LINE-2]]:{{[0-9]+}}: error: instruction not supported on this GPU
+
+s_swappc_b64 s[2:3], 10
+// GFX1250: s_swap_pc_i64 s[2:3], 10 ; encoding: [0x8a,0x49,0x82,0xbe]
+
+s_rfe_i64 s[2:3]
+// GFX1250: s_rfe_i64 s[2:3] ; encoding: [0x02,0x4a,0x80,0xbe]
+// GFX12-ERR: :[[@LINE-2]]:{{[0-9]+}}: error: instruction not supported on this GPU
+
+s_rfe_b64 s[2:3]
+// GFX1250: s_rfe_i64 s[2:3] ; encoding: [0x02,0x4a,0x80,0xbe]
diff --git a/llvm/test/MC/AMDGPU/gfx1250_asm_sopk.s b/llvm/test/MC/AMDGPU/gfx1250_asm_sopk.s
new file mode 100644
index 0000000000000..0945e0dcc9b85
--- /dev/null
+++ b/llvm/test/MC/AMDGPU/gfx1250_asm_sopk.s
@@ -0,0 +1,9 @@
+// RUN: llvm-mc -triple=amdgcn -show-encoding -mcpu=gfx1250 %s | FileCheck --check-prefix=GFX1250 %s
+// RUN: not llvm-mc -triple=amdgcn -mcpu=gfx1200 -show-encoding %s 2>&1 | FileCheck --check-prefixes=GFX12-ERR --implicit-check-not=error: -strict-whitespace %s
+
+s_call_i64 s[0:1], 4660
+// GFX1250: s_call_i64 s[0:1], 4660 ; encoding: [0x34,0x12,0x00,0xba]
+// GFX12-ERR: :[[@LINE-2]]:{{[0-9]+}}: error: instruction not supported on this GPU
+
+s_call_b64 s[0:1], 4660
+// GFX1250: s_call_i64 s[0:1], 4660 ; encoding: [0x34,0x12,0x00,0xba]
diff --git a/llvm/test/MC/Disassembler/AMDGPU/gfx12_dasm_sop1.txt b/llvm/test/MC/Disassembler/AMDGPU/gfx12_dasm_sop1.txt
index 31f61e48126c1..dd3588d5da015 100644
--- a/llvm/test/MC/Disassembler/AMDGPU/gfx12_dasm_sop1.txt
+++ b/llvm/test/MC/Disassembler/AMDGPU/gfx12_dasm_sop1.txt
@@ -1,5 +1,6 @@
# NOTE: Assertions have been autogenerated by utils/update_mc_test_checks.py UTC_ARGS: --version 5
-# RUN: llvm-mc -triple=amdgcn -mcpu=gfx1200 -disassemble -show-encoding < %s | FileCheck -check-prefixes=GFX12 %s
+# RUN: llvm-mc -triple=amdgcn -mcpu=gfx1200 -disassemble -show-encoding < %s | FileCheck -check-prefixes=GFX12,GFX1200 %s
+# RUN: llvm-mc -triple=amdgcn -mcpu=gfx1250 -disassemble -show-encoding < %s | FileCheck -check-prefixes=GFX12,GFX1250 %s
0xff,0x53,0x80,0xbe,0x35,0x12,0x00,0x00
# GFX12: s_alloc_vgpr 0x1235 ; encoding: [0xff,0x53,0x80,0xbe,0x35,0x12,0x00,0x00]
@@ -2270,16 +2271,20 @@
# GFX12: s_ctz_i32_b64 vcc_lo, s[2:3] ; encoding: [0x02,0x09,0xea,0xbe]
0x00,0x47,0xfe,0xbe
-# GFX12: s_getpc_b64 exec ; encoding: [0x00,0x47,0xfe,0xbe]
+# GFX1200: s_getpc_b64 exec ; encoding: [0x00,0x47,0xfe,0xbe]
+# GFX1250: s_get_pc_i64 exec ; encoding: [0x00,0x47,0xfe,0xbe]
0x00,0x47,0x80,0xbe
-# GFX12: s_getpc_b64 s[0:1] ; encoding: [0x00,0x47,0x80,0xbe]
+# GFX1200: s_getpc_b64 s[0:1] ; encoding: [0x00,0x47,0x80,0xbe]
+# GFX1250: s_get_pc_i64 s[0:1] ; encoding: [0x00,0x47,0x80,0xbe]
0x00,0x47,0xe8,0xbe
-# GFX12: s_getpc_b64 s[104:105] ; encoding: [0x00,0x47,0xe8,0xbe]
+# GFX1200: s_getpc_b64 s[104:105] ; encoding: [0x00,0x47,0xe8,0xbe]
+# GFX1250: s_get_pc_i64 s[104:105] ; encoding: [0x00,0x47,0xe8,0xbe]
0x00,0x47,0xea,0xbe
-# GFX12: s_getpc_b64 vcc ; encoding: [0x00,0x47,0xea,0xbe]
+# GFX1200: s_getpc_b64 vcc ; encoding: [0x00,0x47,0xea,0xbe]
+# GFX1250: s_get_pc_i64 vcc ; encoding: [0x00,0x47,0xea,0xbe]
0x01,0x00,0xff,0xbe
# GFX12: s_mov_b32 exec_hi, s1 ; encoding: [0x01,0x00,0xff,0xbe]
@@ -3218,13 +3223,16 @@
# GFX12: s_quadmask_b64 vcc, s[2:3] ; encoding: [0x02,0x1b,0xea,0xbe]
0x00,0x4a,0x80,0xbe
-# GFX12: s_rfe_b64 s[0:1] ; encoding: [0x00,0x4a,0x80,0xbe]
+# GFX1200: s_rfe_b64 s[0:1] ; encoding: [0x00,0x4a,0x80,0xbe]
+# GFX1250: s_rfe_i64 s[0:1] ; encoding: [0x00,0x4a,0x80,0xbe]
0x68,0x4a,0x80,0xbe
-# GFX12: s_rfe_b64 s[104:105] ; encoding: [0x68,0x4a,0x80,0xbe]
+# GFX1200: s_rfe_b64 s[104:105] ; encoding: [0x68,0x4a,0x80,0xbe]
+# GFX1250: s_rfe_i64 s[104:105] ; encoding: [0x68,0x4a,0x80,0xbe]
0x6a,0x4a,0x80,0xbe
-# GFX12: s_rfe_b64 vcc ; encoding: [0x6a,0x4a,0x80,0xbe]
+# GFX1200: s_rfe_b64 vcc ; encoding: [0x6a,0x4a,0x80,0xbe]
+# GFX1250: s_rfe_i64 vcc ; encoding: [0x6a,0x4a,0x80,0xbe]
0x00,0x4c,0x81,0xbe
# GFX12: s_sendmsg_rtn_b32 s1, sendmsg(0, 0, 0) ; encoding: [0x00,0x4c,0x81,0xbe]
@@ -3269,16 +3277,20 @@
# GFX12: s_sendmsg_rtn_b32 s0, sendmsg(MSG_RTN_GET_SE_AID_ID) ; encoding: [0x87,0x4c,0x80,0xbe]
0x00,0x48,0x80,0xbe
-# GFX12: s_setpc_b64 s[0:1] ; encoding: [0x00,0x48,0x80,0xbe]
+# GFX1200: s_setpc_b64 s[0:1] ; encoding: [0x00,0x48,0x80,0xbe]
+# GFX1250: s_set_pc_i64 s[0:1] ; encoding: [0x00,0x48,0x80,0xbe]
0x68,0x48,0x80,0xbe
-# GFX12: s_setpc_b64 s[104:105] ; encoding: [0x68,0x48,0x80,0xbe]
+# GFX1200: s_setpc_b64 s[104:105] ; encoding: [0x68,0x48,0x80,0xbe]
+# GFX1250: s_set_pc_i64 s[104:105] ; encoding: [0x68,0x48,0x80,0xbe]
0x6a,0x48,0x80,0xbe
-# GFX12: s_setpc_b64 vcc ; encoding: [0x6a,0x48,0x80,0xbe]
+# GFX1200: s_setpc_b64 vcc ; encoding: [0x6a,0x48,0x80,0xbe]
+# GFX1250: s_set_pc_i64 vcc ; encoding: [0x6a,0x48,0x80,0xbe]
0xcb,0x48,0xf5,0xbe
-# GFX12: s_setpc_b64 -11/*Invalid immediate*/ ; encoding: [0xf5,0x48,0x80,0xbe]
+# GFX1200: s_setpc_b64 -11/*Invalid immediate*/ ; encoding: [0xf5,0x48,0x80,0xbe]
+# GFX1250: s_set_pc_i64 -11/*Invalid immediate*/ ; encoding: [0xf5,0x48,0x80,0xbe]
0x01,0x0f,0xff,0xbe
# GFX12: s_sext_i32_i16 exec_hi, s1 ; encoding: [0x01,0x0f,0xff,0xbe]
@@ -3401,22 +3413,28 @@
# GFX12: s_sext_i32_i8 vcc_lo, s1 ; encoding: [0x01,0x0e,0xea,0xbe]
0x66,0x49,0x80,0xbe
-# GFX12: s_swappc_b64 s[0:1], s[102:103] ; encoding: [0x66,0x49,0x80,0xbe]
+# GFX1200: s_swappc_b64 s[0:1], s[102:103] ; encoding: [0x66,0x49,0x80,0xbe]
+# GFX1250: s_swap_pc_i64 s[0:1], s[102:103] ; encoding: [0x66,0x49,0x80,0xbe]
0x02,0x49,0x80,0xbe
-# GFX12: s_swappc_b64 s[0:1], s[2:3] ; encoding: [0x02,0x49,0x80,0xbe]
+# GFX1200: s_swappc_b64 s[0:1], s[2:3] ; encoding: [0x02,0x49,0x80,0xbe]
+# GFX1250: s_swap_pc_i64 s[0:1], s[2:3] ; encoding: [0x02,0x49,0x80,0xbe]
0x6a,0x49,0x80,0xbe
-# GFX12: s_swappc_b64 s[0:1], vcc ; encoding: [0x6a,0x49,0x80,0xbe]
+# GFX1200: s_swappc_b64 s[0:1], vcc ; encoding: [0x6a,0x49,0x80,0xbe]
+# GFX1250: s_swap_pc_i64 s[0:1], vcc ; encoding: [0x6a,0x49,0x80,0xbe]
0x66,0x49,0xe8,0xbe
-# GFX12: s_swappc_b64 s[104:105], s[102:103] ; encoding: [0x66,0x49,0xe8,0xbe]
+# GFX1200: s_swappc_b64 s[104:105], s[102:103] ; encoding: [0x66,0x49,0xe8,0xbe]
+# GFX1250: s_swap_pc_i64 s[104:105], s[102:103] ; encoding: [0x66,0x49,0xe8,0xbe]
0x02,0x49,0xe8,0xbe
-# GFX12: s_swappc_b64 s[104:105], s[2:3] ; encoding: [0x02,0x49,0xe8,0xbe]
+# GFX1200: s_swappc_b64 s[104:105], s[2:3] ; encoding: [0x02,0x49,0xe8,0xbe]
+# GFX1250: s_swap_pc_i64 s[104:105], s[2:3] ; encoding: [0x02,0x49,0xe8,0xbe]
0x02,0x49,0xea,0xbe
-# GFX12: s_swappc_b64 vcc, s[2:3] ; encoding: [0x02,0x49,0xea,0xbe]
+# GFX1200: s_swappc_b64 vcc, s[2:3] ; encoding: [0x02,0x49,0xea,0xbe]
+# GFX1250: s_swap_pc_i64 vcc, s[2:3] ; encoding: [0x02,0x49,0xea,0xbe]
0x01,0x1c,0xff,0xbe
# GFX12: s_wqm_b32 exec_hi, s1 ; encoding: [0x01,0x1c,0xff,0xbe]
diff --git a/llvm/test/MC/Disassembler/AMDGPU/gfx12_dasm_sopk.txt b/llvm/test/MC/Disassembler/AMDGPU/gfx12_dasm_sopk.txt
index 3e323ed69216c..49fa263f6bbf8 100644
--- a/llvm/test/MC/Disassembler/AMDGPU/gfx12_dasm_sopk.txt
+++ b/llvm/test/MC/Disassembler/AMDGPU/gfx12_dasm_sopk.txt
@@ -1,5 +1,6 @@
-# RUN: llvm-mc -triple=amdgcn -mcpu=gfx1200 -disassemble -show-encoding < %s | FileCheck -strict-whitespace -check-prefix=GFX12 %s
-# RUN: llvm-mc -triple=amdgcn -mcpu=gfx1200 -mattr=+wavefrontsize64 -disassemble -show-encoding < %s | FileCheck -strict-whitespace -check-prefix=GFX12 %s
+# RUN: llvm-mc -triple=amdgcn -mcpu=gfx1200 -disassemble -show-encoding < %s | FileCheck -strict-whitespace -check-prefixes=GFX12,GFX1200 %s
+# RUN: llvm-mc -triple=amdgcn -mcpu=gfx1200 -mattr=+WavefrontSize64 -disassemble -show-encoding < %s | FileCheck -strict-whitespace -check-prefixes=GFX1200 %s
+# RUN: llvm-mc -triple=amdgcn -mcpu=gfx1250 -disassemble -show-encoding < %s | FileCheck -strict-whitespace -check-prefixes=GFX12,GFX1250 %s
# GFX12: s_addk_co_i32 exec_hi, 0x1234 ; encoding: [0x34,0x12,0xff,0xb7]
0x34,0x12,0xff,0xb7
@@ -25,19 +26,24 @@
# GFX12: s_addk_co_i32 vcc_lo, 0x1234 ; encoding: [0x34,0x12,0xea,0xb7]
0x34,0x12,0xea,0xb7
-# GFX12: s_call_b64 exec, 4660 ; encoding: [0x34,0x12,0x7e,0xba]
+# GFX1200: s_call_b64 exec, 4660 ; encoding: [0x34,0x12,0x7e,0xba]
+# GFX1250: s_call_i64 exec, 4660 ; encoding: [0x34,0x12,0x7e,0xba]
0x34,0x12,0x7e,0xba
-# GFX12: s_call_b64 s[0:1], 4660 ; encoding: [0x34,0x12,0x00,0xba]
+# GFX1200: s_call_b64 s[0:1], 4660 ; encoding: [0x34,0x12,0x00,0xba]
+# GFX1250: s_call_i64 s[0:1], 4660 ; encoding: [0x34,0x12,0x00,0xba]
0x34,0x12,0x00,0xba
-# GFX12: s_call_b64 s[104:105], 4660 ; encoding: [0x34,0x12,0x68,0xba]
+# GFX1200: s_call_b64 s[104:105], 4660 ; encoding: [0x34,0x12,0x68,0xba]
+# GFX1250: s_call_i64 s[104:105], 4660 ; encoding: [0x34,0x12,0x68,0xba]
0x34,0x12,0x68,0xba
-# GFX12: s_call_b64 vcc, 4660 ; encoding: [0x34,0x12,0x6a,0xba]
+# GFX1200: s_call_b64 vcc, 4660 ; encoding: [0x34,0x12,0x6a,0xba]
+# GFX1250: s_call_i64 vcc, 4660 ; encoding: [0x34,0x12,0x6a,0xba]
0x34,0x12,0x6a,0xba
-# GFX12: s_call_b64 null, 4660 ; encoding: [0x34,0x12,0x7c,0xba]
+# GFX1200: s_call_b64 null, 4660 ; encoding: [0x34,0x12,0x7c,0xba]
+# GFX1250: s_call_i64 null, 4660 ; encoding: [0x34,0x12,0x7c,0xba]
0x34,0x12,0x7c,0xba
# GFX12: s_cmovk_i32 exec_hi, 0x1234 ; encoding: [0x34,0x12,0x7f,0xb1]
>From f51d8730b309c14a78764e1b9a2e112e038ed3a0 Mon Sep 17 00:00:00 2001
From: Iris Shi <0.0 at owo.li>
Date: Sun, 22 Jun 2025 13:32:19 +0800
Subject: [PATCH 30/40] [InstSimplify] Simplify 'x u>= 1' to true when x is
known non-zero (#145204)
---
llvm/lib/Analysis/InstructionSimplify.cpp | 11 +-
llvm/test/Transforms/InstSimplify/umax-1.ll | 120 ++++++++++++++++++++
2 files changed, 127 insertions(+), 4 deletions(-)
create mode 100644 llvm/test/Transforms/InstSimplify/umax-1.ll
diff --git a/llvm/lib/Analysis/InstructionSimplify.cpp b/llvm/lib/Analysis/InstructionSimplify.cpp
index d1ac8d9fbdfd1..cb1dae92faf92 100644
--- a/llvm/lib/Analysis/InstructionSimplify.cpp
+++ b/llvm/lib/Analysis/InstructionSimplify.cpp
@@ -2981,7 +2981,7 @@ static Value *simplifyICmpWithZero(CmpPredicate Pred, Value *LHS, Value *RHS,
}
static Value *simplifyICmpWithConstant(CmpPredicate Pred, Value *LHS,
- Value *RHS, const InstrInfoQuery &IIQ) {
+ Value *RHS, const SimplifyQuery &Q) {
Type *ITy = getCompareTy(RHS); // The return type.
Value *X;
@@ -3007,7 +3007,7 @@ static Value *simplifyICmpWithConstant(CmpPredicate Pred, Value *LHS,
return ConstantInt::getTrue(ITy);
ConstantRange LHS_CR =
- computeConstantRange(LHS, CmpInst::isSigned(Pred), IIQ.UseInstrInfo);
+ computeConstantRange(LHS, CmpInst::isSigned(Pred), Q.IIQ.UseInstrInfo);
if (!LHS_CR.isFullSet()) {
if (RHS_CR.contains(LHS_CR))
return ConstantInt::getTrue(ITy);
@@ -3018,13 +3018,16 @@ static Value *simplifyICmpWithConstant(CmpPredicate Pred, Value *LHS,
// (mul nuw/nsw X, MulC) != C --> true (if C is not a multiple of MulC)
// (mul nuw/nsw X, MulC) == C --> false (if C is not a multiple of MulC)
const APInt *MulC;
- if (IIQ.UseInstrInfo && ICmpInst::isEquality(Pred) &&
+ if (Q.IIQ.UseInstrInfo && ICmpInst::isEquality(Pred) &&
((match(LHS, m_NUWMul(m_Value(), m_APIntAllowPoison(MulC))) &&
*MulC != 0 && C->urem(*MulC) != 0) ||
(match(LHS, m_NSWMul(m_Value(), m_APIntAllowPoison(MulC))) &&
*MulC != 0 && C->srem(*MulC) != 0)))
return ConstantInt::get(ITy, Pred == ICmpInst::ICMP_NE);
+ if (Pred == ICmpInst::ICMP_UGE && C->isOne() && isKnownNonZero(LHS, Q))
+ return ConstantInt::getTrue(ITy);
+
return nullptr;
}
@@ -3776,7 +3779,7 @@ static Value *simplifyICmpInst(CmpPredicate Pred, Value *LHS, Value *RHS,
if (Value *V = simplifyICmpWithZero(Pred, LHS, RHS, Q))
return V;
- if (Value *V = simplifyICmpWithConstant(Pred, LHS, RHS, Q.IIQ))
+ if (Value *V = simplifyICmpWithConstant(Pred, LHS, RHS, Q))
return V;
// If both operands have range metadata, use the metadata
diff --git a/llvm/test/Transforms/InstSimplify/umax-1.ll b/llvm/test/Transforms/InstSimplify/umax-1.ll
new file mode 100644
index 0000000000000..77863acb02327
--- /dev/null
+++ b/llvm/test/Transforms/InstSimplify/umax-1.ll
@@ -0,0 +1,120 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=instsimplify -S | FileCheck %s
+
+define i32 @known_non_zero_by_or(i32 %x, i32 %y) {
+; CHECK-LABEL: define i32 @known_non_zero_by_or(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[COND:%.*]] = icmp ne i32 [[X]], 0
+; CHECK-NEXT: br i1 [[COND]], label %[[IF_THEN:.*]], label %[[IF_ELSE:.*]]
+; CHECK: [[IF_THEN]]:
+; CHECK-NEXT: [[VAL:%.*]] = or i32 [[X]], [[Y]]
+; CHECK-NEXT: ret i32 [[VAL]]
+; CHECK: [[IF_ELSE]]:
+; CHECK-NEXT: ret i32 0
+;
+ %cond = icmp ne i32 %x, 0
+ br i1 %cond, label %if.then, label %if.else
+
+if.then:
+ %val = or i32 %x, %y
+ %max = call i32 @llvm.umax.i32(i32 %val, i32 1)
+ ret i32 %max
+
+if.else:
+ ret i32 0
+}
+
+define i32 @known_non_zero_by_mul(i32 %x) {
+; CHECK-LABEL: define i32 @known_non_zero_by_mul(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[COND:%.*]] = icmp ne i32 [[X]], 0
+; CHECK-NEXT: br i1 [[COND]], label %[[IF_THEN:.*]], label %[[IF_ELSE:.*]]
+; CHECK: [[IF_THEN]]:
+; CHECK-NEXT: [[NONZERO1:%.*]] = mul nuw i32 [[X]], 3
+; CHECK-NEXT: ret i32 [[NONZERO1]]
+; CHECK: [[IF_ELSE]]:
+; CHECK-NEXT: ret i32 0
+;
+ %cond = icmp ne i32 %x, 0
+ br i1 %cond, label %if.then, label %if.else
+
+if.then:
+ %nonzero = mul nuw i32 %x, 3
+ %max = call i32 @llvm.umax.i32(i32 %nonzero, i32 1)
+ ret i32 %max
+
+if.else:
+ ret i32 0
+}
+
+define i32 @known_non_zero_commute(i32 %x, i32 %y) {
+; CHECK-LABEL: define i32 @known_non_zero_commute(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[COND:%.*]] = icmp ne i32 [[X]], 0
+; CHECK-NEXT: br i1 [[COND]], label %[[IF_THEN:.*]], label %[[IF_ELSE:.*]]
+; CHECK: [[IF_THEN]]:
+; CHECK-NEXT: [[VAL:%.*]] = or i32 [[X]], [[Y]]
+; CHECK-NEXT: ret i32 [[VAL]]
+; CHECK: [[IF_ELSE]]:
+; CHECK-NEXT: ret i32 0
+;
+ %cond = icmp ne i32 %x, 0
+ br i1 %cond, label %if.then, label %if.else
+
+if.then:
+ %val = or i32 %x, %y
+ %max = call i32 @llvm.umax.i32(i32 1, i32 %val)
+ ret i32 %max
+
+if.else:
+ ret i32 0
+}
+
+; Negative
+define i32 @umax_ge_2(i32 %x, i32 %y) {
+; CHECK-LABEL: define i32 @umax_ge_2(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[COND:%.*]] = icmp ne i32 [[X]], 0
+; CHECK-NEXT: br i1 [[COND]], label %[[IF_THEN:.*]], label %[[IF_ELSE:.*]]
+; CHECK: [[IF_THEN]]:
+; CHECK-NEXT: [[VAL:%.*]] = or i32 [[X]], [[Y]]
+; CHECK-NEXT: [[MAX:%.*]] = call i32 @llvm.umax.i32(i32 [[VAL]], i32 2)
+; CHECK-NEXT: ret i32 [[MAX]]
+; CHECK: [[IF_ELSE]]:
+; CHECK-NEXT: ret i32 0
+;
+ %cond = icmp ne i32 %x, 0
+ br i1 %cond, label %if.then, label %if.else
+
+if.then:
+ %val = or i32 %x, %y
+ %max = call i32 @llvm.umax.i32(i32 %val, i32 2)
+ ret i32 %max
+
+if.else:
+ ret i32 0
+}
+
+define i32 @unknown_by_and(i32 %x, i32 %y) {
+; CHECK-LABEL: define i32 @unknown_by_and(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[COND:%.*]] = icmp ne i32 [[X]], 0
+; CHECK-NEXT: br i1 [[COND]], label %[[IF_THEN:.*]], label %[[IF_ELSE:.*]]
+; CHECK: [[IF_THEN]]:
+; CHECK-NEXT: [[VAL:%.*]] = and i32 [[X]], [[Y]]
+; CHECK-NEXT: [[MAX:%.*]] = call i32 @llvm.umax.i32(i32 [[VAL]], i32 1)
+; CHECK-NEXT: ret i32 [[MAX]]
+; CHECK: [[IF_ELSE]]:
+; CHECK-NEXT: ret i32 0
+;
+ %cond = icmp ne i32 %x, 0
+ br i1 %cond, label %if.then, label %if.else
+
+if.then:
+ %val = and i32 %x, %y
+ %max = call i32 @llvm.umax.i32(i32 %val, i32 1)
+ ret i32 %max
+
+if.else:
+ ret i32 0
+}
>From cbfec48697adeb5e6f5f35acba73a4a1408aea21 Mon Sep 17 00:00:00 2001
From: Abhishek Kaushik <abhishek.kaushik at intel.com>
Date: Sun, 22 Jun 2025 00:22:57 -0700
Subject: [PATCH 31/40] Revert "[X86][NFC] Use std::move to avoid copy"
(#145215)
Reverts llvm/llvm-project#141455
---
llvm/lib/Target/X86/X86ISelLowering.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp
index 53c0da45f2f66..33083c0eba695 100644
--- a/llvm/lib/Target/X86/X86ISelLowering.cpp
+++ b/llvm/lib/Target/X86/X86ISelLowering.cpp
@@ -41266,7 +41266,7 @@ static SDValue combineX86ShufflesRecursively(
resolveTargetShuffleFromZeroables(OpMask, OpUndef, OpZero,
ResolveKnownZeros);
- Mask = std::move(OpMask);
+ Mask = OpMask;
Ops.append(OpInputs.begin(), OpInputs.end());
} else {
resolveTargetShuffleFromZeroables(OpMask, OpUndef, OpZero);
>From 58b939abe5085a750be844cc3c681b40afe98454 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Sun, 22 Jun 2025 08:45:40 +0100
Subject: [PATCH 32/40] [VPlan] Support matching constants in
narrowInterleaveGroups.
Matching constants can trivially be broadcasted, allow them if the same
constant is used for all recipes in a bundle.
---
.../Transforms/Vectorize/VPlanTransforms.cpp | 12 ++---
...interleave-to-widen-memory-constant-ops.ll | 44 +++++--------------
2 files changed, 19 insertions(+), 37 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp b/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp
index d66733cac4d66..ac6be09ef271d 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp
@@ -3115,7 +3115,7 @@ static bool canNarrowLoad(VPWidenRecipe *WideMember0, VPWidenRecipe *WideMember,
unsigned OpIdx, VPValue *OpV, unsigned Idx) {
auto *DefR = OpV->getDefiningRecipe();
if (!DefR)
- return false;
+ return WideMember0->getOperand(OpIdx) == OpV;
if (auto *W = dyn_cast<VPWidenLoadRecipe>(DefR))
return !W->getMask() && WideMember0->getOperand(OpIdx) == OpV;
@@ -3251,7 +3251,10 @@ void VPlanTransforms::narrowInterleaveGroups(VPlan &Plan, ElementCount VF,
return;
// Convert InterleaveGroup \p R to a single VPWidenLoadRecipe.
- auto NarrowOp = [](VPRecipeBase *R) -> VPValue * {
+ auto NarrowOp = [](VPValue *V) -> VPValue * {
+ auto *R = V->getDefiningRecipe();
+ if (!R)
+ return V;
if (auto *LoadGroup = dyn_cast<VPInterleaveRecipe>(R)) {
// Narrow interleave group to wide load, as transformed VPlan will only
// process one original iteration.
@@ -3280,11 +3283,10 @@ void VPlanTransforms::narrowInterleaveGroups(VPlan &Plan, ElementCount VF,
if (auto *WideMember0 = dyn_cast<VPWidenRecipe>(
StoreGroup->getStoredValues()[0]->getDefiningRecipe())) {
for (unsigned Idx = 0, E = WideMember0->getNumOperands(); Idx != E; ++Idx)
- WideMember0->setOperand(
- Idx, NarrowOp(WideMember0->getOperand(Idx)->getDefiningRecipe()));
+ WideMember0->setOperand(Idx, NarrowOp(WideMember0->getOperand(Idx)));
Res = WideMember0;
} else {
- Res = NarrowOp(StoreGroup->getStoredValues()[0]->getDefiningRecipe());
+ Res = NarrowOp(StoreGroup->getStoredValues()[0]);
}
auto *S = new VPWidenStoreRecipe(
diff --git a/llvm/test/Transforms/LoopVectorize/AArch64/transform-narrow-interleave-to-widen-memory-constant-ops.ll b/llvm/test/Transforms/LoopVectorize/AArch64/transform-narrow-interleave-to-widen-memory-constant-ops.ll
index 7d3b3d86b90d4..94f46bfe39738 100644
--- a/llvm/test/Transforms/LoopVectorize/AArch64/transform-narrow-interleave-to-widen-memory-constant-ops.ll
+++ b/llvm/test/Transforms/LoopVectorize/AArch64/transform-narrow-interleave-to-widen-memory-constant-ops.ll
@@ -13,28 +13,18 @@ define void @test_add_double_same_const_args_1(ptr %res, ptr noalias %A, ptr noa
; CHECK-NEXT: br label %[[VECTOR_BODY:.*]]
; CHECK: [[VECTOR_BODY]]:
; CHECK-NEXT: [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
-; CHECK-NEXT: [[TMP1:%.*]] = add i64 [[INDEX]], 2
+; CHECK-NEXT: [[TMP1:%.*]] = add i64 [[INDEX]], 1
; CHECK-NEXT: [[TMP2:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[A]], i64 [[INDEX]]
; CHECK-NEXT: [[TMP3:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[A]], i64 [[TMP1]]
-; CHECK-NEXT: [[WIDE_VEC:%.*]] = load <4 x double>, ptr [[TMP2]], align 4
-; CHECK-NEXT: [[STRIDED_VEC:%.*]] = shufflevector <4 x double> [[WIDE_VEC]], <4 x double> poison, <2 x i32> <i32 0, i32 2>
-; CHECK-NEXT: [[STRIDED_VEC1:%.*]] = shufflevector <4 x double> [[WIDE_VEC]], <4 x double> poison, <2 x i32> <i32 1, i32 3>
-; CHECK-NEXT: [[WIDE_VEC2:%.*]] = load <4 x double>, ptr [[TMP3]], align 4
-; CHECK-NEXT: [[STRIDED_VEC3:%.*]] = shufflevector <4 x double> [[WIDE_VEC2]], <4 x double> poison, <2 x i32> <i32 0, i32 2>
-; CHECK-NEXT: [[STRIDED_VEC4:%.*]] = shufflevector <4 x double> [[WIDE_VEC2]], <4 x double> poison, <2 x i32> <i32 1, i32 3>
-; CHECK-NEXT: [[TMP4:%.*]] = fadd <2 x double> [[STRIDED_VEC]], splat (double 1.000000e+00)
-; CHECK-NEXT: [[TMP5:%.*]] = fadd <2 x double> [[STRIDED_VEC3]], splat (double 1.000000e+00)
+; CHECK-NEXT: [[STRIDED_VEC1:%.*]] = load <2 x double>, ptr [[TMP2]], align 4
+; CHECK-NEXT: [[STRIDED_VEC4:%.*]] = load <2 x double>, ptr [[TMP3]], align 4
; CHECK-NEXT: [[TMP6:%.*]] = fadd <2 x double> [[STRIDED_VEC1]], splat (double 1.000000e+00)
; CHECK-NEXT: [[TMP7:%.*]] = fadd <2 x double> [[STRIDED_VEC4]], splat (double 1.000000e+00)
; CHECK-NEXT: [[TMP8:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[RES]], i64 [[INDEX]]
; CHECK-NEXT: [[TMP9:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[RES]], i64 [[TMP1]]
-; CHECK-NEXT: [[TMP10:%.*]] = shufflevector <2 x double> [[TMP4]], <2 x double> [[TMP6]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
-; CHECK-NEXT: [[INTERLEAVED_VEC:%.*]] = shufflevector <4 x double> [[TMP10]], <4 x double> poison, <4 x i32> <i32 0, i32 2, i32 1, i32 3>
-; CHECK-NEXT: store <4 x double> [[INTERLEAVED_VEC]], ptr [[TMP8]], align 4
-; CHECK-NEXT: [[TMP11:%.*]] = shufflevector <2 x double> [[TMP5]], <2 x double> [[TMP7]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
-; CHECK-NEXT: [[INTERLEAVED_VEC5:%.*]] = shufflevector <4 x double> [[TMP11]], <4 x double> poison, <4 x i32> <i32 0, i32 2, i32 1, i32 3>
-; CHECK-NEXT: store <4 x double> [[INTERLEAVED_VEC5]], ptr [[TMP9]], align 4
-; CHECK-NEXT: [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 4
+; CHECK-NEXT: store <2 x double> [[TMP6]], ptr [[TMP8]], align 4
+; CHECK-NEXT: store <2 x double> [[TMP7]], ptr [[TMP9]], align 4
+; CHECK-NEXT: [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 2
; CHECK-NEXT: [[TMP12:%.*]] = icmp eq i64 [[INDEX_NEXT]], 100
; CHECK-NEXT: br i1 [[TMP12]], label %[[MIDDLE_BLOCK:.*]], label %[[VECTOR_BODY]], !llvm.loop [[LOOP0:![0-9]+]]
; CHECK: [[MIDDLE_BLOCK]]:
@@ -73,28 +63,18 @@ define void @test_add_double_same_const_args_2(ptr %res, ptr noalias %A, ptr noa
; CHECK-NEXT: br label %[[VECTOR_BODY:.*]]
; CHECK: [[VECTOR_BODY]]:
; CHECK-NEXT: [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
-; CHECK-NEXT: [[TMP1:%.*]] = add i64 [[INDEX]], 2
+; CHECK-NEXT: [[TMP1:%.*]] = add i64 [[INDEX]], 1
; CHECK-NEXT: [[TMP2:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[B]], i64 [[INDEX]]
; CHECK-NEXT: [[TMP3:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[B]], i64 [[TMP1]]
-; CHECK-NEXT: [[WIDE_VEC:%.*]] = load <4 x double>, ptr [[TMP2]], align 4
-; CHECK-NEXT: [[STRIDED_VEC:%.*]] = shufflevector <4 x double> [[WIDE_VEC]], <4 x double> poison, <2 x i32> <i32 0, i32 2>
-; CHECK-NEXT: [[STRIDED_VEC1:%.*]] = shufflevector <4 x double> [[WIDE_VEC]], <4 x double> poison, <2 x i32> <i32 1, i32 3>
-; CHECK-NEXT: [[WIDE_VEC2:%.*]] = load <4 x double>, ptr [[TMP3]], align 4
-; CHECK-NEXT: [[STRIDED_VEC3:%.*]] = shufflevector <4 x double> [[WIDE_VEC2]], <4 x double> poison, <2 x i32> <i32 0, i32 2>
-; CHECK-NEXT: [[STRIDED_VEC4:%.*]] = shufflevector <4 x double> [[WIDE_VEC2]], <4 x double> poison, <2 x i32> <i32 1, i32 3>
-; CHECK-NEXT: [[TMP4:%.*]] = fadd <2 x double> splat (double 1.000000e+00), [[STRIDED_VEC]]
-; CHECK-NEXT: [[TMP5:%.*]] = fadd <2 x double> splat (double 1.000000e+00), [[STRIDED_VEC3]]
+; CHECK-NEXT: [[STRIDED_VEC1:%.*]] = load <2 x double>, ptr [[TMP2]], align 4
+; CHECK-NEXT: [[STRIDED_VEC4:%.*]] = load <2 x double>, ptr [[TMP3]], align 4
; CHECK-NEXT: [[TMP6:%.*]] = fadd <2 x double> splat (double 1.000000e+00), [[STRIDED_VEC1]]
; CHECK-NEXT: [[TMP7:%.*]] = fadd <2 x double> splat (double 1.000000e+00), [[STRIDED_VEC4]]
; CHECK-NEXT: [[TMP8:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[RES]], i64 [[INDEX]]
; CHECK-NEXT: [[TMP9:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[RES]], i64 [[TMP1]]
-; CHECK-NEXT: [[TMP10:%.*]] = shufflevector <2 x double> [[TMP4]], <2 x double> [[TMP6]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
-; CHECK-NEXT: [[INTERLEAVED_VEC:%.*]] = shufflevector <4 x double> [[TMP10]], <4 x double> poison, <4 x i32> <i32 0, i32 2, i32 1, i32 3>
-; CHECK-NEXT: store <4 x double> [[INTERLEAVED_VEC]], ptr [[TMP8]], align 4
-; CHECK-NEXT: [[TMP11:%.*]] = shufflevector <2 x double> [[TMP5]], <2 x double> [[TMP7]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
-; CHECK-NEXT: [[INTERLEAVED_VEC5:%.*]] = shufflevector <4 x double> [[TMP11]], <4 x double> poison, <4 x i32> <i32 0, i32 2, i32 1, i32 3>
-; CHECK-NEXT: store <4 x double> [[INTERLEAVED_VEC5]], ptr [[TMP9]], align 4
-; CHECK-NEXT: [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 4
+; CHECK-NEXT: store <2 x double> [[TMP6]], ptr [[TMP8]], align 4
+; CHECK-NEXT: store <2 x double> [[TMP7]], ptr [[TMP9]], align 4
+; CHECK-NEXT: [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 2
; CHECK-NEXT: [[TMP12:%.*]] = icmp eq i64 [[INDEX_NEXT]], 100
; CHECK-NEXT: br i1 [[TMP12]], label %[[MIDDLE_BLOCK:.*]], label %[[VECTOR_BODY]], !llvm.loop [[LOOP4:![0-9]+]]
; CHECK: [[MIDDLE_BLOCK]]:
>From 6ae5b89553a9393cc9ab68c0d776a506381a3009 Mon Sep 17 00:00:00 2001
From: Evan Liu <liuyievan at gmail.com>
Date: Sun, 22 Jun 2025 02:21:47 -0700
Subject: [PATCH 33/40] Make getStridesAndOffset const (#145148)
Make getStridesAndOffset const.
---
mlir/include/mlir/IR/BuiltinTypes.td | 4 ++--
mlir/lib/IR/BuiltinTypes.cpp | 5 +++--
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/mlir/include/mlir/IR/BuiltinTypes.td b/mlir/include/mlir/IR/BuiltinTypes.td
index 55d64d663f7ed..89ade79a3ac02 100644
--- a/mlir/include/mlir/IR/BuiltinTypes.td
+++ b/mlir/include/mlir/IR/BuiltinTypes.td
@@ -863,11 +863,11 @@ def Builtin_MemRef : Builtin_Type<"MemRef", "memref", [
/// the distance in the number of elements between successive entries along
/// a particular dimension.
LogicalResult getStridesAndOffset(SmallVectorImpl<int64_t> &strides,
- int64_t &offset);
+ int64_t &offset) const;
/// Wrapper around getStridesAndOffset(SmallVectorImpl<int64_t>, int64_t)
/// that will assert if the logical result is not succeeded.
- std::pair<SmallVector<int64_t>, int64_t> getStridesAndOffset();
+ std::pair<SmallVector<int64_t>, int64_t> getStridesAndOffset() const;
/// Return "true" if the layout is compatible with strided semantics.
bool isStrided();
diff --git a/mlir/lib/IR/BuiltinTypes.cpp b/mlir/lib/IR/BuiltinTypes.cpp
index 97bab479c79bf..e3a00ac5a14b1 100644
--- a/mlir/lib/IR/BuiltinTypes.cpp
+++ b/mlir/lib/IR/BuiltinTypes.cpp
@@ -730,11 +730,12 @@ MemRefType MemRefType::canonicalizeStridedLayout() {
}
LogicalResult MemRefType::getStridesAndOffset(SmallVectorImpl<int64_t> &strides,
- int64_t &offset) {
+ int64_t &offset) const {
return getLayout().getStridesAndOffset(getShape(), strides, offset);
}
-std::pair<SmallVector<int64_t>, int64_t> MemRefType::getStridesAndOffset() {
+std::pair<SmallVector<int64_t>, int64_t>
+MemRefType::getStridesAndOffset() const {
SmallVector<int64_t> strides;
int64_t offset;
LogicalResult status = getStridesAndOffset(strides, offset);
>From 075cb691a5e810f7114369c67b475dfd9127d4af Mon Sep 17 00:00:00 2001
From: Mehdi Amini <joker.eph at gmail.com>
Date: Sun, 22 Jun 2025 11:40:01 +0200
Subject: [PATCH 34/40] [MLIR] Add logging/tracing to DataFlow analysis and
RemoveDeadValues (NFC) (#144695)
Debugging issues with this pass is quite difficult at the moment, this
should help.
---
.../Analysis/DataFlow/DeadCodeAnalysis.cpp | 81 +++++++++++++++++--
.../Analysis/DataFlow/LivenessAnalysis.cpp | 51 +++++++++++-
mlir/lib/Transforms/RemoveDeadValues.cpp | 56 +++++++++++--
3 files changed, 176 insertions(+), 12 deletions(-)
diff --git a/mlir/lib/Analysis/DataFlow/DeadCodeAnalysis.cpp b/mlir/lib/Analysis/DataFlow/DeadCodeAnalysis.cpp
index e805e21d878bf..1abdfcbf3496f 100644
--- a/mlir/lib/Analysis/DataFlow/DeadCodeAnalysis.cpp
+++ b/mlir/lib/Analysis/DataFlow/DeadCodeAnalysis.cpp
@@ -22,9 +22,14 @@
#include "mlir/Interfaces/ControlFlowInterfaces.h"
#include "mlir/Support/LLVM.h"
#include "llvm/Support/Casting.h"
+#include "llvm/Support/Debug.h"
#include <cassert>
#include <optional>
+#define DEBUG_TYPE "dead-code-analysis"
+#define DBGS() (llvm::dbgs() << '[' << DEBUG_TYPE << "] ")
+#define LDBG(X) LLVM_DEBUG(DBGS() << X << "\n")
+
using namespace mlir;
using namespace mlir::dataflow;
@@ -122,6 +127,7 @@ DeadCodeAnalysis::DeadCodeAnalysis(DataFlowSolver &solver)
}
LogicalResult DeadCodeAnalysis::initialize(Operation *top) {
+ LDBG("Initializing DeadCodeAnalysis for top-level op: " << top->getName());
// Mark the top-level blocks as executable.
for (Region ®ion : top->getRegions()) {
if (region.empty())
@@ -129,6 +135,7 @@ LogicalResult DeadCodeAnalysis::initialize(Operation *top) {
auto *state =
getOrCreate<Executable>(getProgramPointBefore(®ion.front()));
propagateIfChanged(state, state->setToLive());
+ LDBG("Marked entry block live for region in op: " << top->getName());
}
// Mark as overdefined the predecessors of symbol callables with potentially
@@ -139,13 +146,18 @@ LogicalResult DeadCodeAnalysis::initialize(Operation *top) {
}
void DeadCodeAnalysis::initializeSymbolCallables(Operation *top) {
+ LDBG("[init] Entering initializeSymbolCallables for top-level op: "
+ << top->getName());
analysisScope = top;
auto walkFn = [&](Operation *symTable, bool allUsesVisible) {
+ LDBG("[init] Processing symbol table op: " << symTable->getName());
Region &symbolTableRegion = symTable->getRegion(0);
Block *symbolTableBlock = &symbolTableRegion.front();
bool foundSymbolCallable = false;
for (auto callable : symbolTableBlock->getOps<CallableOpInterface>()) {
+ LDBG("[init] Found CallableOpInterface: "
+ << callable.getOperation()->getName());
Region *callableRegion = callable.getCallableRegion();
if (!callableRegion)
continue;
@@ -159,6 +171,8 @@ void DeadCodeAnalysis::initializeSymbolCallables(Operation *top) {
auto *state =
getOrCreate<PredecessorState>(getProgramPointAfter(callable));
propagateIfChanged(state, state->setHasUnknownPredecessors());
+ LDBG("[init] Marked callable as having unknown predecessors: "
+ << callable.getOperation()->getName());
}
foundSymbolCallable = true;
}
@@ -173,10 +187,15 @@ void DeadCodeAnalysis::initializeSymbolCallables(Operation *top) {
if (!uses) {
// If we couldn't gather the symbol uses, conservatively assume that
// we can't track information for any nested symbols.
+ LDBG("[init] Could not gather symbol uses, conservatively marking "
+ "all nested callables as having unknown predecessors");
return top->walk([&](CallableOpInterface callable) {
auto *state =
getOrCreate<PredecessorState>(getProgramPointAfter(callable));
propagateIfChanged(state, state->setHasUnknownPredecessors());
+ LDBG("[init] Marked nested callable as "
+ "having unknown predecessors: "
+ << callable.getOperation()->getName());
});
}
@@ -190,10 +209,15 @@ void DeadCodeAnalysis::initializeSymbolCallables(Operation *top) {
continue;
auto *state = getOrCreate<PredecessorState>(getProgramPointAfter(symbol));
propagateIfChanged(state, state->setHasUnknownPredecessors());
+ LDBG("[init] Found non-call use for symbol, "
+ "marked as having unknown predecessors: "
+ << symbol->getName());
}
};
SymbolTable::walkSymbolTables(top, /*allSymUsesVisible=*/!top->getBlock(),
walkFn);
+ LDBG("[init] Finished initializeSymbolCallables for top-level op: "
+ << top->getName());
}
/// Returns true if the operation is a returning terminator in region
@@ -205,9 +229,12 @@ static bool isRegionOrCallableReturn(Operation *op) {
}
LogicalResult DeadCodeAnalysis::initializeRecursively(Operation *op) {
+ LDBG("[init] Entering initializeRecursively for op: " << op->getName()
+ << " at " << op);
// Initialize the analysis by visiting every op with control-flow semantics.
if (op->getNumRegions() || op->getNumSuccessors() ||
isRegionOrCallableReturn(op) || isa<CallOpInterface>(op)) {
+ LDBG("[init] Visiting op with control-flow semantics: " << *op);
// When the liveness of the parent block changes, make sure to re-invoke the
// analysis on the op.
if (op->getBlock())
@@ -218,14 +245,22 @@ LogicalResult DeadCodeAnalysis::initializeRecursively(Operation *op) {
return failure();
}
// Recurse on nested operations.
- for (Region ®ion : op->getRegions())
- for (Operation &op : region.getOps())
- if (failed(initializeRecursively(&op)))
+ for (Region ®ion : op->getRegions()) {
+ LDBG("[init] Recursing into region of op: " << op->getName());
+ for (Operation &nestedOp : region.getOps()) {
+ LDBG("[init] Recursing into nested op: " << nestedOp.getName() << " at "
+ << &nestedOp);
+ if (failed(initializeRecursively(&nestedOp)))
return failure();
+ }
+ }
+ LDBG("[init] Finished initializeRecursively for op: " << op->getName()
+ << " at " << op);
return success();
}
void DeadCodeAnalysis::markEdgeLive(Block *from, Block *to) {
+ LDBG("Marking edge live from block " << from << " to block " << to);
auto *state = getOrCreate<Executable>(getProgramPointBefore(to));
propagateIfChanged(state, state->setToLive());
auto *edgeState =
@@ -234,37 +269,48 @@ void DeadCodeAnalysis::markEdgeLive(Block *from, Block *to) {
}
void DeadCodeAnalysis::markEntryBlocksLive(Operation *op) {
+ LDBG("Marking entry blocks live for op: " << op->getName());
for (Region ®ion : op->getRegions()) {
if (region.empty())
continue;
auto *state =
getOrCreate<Executable>(getProgramPointBefore(®ion.front()));
propagateIfChanged(state, state->setToLive());
+ LDBG("Marked entry block live for region in op: " << op->getName());
}
}
LogicalResult DeadCodeAnalysis::visit(ProgramPoint *point) {
+ LDBG("Visiting program point: " << point << " " << *point);
if (point->isBlockStart())
return success();
Operation *op = point->getPrevOp();
+ LDBG("Visiting operation: " << *op);
// If the parent block is not executable, there is nothing to do.
if (op->getBlock() != nullptr &&
- !getOrCreate<Executable>(getProgramPointBefore(op->getBlock()))->isLive())
+ !getOrCreate<Executable>(getProgramPointBefore(op->getBlock()))
+ ->isLive()) {
+ LDBG("Parent block not live, skipping op: " << *op);
return success();
+ }
// We have a live call op. Add this as a live predecessor of the callee.
- if (auto call = dyn_cast<CallOpInterface>(op))
+ if (auto call = dyn_cast<CallOpInterface>(op)) {
+ LDBG("Visiting call operation: " << *op);
visitCallOperation(call);
+ }
// Visit the regions.
if (op->getNumRegions()) {
// Check if we can reason about the region control-flow.
if (auto branch = dyn_cast<RegionBranchOpInterface>(op)) {
+ LDBG("Visiting region branch operation: " << *op);
visitRegionBranchOperation(branch);
// Check if this is a callable operation.
} else if (auto callable = dyn_cast<CallableOpInterface>(op)) {
+ LDBG("Visiting callable operation: " << *op);
const auto *callsites = getOrCreateFor<PredecessorState>(
getProgramPointAfter(op), getProgramPointAfter(callable));
@@ -276,16 +322,19 @@ LogicalResult DeadCodeAnalysis::visit(ProgramPoint *point) {
// Otherwise, conservatively mark all entry blocks as executable.
} else {
+ LDBG("Marking all entry blocks live for op: " << *op);
markEntryBlocksLive(op);
}
}
if (isRegionOrCallableReturn(op)) {
if (auto branch = dyn_cast<RegionBranchOpInterface>(op->getParentOp())) {
+ LDBG("Visiting region terminator: " << *op);
// Visit the exiting terminator of a region.
visitRegionTerminator(op, branch);
} else if (auto callable =
dyn_cast<CallableOpInterface>(op->getParentOp())) {
+ LDBG("Visiting callable terminator: " << *op);
// Visit the exiting terminator of a callable.
visitCallableTerminator(op, callable);
}
@@ -294,10 +343,12 @@ LogicalResult DeadCodeAnalysis::visit(ProgramPoint *point) {
if (op->getNumSuccessors()) {
// Check if we can reason about the control-flow.
if (auto branch = dyn_cast<BranchOpInterface>(op)) {
+ LDBG("Visiting branch operation: " << *op);
visitBranchOperation(branch);
// Otherwise, conservatively mark all successors as exectuable.
} else {
+ LDBG("Marking all successors live for op: " << *op);
for (Block *successor : op->getSuccessors())
markEdgeLive(op->getBlock(), successor);
}
@@ -307,6 +358,7 @@ LogicalResult DeadCodeAnalysis::visit(ProgramPoint *point) {
}
void DeadCodeAnalysis::visitCallOperation(CallOpInterface call) {
+ LDBG("visitCallOperation: " << call.getOperation()->getName());
Operation *callableOp = call.resolveCallableInTable(&symbolTable);
// A call to a externally-defined callable has unknown predecessors.
@@ -329,11 +381,15 @@ void DeadCodeAnalysis::visitCallOperation(CallOpInterface call) {
auto *callsites =
getOrCreate<PredecessorState>(getProgramPointAfter(callableOp));
propagateIfChanged(callsites, callsites->join(call));
+ LDBG("Added callsite as predecessor for callable: "
+ << callableOp->getName());
} else {
// Mark this call op's predecessors as overdefined.
auto *predecessors =
getOrCreate<PredecessorState>(getProgramPointAfter(call));
propagateIfChanged(predecessors, predecessors->setHasUnknownPredecessors());
+ LDBG("Marked call op's predecessors as unknown for: "
+ << call.getOperation()->getName());
}
}
@@ -365,6 +421,7 @@ DeadCodeAnalysis::getOperandValues(Operation *op) {
}
void DeadCodeAnalysis::visitBranchOperation(BranchOpInterface branch) {
+ LDBG("visitBranchOperation: " << branch.getOperation()->getName());
// Try to deduce a single successor for the branch.
std::optional<SmallVector<Attribute>> operands = getOperandValues(branch);
if (!operands)
@@ -372,15 +429,18 @@ void DeadCodeAnalysis::visitBranchOperation(BranchOpInterface branch) {
if (Block *successor = branch.getSuccessorForOperands(*operands)) {
markEdgeLive(branch->getBlock(), successor);
+ LDBG("Branch has single successor: " << successor);
} else {
// Otherwise, mark all successors as executable and outgoing edges.
for (Block *successor : branch->getSuccessors())
markEdgeLive(branch->getBlock(), successor);
+ LDBG("Branch has multiple/all successors live");
}
}
void DeadCodeAnalysis::visitRegionBranchOperation(
RegionBranchOpInterface branch) {
+ LDBG("visitRegionBranchOperation: " << branch.getOperation()->getName());
// Try to deduce which regions are executable.
std::optional<SmallVector<Attribute>> operands = getOperandValues(branch);
if (!operands)
@@ -397,16 +457,19 @@ void DeadCodeAnalysis::visitRegionBranchOperation(
// Mark the entry block as executable.
auto *state = getOrCreate<Executable>(point);
propagateIfChanged(state, state->setToLive());
+ LDBG("Marked region successor live: " << point);
// Add the parent op as a predecessor.
auto *predecessors = getOrCreate<PredecessorState>(point);
propagateIfChanged(
predecessors,
predecessors->join(branch, successor.getSuccessorInputs()));
+ LDBG("Added region branch as predecessor for successor: " << point);
}
}
void DeadCodeAnalysis::visitRegionTerminator(Operation *op,
RegionBranchOpInterface branch) {
+ LDBG("visitRegionTerminator: " << *op);
std::optional<SmallVector<Attribute>> operands = getOperandValues(op);
if (!operands)
return;
@@ -425,6 +488,7 @@ void DeadCodeAnalysis::visitRegionTerminator(Operation *op,
auto *state =
getOrCreate<Executable>(getProgramPointBefore(®ion->front()));
propagateIfChanged(state, state->setToLive());
+ LDBG("Marked region entry block live for region: " << region);
predecessors = getOrCreate<PredecessorState>(
getProgramPointBefore(®ion->front()));
} else {
@@ -434,11 +498,14 @@ void DeadCodeAnalysis::visitRegionTerminator(Operation *op,
}
propagateIfChanged(predecessors,
predecessors->join(op, successor.getSuccessorInputs()));
+ LDBG("Added region terminator as predecessor for successor: "
+ << (successor.getSuccessor() ? "region entry" : "parent op"));
}
}
void DeadCodeAnalysis::visitCallableTerminator(Operation *op,
CallableOpInterface callable) {
+ LDBG("visitCallableTerminator: " << *op);
// Add as predecessors to all callsites this return op.
auto *callsites = getOrCreateFor<PredecessorState>(
getProgramPointAfter(op), getProgramPointAfter(callable));
@@ -449,11 +516,15 @@ void DeadCodeAnalysis::visitCallableTerminator(Operation *op,
getOrCreate<PredecessorState>(getProgramPointAfter(predecessor));
if (canResolve) {
propagateIfChanged(predecessors, predecessors->join(op));
+ LDBG("Added callable terminator as predecessor for callsite: "
+ << predecessor->getName());
} else {
// If the terminator is not a return-like, then conservatively assume we
// can't resolve the predecessor.
propagateIfChanged(predecessors,
predecessors->setHasUnknownPredecessors());
+ LDBG("Could not resolve callable terminator for callsite: "
+ << predecessor->getName());
}
}
}
diff --git a/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp b/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp
index 24a78400eb84a..6a12fe3acc2c2 100644
--- a/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp
+++ b/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp
@@ -10,6 +10,7 @@
#include <cassert>
#include <mlir/Analysis/DataFlow/LivenessAnalysis.h>
+#include <llvm/Support/Debug.h>
#include <mlir/Analysis/DataFlow/SparseAnalysis.h>
#include <mlir/Analysis/DataFlow/Utils.h>
#include <mlir/Analysis/DataFlowFramework.h>
@@ -19,6 +20,10 @@
#include <mlir/Interfaces/SideEffectInterfaces.h>
#include <mlir/Support/LLVM.h>
+#define DEBUG_TYPE "liveness-analysis"
+#define DBGS() (llvm::dbgs() << '[' << DEBUG_TYPE << "] ")
+#define LDBG(X) LLVM_DEBUG(DBGS() << X << "\n")
+
using namespace mlir;
using namespace mlir::dataflow;
@@ -76,28 +81,46 @@ ChangeResult Liveness::meet(const AbstractSparseLattice &other) {
LogicalResult
LivenessAnalysis::visitOperation(Operation *op, ArrayRef<Liveness *> operands,
ArrayRef<const Liveness *> results) {
+ LLVM_DEBUG(DBGS() << "[visitOperation] Enter: ";
+ op->print(llvm::dbgs(), OpPrintingFlags().skipRegions());
+ llvm::dbgs() << "\n");
// This marks values of type (1.a) and (4) liveness as "live".
if (!isMemoryEffectFree(op) || op->hasTrait<OpTrait::ReturnLike>()) {
- for (auto *operand : operands)
+ LDBG("[visitOperation] Operation has memory effects or is "
+ "return-like, marking operands live");
+ for (auto *operand : operands) {
+ LDBG(" [visitOperation] Marking operand live: "
+ << operand << " (" << operand->isLive << ")");
propagateIfChanged(operand, operand->markLive());
+ }
}
// This marks values of type (3) liveness as "live".
bool foundLiveResult = false;
for (const Liveness *r : results) {
if (r->isLive && !foundLiveResult) {
+ LDBG("[visitOperation] Found live result, "
+ "meeting all operands with result: "
+ << r);
// It is assumed that each operand is used to compute each result of an
// op. Thus, if at least one result is live, each operand is live.
- for (Liveness *operand : operands)
+ for (Liveness *operand : operands) {
+ LDBG(" [visitOperation] Meeting operand: " << operand
+ << " with result: " << r);
meet(operand, *r);
+ }
foundLiveResult = true;
}
+ LDBG("[visitOperation] Adding dependency for result: " << r << " after op: "
+ << *op);
addDependency(const_cast<Liveness *>(r), getProgramPointAfter(op));
}
return success();
}
void LivenessAnalysis::visitBranchOperand(OpOperand &operand) {
+ LDBG("Visiting branch operand: " << operand.get()
+ << " in op: " << *operand.getOwner());
// We know (at the moment) and assume (for the future) that `operand` is a
// non-forwarded branch operand of a `RegionBranchOpInterface`,
// `BranchOpInterface`, `RegionBranchTerminatorOpInterface` or return-like op.
@@ -129,6 +152,9 @@ void LivenessAnalysis::visitBranchOperand(OpOperand &operand) {
for (Value result : op->getResults()) {
if (getLatticeElement(result)->isLive) {
mayLive = true;
+ LDBG("[visitBranchOperand] Non-forwarded branch "
+ "operand may be live due to live result: "
+ << result);
break;
}
}
@@ -148,6 +174,8 @@ void LivenessAnalysis::visitBranchOperand(OpOperand &operand) {
// Therefore, we conservatively consider the non-forwarded operand of the
// branch operation may live.
mayLive = true;
+ LDBG("[visitBranchOperand] Non-forwarded branch operand may "
+ "be live due to branch op interface");
} else {
Operation *parentOp = op->getParentOp();
assert(isa<RegionBranchOpInterface>(parentOp) &&
@@ -163,6 +191,9 @@ void LivenessAnalysis::visitBranchOperand(OpOperand &operand) {
for (Value result : parentOp->getResults()) {
if (getLatticeElement(result)->isLive) {
mayLive = true;
+ LDBG("[visitBranchOperand] Non-forwarded branch "
+ "operand may be live due to parent live result: "
+ << result);
break;
}
}
@@ -183,6 +214,9 @@ void LivenessAnalysis::visitBranchOperand(OpOperand &operand) {
for (Operation &nestedOp : *block) {
if (!isMemoryEffectFree(&nestedOp)) {
mayLive = true;
+ LDBG("Non-forwarded branch operand may be "
+ "live due to memory effect in block: "
+ << block);
break;
}
}
@@ -190,6 +224,7 @@ void LivenessAnalysis::visitBranchOperand(OpOperand &operand) {
if (mayLive) {
Liveness *operandLiveness = getLatticeElement(operand.get());
+ LDBG("Marking branch operand live: " << operand.get());
propagateIfChanged(operandLiveness, operandLiveness->markLive());
}
@@ -201,6 +236,7 @@ void LivenessAnalysis::visitBranchOperand(OpOperand &operand) {
SmallVector<const Liveness *, 4> resultsLiveness;
for (const Value result : op->getResults())
resultsLiveness.push_back(getLatticeElement(result));
+ LDBG("Visiting operation for non-forwarded branch operand: " << *op);
(void)visitOperation(op, operandLiveness, resultsLiveness);
// We also visit the parent op with the parent's results and this operand if
@@ -213,10 +249,14 @@ void LivenessAnalysis::visitBranchOperand(OpOperand &operand) {
SmallVector<const Liveness *, 4> parentResultsLiveness;
for (const Value parentResult : parentOp->getResults())
parentResultsLiveness.push_back(getLatticeElement(parentResult));
+ LDBG("Visiting parent operation for non-forwarded branch operand: "
+ << *parentOp);
(void)visitOperation(parentOp, operandLiveness, parentResultsLiveness);
}
void LivenessAnalysis::visitCallOperand(OpOperand &operand) {
+ LDBG("Visiting call operand: " << operand.get()
+ << " in op: " << *operand.getOwner());
// We know (at the moment) and assume (for the future) that `operand` is a
// non-forwarded call operand of an op implementing `CallOpInterface`.
assert(isa<CallOpInterface>(operand.getOwner()) &&
@@ -229,14 +269,18 @@ void LivenessAnalysis::visitCallOperand(OpOperand &operand) {
// This marks values of type (1.c) liveness as "live". A non-forwarded
// call operand is live.
Liveness *operandLiveness = getLatticeElement(operand.get());
+ LDBG("Marking call operand live: " << operand.get());
propagateIfChanged(operandLiveness, operandLiveness->markLive());
}
void LivenessAnalysis::setToExitState(Liveness *lattice) {
+ LDBG("setToExitState for lattice: " << lattice);
if (lattice->isLive) {
+ LDBG("Lattice already live, nothing to do");
return;
}
// This marks values of type (2) liveness as "live".
+ LDBG("Marking lattice live due to exit state");
(void)lattice->markLive();
propagateIfChanged(lattice, ChangeResult::Change);
}
@@ -246,11 +290,14 @@ void LivenessAnalysis::setToExitState(Liveness *lattice) {
//===----------------------------------------------------------------------===//
RunLivenessAnalysis::RunLivenessAnalysis(Operation *op) {
+ LDBG("Constructing RunLivenessAnalysis for op: " << op->getName());
SymbolTableCollection symbolTable;
loadBaselineAnalyses(solver);
solver.load<LivenessAnalysis>(symbolTable);
+ LDBG("Initializing and running solver");
(void)solver.initializeAndRun(op);
+ LDBG("Dumping liveness state for op");
}
const Liveness *RunLivenessAnalysis::getLiveness(Value val) {
diff --git a/mlir/lib/Transforms/RemoveDeadValues.cpp b/mlir/lib/Transforms/RemoveDeadValues.cpp
index 08dfea8eb2648..ad21ce8f18700 100644
--- a/mlir/lib/Transforms/RemoveDeadValues.cpp
+++ b/mlir/lib/Transforms/RemoveDeadValues.cpp
@@ -52,12 +52,17 @@
#include "mlir/Transforms/FoldUtils.h"
#include "mlir/Transforms/Passes.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/Debug.h"
#include <cassert>
#include <cstddef>
#include <memory>
#include <optional>
#include <vector>
+#define DEBUG_TYPE "remove-dead-values"
+#define DBGS() (llvm::dbgs() << '[' << DEBUG_TYPE << "] ")
+#define LDBG(X) LLVM_DEBUG(DBGS() << X << "\n")
+
namespace mlir {
#define GEN_PASS_DEF_REMOVEDEADVALUES
#include "mlir/Transforms/Passes.h.inc"
@@ -115,12 +120,23 @@ struct RDVFinalCleanupList {
static bool hasLive(ValueRange values, const DenseSet<Value> &nonLiveSet,
RunLivenessAnalysis &la) {
for (Value value : values) {
- if (nonLiveSet.contains(value))
+ if (nonLiveSet.contains(value)) {
+ LDBG("Value " << value << " is already marked non-live (dead)");
continue;
+ }
const Liveness *liveness = la.getLiveness(value);
- if (!liveness || liveness->isLive)
+ if (!liveness) {
+ LDBG("Value " << value
+ << " has no liveness info, conservatively considered live");
return true;
+ }
+ if (liveness->isLive) {
+ LDBG("Value " << value << " is live according to liveness analysis");
+ return true;
+ } else {
+ LDBG("Value " << value << " is dead according to liveness analysis");
+ }
}
return false;
}
@@ -134,6 +150,8 @@ static BitVector markLives(ValueRange values, const DenseSet<Value> &nonLiveSet,
for (auto [index, value] : llvm::enumerate(values)) {
if (nonLiveSet.contains(value)) {
lives.reset(index);
+ LDBG("Value " << value << " is already marked non-live (dead) at index "
+ << index);
continue;
}
@@ -144,8 +162,19 @@ static BitVector markLives(ValueRange values, const DenseSet<Value> &nonLiveSet,
// of the results of an op and we know that these new values are live
// (because they weren't erased) and also their liveness is null because
// liveness analysis ran before their creation.
- if (liveness && !liveness->isLive)
+ if (!liveness) {
+ LDBG("Value " << value << " at index " << index
+ << " has no liveness info, conservatively considered live");
+ continue;
+ }
+ if (!liveness->isLive) {
lives.reset(index);
+ LDBG("Value " << value << " at index " << index
+ << " is dead according to liveness analysis");
+ } else {
+ LDBG("Value " << value << " at index " << index
+ << " is live according to liveness analysis");
+ }
}
return lives;
@@ -160,6 +189,8 @@ static void collectNonLiveValues(DenseSet<Value> &nonLiveSet, ValueRange range,
if (!nonLive[index])
continue;
nonLiveSet.insert(result);
+ LDBG("Marking value " << result << " as non-live (dead) at index "
+ << index);
}
}
@@ -229,9 +260,16 @@ static SmallVector<OpOperand *> operandsToOpOperands(OperandRange operands) {
static void processSimpleOp(Operation *op, RunLivenessAnalysis &la,
DenseSet<Value> &nonLiveSet,
RDVFinalCleanupList &cl) {
- if (!isMemoryEffectFree(op) || hasLive(op->getResults(), nonLiveSet, la))
+ LDBG("Processing simple op: " << *op);
+ if (!isMemoryEffectFree(op) || hasLive(op->getResults(), nonLiveSet, la)) {
+ LDBG("Simple op is not memory effect free or has live results, skipping: "
+ << *op);
return;
+ }
+ LDBG("Simple op has all dead results and is memory effect free, scheduling "
+ "for removal: "
+ << *op);
cl.operations.push_back(op);
collectNonLiveValues(nonLiveSet, op->getResults(),
BitVector(op->getNumResults(), true));
@@ -250,8 +288,12 @@ static void processSimpleOp(Operation *op, RunLivenessAnalysis &la,
static void processFuncOp(FunctionOpInterface funcOp, Operation *module,
RunLivenessAnalysis &la, DenseSet<Value> &nonLiveSet,
RDVFinalCleanupList &cl) {
- if (funcOp.isPublic() || funcOp.isExternal())
+ LDBG("Processing function op: " << funcOp.getOperation()->getName());
+ if (funcOp.isPublic() || funcOp.isExternal()) {
+ LDBG("Function is public or external, skipping: "
+ << funcOp.getOperation()->getName());
return;
+ }
// Get the list of unnecessary (non-live) arguments in `nonLiveArgs`.
SmallVector<Value> arguments(funcOp.getArguments());
@@ -369,6 +411,9 @@ static void processRegionBranchOp(RegionBranchOpInterface regionBranchOp,
RunLivenessAnalysis &la,
DenseSet<Value> &nonLiveSet,
RDVFinalCleanupList &cl) {
+ LLVM_DEBUG(DBGS() << "Processing region branch op: "; regionBranchOp->print(
+ llvm::dbgs(), OpPrintingFlags().skipRegions());
+ llvm::dbgs() << "\n");
// Mark live results of `regionBranchOp` in `liveResults`.
auto markLiveResults = [&](BitVector &liveResults) {
liveResults = markLives(regionBranchOp->getResults(), nonLiveSet, la);
@@ -654,6 +699,7 @@ static void processRegionBranchOp(RegionBranchOpInterface regionBranchOp,
static void processBranchOp(BranchOpInterface branchOp, RunLivenessAnalysis &la,
DenseSet<Value> &nonLiveSet,
RDVFinalCleanupList &cl) {
+ LDBG("Processing branch op: " << *branchOp);
unsigned numSuccessors = branchOp->getNumSuccessors();
for (unsigned succIdx = 0; succIdx < numSuccessors; ++succIdx) {
>From 8583882bdcabc37982e76154d65d07a9aecd2a16 Mon Sep 17 00:00:00 2001
From: David Green <david.green at arm.com>
Date: Sun, 22 Jun 2025 10:52:13 +0100
Subject: [PATCH 35/40] [AArch64] Remove unnecessary DL variable. NFC
---
llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp | 1 -
1 file changed, 1 deletion(-)
diff --git a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
index 8c6f272a8c8da..15e38e6cb2408 100644
--- a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
@@ -6369,7 +6369,6 @@ bool AArch64TTIImpl::isProfitableToSinkOperands(
// the backend to generate a umull.
unsigned Bitwidth = I->getType()->getScalarSizeInBits();
APInt UpperMask = APInt::getHighBitsSet(Bitwidth, Bitwidth / 2);
- const DataLayout &DL = I->getDataLayout();
if (!MaskedValueIsZero(OperandInstr, UpperMask, DL))
continue;
NumZExts++;
>From f78819aeef32e50ac3fec9a175b70a971b7c10e5 Mon Sep 17 00:00:00 2001
From: Jim Lin <jim at andestech.com>
Date: Sun, 22 Jun 2025 17:52:30 +0800
Subject: [PATCH 36/40] Revert "Revert "[RISCV] Remove B and Zbc extension from
Andes series cpus." (#144402)"
Since the fix https://github.com/llvm/llvm-project/pull/144848 for post-commit CI failure
has landed.
This reverts commit f83d09a1f60aee28a8ed9020cd72971ec2885f24.
---
.../Driver/print-enabled-extensions/riscv-andes-a25.c | 7 +------
.../Driver/print-enabled-extensions/riscv-andes-a45.c | 6 +-----
.../Driver/print-enabled-extensions/riscv-andes-ax25.c | 7 +------
.../Driver/print-enabled-extensions/riscv-andes-ax45.c | 6 +-----
.../Driver/print-enabled-extensions/riscv-andes-n45.c | 6 +-----
.../Driver/print-enabled-extensions/riscv-andes-nx45.c | 6 +-----
llvm/lib/Target/RISCV/RISCVProcessors.td | 8 --------
llvm/test/tools/llvm-mca/RISCV/Andes45/gpr.s | 2 +-
8 files changed, 7 insertions(+), 41 deletions(-)
diff --git a/clang/test/Driver/print-enabled-extensions/riscv-andes-a25.c b/clang/test/Driver/print-enabled-extensions/riscv-andes-a25.c
index d8b3848d84520..cfb4d0ed58d11 100644
--- a/clang/test/Driver/print-enabled-extensions/riscv-andes-a25.c
+++ b/clang/test/Driver/print-enabled-extensions/riscv-andes-a25.c
@@ -10,7 +10,6 @@
// CHECK-NEXT: f 2.2 'F' (Single-Precision Floating-Point)
// CHECK-NEXT: d 2.2 'D' (Double-Precision Floating-Point)
// CHECK-NEXT: c 2.0 'C' (Compressed Instructions)
-// CHECK-NEXT: b 1.0 'B' (the collection of the Zba, Zbb, Zbs extensions)
// CHECK-NEXT: zicsr 2.0 'Zicsr' (CSRs)
// CHECK-NEXT: zifencei 2.0 'Zifencei' (fence.i)
// CHECK-NEXT: zmmul 1.0 'Zmmul' (Integer Multiplication)
@@ -19,12 +18,8 @@
// CHECK-NEXT: zca 1.0 'Zca' (part of the C extension, excluding compressed floating point loads/stores)
// CHECK-NEXT: zcd 1.0 'Zcd' (Compressed Double-Precision Floating-Point Instructions)
// CHECK-NEXT: zcf 1.0 'Zcf' (Compressed Single-Precision Floating-Point Instructions)
-// CHECK-NEXT: zba 1.0 'Zba' (Address Generation Instructions)
-// CHECK-NEXT: zbb 1.0 'Zbb' (Basic Bit-Manipulation)
-// CHECK-NEXT: zbc 1.0 'Zbc' (Carry-Less Multiplication)
-// CHECK-NEXT: zbs 1.0 'Zbs' (Single-Bit Instructions)
// CHECK-NEXT: xandesperf 5.0 'XAndesPerf' (Andes Performance Extension)
// CHECK-EMPTY:
// CHECK-NEXT: Experimental extensions
// CHECK-EMPTY:
-// CHECK-NEXT: ISA String: rv32i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_b1p0_zicsr2p0_zifencei2p0_zmmul1p0_zaamo1p0_zalrsc1p0_zca1p0_zcd1p0_zcf1p0_zba1p0_zbb1p0_zbc1p0_zbs1p0_xandesperf5p0
+// CHECK-NEXT: ISA String: rv32i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0_zifencei2p0_zmmul1p0_zaamo1p0_zalrsc1p0_zca1p0_zcd1p0_zcf1p0_xandesperf5p0
diff --git a/clang/test/Driver/print-enabled-extensions/riscv-andes-a45.c b/clang/test/Driver/print-enabled-extensions/riscv-andes-a45.c
index a0a1c35911409..3c3c554dffc57 100644
--- a/clang/test/Driver/print-enabled-extensions/riscv-andes-a45.c
+++ b/clang/test/Driver/print-enabled-extensions/riscv-andes-a45.c
@@ -10,7 +10,6 @@
// CHECK-NEXT: f 2.2 'F' (Single-Precision Floating-Point)
// CHECK-NEXT: d 2.2 'D' (Double-Precision Floating-Point)
// CHECK-NEXT: c 2.0 'C' (Compressed Instructions)
-// CHECK-NEXT: b 1.0 'B' (the collection of the Zba, Zbb, Zbs extensions)
// CHECK-NEXT: zicsr 2.0 'Zicsr' (CSRs)
// CHECK-NEXT: zifencei 2.0 'Zifencei' (fence.i)
// CHECK-NEXT: zmmul 1.0 'Zmmul' (Integer Multiplication)
@@ -19,11 +18,8 @@
// CHECK-NEXT: zca 1.0 'Zca' (part of the C extension, excluding compressed floating point loads/stores)
// CHECK-NEXT: zcd 1.0 'Zcd' (Compressed Double-Precision Floating-Point Instructions)
// CHECK-NEXT: zcf 1.0 'Zcf' (Compressed Single-Precision Floating-Point Instructions)
-// CHECK-NEXT: zba 1.0 'Zba' (Address Generation Instructions)
-// CHECK-NEXT: zbb 1.0 'Zbb' (Basic Bit-Manipulation)
-// CHECK-NEXT: zbs 1.0 'Zbs' (Single-Bit Instructions)
// CHECK-NEXT: xandesperf 5.0 'XAndesPerf' (Andes Performance Extension)
// CHECK-EMPTY:
// CHECK-NEXT: Experimental extensions
// CHECK-EMPTY:
-// CHECK-NEXT: ISA String: rv32i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_b1p0_zicsr2p0_zifencei2p0_zmmul1p0_zaamo1p0_zalrsc1p0_zca1p0_zcd1p0_zcf1p0_zba1p0_zbb1p0_zbs1p0_xandesperf5p0
+// CHECK-NEXT: ISA String: rv32i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0_zifencei2p0_zmmul1p0_zaamo1p0_zalrsc1p0_zca1p0_zcd1p0_zcf1p0_xandesperf5p0
diff --git a/clang/test/Driver/print-enabled-extensions/riscv-andes-ax25.c b/clang/test/Driver/print-enabled-extensions/riscv-andes-ax25.c
index 3f933ecd8ac83..70100a0a8df13 100644
--- a/clang/test/Driver/print-enabled-extensions/riscv-andes-ax25.c
+++ b/clang/test/Driver/print-enabled-extensions/riscv-andes-ax25.c
@@ -10,7 +10,6 @@
// CHECK-NEXT: f 2.2 'F' (Single-Precision Floating-Point)
// CHECK-NEXT: d 2.2 'D' (Double-Precision Floating-Point)
// CHECK-NEXT: c 2.0 'C' (Compressed Instructions)
-// CHECK-NEXT: b 1.0 'B' (the collection of the Zba, Zbb, Zbs extensions)
// CHECK-NEXT: zicsr 2.0 'Zicsr' (CSRs)
// CHECK-NEXT: zifencei 2.0 'Zifencei' (fence.i)
// CHECK-NEXT: zmmul 1.0 'Zmmul' (Integer Multiplication)
@@ -18,12 +17,8 @@
// CHECK-NEXT: zalrsc 1.0 'Zalrsc' (Load-Reserved/Store-Conditional)
// CHECK-NEXT: zca 1.0 'Zca' (part of the C extension, excluding compressed floating point loads/stores)
// CHECK-NEXT: zcd 1.0 'Zcd' (Compressed Double-Precision Floating-Point Instructions)
-// CHECK-NEXT: zba 1.0 'Zba' (Address Generation Instructions)
-// CHECK-NEXT: zbb 1.0 'Zbb' (Basic Bit-Manipulation)
-// CHECK-NEXT: zbc 1.0 'Zbc' (Carry-Less Multiplication)
-// CHECK-NEXT: zbs 1.0 'Zbs' (Single-Bit Instructions)
// CHECK-NEXT: xandesperf 5.0 'XAndesPerf' (Andes Performance Extension)
// CHECK-EMPTY:
// CHECK-NEXT: Experimental extensions
// CHECK-EMPTY:
-// CHECK-NEXT: ISA String: rv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_b1p0_zicsr2p0_zifencei2p0_zmmul1p0_zaamo1p0_zalrsc1p0_zca1p0_zcd1p0_zba1p0_zbb1p0_zbc1p0_zbs1p0_xandesperf5p0
+// CHECK-NEXT: ISA String: rv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0_zifencei2p0_zmmul1p0_zaamo1p0_zalrsc1p0_zca1p0_zcd1p0_xandesperf5p0
diff --git a/clang/test/Driver/print-enabled-extensions/riscv-andes-ax45.c b/clang/test/Driver/print-enabled-extensions/riscv-andes-ax45.c
index 6460d701411bc..d2b1a32e321e5 100644
--- a/clang/test/Driver/print-enabled-extensions/riscv-andes-ax45.c
+++ b/clang/test/Driver/print-enabled-extensions/riscv-andes-ax45.c
@@ -10,7 +10,6 @@
// CHECK-NEXT: f 2.2 'F' (Single-Precision Floating-Point)
// CHECK-NEXT: d 2.2 'D' (Double-Precision Floating-Point)
// CHECK-NEXT: c 2.0 'C' (Compressed Instructions)
-// CHECK-NEXT: b 1.0 'B' (the collection of the Zba, Zbb, Zbs extensions)
// CHECK-NEXT: zicsr 2.0 'Zicsr' (CSRs)
// CHECK-NEXT: zifencei 2.0 'Zifencei' (fence.i)
// CHECK-NEXT: zmmul 1.0 'Zmmul' (Integer Multiplication)
@@ -18,11 +17,8 @@
// CHECK-NEXT: zalrsc 1.0 'Zalrsc' (Load-Reserved/Store-Conditional)
// CHECK-NEXT: zca 1.0 'Zca' (part of the C extension, excluding compressed floating point loads/stores)
// CHECK-NEXT: zcd 1.0 'Zcd' (Compressed Double-Precision Floating-Point Instructions)
-// CHECK-NEXT: zba 1.0 'Zba' (Address Generation Instructions)
-// CHECK-NEXT: zbb 1.0 'Zbb' (Basic Bit-Manipulation)
-// CHECK-NEXT: zbs 1.0 'Zbs' (Single-Bit Instructions)
// CHECK-NEXT: xandesperf 5.0 'XAndesPerf' (Andes Performance Extension)
// CHECK-EMPTY:
// CHECK-NEXT: Experimental extensions
// CHECK-EMPTY:
-// CHECK-NEXT: ISA String: rv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_b1p0_zicsr2p0_zifencei2p0_zmmul1p0_zaamo1p0_zalrsc1p0_zca1p0_zcd1p0_zba1p0_zbb1p0_zbs1p0_xandesperf5p0
+// CHECK-NEXT: ISA String: rv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0_zifencei2p0_zmmul1p0_zaamo1p0_zalrsc1p0_zca1p0_zcd1p0_xandesperf5p0
diff --git a/clang/test/Driver/print-enabled-extensions/riscv-andes-n45.c b/clang/test/Driver/print-enabled-extensions/riscv-andes-n45.c
index 4d9c514b756e6..1a2c30bfc7a2e 100644
--- a/clang/test/Driver/print-enabled-extensions/riscv-andes-n45.c
+++ b/clang/test/Driver/print-enabled-extensions/riscv-andes-n45.c
@@ -10,7 +10,6 @@
// CHECK-NEXT: f 2.2 'F' (Single-Precision Floating-Point)
// CHECK-NEXT: d 2.2 'D' (Double-Precision Floating-Point)
// CHECK-NEXT: c 2.0 'C' (Compressed Instructions)
-// CHECK-NEXT: b 1.0 'B' (the collection of the Zba, Zbb, Zbs extensions)
// CHECK-NEXT: zicsr 2.0 'Zicsr' (CSRs)
// CHECK-NEXT: zifencei 2.0 'Zifencei' (fence.i)
// CHECK-NEXT: zmmul 1.0 'Zmmul' (Integer Multiplication)
@@ -19,11 +18,8 @@
// CHECK-NEXT: zca 1.0 'Zca' (part of the C extension, excluding compressed floating point loads/stores)
// CHECK-NEXT: zcd 1.0 'Zcd' (Compressed Double-Precision Floating-Point Instructions)
// CHECK-NEXT: zcf 1.0 'Zcf' (Compressed Single-Precision Floating-Point Instructions)
-// CHECK-NEXT: zba 1.0 'Zba' (Address Generation Instructions)
-// CHECK-NEXT: zbb 1.0 'Zbb' (Basic Bit-Manipulation)
-// CHECK-NEXT: zbs 1.0 'Zbs' (Single-Bit Instructions)
// CHECK-NEXT: xandesperf 5.0 'XAndesPerf' (Andes Performance Extension)
// CHECK-EMPTY:
// CHECK-NEXT: Experimental extensions
// CHECK-EMPTY:
-// CHECK-NEXT: ISA String: rv32i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_b1p0_zicsr2p0_zifencei2p0_zmmul1p0_zaamo1p0_zalrsc1p0_zca1p0_zcd1p0_zcf1p0_zba1p0_zbb1p0_zbs1p0_xandesperf5p0
+// CHECK-NEXT: ISA String: rv32i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0_zifencei2p0_zmmul1p0_zaamo1p0_zalrsc1p0_zca1p0_zcd1p0_zcf1p0_xandesperf5p0
diff --git a/clang/test/Driver/print-enabled-extensions/riscv-andes-nx45.c b/clang/test/Driver/print-enabled-extensions/riscv-andes-nx45.c
index 5eaada3f9e164..50c38da3bd034 100644
--- a/clang/test/Driver/print-enabled-extensions/riscv-andes-nx45.c
+++ b/clang/test/Driver/print-enabled-extensions/riscv-andes-nx45.c
@@ -10,7 +10,6 @@
// CHECK-NEXT: f 2.2 'F' (Single-Precision Floating-Point)
// CHECK-NEXT: d 2.2 'D' (Double-Precision Floating-Point)
// CHECK-NEXT: c 2.0 'C' (Compressed Instructions)
-// CHECK-NEXT: b 1.0 'B' (the collection of the Zba, Zbb, Zbs extensions)
// CHECK-NEXT: zicsr 2.0 'Zicsr' (CSRs)
// CHECK-NEXT: zifencei 2.0 'Zifencei' (fence.i)
// CHECK-NEXT: zmmul 1.0 'Zmmul' (Integer Multiplication)
@@ -18,11 +17,8 @@
// CHECK-NEXT: zalrsc 1.0 'Zalrsc' (Load-Reserved/Store-Conditional)
// CHECK-NEXT: zca 1.0 'Zca' (part of the C extension, excluding compressed floating point loads/stores)
// CHECK-NEXT: zcd 1.0 'Zcd' (Compressed Double-Precision Floating-Point Instructions)
-// CHECK-NEXT: zba 1.0 'Zba' (Address Generation Instructions)
-// CHECK-NEXT: zbb 1.0 'Zbb' (Basic Bit-Manipulation)
-// CHECK-NEXT: zbs 1.0 'Zbs' (Single-Bit Instructions)
// CHECK-NEXT: xandesperf 5.0 'XAndesPerf' (Andes Performance Extension)
// CHECK-EMPTY:
// CHECK-NEXT: Experimental extensions
// CHECK-EMPTY:
-// CHECK-NEXT: ISA String: rv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_b1p0_zicsr2p0_zifencei2p0_zmmul1p0_zaamo1p0_zalrsc1p0_zca1p0_zcd1p0_zba1p0_zbb1p0_zbs1p0_xandesperf5p0
+// CHECK-NEXT: ISA String: rv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0_zifencei2p0_zmmul1p0_zaamo1p0_zalrsc1p0_zca1p0_zcd1p0_xandesperf5p0
diff --git a/llvm/lib/Target/RISCV/RISCVProcessors.td b/llvm/lib/Target/RISCV/RISCVProcessors.td
index 32f4ab607a34c..d7e6c71ea062e 100644
--- a/llvm/lib/Target/RISCV/RISCVProcessors.td
+++ b/llvm/lib/Target/RISCV/RISCVProcessors.td
@@ -703,8 +703,6 @@ def ANDES_A25 : RISCVProcessorModel<"andes-a25",
FeatureStdExtF,
FeatureStdExtD,
FeatureStdExtC,
- FeatureStdExtB,
- FeatureStdExtZbc,
FeatureVendorXAndesPerf]>;
def ANDES_AX25 : RISCVProcessorModel<"andes-ax25",
@@ -718,8 +716,6 @@ def ANDES_AX25 : RISCVProcessorModel<"andes-ax25",
FeatureStdExtF,
FeatureStdExtD,
FeatureStdExtC,
- FeatureStdExtB,
- FeatureStdExtZbc,
FeatureVendorXAndesPerf]>;
defvar Andes45TuneFeatures = [TuneAndes45,
@@ -741,7 +737,6 @@ def ANDES_N45 : RISCVProcessorModel<"andes-n45",
FeatureStdExtF,
FeatureStdExtD,
FeatureStdExtC,
- FeatureStdExtB,
FeatureVendorXAndesPerf],
Andes45TuneFeatures>;
@@ -756,7 +751,6 @@ def ANDES_NX45 : RISCVProcessorModel<"andes-nx45",
FeatureStdExtF,
FeatureStdExtD,
FeatureStdExtC,
- FeatureStdExtB,
FeatureVendorXAndesPerf],
Andes45TuneFeatures>;
@@ -771,7 +765,6 @@ def ANDES_A45 : RISCVProcessorModel<"andes-a45",
FeatureStdExtF,
FeatureStdExtD,
FeatureStdExtC,
- FeatureStdExtB,
FeatureVendorXAndesPerf],
Andes45TuneFeatures>;
@@ -786,6 +779,5 @@ def ANDES_AX45 : RISCVProcessorModel<"andes-ax45",
FeatureStdExtF,
FeatureStdExtD,
FeatureStdExtC,
- FeatureStdExtB,
FeatureVendorXAndesPerf],
Andes45TuneFeatures>;
diff --git a/llvm/test/tools/llvm-mca/RISCV/Andes45/gpr.s b/llvm/test/tools/llvm-mca/RISCV/Andes45/gpr.s
index f6dc6eef3f0ff..d90dce8c5c3fc 100644
--- a/llvm/test/tools/llvm-mca/RISCV/Andes45/gpr.s
+++ b/llvm/test/tools/llvm-mca/RISCV/Andes45/gpr.s
@@ -1,5 +1,5 @@
# NOTE: Assertions have been autogenerated by utils/update_mca_test_checks.py
-# RUN: llvm-mca -mtriple=riscv64 -mcpu=andes-nx45 -mattr=+zbc -timeline -iterations=1 < %s | FileCheck %s
+# RUN: llvm-mca -mtriple=riscv64 -mcpu=andes-nx45 -mattr=+b,+zbc -timeline -iterations=1 < %s | FileCheck %s
# Two ALUs without dependency can be dispatched in the same cycle.
add a0, a0, a0
>From c7d9eabf4a9c1c8c70b5976ea775fd3d143e93f7 Mon Sep 17 00:00:00 2001
From: Patryk Wychowaniec <pwychowaniec at pm.me>
Date: Sun, 22 Jun 2025 12:18:00 +0200
Subject: [PATCH 37/40] [AVR] Don't apply post-indexing on mismatched pointers
(#145224)
fixes https://github.com/llvm/llvm-project/issues/143247
---
llvm/lib/Target/AVR/AVRISelLowering.cpp | 9 +++++++
llvm/test/CodeGen/AVR/bug-143247.ll | 36 +++++++++++++++++++++++++
2 files changed, 45 insertions(+)
create mode 100644 llvm/test/CodeGen/AVR/bug-143247.ll
diff --git a/llvm/lib/Target/AVR/AVRISelLowering.cpp b/llvm/lib/Target/AVR/AVRISelLowering.cpp
index 9747ad0c5cd58..3955f2a252e76 100644
--- a/llvm/lib/Target/AVR/AVRISelLowering.cpp
+++ b/llvm/lib/Target/AVR/AVRISelLowering.cpp
@@ -1071,14 +1071,17 @@ bool AVRTargetLowering::getPostIndexedAddressParts(SDNode *N, SDNode *Op,
ISD::MemIndexedMode &AM,
SelectionDAG &DAG) const {
EVT VT;
+ SDValue Ptr;
SDLoc DL(N);
if (const LoadSDNode *LD = dyn_cast<LoadSDNode>(N)) {
VT = LD->getMemoryVT();
+ Ptr = LD->getBasePtr();
if (LD->getExtensionType() != ISD::NON_EXTLOAD)
return false;
} else if (const StoreSDNode *ST = dyn_cast<StoreSDNode>(N)) {
VT = ST->getMemoryVT();
+ Ptr = ST->getBasePtr();
// We can not store to program memory.
if (AVR::isProgramMemoryAccess(ST))
return false;
@@ -1115,6 +1118,12 @@ bool AVRTargetLowering::getPostIndexedAddressParts(SDNode *N, SDNode *Op,
return false;
Base = Op->getOperand(0);
+
+ // Post-indexing updates the base, so it's not a valid transform
+ // if that's not the same as the load's pointer.
+ if (Ptr != Base)
+ return false;
+
Offset = DAG.getConstant(RHSC, DL, MVT::i8);
AM = ISD::POST_INC;
diff --git a/llvm/test/CodeGen/AVR/bug-143247.ll b/llvm/test/CodeGen/AVR/bug-143247.ll
new file mode 100644
index 0000000000000..07c4c6562c950
--- /dev/null
+++ b/llvm/test/CodeGen/AVR/bug-143247.ll
@@ -0,0 +1,36 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; RUN: llc < %s -O=2 -mtriple=avr-none --mcpu=avr128db28 -verify-machineinstrs | FileCheck %s
+
+declare dso_local void @nil(i16 noundef) addrspace(1)
+
+define void @complex_sbi() {
+; CHECK-LABEL: complex_sbi:
+; CHECK: ; %bb.0: ; %entry
+; CHECK-NEXT: push r16
+; CHECK-NEXT: push r17
+; CHECK-NEXT: ldi r24, 0
+; CHECK-NEXT: ldi r25, 0
+; CHECK-NEXT: .LBB0_1: ; %while.cond
+; CHECK-NEXT: ; =>This Inner Loop Header: Depth=1
+; CHECK-NEXT: sbi 1, 7
+; CHECK-NEXT: adiw r24, 1
+; CHECK-NEXT: movw r16, r24
+; CHECK-NEXT: andi r24, 15
+; CHECK-NEXT: andi r25, 0
+; CHECK-NEXT: adiw r24, 1
+; CHECK-NEXT: call nil
+; CHECK-NEXT: movw r24, r16
+; CHECK-NEXT: rjmp .LBB0_1
+entry:
+ br label %while.cond
+while.cond:
+ %s.0 = phi i16 [ 0, %entry ], [ %inc, %while.cond ]
+ %inc = add nuw nsw i16 %s.0, 1
+ %0 = load volatile i8, ptr inttoptr (i16 1 to ptr), align 1
+ %or = or i8 %0, -128
+ store volatile i8 %or, ptr inttoptr (i16 1 to ptr), align 1
+ %and = and i16 %inc, 15
+ %add = add nuw nsw i16 %and, 1
+ tail call addrspace(1) void @nil(i16 noundef %add)
+ br label %while.cond
+}
>From d2c0451d05d95c98727d2447abd1cb4bfed90890 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Sun, 22 Jun 2025 11:40:58 +0100
Subject: [PATCH 38/40] [lldb][DWAFASTParserClang][NFC] Rename
GetCXXObjectParameter to GetObjectParameter
Since this is used for Objective-C too.
---
.../SymbolFile/DWARF/DWARFASTParserClang.cpp | 12 ++++--------
.../SymbolFile/DWARF/DWARFASTParserClang.h | 16 +++++++++++++---
.../DWARF/DWARFASTParserClangTests.cpp | 7 +++----
3 files changed, 20 insertions(+), 15 deletions(-)
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
index 4f79c8aa3f811..a4cb608edd8b4 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
@@ -159,13 +159,9 @@ static bool TagIsRecordType(dw_tag_t tag) {
}
}
-/// Get the object parameter DIE if one exists, otherwise returns
-/// a default DWARFDIE. If \c containing_decl_ctx is not a valid
-/// C++ declaration context for class methods, assume no object
-/// parameter exists for the given \c subprogram.
DWARFDIE
-DWARFASTParserClang::GetCXXObjectParameter(const DWARFDIE &subprogram,
- const DWARFDIE &decl_ctx_die) {
+DWARFASTParserClang::GetObjectParameter(const DWARFDIE &subprogram,
+ const DWARFDIE &decl_ctx_die) {
assert(subprogram);
assert(subprogram.Tag() == DW_TAG_subprogram ||
subprogram.Tag() == DW_TAG_inlined_subroutine ||
@@ -1305,7 +1301,7 @@ DWARFASTParserClang::ParseSubroutine(const DWARFDIE &die,
clang::CallingConv calling_convention =
ConvertDWARFCallingConventionToClang(attrs);
- const DWARFDIE object_parameter = GetCXXObjectParameter(die, decl_ctx_die);
+ const DWARFDIE object_parameter = GetObjectParameter(die, decl_ctx_die);
// clang_type will get the function prototype clang type after this
// call
@@ -2417,7 +2413,7 @@ DWARFASTParserClang::ConstructDemangledNameFromDWARF(const DWARFDIE &die) {
assert(containing_decl_ctx);
const unsigned cv_quals =
- GetCXXMethodCVQuals(die, GetCXXObjectParameter(die, decl_ctx_die));
+ GetCXXMethodCVQuals(die, GetObjectParameter(die, decl_ctx_die));
ParseChildParameters(containing_decl_ctx, die, is_variadic,
has_template_params, param_types, param_names);
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h
index 111604ce4068a..e57fc503d34c1 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h
@@ -112,9 +112,19 @@ class DWARFASTParserClang : public lldb_private::plugin::dwarf::DWARFASTParser {
void MapDeclDIEToDefDIE(const lldb_private::plugin::dwarf::DWARFDIE &decl_die,
const lldb_private::plugin::dwarf::DWARFDIE &def_die);
- lldb_private::plugin::dwarf::DWARFDIE GetCXXObjectParameter(
- const lldb_private::plugin::dwarf::DWARFDIE &subprogram,
- const lldb_private::plugin::dwarf::DWARFDIE &decl_ctx_die);
+ /// Get the object parameter DIE if one exists, otherwise returns
+ /// a default DWARFDIE.
+ ///
+ /// \param[in] subprogram DIE of function for which to get the object
+ /// parameter. \param[in] containing_decl_ctx DIE representing declaration
+ /// context of \a subprogram. If this DIE isn't a valid declaration context
+ /// for class methods, assume no object parameter exists.
+ ///
+ /// \returns DIE of object parameter if one exists.
+ ///
+ lldb_private::plugin::dwarf::DWARFDIE
+ GetObjectParameter(const lldb_private::plugin::dwarf::DWARFDIE &subprogram,
+ const lldb_private::plugin::dwarf::DWARFDIE &decl_ctx_die);
protected:
/// Protected typedefs and members.
diff --git a/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp b/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp
index 2d4b79fed4a55..f18e938dbc4c9 100644
--- a/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp
+++ b/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp
@@ -898,8 +898,7 @@ TEST_F(DWARFASTParserClangTests, TestParseDWARFAttributes_ObjectPointer) {
auto param_die = decl_die.GetFirstChild();
ASSERT_TRUE(param_die.IsValid());
- EXPECT_EQ(param_die,
- ast_parser.GetCXXObjectParameter(decl_die, context_die));
+ EXPECT_EQ(param_die, ast_parser.GetObjectParameter(decl_die, context_die));
}
{
@@ -912,8 +911,8 @@ TEST_F(DWARFASTParserClangTests, TestParseDWARFAttributes_ObjectPointer) {
auto param_die = subprogram_definition.GetFirstChild();
ASSERT_TRUE(param_die.IsValid());
- EXPECT_EQ(param_die, ast_parser.GetCXXObjectParameter(subprogram_definition,
- context_die));
+ EXPECT_EQ(param_die, ast_parser.GetObjectParameter(subprogram_definition,
+ context_die));
}
}
>From 2f1558ae8c1c90a6091dbc821fd5438f5136b8ae Mon Sep 17 00:00:00 2001
From: Nicolas Vasilache <nico.vasilache at amd.com>
Date: Wed, 18 Jun 2025 19:14:31 +0200
Subject: [PATCH 39/40] [mlir][transform] Plumb a simplified form of AffineMin
folding into transform.pad-tiling-interface
This revision introduces a simple variant of AffineMin folding in makeComposedFoldedAffineApply
and makes use of it in transform.pad-tiling-interface.
Since this version explicitly call ValueBoundsInterface, it may be too expensive and is
only activate behind a flag.
It results in better foldings when mixing tiling and padding, including with dynamic shapes.
This should be further composed with #145068 to provide full simplification and address
the remaining TODO in the test.
---
.../mlir/Dialect/Affine/IR/AffineOps.h | 18 ++-
.../mlir/Interfaces/ValueBoundsOpInterface.h | 2 +-
mlir/lib/Dialect/Affine/IR/AffineOps.cpp | 134 ++++++++++++++----
.../Linalg/Transforms/PadTilingInterface.cpp | 5 +-
.../lib/Interfaces/ValueBoundsOpInterface.cpp | 2 +-
...m-op-pad-tiling-interface-multiple-of.mlir | 131 +++++++++++++++++
6 files changed, 251 insertions(+), 41 deletions(-)
diff --git a/mlir/include/mlir/Dialect/Affine/IR/AffineOps.h b/mlir/include/mlir/Dialect/Affine/IR/AffineOps.h
index 6fdb72c370e6d..2091faa6b0b02 100644
--- a/mlir/include/mlir/Dialect/Affine/IR/AffineOps.h
+++ b/mlir/include/mlir/Dialect/Affine/IR/AffineOps.h
@@ -410,9 +410,11 @@ void canonicalizeSetAndOperands(IntegerSet *set,
/// other AffineApplyOps supplying those operands. The operands of the resulting
/// AffineApplyOp do not change the length of AffineApplyOp chains.
AffineApplyOp makeComposedAffineApply(OpBuilder &b, Location loc, AffineMap map,
- ArrayRef<OpFoldResult> operands);
+ ArrayRef<OpFoldResult> operands,
+ bool composeAffineMin = false);
AffineApplyOp makeComposedAffineApply(OpBuilder &b, Location loc, AffineExpr e,
- ArrayRef<OpFoldResult> operands);
+ ArrayRef<OpFoldResult> operands,
+ bool composeAffineMin = false);
/// Constructs an AffineApplyOp that applies `map` to `operands` after composing
/// the map with the maps of any other AffineApplyOp supplying the operands,
@@ -421,16 +423,19 @@ AffineApplyOp makeComposedAffineApply(OpBuilder &b, Location loc, AffineExpr e,
/// map.
OpFoldResult makeComposedFoldedAffineApply(OpBuilder &b, Location loc,
AffineMap map,
- ArrayRef<OpFoldResult> operands);
+ ArrayRef<OpFoldResult> operands,
+ bool composeAffineMin = false);
/// Variant of `makeComposedFoldedAffineApply` that applies to an expression.
OpFoldResult makeComposedFoldedAffineApply(OpBuilder &b, Location loc,
AffineExpr expr,
- ArrayRef<OpFoldResult> operands);
+ ArrayRef<OpFoldResult> operands,
+ bool composeAffineMin = false);
/// Variant of `makeComposedFoldedAffineApply` suitable for multi-result maps.
/// Note that this may create as many affine.apply operations as the map has
/// results given that affine.apply must be single-result.
SmallVector<OpFoldResult> makeComposedFoldedMultiResultAffineApply(
- OpBuilder &b, Location loc, AffineMap map, ArrayRef<OpFoldResult> operands);
+ OpBuilder &b, Location loc, AffineMap map, ArrayRef<OpFoldResult> operands,
+ bool composeAffineMin = false);
/// Returns an AffineMinOp obtained by composing `map` and `operands` with
/// AffineApplyOps supplying those operands.
@@ -459,7 +464,8 @@ OpFoldResult makeComposedFoldedAffineMax(OpBuilder &b, Location loc,
/// terminal symbol, i.e., a symbol defined at the top level or a block/function
/// argument.
void fullyComposeAffineMapAndOperands(AffineMap *map,
- SmallVectorImpl<Value> *operands);
+ SmallVectorImpl<Value> *operands,
+ bool composeAffineMin = false);
} // namespace affine
} // namespace mlir
diff --git a/mlir/include/mlir/Interfaces/ValueBoundsOpInterface.h b/mlir/include/mlir/Interfaces/ValueBoundsOpInterface.h
index 337314143c80c..523df173093fa 100644
--- a/mlir/include/mlir/Interfaces/ValueBoundsOpInterface.h
+++ b/mlir/include/mlir/Interfaces/ValueBoundsOpInterface.h
@@ -135,7 +135,7 @@ class ValueBoundsConstraintSet
/// Construct a variable for a map and its operands.
Variable(AffineMap map, ArrayRef<Variable> mapOperands);
- Variable(AffineMap map, ArrayRef<Value> mapOperands);
+ Variable(AffineMap map, ValueRange mapOperands);
MLIRContext *getContext() const { return map.getContext(); }
diff --git a/mlir/lib/Dialect/Affine/IR/AffineOps.cpp b/mlir/lib/Dialect/Affine/IR/AffineOps.cpp
index 3d09c6a9b2c24..06b7910736727 100644
--- a/mlir/lib/Dialect/Affine/IR/AffineOps.cpp
+++ b/mlir/lib/Dialect/Affine/IR/AffineOps.cpp
@@ -11,12 +11,14 @@
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Dialect/UB/IR/UBOps.h"
#include "mlir/Dialect/Utils/StaticValueUtils.h"
+#include "mlir/IR/AffineExpr.h"
#include "mlir/IR/AffineExprVisitor.h"
#include "mlir/IR/IRMapping.h"
#include "mlir/IR/IntegerSet.h"
#include "mlir/IR/Matchers.h"
#include "mlir/IR/OpDefinition.h"
#include "mlir/IR/PatternMatch.h"
+#include "mlir/IR/Value.h"
#include "mlir/Interfaces/ShapedOpInterfaces.h"
#include "mlir/Interfaces/ValueBoundsOpInterface.h"
#include "mlir/Transforms/InliningUtils.h"
@@ -26,7 +28,9 @@
#include "llvm/ADT/SmallVectorExtras.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/Support/Debug.h"
+#include "llvm/Support/LogicalResult.h"
#include "llvm/Support/MathExtras.h"
+#include <limits>
#include <numeric>
#include <optional>
@@ -1042,6 +1046,59 @@ simplifyMapWithOperands(AffineMap &map, ArrayRef<Value> operands) {
map.getContext());
}
+/// Assuming `dimOrSym` is a quantity in `map` that is defined by `minOp`,
+/// replaces the patterns:
+/// ```
+/// dimOrSym.ceildiv(cst) * cst
+/// (dimOrSym + cst - 1).floordiv(cst) * cst
+/// ```
+/// by `cst` in `map`.
+/// This simplification is valid iff `minOp` is guaranteed to be nonnegative.
+/// Additionally, allows the caller to pass `affineMinKnownToBeNonNegative` to
+/// inject static information that may not be statically discoverable.
+/// Warning: ValueBoundsConstraintSet::computeConstantBound is needed to check
+/// for the nonnegative case, if `affineMinKnownToBeNonNegative` is false.
+static LogicalResult replaceAffineMinBoundingBoxExpression(
+ AffineMinOp minOp, AffineExpr dimOrSym, AffineMap *map,
+ bool affineMinKnownToBeNonNegative = false) {
+ auto affineMinMap = minOp.getAffineMap();
+ if (!affineMinKnownToBeNonNegative) {
+ ValueRange values = minOp->getOperands();
+ for (unsigned i = 0, e = affineMinMap.getNumResults(); i < e; ++i) {
+ AffineMap row = affineMinMap.getSubMap(ArrayRef<unsigned>{i});
+ FailureOr<int64_t> lowerBound =
+ ValueBoundsConstraintSet::computeConstantBound(
+ presburger::BoundType::LB, {row, values},
+ /*stopCondition=*/nullptr,
+ /*closedUB=*/true);
+ if (failed(lowerBound) || lowerBound.value() < 0)
+ return failure();
+ }
+ }
+
+ AffineMap initialMap = *map;
+ for (unsigned i = 0, e = affineMinMap.getNumResults(); i != e; ++i) {
+ auto m = affineMinMap.getSubMap(ArrayRef<unsigned>{i});
+ // TODO: this should also work with nonnegative symbolic divisors.
+ if (!m.isSingleConstant())
+ continue;
+
+ auto cst = m.getSingleConstantResult();
+ DenseMap<AffineExpr, AffineExpr> repl;
+ // dimOrSym.ceilDiv(cst) * cst -> cst
+ repl[dimOrSym.ceilDiv(cst) * cst] =
+ getAffineConstantExpr(cst, minOp.getContext());
+ // (dimOrSym + cst - 1).floorDiv(cst) * cst -> cst
+ repl[(dimOrSym + cst - 1).floorDiv(cst) * cst] =
+ getAffineConstantExpr(cst, minOp.getContext());
+ auto newMap = map->replace(repl);
+ if (newMap == *map)
+ continue;
+ *map = newMap;
+ }
+ return success(*map != initialMap);
+}
+
/// Replace all occurrences of AffineExpr at position `pos` in `map` by the
/// defining AffineApplyOp expression and operands.
/// When `dimOrSymbolPosition < dims.size()`, AffineDimExpr@[pos] is replaced.
@@ -1052,10 +1109,13 @@ simplifyMapWithOperands(AffineMap &map, ArrayRef<Value> operands) {
/// 2. `map` dim and symbols are gradually shifted to higher positions.
/// 3. Old `dim` and `sym` entries are replaced by nullptr
/// This avoids the need for any bookkeeping.
+/// If `replaceAffineMin` is set to true, additionally triggers more expensive
+/// replacements involving affine_min operations.
static LogicalResult replaceDimOrSym(AffineMap *map,
unsigned dimOrSymbolPosition,
SmallVectorImpl<Value> &dims,
- SmallVectorImpl<Value> &syms) {
+ SmallVectorImpl<Value> &syms,
+ bool replaceAffineMin) {
MLIRContext *ctx = map->getContext();
bool isDimReplacement = (dimOrSymbolPosition < dims.size());
unsigned pos = isDimReplacement ? dimOrSymbolPosition
@@ -1064,6 +1124,13 @@ static LogicalResult replaceDimOrSym(AffineMap *map,
if (!v)
return failure();
+ auto minOp = v.getDefiningOp<AffineMinOp>();
+ if (minOp && replaceAffineMin) {
+ AffineExpr dimOrSym = isDimReplacement ? getAffineDimExpr(pos, ctx)
+ : getAffineSymbolExpr(pos, ctx);
+ return replaceAffineMinBoundingBoxExpression(minOp, dimOrSym, map);
+ }
+
auto affineApply = v.getDefiningOp<AffineApplyOp>();
if (!affineApply)
return failure();
@@ -1101,7 +1168,8 @@ static LogicalResult replaceDimOrSym(AffineMap *map,
/// iteratively. Perform canonicalization of map and operands as well as
/// AffineMap simplification. `map` and `operands` are mutated in place.
static void composeAffineMapAndOperands(AffineMap *map,
- SmallVectorImpl<Value> *operands) {
+ SmallVectorImpl<Value> *operands,
+ bool composeAffineMin = false) {
if (map->getNumResults() == 0) {
canonicalizeMapAndOperands(map, operands);
*map = simplifyAffineMap(*map);
@@ -1122,7 +1190,8 @@ static void composeAffineMapAndOperands(AffineMap *map,
while (true) {
bool changed = false;
for (unsigned pos = 0; pos != dims.size() + syms.size(); ++pos)
- if ((changed |= succeeded(replaceDimOrSym(map, pos, dims, syms))))
+ if ((changed |=
+ succeeded(replaceDimOrSym(map, pos, dims, syms, composeAffineMin))))
break;
if (!changed)
break;
@@ -1163,38 +1232,41 @@ static void composeAffineMapAndOperands(AffineMap *map,
}
void mlir::affine::fullyComposeAffineMapAndOperands(
- AffineMap *map, SmallVectorImpl<Value> *operands) {
+ AffineMap *map, SmallVectorImpl<Value> *operands, bool composeAffineMin) {
while (llvm::any_of(*operands, [](Value v) {
return isa_and_nonnull<AffineApplyOp>(v.getDefiningOp());
})) {
- composeAffineMapAndOperands(map, operands);
+ composeAffineMapAndOperands(map, operands, composeAffineMin);
}
}
AffineApplyOp
mlir::affine::makeComposedAffineApply(OpBuilder &b, Location loc, AffineMap map,
- ArrayRef<OpFoldResult> operands) {
+ ArrayRef<OpFoldResult> operands,
+ bool composeAffineMin) {
SmallVector<Value> valueOperands;
map = foldAttributesIntoMap(b, map, operands, valueOperands);
- composeAffineMapAndOperands(&map, &valueOperands);
+ composeAffineMapAndOperands(&map, &valueOperands, composeAffineMin);
assert(map);
return b.create<AffineApplyOp>(loc, map, valueOperands);
}
AffineApplyOp
mlir::affine::makeComposedAffineApply(OpBuilder &b, Location loc, AffineExpr e,
- ArrayRef<OpFoldResult> operands) {
+ ArrayRef<OpFoldResult> operands,
+ bool composeAffineMin) {
return makeComposedAffineApply(
b, loc,
AffineMap::inferFromExprList(ArrayRef<AffineExpr>{e}, b.getContext())
.front(),
- operands);
+ operands, composeAffineMin);
}
/// Composes the given affine map with the given list of operands, pulling in
/// the maps from any affine.apply operations that supply the operands.
static void composeMultiResultAffineMap(AffineMap &map,
- SmallVectorImpl<Value> &operands) {
+ SmallVectorImpl<Value> &operands,
+ bool composeAffineMin = false) {
// Compose and canonicalize each expression in the map individually because
// composition only applies to single-result maps, collecting potentially
// duplicate operands in a single list with shifted dimensions and symbols.
@@ -1203,7 +1275,8 @@ static void composeMultiResultAffineMap(AffineMap &map,
for (unsigned i : llvm::seq<unsigned>(0, map.getNumResults())) {
SmallVector<Value> submapOperands(operands.begin(), operands.end());
AffineMap submap = map.getSubMap({i});
- fullyComposeAffineMapAndOperands(&submap, &submapOperands);
+ fullyComposeAffineMapAndOperands(&submap, &submapOperands,
+ composeAffineMin);
canonicalizeMapAndOperands(&submap, &submapOperands);
unsigned numNewDims = submap.getNumDims();
submap = submap.shiftDims(dims.size()).shiftSymbols(symbols.size());
@@ -1221,10 +1294,9 @@ static void composeMultiResultAffineMap(AffineMap &map,
canonicalizeMapAndOperands(&map, &operands);
}
-OpFoldResult
-mlir::affine::makeComposedFoldedAffineApply(OpBuilder &b, Location loc,
- AffineMap map,
- ArrayRef<OpFoldResult> operands) {
+OpFoldResult mlir::affine::makeComposedFoldedAffineApply(
+ OpBuilder &b, Location loc, AffineMap map, ArrayRef<OpFoldResult> operands,
+ bool composeAffineMin) {
assert(map.getNumResults() == 1 && "building affine.apply with !=1 result");
// Create new builder without a listener, so that no notification is
@@ -1236,7 +1308,7 @@ mlir::affine::makeComposedFoldedAffineApply(OpBuilder &b, Location loc,
// Create op.
AffineApplyOp applyOp =
- makeComposedAffineApply(newBuilder, loc, map, operands);
+ makeComposedAffineApply(newBuilder, loc, map, operands, composeAffineMin);
// Get constant operands.
SmallVector<Attribute> constOperands(applyOp->getNumOperands());
@@ -1256,26 +1328,25 @@ mlir::affine::makeComposedFoldedAffineApply(OpBuilder &b, Location loc,
return llvm::getSingleElement(foldResults);
}
-OpFoldResult
-mlir::affine::makeComposedFoldedAffineApply(OpBuilder &b, Location loc,
- AffineExpr expr,
- ArrayRef<OpFoldResult> operands) {
+OpFoldResult mlir::affine::makeComposedFoldedAffineApply(
+ OpBuilder &b, Location loc, AffineExpr expr,
+ ArrayRef<OpFoldResult> operands, bool composeAffineMin) {
return makeComposedFoldedAffineApply(
b, loc,
AffineMap::inferFromExprList(ArrayRef<AffineExpr>{expr}, b.getContext())
.front(),
- operands);
+ operands, composeAffineMin);
}
SmallVector<OpFoldResult>
mlir::affine::makeComposedFoldedMultiResultAffineApply(
- OpBuilder &b, Location loc, AffineMap map,
- ArrayRef<OpFoldResult> operands) {
- return llvm::map_to_vector(llvm::seq<unsigned>(0, map.getNumResults()),
- [&](unsigned i) {
- return makeComposedFoldedAffineApply(
- b, loc, map.getSubMap({i}), operands);
- });
+ OpBuilder &b, Location loc, AffineMap map, ArrayRef<OpFoldResult> operands,
+ bool composeAffineMin) {
+ return llvm::map_to_vector(
+ llvm::seq<unsigned>(0, map.getNumResults()), [&](unsigned i) {
+ return makeComposedFoldedAffineApply(b, loc, map.getSubMap({i}),
+ operands, composeAffineMin);
+ });
}
template <typename OpTy>
@@ -3024,7 +3095,8 @@ void AffineIfOp::build(OpBuilder &builder, OperationState &result,
/// `set` by composing the maps of such affine.apply ops with the integer
/// set constraints.
static void composeSetAndOperands(IntegerSet &set,
- SmallVectorImpl<Value> &operands) {
+ SmallVectorImpl<Value> &operands,
+ bool composeAffineMin) {
// We will simply reuse the API of the map composition by viewing the LHSs of
// the equalities and inequalities of `set` as the affine exprs of an affine
// map. Convert to equivalent map, compose, and convert back to set.
@@ -3035,7 +3107,7 @@ static void composeSetAndOperands(IntegerSet &set,
[](Value v) { return v.getDefiningOp<AffineApplyOp>(); }))
return;
- composeAffineMapAndOperands(&map, &operands);
+ composeAffineMapAndOperands(&map, &operands, composeAffineMin);
set = IntegerSet::get(map.getNumDims(), map.getNumSymbols(), map.getResults(),
set.getEqFlags());
}
@@ -3044,7 +3116,7 @@ static void composeSetAndOperands(IntegerSet &set,
LogicalResult AffineIfOp::fold(FoldAdaptor, SmallVectorImpl<OpFoldResult> &) {
auto set = getIntegerSet();
SmallVector<Value, 4> operands(getOperands());
- composeSetAndOperands(set, operands);
+ composeSetAndOperands(set, operands, /*composeAffineMin=*/false);
canonicalizeSetAndOperands(&set, &operands);
// Check if the canonicalization or composition led to any change.
diff --git a/mlir/lib/Dialect/Linalg/Transforms/PadTilingInterface.cpp b/mlir/lib/Dialect/Linalg/Transforms/PadTilingInterface.cpp
index 5383ae48aeb3a..42dac0776bace 100644
--- a/mlir/lib/Dialect/Linalg/Transforms/PadTilingInterface.cpp
+++ b/mlir/lib/Dialect/Linalg/Transforms/PadTilingInterface.cpp
@@ -84,7 +84,7 @@ SmallVector<OpFoldResult> linalg::computePaddedShape(
getDimsToSize(rewriter, indexingSizes, options);
// For each dimension in the operand's shape, iterate over indexingSizes and
- // add
+ // add the various term contributions.
for (const auto &enResults : enumerate(indexingMap.getResults())) {
int64_t resultIndex = enResults.index();
AffineMap partialIndexingMap = indexingMap.getSubMap(
@@ -122,7 +122,8 @@ SmallVector<OpFoldResult> linalg::computePaddedShape(
AffineMap composedMap = projectedMap.compose(ceilMap);
OpFoldResult paddingDimOfr = affine::makeComposedFoldedAffineApply(
rewriter, loc, composedMap,
- {indexingSizes[paddingDim], paddingSize});
+ {indexingSizes[paddingDim], paddingSize},
+ /*composeAffineMin=*/true);
terms.push_back(paddingDimOfr);
} else {
// Otherwise just set to paddingSize.
diff --git a/mlir/lib/Interfaces/ValueBoundsOpInterface.cpp b/mlir/lib/Interfaces/ValueBoundsOpInterface.cpp
index 87f883c2e6485..d858ab3a6406a 100644
--- a/mlir/lib/Interfaces/ValueBoundsOpInterface.cpp
+++ b/mlir/lib/Interfaces/ValueBoundsOpInterface.cpp
@@ -146,7 +146,7 @@ ValueBoundsConstraintSet::Variable::Variable(AffineMap map,
}
ValueBoundsConstraintSet::Variable::Variable(AffineMap map,
- ArrayRef<Value> mapOperands)
+ ValueRange mapOperands)
: Variable(map, llvm::map_to_vector(mapOperands,
[](Value v) { return Variable(v); })) {}
diff --git a/mlir/test/Dialect/Linalg/transform-op-pad-tiling-interface-multiple-of.mlir b/mlir/test/Dialect/Linalg/transform-op-pad-tiling-interface-multiple-of.mlir
index 5ac35c14be3fb..4fcbcbb2a18e3 100644
--- a/mlir/test/Dialect/Linalg/transform-op-pad-tiling-interface-multiple-of.mlir
+++ b/mlir/test/Dialect/Linalg/transform-op-pad-tiling-interface-multiple-of.mlir
@@ -136,3 +136,134 @@ module {
}
}
}
+
+// -----
+
+// CHECK-DAG: #[[$MAP0:.*]] = affine_map<()[s0, s1] -> (-s1 + (s0 ceildiv 16) * 16)>
+// CHECK-DAG: #[[$MAP1:.*]] = affine_map<(d0)[s0] -> (-d0 + s0, 16)>
+
+// CHECK-LABEL: pad_lhs
+func.func @pad_lhs(
+ %arg0: tensor<24x?xf32>, %arg1: tensor<?x25xf32>, %arg2: tensor<24x25xf32>)
+ -> tensor<24x25xf32>
+{
+ // CHECK: %[[D0_0:.*]] = tensor.dim
+ // CHECK: %[[D0_1:.*]] = tensor.dim
+ // CHECK: %[[H0:.*]] = affine.apply #[[$MAP0]]()[%[[D0_0]], %[[D0_1]]]
+ // CHECK: tensor.pad %{{.*}} low[0, 0] high[0, %[[H0]]]
+ // CHECK: : tensor<24x?xf32> to tensor<24x?xf32>
+
+ // CHECK: %[[D0_2:.*]] = tensor.dim
+ // CHECK: %[[H1:.*]] = affine.apply #[[$MAP0]]()[%[[D0_0]], %[[D0_2]]]
+ // CHECK: tensor.pad %{{.*}} low[0, 0] high[%[[H1]], 0]
+ // CHECK: : tensor<?x25xf32> to tensor<?x25xf32>
+ // CHECK: scf.for %{{.*}} -> (tensor<24x25xf32>)
+
+ // TODO: Not yet simplified enough..
+ // CHECK: linalg.matmul ins(%{{.*}}, %{{.*}}: tensor<8x?xf32>, tensor<?x25xf32>) outs(%extracted_slice_5 : tensor<8x25xf32>) -> tensor<8x25xf32>
+
+ // CHECK: tensor.insert_slice %{{.*}} into %{{.*}}[%{{.*}}, 0] [8, 25] [1, 1]
+ // CHECK-SAME: : tensor<8x25xf32> into tensor<24x25xf32>
+ %0 = linalg.matmul ins(%arg0, %arg1 : tensor<24x?xf32>, tensor<?x25xf32>) outs(%arg2 : tensor<24x25xf32>) -> tensor<24x25xf32>
+ func.return %0 : tensor<24x25xf32>
+}
+
+module attributes {transform.with_named_sequence} {
+ transform.named_sequence @__transform_main(%arg1: !transform.any_op {transform.readonly}) {
+ %matmul = transform.structured.match ops{["linalg.matmul"]} in %arg1
+ : (!transform.any_op) -> !transform.any_op
+
+ // Pad then tile should produce static shapes.
+ %matmul_padded, %_ = transform.structured.pad_tiling_interface %matmul to padding_sizes [8, 16] pad_to_multiple_of {
+ padding_values=[0.0: f32, 0.0 : f32, 0.0 : f32],
+ padding_dimensions=[0, 2]
+ } : (!transform.any_op) -> (!transform.any_op, !transform.any_op)
+
+ %m, %l0, %l1 = transform.structured.tile_using_for %matmul_padded tile_sizes [8, 0, 16]
+ : (!transform.any_op) -> (!transform.any_op, !transform.any_op, !transform.any_op)
+
+ transform.yield
+ }
+}
+
+// -----
+
+// CHECK-DAG: #[[$MAP0:.*]] = affine_map<(d0)[s0] -> (-d0 + s0, 16)>
+// CHECK-DAG: #[[$MAP1:.*]] = affine_map<(d0) -> (-d0 + 16)>
+
+// CHECK-LABEL: pad_lhs
+func.func @pad_lhs(
+ %arg0: tensor<24x?xf32>, %arg1: tensor<?x25xf32>, %arg2: tensor<24x25xf32>)
+ -> tensor<24x25xf32>
+{
+ // CHECK: scf.for %{{.*}} -> (tensor<24x25xf32>)
+ // CHECK: %[[MIN:.*]] = affine.min #[[$MAP0]](%{{.*}})
+ // CHECK: %[[H0:.*]] = affine.apply #[[$MAP1]](%[[MIN]])
+ // CHECK: tensor.pad %{{.*}} low[0, 0] high[0, %[[H0]]]
+ // CHECK: : tensor<8x?xf32> to tensor<8x16xf32>
+
+ // CHECK: %[[H1:.*]] = affine.apply #[[$MAP1]](%[[MIN]])
+ // CHECK: tensor.pad %{{.*}} low[0, 0] high[%[[H1]], 0]
+ // CHECK: : tensor<?x25xf32> to tensor<16x25xf32>
+
+ // CHECK: linalg.matmul ins(%{{.*}}, %{{.*}} : tensor<8x16xf32>, tensor<16x25xf32>) outs(%{{.*}} : tensor<8x25xf32>) -> tensor<8x25xf32>
+
+ // CHECK: tensor.insert_slice %{{.*}} into %{{.*}}[%{{.*}}, 0] [8, 25] [1, 1]
+ // CHECK-SAME: : tensor<8x25xf32> into tensor<24x25xf32>
+ %0 = linalg.matmul ins(%arg0, %arg1 : tensor<24x?xf32>, tensor<?x25xf32>) outs(%arg2 : tensor<24x25xf32>) -> tensor<24x25xf32>
+ func.return %0 : tensor<24x25xf32>
+}
+
+module attributes {transform.with_named_sequence} {
+ transform.named_sequence @__transform_main(%arg1: !transform.any_op {transform.readonly}) {
+ %matmul = transform.structured.match ops{["linalg.matmul"]} in %arg1
+ : (!transform.any_op) -> !transform.any_op
+
+ // Tile then pad should produce static shapes.
+ %m, %l0, %l1 = transform.structured.tile_using_for %matmul tile_sizes [8, 0, 16]
+ : (!transform.any_op) -> (!transform.any_op, !transform.any_op, !transform.any_op)
+
+ %matmul_padded, %_ = transform.structured.pad_tiling_interface %m to padding_sizes [8, 16] pad_to_multiple_of {
+ padding_values=[0.0: f32, 0.0 : f32, 0.0 : f32],
+ padding_dimensions=[0, 2]
+ } : (!transform.any_op) -> (!transform.any_op, !transform.any_op)
+
+ transform.yield
+ }
+}
+
+// -----
+
+// CHECK-DAG: #[[$MAP0:.*]] = affine_map<(d0) -> (-d0 + 20, 8)>
+// CHECK-DAG: #[[$MAP1:.*]] = affine_map<(d0)[s0] -> (-d0 + s0, 16)>
+// CHECK-DAG: #[[$MAP2:.*]] = affine_map<(d0) -> (-d0 + 8)>
+// CHECK-DAG: #[[$MAP3:.*]] = affine_map<(d0) -> (-d0 + 16)>
+
+// CHECK-LABEL: pad_lhs
+func.func @pad_lhs(
+ %arg0: tensor<20x?xf32>, %arg1: tensor<?x25xf32>, %arg2: tensor<20x25xf32>)
+ -> tensor<20x25xf32>
+{
+ // CHECK: linalg.matmul ins(%{{.*}}, %{{.*}} : tensor<8x16xf32>, tensor<16x25xf32>) outs(%{{.*}} : tensor<8x25xf32>) -> tensor<8x25xf32>
+ %0 = linalg.matmul ins(%arg0, %arg1 : tensor<20x?xf32>, tensor<?x25xf32>) outs(%arg2 : tensor<20x25xf32>) -> tensor<20x25xf32>
+ func.return %0 : tensor<20x25xf32>
+}
+
+module attributes {transform.with_named_sequence} {
+ transform.named_sequence @__transform_main(%arg1: !transform.any_op {transform.readonly}) {
+ %matmul = transform.structured.match ops{["linalg.matmul"]} in %arg1
+ : (!transform.any_op) -> !transform.any_op
+
+ // Tile then pad should produce static shapes.
+ %m, %l0, %l1 = transform.structured.tile_using_for %matmul tile_sizes [8, 0, 16]
+ : (!transform.any_op) -> (!transform.any_op, !transform.any_op, !transform.any_op)
+
+ %matmul_padded, %_ = transform.structured.pad_tiling_interface %m to padding_sizes [8, 16] pad_to_multiple_of {
+ padding_values=[0.0: f32, 0.0 : f32, 0.0 : f32],
+ padding_dimensions=[0, 2]
+ } : (!transform.any_op) -> (!transform.any_op, !transform.any_op)
+
+ transform.yield
+ }
+}
+
>From 512be3adf3bda6baeb41da93a94fd235c1eb39b9 Mon Sep 17 00:00:00 2001
From: Nicolas Vasilache <nico.vasilache at amd.com>
Date: Sun, 22 Jun 2025 14:24:48 +0200
Subject: [PATCH 40/40] [mlir][transform] Drop redundant padding_dimensions
spec from pad_tiling_interface
This revision aligns padding specification in pad_tiling_interface to that of tiling specification.
Dimensions that should be skipped are specified by "padding by 0".
Trailing dimensions that are ignored are automatically completed to "pad to 0".
---
.../Linalg/TransformOps/LinalgTransformOps.td | 25 +++++++----
.../TransformOps/LinalgTransformOps.cpp | 21 +--------
.../Linalg/Transforms/PadTilingInterface.cpp | 45 +++++++++----------
...m-op-pad-tiling-interface-multiple-of.mlir | 28 +++++-------
.../transform-op-pad-tiling-interface.mlir | 12 ++---
5 files changed, 54 insertions(+), 77 deletions(-)
diff --git a/mlir/include/mlir/Dialect/Linalg/TransformOps/LinalgTransformOps.td b/mlir/include/mlir/Dialect/Linalg/TransformOps/LinalgTransformOps.td
index cf3f2b70580da..c5650470fdc8d 100644
--- a/mlir/include/mlir/Dialect/Linalg/TransformOps/LinalgTransformOps.td
+++ b/mlir/include/mlir/Dialect/Linalg/TransformOps/LinalgTransformOps.td
@@ -1195,17 +1195,29 @@ def PadTilingInterfaceOp : Op<Transform_Dialect, "structured.pad_tiling_interfac
TransformOpInterface,
ReportTrackingListenerFailuresOpTrait]> {
let description = [{
- Pads the operations pointed to by the target handle using the options
- provided as operation attributes. The operation returns a handle to the
- padded operation and to the padding operation ("tensor.pad").
+ Pads the **iteration domain** of the operations pointed to by the target
+ handle using the options provided as operation attributes. Padding the
+ iteration domain induces a padding of the operands that is consistent
+ across the op semantics and, unlike for simple elementwise ops, may not be
+ trivially deducible or specifiable on operands only (e.g. convolutions).
+
+ The specification of `padding_sizes` follows that of `tile_sizes` during
+ tiling: the value "0" on a particular iterator encode "no padding". Like in
+ the case of tiling, an automatic completion by 0 to the operation rank
+ occurs.
+
+ This transformation returns a handle to the padded operation and to the
+ padding operation ("tensor.pad").
TODO: in the future this should be moved out of a specific Linalg
implementation file and into a more general "Structured" file.
#### Return modes
- This operation ignores non-Linalg ops and drops them in the return.
- In the future, this operation will support all TilingInterfaceOps.
+ This operation ignores non-IndexingMapOpInterface ops and drops them in the
+ return. In the future, this operation will support all TilingInterfaceOps
+ for which the contract between iteration domain and operands can be
+ reified.
This operation may produce a definite failure if the padding fails for any
reason.
@@ -1219,7 +1231,6 @@ def PadTilingInterfaceOp : Op<Transform_Dialect, "structured.pad_tiling_interfac
let arguments =
(ins TransformHandleTypeInterface:$target,
DefaultValuedAttr<ArrayAttr, "{}">:$padding_values,
- DefaultValuedAttr<I64ArrayAttr, "{}">:$padding_dimensions,
Variadic<TransformAnyParamTypeOrAnyHandle>:$padding_sizes,
DefaultValuedOptionalAttr<DenseI64ArrayAttr, "{}">:
$static_padding_sizes,
@@ -1245,11 +1256,9 @@ def PadTilingInterfaceOp : Op<Transform_Dialect, "structured.pad_tiling_interfac
// add/mul ring at the moment.
// TODO: support other operations (e.g. min, max etc).
OpBuilder<(ins "Value":$target,
- "ArrayRef<int64_t>":$paddingDimensions,
CArg<"ArrayRef<int64_t>", "{}">:$staticPaddingSizes,
CArg<"bool", "false">:$padToMultipleOf)>,
OpBuilder<(ins "Value":$target,
- "ArrayRef<int64_t>":$paddingDimensions,
"ArrayRef<OpFoldResult>":$mixedPadPaddingSizes,
CArg<"bool", "false">:$usePrescribedTensorShapes)>
];
diff --git a/mlir/lib/Dialect/Linalg/TransformOps/LinalgTransformOps.cpp b/mlir/lib/Dialect/Linalg/TransformOps/LinalgTransformOps.cpp
index 5d55adbf46f36..d9a0ba02f4fe4 100644
--- a/mlir/lib/Dialect/Linalg/TransformOps/LinalgTransformOps.cpp
+++ b/mlir/lib/Dialect/Linalg/TransformOps/LinalgTransformOps.cpp
@@ -2163,7 +2163,6 @@ LogicalResult transform::PadOp::verify() {
void transform::PadTilingInterfaceOp::build(OpBuilder &b,
OperationState &result,
Value target,
- ArrayRef<int64_t> paddingDimensions,
ArrayRef<int64_t> paddingSizes,
bool padToMultipleOf) {
auto resultType = transform::AnyOpType::get(b.getContext());
@@ -2172,7 +2171,6 @@ void transform::PadTilingInterfaceOp::build(OpBuilder &b,
/*types=*/TypeRange{resultType, resultType},
/*target=*/target,
/*paddingValues=*/ArrayAttr(), // let inference handle this
- /*paddingDimensions=*/b.getI64ArrayAttr(paddingDimensions),
/*paddingSizes=*/ValueRange{},
/*paddingSizes=*/
(paddingSizes.empty() ? DenseI64ArrayAttr()
@@ -2183,7 +2181,6 @@ void transform::PadTilingInterfaceOp::build(OpBuilder &b,
void transform::PadTilingInterfaceOp::build(
OpBuilder &b, OperationState &result, Value target,
- ArrayRef<int64_t> paddingDimensions,
ArrayRef<OpFoldResult> mixedPaddingSizes, bool padToMultipleOf) {
auto resultType = transform::AnyOpType::get(b.getContext());
SmallVector<int64_t> staticPaddingSizes;
@@ -2195,7 +2192,6 @@ void transform::PadTilingInterfaceOp::build(
/*types=*/TypeRange{resultType, resultType},
/*target=*/target,
/*paddingValues=*/ArrayAttr(), // let inference handle this
- /*paddingDimensions=*/b.getI64ArrayAttr(paddingDimensions),
/*paddingSizes=*/dynamicPaddingSizes,
/*paddingSizes=*/staticPaddingSizes,
/*usePrescribedTensorShapes=*/padToMultipleOf);
@@ -2277,8 +2273,6 @@ transform::PadTilingInterfaceOp::apply(transform::TransformRewriter &rewriter,
TilingInterface paddedOp;
PadTilingInterfaceOptions options;
options.setPaddingValues(paddingValues)
- .setPaddingDimensions(
- extractFromIntegerArrayAttr<int64_t>(getPaddingDimensions()))
.setPaddingSizes(getMixedPaddingSizes())
.setPadToMultipleOf(getPadToMultipleOf());
@@ -2303,20 +2297,7 @@ transform::PadTilingInterfaceOp::apply(transform::TransformRewriter &rewriter,
return DiagnosedSilenceableFailure::success();
}
-LogicalResult transform::PadTilingInterfaceOp::verify() {
- SmallVector<int64_t> paddingDimensions =
- extractFromIntegerArrayAttr<int64_t>(getPaddingDimensions());
- if (any_of(paddingDimensions,
- [](int64_t paddingDimension) { return paddingDimension < 0; })) {
- return emitOpError() << "expects padding_dimensions to contain positive "
- "integers, found "
- << getPaddingDimensions();
- }
- if (getMixedPaddingSizes().size() != paddingDimensions.size()) {
- return emitOpError() << "expects as many multiples as padding_dimensions";
- }
- return success();
-}
+LogicalResult transform::PadTilingInterfaceOp::verify() { return success(); }
//===---------------------------------------------------------------------===//
// HoistPadOp
diff --git a/mlir/lib/Dialect/Linalg/Transforms/PadTilingInterface.cpp b/mlir/lib/Dialect/Linalg/Transforms/PadTilingInterface.cpp
index 42dac0776bace..eda3373b4d639 100644
--- a/mlir/lib/Dialect/Linalg/Transforms/PadTilingInterface.cpp
+++ b/mlir/lib/Dialect/Linalg/Transforms/PadTilingInterface.cpp
@@ -32,29 +32,27 @@ using namespace mlir::tensor;
#define DBGSNL() (llvm::dbgs() << "\n")
/// Form a "full-rank" padding specification so that the application is easy.
-static llvm::SmallDenseMap<int64_t, OpFoldResult>
-getDimsToSize(Builder &b, ArrayRef<OpFoldResult> indexingSizes,
- const PadTilingInterfaceOptions &options) {
- llvm::SmallDenseMap<int64_t, OpFoldResult> dimsToSize;
- for (const auto &[paddingDim, paddingSize] :
- llvm::zip_equal(options.paddingDimensions, options.paddingSizes)) {
- dimsToSize[paddingDim] = paddingSize;
- }
+static SmallVector<OpFoldResult>
+getFullRankPaddingSizes(Builder &b, ArrayRef<OpFoldResult> indexingSizes,
+ const PadTilingInterfaceOptions &options) {
+ SmallVector<OpFoldResult> paddingSizes;
// Complete the padding specification to specify all dimensions.
- for (int64_t idx = 0, e = indexingSizes.size(); idx != e; ++idx) {
- if (dimsToSize.find(idx) != dimsToSize.end())
- continue;
- // If a dimension is not specified, either complete with:
+ for (size_t idx = 0, e = indexingSizes.size(); idx != e; ++idx) {
+ // Complete to zero if needed.
+ paddingSizes.push_back(options.paddingSizes.size() > idx
+ ? options.paddingSizes[idx]
+ : b.getIndexAttr(0));
+ // If a dimension is zero (either specified or completed), replace by:
// - 1 if we are padding to the next multiple of.
// - indexingSizes[idx] otherwise
- dimsToSize[idx] =
- options.padToMultipleOf ? b.getIndexAttr(1) : indexingSizes[idx];
- }
- for (int64_t idx = 0, e = indexingSizes.size(); idx != e; ++idx) {
- LLVM_DEBUG(DBGS() << "----idx: " << idx << " : " << dimsToSize[idx]
+ if (isZeroInteger(paddingSizes[idx])) {
+ paddingSizes[idx] =
+ options.padToMultipleOf ? b.getIndexAttr(1) : indexingSizes[idx];
+ }
+ LLVM_DEBUG(DBGS() << "----idx: " << idx << " : " << paddingSizes[idx]
<< "\n");
}
- return dimsToSize;
+ return paddingSizes;
}
/// Compute the padded shape of the given value `v` of `RankedTensorType` given
@@ -80,8 +78,8 @@ SmallVector<OpFoldResult> linalg::computePaddedShape(
"rank");
// "Full-rank" padding specification.
- llvm::SmallDenseMap<int64_t, OpFoldResult> dimsToSize =
- getDimsToSize(rewriter, indexingSizes, options);
+ SmallVector<OpFoldResult> paddingSizes =
+ getFullRankPaddingSizes(rewriter, indexingSizes, options);
// For each dimension in the operand's shape, iterate over indexingSizes and
// add the various term contributions.
@@ -97,7 +95,9 @@ SmallVector<OpFoldResult> linalg::computePaddedShape(
// Find all padding dimensions that contribute to this operand dimension
// and compute the padded term contribution to the final padded shape.
SmallVector<OpFoldResult> terms;
- for (const auto &[paddingDim, paddingSize] : dimsToSize) {
+ for (size_t paddingDim = 0, e = paddingSizes.size(); paddingDim != e;
+ ++paddingDim) {
+ OpFoldResult paddingSize = paddingSizes[paddingDim];
LLVM_DEBUG(DBGS() << "------try apply padding of dim: " << paddingDim
<< " to: " << paddingSize << "\n");
if (!enResults.value().isFunctionOfDim(paddingDim))
@@ -224,9 +224,6 @@ linalg::rewriteAsPaddedOp(RewriterBase &rewriter, TilingInterface opToPad,
SmallVector<tensor::PadOp> &padOps,
PadSizeComputationFunction computePaddingSizeFun) {
LLVM_DEBUG(DBGS() << "Start rewriteAsPaddedOp : " << opToPad << "\n");
- assert(constOptions.paddingSizes.size() ==
- constOptions.paddingDimensions.size() &&
- "invalid number of elements in padToMultipleOf");
Location loc = opToPad.getLoc();
PadTilingInterfaceOptions options(constOptions);
diff --git a/mlir/test/Dialect/Linalg/transform-op-pad-tiling-interface-multiple-of.mlir b/mlir/test/Dialect/Linalg/transform-op-pad-tiling-interface-multiple-of.mlir
index 4fcbcbb2a18e3..2bba309953570 100644
--- a/mlir/test/Dialect/Linalg/transform-op-pad-tiling-interface-multiple-of.mlir
+++ b/mlir/test/Dialect/Linalg/transform-op-pad-tiling-interface-multiple-of.mlir
@@ -36,8 +36,7 @@ module attributes {transform.with_named_sequence} {
// Tile to 5 then pad to 8 (supposedly to better hit vector ops).
%matmul_l1, %loops_l1 = transform.structured.tile_using_for %matmul tile_sizes [5] : (!transform.any_op) -> (!transform.any_op, !transform.any_op)
%matmul_padded, %_ = transform.structured.pad_tiling_interface %matmul_l1 to padding_sizes [8] pad_to_multiple_of {
- padding_values=[0.0: f32, 0.0 : f32, 0.0 : f32],
- padding_dimensions=[0]
+ padding_values=[0.0: f32, 0.0 : f32, 0.0 : f32]
} : (!transform.any_op) -> (!transform.any_op, !transform.any_op)
transform.yield
@@ -73,9 +72,8 @@ module {
module attributes {transform.with_named_sequence} {
transform.named_sequence @__transform_main(%arg0: !transform.any_op {transform.readonly}) {
%0 = transform.structured.match ops{["linalg.generic"]} in %arg0 : (!transform.any_op) -> !transform.any_op
- %padded, %pad = transform.structured.pad_tiling_interface %0 to padding_sizes [3, 5] pad_to_multiple_of {
- padding_dimensions = [0, 2],
- padding_values = [0.000000e+00 : f32, 0.000000e+00 : f32, 0.000000e+00 : f32]
+ %padded, %pad = transform.structured.pad_tiling_interface %0 to padding_sizes [3, 0, 5] pad_to_multiple_of {
+ padding_values = [0.0 : f32, 0.0 : f32, 0.0 : f32]
} : (!transform.any_op) -> (!transform.any_op, !transform.any_op)
transform.yield
}
@@ -128,9 +126,8 @@ module {
module attributes {transform.with_named_sequence} {
transform.named_sequence @__transform_main(%arg0: !transform.any_op {transform.readonly}) {
%0 = transform.structured.match ops{["linalg.generic"]} in %arg0 : (!transform.any_op) -> !transform.any_op
- %padded, %pad = transform.structured.pad_tiling_interface %0 to padding_sizes [3, 5] pad_to_multiple_of {
- padding_dimensions = [0, 2],
- padding_values = [0.000000e+00 : f32, 0.000000e+00 : f32, 0.000000e+00 : f32]
+ %padded, %pad = transform.structured.pad_tiling_interface %0 to padding_sizes [3, 0, 5] pad_to_multiple_of {
+ padding_values = [0.0 : f32, 0.0 : f32, 0.0 : f32]
} : (!transform.any_op) -> (!transform.any_op, !transform.any_op)
transform.yield
}
@@ -174,9 +171,8 @@ module attributes {transform.with_named_sequence} {
: (!transform.any_op) -> !transform.any_op
// Pad then tile should produce static shapes.
- %matmul_padded, %_ = transform.structured.pad_tiling_interface %matmul to padding_sizes [8, 16] pad_to_multiple_of {
- padding_values=[0.0: f32, 0.0 : f32, 0.0 : f32],
- padding_dimensions=[0, 2]
+ %matmul_padded, %_ = transform.structured.pad_tiling_interface %matmul to padding_sizes [8, 0, 16] pad_to_multiple_of {
+ padding_values=[0.0: f32, 0.0 : f32, 0.0 : f32]
} : (!transform.any_op) -> (!transform.any_op, !transform.any_op)
%m, %l0, %l1 = transform.structured.tile_using_for %matmul_padded tile_sizes [8, 0, 16]
@@ -223,9 +219,8 @@ module attributes {transform.with_named_sequence} {
%m, %l0, %l1 = transform.structured.tile_using_for %matmul tile_sizes [8, 0, 16]
: (!transform.any_op) -> (!transform.any_op, !transform.any_op, !transform.any_op)
- %matmul_padded, %_ = transform.structured.pad_tiling_interface %m to padding_sizes [8, 16] pad_to_multiple_of {
- padding_values=[0.0: f32, 0.0 : f32, 0.0 : f32],
- padding_dimensions=[0, 2]
+ %matmul_padded, %_ = transform.structured.pad_tiling_interface %m to padding_sizes [8, 0, 16] pad_to_multiple_of {
+ padding_values=[0.0: f32, 0.0 : f32, 0.0 : f32]
} : (!transform.any_op) -> (!transform.any_op, !transform.any_op)
transform.yield
@@ -258,9 +253,8 @@ module attributes {transform.with_named_sequence} {
%m, %l0, %l1 = transform.structured.tile_using_for %matmul tile_sizes [8, 0, 16]
: (!transform.any_op) -> (!transform.any_op, !transform.any_op, !transform.any_op)
- %matmul_padded, %_ = transform.structured.pad_tiling_interface %m to padding_sizes [8, 16] pad_to_multiple_of {
- padding_values=[0.0: f32, 0.0 : f32, 0.0 : f32],
- padding_dimensions=[0, 2]
+ %matmul_padded, %_ = transform.structured.pad_tiling_interface %m to padding_sizes [8, 0, 16] pad_to_multiple_of {
+ padding_values=[0.0: f32, 0.0 : f32, 0.0 : f32]
} : (!transform.any_op) -> (!transform.any_op, !transform.any_op)
transform.yield
diff --git a/mlir/test/Dialect/Linalg/transform-op-pad-tiling-interface.mlir b/mlir/test/Dialect/Linalg/transform-op-pad-tiling-interface.mlir
index f0a410fa4015f..26c03ed309c05 100644
--- a/mlir/test/Dialect/Linalg/transform-op-pad-tiling-interface.mlir
+++ b/mlir/test/Dialect/Linalg/transform-op-pad-tiling-interface.mlir
@@ -18,8 +18,7 @@ module attributes {transform.with_named_sequence} {
: (!transform.any_op) -> (!transform.any_op, !transform.any_op)
%fill_padded, %_ = transform.structured.pad_tiling_interface %fill_l1 to padding_sizes [8] {
- padding_values=[0.0 : f32, 0.0 : f32],
- padding_dimensions=[0]
+ padding_values=[0.0 : f32, 0.0 : f32]
} : (!transform.any_op) -> (!transform.any_op, !transform.any_op)
transform.yield
@@ -55,8 +54,7 @@ module attributes {transform.with_named_sequence} {
// Tile to 5 then pad to 8 (supposedly to better hit vector ops).
%matmul_l1, %loops_l1 = transform.structured.tile_using_for %matmul tile_sizes [5] : (!transform.any_op) -> (!transform.any_op, !transform.any_op)
%matmul_padded, %_ = transform.structured.pad_tiling_interface %matmul_l1 to padding_sizes [8] {
- padding_values=[0.0: f32, 0.0 : f32, 0.0 : f32],
- padding_dimensions=[0]
+ padding_values=[0.0: f32, 0.0 : f32, 0.0 : f32]
} : (!transform.any_op) -> (!transform.any_op, !transform.any_op)
transform.yield
@@ -91,8 +89,7 @@ module {
module attributes {transform.with_named_sequence} {
transform.named_sequence @__transform_main(%arg0: !transform.any_op {transform.readonly}) {
%0 = transform.structured.match ops{["linalg.generic"]} in %arg0 : (!transform.any_op) -> !transform.any_op
- %padded, %pad = transform.structured.pad_tiling_interface %0 to padding_sizes [8, 14] {
- padding_dimensions = [0, 2],
+ %padded, %pad = transform.structured.pad_tiling_interface %0 to padding_sizes [8, 0, 14] {
padding_values = [0.000000e+00 : f32, 0.000000e+00 : f32, 0.000000e+00 : f32]
} : (!transform.any_op) -> (!transform.any_op, !transform.any_op)
transform.yield
@@ -147,8 +144,7 @@ module {
module attributes {transform.with_named_sequence} {
transform.named_sequence @__transform_main(%arg0: !transform.any_op {transform.readonly}) {
%0 = transform.structured.match ops{["linalg.generic"]} in %arg0 : (!transform.any_op) -> !transform.any_op
- %padded, %pad = transform.structured.pad_tiling_interface %0 to padding_sizes [8, 14] {
- padding_dimensions = [0, 2],
+ %padded, %pad = transform.structured.pad_tiling_interface %0 to padding_sizes [8, 0, 14] {
padding_values = [0.000000e+00 : f32, 0.000000e+00 : f32, 0.000000e+00 : f32]
} : (!transform.any_op) -> (!transform.any_op, !transform.any_op)
transform.yield
More information about the llvm-branch-commits
mailing list