diff --git a/absl/algorithm/container.h b/absl/algorithm/container.h index 9d2b48e..5c2d376 100644 --- a/absl/algorithm/container.h +++ b/absl/algorithm/container.h
@@ -1169,7 +1169,9 @@ // Container-based version of the <algorithm> `std::sort()` function // to sort elements in ascending order of their values. template <typename C> -constexpr void c_sort(C& c) { +constexpr std::enable_if_t< + container_algorithm_internal::IsPermissibleDestinationRange<C>::value, void> +c_sort(C&& c) { std::sort(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c)); } @@ -1177,7 +1179,9 @@ // Overload of c_sort() for performing a `comp` comparison other than the // default `operator<`. template <typename C, typename LessThan> -constexpr void c_sort(C& c, LessThan&& comp) { +constexpr std::enable_if_t< + container_algorithm_internal::IsPermissibleDestinationRange<C>::value, void> +c_sort(C&& c, LessThan&& comp) { std::sort(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), std::forward<LessThan>(comp)); @@ -1189,7 +1193,9 @@ // to sort elements in ascending order of their values, preserving the order // of equivalents. template <typename C> -void c_stable_sort(C& c) { +std::enable_if_t< + container_algorithm_internal::IsPermissibleDestinationRange<C>::value, void> +c_stable_sort(C&& c) { std::stable_sort(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c)); } @@ -1197,7 +1203,9 @@ // Overload of c_stable_sort() for performing a `comp` comparison other than the // default `operator<`. template <typename C, typename LessThan> -void c_stable_sort(C& c, LessThan&& comp) { +std::enable_if_t< + container_algorithm_internal::IsPermissibleDestinationRange<C>::value, void> +c_stable_sort(C&& c, LessThan&& comp) { std::stable_sort(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), std::forward<LessThan>(comp));
diff --git a/absl/algorithm/container_test.cc b/absl/algorithm/container_test.cc index e025382..50a621e 100644 --- a/absl/algorithm/container_test.cc +++ b/absl/algorithm/container_test.cc
@@ -1338,6 +1338,18 @@ EXPECT_THAT(test_vector, ElementsAre(4, 3, 2, 1)); } +TEST(MutatingTest, SortTemporarySpan) { + std::vector<int> test_vector = {2, 3, 1, 4}; + absl::c_sort(absl::MakeSpan(test_vector)); + EXPECT_THAT(test_vector, ElementsAre(1, 2, 3, 4)); +} + +TEST(MutatingTest, SortTemporarySpanWithPredicate) { + std::vector<int> test_vector = {2, 3, 1, 4}; + absl::c_sort(absl::MakeSpan(test_vector), std::greater<int>()); + EXPECT_THAT(test_vector, ElementsAre(4, 3, 2, 1)); +} + // For absl::c_stable_sort tests. Needs an operator< that does not cover all // fields so that the test can check the sort preserves order of equal elements. struct Element { @@ -1374,6 +1386,24 @@ IsElement(1, 1), IsElement(1, 0))); } +TEST(MutatingTest, StableSortTemporarySpan) { + std::vector<Element> test_vector = {{1, 1}, {2, 1}, {2, 0}, {1, 0}, {2, 2}}; + absl::c_stable_sort(absl::MakeSpan(test_vector)); + EXPECT_THAT(test_vector, + ElementsAre(IsElement(1, 1), IsElement(1, 0), IsElement(2, 1), + IsElement(2, 0), IsElement(2, 2))); +} + +TEST(MutatingTest, StableSortTemporarySpanWithPredicate) { + std::vector<Element> test_vector = {{1, 1}, {2, 1}, {2, 0}, {1, 0}, {2, 2}}; + absl::c_stable_sort( + absl::MakeSpan(test_vector), + [](const Element& e1, const Element& e2) { return e2 < e1; }); + EXPECT_THAT(test_vector, + ElementsAre(IsElement(2, 1), IsElement(2, 0), IsElement(2, 2), + IsElement(1, 1), IsElement(1, 0))); +} + TEST(MutatingTest, ReplaceCopyIf) { const std::vector<int> initial = {1, 2, 3, 4, 5}; const std::vector<int> expected = {0, 2, 0, 4, 0};
diff --git a/absl/base/attributes.h b/absl/base/attributes.h index e432bb7..1e4c7c3 100644 --- a/absl/base/attributes.h +++ b/absl/base/attributes.h
@@ -88,8 +88,13 @@ #if ABSL_HAVE_ATTRIBUTE(format) || (defined(__GNUC__) && !defined(__clang__)) #define ABSL_PRINTF_ATTRIBUTE(string_index, first_to_check) \ __attribute__((__format__(__printf__, string_index, first_to_check))) +#if ABSL_HAVE_CPP_ATTRIBUTE(gnu::format) +#define ABSL_SCANF_ATTRIBUTE(string_index, first_to_check) \ + [[gnu::format(scanf, string_index, first_to_check)]] +#else #define ABSL_SCANF_ATTRIBUTE(string_index, first_to_check) \ __attribute__((__format__(__scanf__, string_index, first_to_check))) +#endif #else #define ABSL_PRINTF_ATTRIBUTE(string_index, first_to_check) #define ABSL_SCANF_ATTRIBUTE(string_index, first_to_check) @@ -475,26 +480,16 @@ // Example: // // int foo() ABSL_ATTRIBUTE_HOT; -#if defined(__cplusplus) && defined(__clang__) && \ - ABSL_HAVE_CPP_ATTRIBUTE(gnu::hot) -#define ABSL_ATTRIBUTE_HOT \ - _Pragma("clang diagnostic push") \ - _Pragma("clang diagnostic error \"-Wignored-attributes\"") \ - [[gnu::hot]] /* */ \ - _Pragma("clang diagnostic pop") +#if defined(__cplusplus) && ABSL_HAVE_CPP_ATTRIBUTE(gnu::hot) +#define ABSL_ATTRIBUTE_HOT [[gnu::hot]] #elif ABSL_HAVE_ATTRIBUTE(hot) || (defined(__GNUC__) && !defined(__clang__)) #define ABSL_ATTRIBUTE_HOT __attribute__((hot)) #else #define ABSL_ATTRIBUTE_HOT #endif -#if defined(__cplusplus) && defined(__clang__) && \ - ABSL_HAVE_CPP_ATTRIBUTE(gnu::cold) -#define ABSL_ATTRIBUTE_COLD \ - _Pragma("clang diagnostic push") \ - _Pragma("clang diagnostic error \"-Wignored-attributes\"") \ - [[gnu::cold]] /* */ \ - _Pragma("clang diagnostic pop") +#if defined(__cplusplus) && ABSL_HAVE_CPP_ATTRIBUTE(gnu::cold) +#define ABSL_ATTRIBUTE_COLD [[gnu::cold]] #elif ABSL_HAVE_ATTRIBUTE(cold) || (defined(__GNUC__) && !defined(__clang__)) #define ABSL_ATTRIBUTE_COLD __attribute__((cold)) #else @@ -851,7 +846,7 @@ // Note that `ABSL_CONST_INIT` must not be used on a variable declared // constexpr. #if defined(__cpp_constinit) && __cpp_constinit >= 201907L -#define ABSL_CONST_INIT constinit +#define ABSL_CONST_INIT constinit // NOLINT(clang-diagnostic-pre-c++20-compat) #elif ABSL_HAVE_CPP_ATTRIBUTE(clang::require_constant_initialization) #define ABSL_CONST_INIT [[clang::require_constant_initialization]] #else
diff --git a/absl/base/internal/cpu_detect.cc b/absl/base/internal/cpu_detect.cc index b498465..43b6c4b 100644 --- a/absl/base/internal/cpu_detect.cc +++ b/absl/base/internal/cpu_detect.cc
@@ -231,6 +231,8 @@ switch (model_num) { case 0x2: return CpuType::kAmdTurin; + case 0x50: + return CpuType::kAmdVenice; default: return CpuType::kUnknown; }
diff --git a/absl/base/internal/cpu_detect.h b/absl/base/internal/cpu_detect.h index 5ca993f..06eb72a 100644 --- a/absl/base/internal/cpu_detect.h +++ b/absl/base/internal/cpu_detect.h
@@ -32,6 +32,7 @@ kAmdGenoa, kAmdSiena, kAmdTurin, + kAmdVenice, kAmdRyzenV3000, kIntelCascadelakeXeon, kIntelSkylakeXeon,
diff --git a/absl/base/internal/raw_logging.cc b/absl/base/internal/raw_logging.cc index 7c39f6d..848e69f 100644 --- a/absl/base/internal/raw_logging.cc +++ b/absl/base/internal/raw_logging.cc
@@ -89,8 +89,8 @@ // consumed bytes, and return whether the message fit without truncation. If // truncation occurred, if possible leave room in the buffer for the message // kTruncated[]. -bool VADoRawLog(char** buf, int* size, const char* format, va_list ap) - ABSL_PRINTF_ATTRIBUTE(3, 0); +ABSL_PRINTF_ATTRIBUTE(3, 0) +bool VADoRawLog(char** buf, int* size, const char* format, va_list ap); bool VADoRawLog(char** buf, int* size, const char* format, va_list ap) { if (*size < 0) return false; int n = vsnprintf(*buf, static_cast<size_t>(*size), format, ap); @@ -117,8 +117,8 @@ // Helper for RawLog below. // *DoRawLog writes to *buf of *size and move them past the written portion. // It returns true iff there was no overflow or error. -bool DoRawLog(char** buf, int* size, const char* format, ...) - ABSL_PRINTF_ATTRIBUTE(3, 4); +ABSL_PRINTF_ATTRIBUTE(3, 4) +bool DoRawLog(char** buf, int* size, const char* format, ...); bool DoRawLog(char** buf, int* size, const char* format, ...) { if (*size < 0) return false; va_list ap; @@ -143,8 +143,9 @@ ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES absl::base_internal::AtomicHook<AbortHook> abort_hook; +ABSL_PRINTF_ATTRIBUTE(4, 0) void RawLogVA(absl::LogSeverity severity, const char* file, int line, - const char* format, va_list ap) ABSL_PRINTF_ATTRIBUTE(4, 0); + const char* format, va_list ap); void RawLogVA(absl::LogSeverity severity, const char* file, int line, const char* format, va_list ap) { char buffer[kLogBufSize];
diff --git a/absl/base/internal/raw_logging.h b/absl/base/internal/raw_logging.h index 8637030..9cc270b 100644 --- a/absl/base/internal/raw_logging.h +++ b/absl/base/internal/raw_logging.h
@@ -128,8 +128,9 @@ // Logs format... at "severity" level, reporting it // as called from file:line. // This does not allocate memory or acquire locks. +ABSL_PRINTF_ATTRIBUTE(4, 5) void RawLog(absl::LogSeverity severity, const char* file, int line, - const char* format, ...) ABSL_PRINTF_ATTRIBUTE(4, 5); + const char* format, ...); // Writes the provided buffer directly to stderr, in a signal-safe, low-level // manner. Preserves errno.
diff --git a/absl/base/thread_annotations.h b/absl/base/thread_annotations.h index 4a3f3e3..cede48c 100644 --- a/absl/base/thread_annotations.h +++ b/absl/base/thread_annotations.h
@@ -188,7 +188,9 @@ // acquired, and the destructor should use `UNLOCK_FUNCTION()` with no // arguments; the analysis will assume that the destructor unlocks whatever the // constructor locked. -#if ABSL_HAVE_ATTRIBUTE(scoped_lockable) +#if ABSL_HAVE_CPP_ATTRIBUTE(clang::scoped_lockable) +#define ABSL_SCOPED_LOCKABLE [[clang::scoped_lockable]] +#elif ABSL_HAVE_ATTRIBUTE(scoped_lockable) #define ABSL_SCOPED_LOCKABLE __attribute__((scoped_lockable)) #else #define ABSL_SCOPED_LOCKABLE
diff --git a/absl/container/BUILD.bazel b/absl/container/BUILD.bazel index d325e39..850eaec 100644 --- a/absl/container/BUILD.bazel +++ b/absl/container/BUILD.bazel
@@ -1488,6 +1488,7 @@ "//absl/base:core_headers", "//absl/base:hardening", "//absl/base:iterator_traits_internal", + "//absl/cleanup", ], )
diff --git a/absl/container/CMakeLists.txt b/absl/container/CMakeLists.txt index 79bcee0..7a2641c 100644 --- a/absl/container/CMakeLists.txt +++ b/absl/container/CMakeLists.txt
@@ -1239,6 +1239,7 @@ LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS + absl::cleanup absl::config absl::core_headers absl::hardening
diff --git a/absl/container/chunked_queue.h b/absl/container/chunked_queue.h index 8ada208..54cb6d8 100644 --- a/absl/container/chunked_queue.h +++ b/absl/container/chunked_queue.h
@@ -35,6 +35,8 @@ // shrink_to_fit() // resize() // assign() +// push_front() +// emplace_front() // push_back() // emplace_back() // pop_front() @@ -110,6 +112,7 @@ #include "absl/base/internal/hardening.h" #include "absl/base/internal/iterator_traits.h" #include "absl/base/macros.h" +#include "absl/cleanup/cleanup.h" #include "absl/container/internal/chunked_queue.h" #include "absl/container/internal/layout.h" @@ -142,20 +145,20 @@ protected: iterator_common() = default; - explicit iterator_common(Block* b) - : block(b), ptr(b->start()), limit(b->limit()) {} + explicit iterator_common(Block* b) : block(b), ptr(b->start()) {} void Incr() { // If we do not have a next block, make ptr point one past the end of this // block. If we do have a next block, make ptr point to the first element // of the next block. ++ptr; - if (ptr == limit && block->next()) *this = iterator_common(block->next()); + if (ptr == block->limit() && block->next()) + *this = iterator_common(block->next()); } void IncrBy(size_t n) { - while (n > static_cast<size_t>(limit - ptr)) { - n -= static_cast<size_t>(limit - ptr); + while (n > static_cast<size_t>(block->limit() - ptr)) { + n -= static_cast<size_t>(block->limit() - ptr); *this = iterator_common(block->next()); } ptr += n; @@ -163,7 +166,6 @@ Block* block = nullptr; T* ptr = nullptr; - T* limit = nullptr; }; // CT can be either T or const T. @@ -403,6 +405,20 @@ // Replaces the contents with the elements from the initializer list `il`. void assign(std::initializer_list<T> il) { assign(il.begin(), il.end()); } + // Prepends the given element value to the beginning of the container. + // Invalidates `end()` iterator if the container was empty. References to + // other elements remain valid. + void push_front(const T& val) { emplace_front(val); } + void push_front(T&& val) { emplace_front(std::move(val)); } + + // Prepends a new element to the beginning of the container. + // The element is constructed in-place with `args`. + // Returns a reference to the new element. + // Invalidates `end()` iterator if the container was empty. References to + // other elements remain valid. + template <typename... A> + T& emplace_front(A&&... args); + // Appends the given element value to the end of the container. // Invalidates `end()` iterator. References to other elements remain valid. void push_back(const T& val) { emplace_back(val); } @@ -502,7 +518,7 @@ void RangeInit(Iter first, Iter last, std::input_iterator_tag) { while (first != last) { AddTailBlock(); - for (; first != last && tail_.ptr != tail_.limit; + for (; first != last && tail_.ptr != tail_.block->limit(); ++alloc_and_size_.size, ++tail_.ptr, ++first) { AllocatorTraits::construct(alloc_and_size_.allocator(), tail_.ptr, *first); @@ -538,10 +554,13 @@ } void AddTailBlock(); - size_t NewBlockSize() { - // Double the last block size and bound to [kBlockSizeMin, kBlockSizeMax]. - if (!tail_.block) return kBlockSizeMin; - return (std::min)(kBlockSizeMax, 2 * tail_.block->size()); + // Returns the capacity (in elements) to allocate for a new block adjacent to + // `b` (`head_.block` when prepending or `tail_.block` when appending). Starts + // at `kBlockSizeMin` when `b` is null, otherwise doubles `b->size()` and + // clamps to `[kBlockSizeMin, kBlockSizeMax]`. + size_t NewBlockSize(Block* b) { + if (!b) return kBlockSizeMin; + return (std::min)(kBlockSizeMax, 2 * b->size()); } T* AllocateBack(); @@ -556,16 +575,17 @@ // (1) When we have just one block: // [head_.ptr .. tail_.ptr-1] // (2) When we have multiple blocks: - // [head_.ptr .. head_.limit-1] + // [head_.ptr .. head_.block->limit()-1] // ... concatenation of all elements from interior blocks ... - // [tail_.ptr .. tail_.limit-1] + // [tail_.block->start() .. tail_.ptr-1] // // Rep invariants: // When have just one block: - // head_.limit == tail_.limit == &head_.block->element[kBlockSize] - // Always: - // head_.ptr <= head_.limit - // tail_.ptr <= tail_.limit + // head_.block->limit() == tail_.block->limit() == + // &head_.block->element[kBlockSize] + // Always (when non-empty): + // head_.ptr <= head_.block->limit() + // tail_.ptr <= tail_.block->limit() iterator head_; iterator tail_; @@ -628,11 +648,12 @@ template <typename T, size_t BLo, size_t BHi, typename Allocator> void chunked_queue<T, BLo, BHi, Allocator>::resize(size_t new_size) { while (new_size > size()) { - if (tail_.ptr == tail_.limit) { + if (!tail_.block || tail_.ptr == tail_.block->limit()) { AddTailBlock(); } - size_t to_add = (std::min)(new_size - size(), - static_cast<size_t>(tail_.limit - tail_.ptr)); + size_t to_add = + (std::min)(new_size - size(), + static_cast<size_t>(tail_.block->limit() - tail_.ptr)); T* start = tail_.ptr; T* limit = start + to_add; Construct(start, limit); @@ -650,9 +671,70 @@ } template <typename T, size_t BLo, size_t BHi, typename Allocator> +template <typename... A> +T& chunked_queue<T, BLo, BHi, Allocator>::emplace_front(A&&... args) { + // Per "The set of elements in the queue" / "Rep invariants" (documented above + // `head_` and `tail_`), in a multi-block queue the head block holds + // `[head_.ptr .. head_.block->limit()-1]`, interior blocks hold all elements + // `[b->start() .. b->limit()-1]`, and the tail block holds + // `[tail_.block->start() .. tail_.ptr-1]` (as also assumed by `block_begin()` + // and `iterator_common::Incr()`). Therefore, a head block is populated from + // `b->limit() - 1` down to `b->start()` before a new head block can be + // prepended. + + // Determine the target block `b` and element slot `storage`, allocating a new + // head block if needed. + Block* b = head_.block; + T* storage; + if (empty()) { + // Empty queue: allocate the first block if none exists, or reuse the empty + // block retained by `clear()` / `pop_front()`. + if (!b) { + b = Block::New(NewBlockSize(b), &alloc_and_size_.allocator()); + } + storage = b->limit() - 1; + } else if (head_.ptr == head_.block->start()) { + // Full head block (no spare capacity before `head_.ptr`): allocate a new + // head block to prepend. + b = Block::New(NewBlockSize(b), &alloc_and_size_.allocator()); + storage = b->limit() - 1; + } else { + // Partially filled head block: use the slot immediately before `head_.ptr`. + storage = head_.ptr - 1; + } + + // Construct the new element in `storage`. + { + // Exception safety: Unlike `emplace_back()` (where `AddTailBlock()` links + // the new block into `tail_` before construction with `tail_.ptr == + // b->start()`, so the container owns it if construction throws), `b` is not + // linked into `head_` until after construction succeeds so that `head_` + // remains valid on throw. Delete `b` if construction throws. + absl::Cleanup cleanup = [&] { + if (b != head_.block) Block::Delete(b, &alloc_and_size_.allocator()); + }; + AllocatorTraits::construct(alloc_and_size_.allocator(), storage, + std::forward<A>(args)...); + std::move(cleanup).Cancel(); + } + + // Link the new block (if allocated) and update queue iterators and size. + if (empty()) { + tail_.block = b; + tail_.ptr = b->limit(); + } else if (b != head_.block) { + b->set_next(head_.block); + } + head_.block = b; + head_.ptr = storage; + ++alloc_and_size_.size; + return *storage; +} + +template <typename T, size_t BLo, size_t BHi, typename Allocator> inline void chunked_queue<T, BLo, BHi, Allocator>::AddTailBlock() { - ABSL_ASSERT(tail_.ptr == tail_.limit); - auto* b = Block::New(NewBlockSize(), &alloc_and_size_.allocator()); + ABSL_ASSERT(!tail_.block || tail_.ptr == tail_.block->limit()); + auto* b = Block::New(NewBlockSize(tail_.block), &alloc_and_size_.allocator()); if (!head_.block) { ABSL_ASSERT(!tail_.block); head_ = iterator(b); @@ -665,7 +747,7 @@ template <typename T, size_t BLo, size_t BHi, typename Allocator> inline T* chunked_queue<T, BLo, BHi, Allocator>::AllocateBack() { - if (tail_.ptr == tail_.limit) { + if (!tail_.block || tail_.ptr == tail_.block->limit()) { AddTailBlock(); } return tail_.ptr; @@ -677,7 +759,7 @@ return; } ABSL_ASSERT(i.ptr); - ABSL_ASSERT(i.limit); + ABSL_ASSERT(i.block->limit()); alloc_and_size_.size -= Destroy(i.ptr, block_end(i.block)); Block* b = i.block->next(); while (b) { @@ -717,7 +799,7 @@ head_.ptr = tail_.ptr = head_.block->start(); return; } - if (head_.ptr == head_.limit) { + if (head_.ptr == head_.block->limit()) { Block* n = head_.block->next(); Block::Delete(head_.block, &alloc_and_size_.allocator()); head_ = iterator(n);
diff --git a/absl/container/chunked_queue_benchmark.cc b/absl/container/chunked_queue_benchmark.cc index ee4d3c1..3c031ed 100644 --- a/absl/container/chunked_queue_benchmark.cc +++ b/absl/container/chunked_queue_benchmark.cc
@@ -38,6 +38,16 @@ ~forward_list_queue() = default; template <typename... Args> + void emplace_front(Args&&... args) { + if (list_.empty()) { + list_.emplace_front(std::forward<Args>(args)...); + tail_ = list_.begin(); + } else { + list_.emplace_front(std::forward<Args>(args)...); + } + } + + template <typename... Args> void emplace_back(Args&&... args) { if (list_.empty()) { list_.emplace_front(std::forward<Args>(args)...); @@ -48,6 +58,7 @@ } } + void push_front(const T& value) { emplace_front(value); } void push_back(const T& value) { emplace_back(value); } iterator begin() { return list_.begin(); } iterator end() { return list_.end(); } @@ -146,6 +157,31 @@ BENCHMARK_TEMPLATE(BM_destroy, ExpChunked<Element>)->Apply(CustomArgs); template <class Q> +void BM_push_front(benchmark::State& state) { + const int64_t num_elements = state.range(0); + + state.SetItemsProcessed(state.max_iterations * num_elements); + for (auto s : state) { + state.PauseTiming(); + Q q; + state.ResumeTiming(); + for (int j = 0; j < num_elements; j++) q.push_front(j); + benchmark::DoNotOptimize(q); + } +} + +BENCHMARK_TEMPLATE(BM_push_front, Deque<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_front, List<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_front, FwdList<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_front, Chunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_front, ExpChunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_front, Deque<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_front, List<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_front, FwdList<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_front, Chunked<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_front, ExpChunked<Element>)->Apply(CustomArgs); + +template <class Q> void BM_push_back(benchmark::State& state) { const int64_t num_elements = state.range(0);
diff --git a/absl/container/chunked_queue_test.cc b/absl/container/chunked_queue_test.cc index 22ce944..b4604b6 100644 --- a/absl/container/chunked_queue_test.cc +++ b/absl/container/chunked_queue_test.cc
@@ -346,6 +346,59 @@ EXPECT_EQ(0, q.size()); } +TEST(ChunkedQueue, PushFrontAndPop) { + absl::chunked_queue<int64_t> q; + EXPECT_TRUE(q.empty()); + EXPECT_EQ(0, q.size()); + // Push enough elements to exercise exponential block growth from + // `kBlockSizeMin` (1) up to `kBlockSizeMax` (128) across multiple max-sized + // head blocks (`1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 = 255`). + constexpr int kNumElements = 500; + for (int i = 0; i < kNumElements; i++) { + q.push_front(i); + EXPECT_EQ(q.front(), i) << ": iteration " << i; + EXPECT_EQ(q.back(), 0) << ": iteration " << i; + EXPECT_FALSE(q.empty()); + EXPECT_EQ(i + 1, q.size()); + } + for (int i = 0; i < kNumElements; i++) { + EXPECT_FALSE(q.empty()); + EXPECT_EQ(kNumElements - i, q.size()); + EXPECT_EQ(q.front(), kNumElements - 1 - i); + EXPECT_EQ(q.back(), 0); + q.pop_front(); + } + EXPECT_TRUE(q.empty()); + EXPECT_EQ(0, q.size()); +} + +TEST(ChunkedQueue, PushFrontAndPushBack) { + absl::chunked_queue<int64_t, 4> q; + q.push_front(2); + q.push_back(3); + q.push_front(1); + q.push_back(4); + q.push_front(0); + q.push_back(5); + EXPECT_THAT(q, ElementsAre(0, 1, 2, 3, 4, 5)); + EXPECT_EQ(q.front(), 0); + EXPECT_EQ(q.back(), 5); + + q.pop_front(); + q.push_front(-1); + EXPECT_THAT(q, ElementsAre(-1, 1, 2, 3, 4, 5)); + + q.clear(); + EXPECT_TRUE(q.empty()); + q.push_front(10); + EXPECT_THAT(q, ElementsAre(10)); + EXPECT_EQ(q.front(), 10); + EXPECT_EQ(q.back(), 10); + q.push_front(9); + q.push_back(11); + EXPECT_THAT(q, ElementsAre(9, 10, 11)); +} + TEST(ChunkedQueue, Swap) { absl::chunked_queue<int64_t> q1; absl::chunked_queue<int64_t> q2; @@ -662,6 +715,28 @@ EXPECT_EQ(*++i, 2); } +// Verifies that `push_front` on a non-empty queue does not invalidate +// iterators to existing elements, both when prepending into remaining space in +// the current head block (`1`) and when allocating a new head block (`0`), and +// that advancing `begin()` across the block boundary reaches the existing +// iterator `i`. +TEST(ChunkedQueue, Iterator_PushFrontInvalidate) { + absl::chunked_queue<int64_t, 2> q; + q.push_front(2); + auto i = q.begin(); + EXPECT_EQ(*i, 2); + q.push_front(1); + EXPECT_EQ(*i, 2); + q.push_front(0); + EXPECT_EQ(*i, 2); + auto j = q.begin(); + EXPECT_EQ(*j, 0); + EXPECT_EQ(*++j, 1); + EXPECT_EQ(*++j, 2); + EXPECT_TRUE(j == i); + EXPECT_TRUE(++j == q.end()); +} + struct MyType { static int constructor_calls; static int destructor_calls; @@ -707,13 +782,56 @@ absl::chunked_queue<std::unique_ptr<int>> q; q.push_back(std::make_unique<int>(10)); q.push_back(std::make_unique<int>(11)); + q.push_front(std::make_unique<int>(9)); + EXPECT_EQ(9, *q.front()); + q.pop_front(); EXPECT_EQ(10, *q.front()); q.pop_front(); EXPECT_EQ(11, *q.front()); q.pop_front(); } +// Verifies that `emplace_front` constructs elements in-place at the front of +// the queue, returns a reference to the newly inserted element, and preserves +// existing elements at the back. +TEST(ChunkedQueue, EmplaceFrontUpdatesFrontAndReturnsReference) { + absl::chunked_queue<std::pair<int, int>> q; + auto& v1 = q.emplace_front(1, 2); + EXPECT_THAT(v1, Pair(1, 2)); + EXPECT_THAT(q.front(), Pair(1, 2)); + EXPECT_EQ(&v1, &q.front()); + EXPECT_EQ(&v1, &q.back()); + + auto& v2 = q.emplace_front(3, 4); + EXPECT_THAT(v2, Pair(3, 4)); + EXPECT_THAT(q.front(), Pair(3, 4)); + EXPECT_THAT(q.back(), Pair(1, 2)); + EXPECT_EQ(&v2, &q.front()); + EXPECT_EQ(&v1, &q.back()); +} + +// Verifies that `emplace_front` forwards move-only arguments to construct the +// element in-place. +TEST(ChunkedQueue, EmplaceFrontMoveOnlyType) { + absl::chunked_queue<std::pair<std::unique_ptr<int>, std::string>> q; + auto& v = q.emplace_front(std::make_unique<int>(11), "val12"); + EXPECT_THAT(v, Pair(Pointee(11), "val12")); + EXPECT_THAT(q.front(), Pair(Pointee(11), "val12")); +} + +TEST(ChunkedQueue, OveralignmentEmplaceFront) { + struct alignas(64) Overaligned { + int x; + int y; + }; + absl::chunked_queue<Overaligned, 1, 8> q; + for (int i = 0; i < 10; ++i) { + auto& v = q.emplace_front(Overaligned{i, i}); + EXPECT_EQ(reinterpret_cast<uintptr_t>(&v) % 64, 0); + } +} + TEST(ChunkedQueue, EmplaceBack1) { absl::chunked_queue<std::pair<int, int>> q; auto& v = q.emplace_back(1, 2); @@ -756,7 +874,6 @@ struct MockIterator { void* block; void* ptr; - void* limit; }; MockIterator head; MockIterator tail; @@ -866,4 +983,111 @@ } #endif +TEST(ChunkedQueue, StrongExceptionSafetyEmplaceFrontEmptyQueue) { +#ifndef ABSL_HAVE_EXCEPTIONS + GTEST_SKIP() << "Exceptions are not enabled."; +#else + int ctor_count = 0; + int dtor_count = 0; + absl::chunked_queue<ThrowingCtor> q; + + try { + q.emplace_front(5, true, &ctor_count, &dtor_count); + } catch (...) { + } + + EXPECT_TRUE(q.empty()); + EXPECT_EQ(ctor_count, 0); + EXPECT_EQ(dtor_count, 0); +#endif +} + +TEST(ChunkedQueue, StrongExceptionSafetyEmplaceFrontNewHeadBlock) { +#ifndef ABSL_HAVE_EXCEPTIONS + GTEST_SKIP() << "Exceptions are not enabled."; +#else + int ctor_count = 0; + int dtor_count = 0; + absl::chunked_queue<ThrowingCtor> q; + q.emplace_front(10, false, &ctor_count, &dtor_count); + ASSERT_EQ(q.size(), 1); + ASSERT_EQ(ctor_count, 1); + ASSERT_EQ(dtor_count, 0); + + // The first block has size 1, so the next emplace_front allocates a new head + // block and must delete it if element construction throws. + try { + q.emplace_front(15, true, &ctor_count, &dtor_count); + } catch (...) { + } + + EXPECT_EQ(q.size(), 1); + EXPECT_EQ(ctor_count, 1); + EXPECT_EQ(dtor_count, 0); + EXPECT_EQ(q.front().value, 10); + EXPECT_EQ(q.back().value, 10); + + q.clear(); + EXPECT_EQ(q.size(), 0); + EXPECT_EQ(dtor_count, 1); +#endif +} + +TEST(ChunkedQueue, StrongExceptionSafetyEmplaceFrontExistingHeadBlock) { +#ifndef ABSL_HAVE_EXCEPTIONS + GTEST_SKIP() << "Exceptions are not enabled."; +#else + int ctor_count = 0; + int dtor_count = 0; + absl::chunked_queue<ThrowingCtor, 2> q; + q.emplace_front(10, false, &ctor_count, &dtor_count); + ASSERT_EQ(q.size(), 1); + ASSERT_EQ(ctor_count, 1); + ASSERT_EQ(dtor_count, 0); + + // The head block has capacity 2 with 1 element, so the next emplace_front + // constructs into the remaining slot in the existing head block. + try { + q.emplace_front(20, true, &ctor_count, &dtor_count); + } catch (...) { + } + + EXPECT_EQ(q.size(), 1); + EXPECT_EQ(ctor_count, 1); + EXPECT_EQ(dtor_count, 0); + EXPECT_EQ(q.front().value, 10); + EXPECT_EQ(q.back().value, 10); + + q.clear(); + EXPECT_EQ(q.size(), 0); + EXPECT_EQ(dtor_count, 1); +#endif +} + +TEST(ChunkedQueue, StrongExceptionSafetyEmplaceFrontClearedQueue) { +#ifndef ABSL_HAVE_EXCEPTIONS + GTEST_SKIP() << "Exceptions are not enabled."; +#else + int ctor_count = 0; + int dtor_count = 0; + absl::chunked_queue<ThrowingCtor> q; + q.emplace_front(10, false, &ctor_count, &dtor_count); + q.clear(); + ASSERT_TRUE(q.empty()); + ASSERT_EQ(ctor_count, 1); + ASSERT_EQ(dtor_count, 1); + + // `clear()` leaves one empty block allocated; throwing while constructing + // into that retained block must leave the queue empty and valid. + try { + q.emplace_front(20, true, &ctor_count, &dtor_count); + } catch (...) { + } + + EXPECT_TRUE(q.empty()); + EXPECT_EQ(ctor_count, 1); + EXPECT_EQ(dtor_count, 1); +#endif +} + } // namespace
diff --git a/absl/container/internal/container_memory.h b/absl/container/internal/container_memory.h index 01fe8d0..c408131 100644 --- a/absl/container/internal/container_memory.h +++ b/absl/container/internal/container_memory.h
@@ -562,6 +562,11 @@ size_t operator()(size_t seed) const { return HashElement<Hash, kIsAbsl, kSeedShift>{hash, seed}(key); } + + // Static version of the above to pass to type erased functions. + static size_t GetHash(void* data, size_t seed) { + return (*static_cast<const HashKey*>(data))(seed); + } const Hash& hash; const Key& key; };
diff --git a/absl/container/internal/raw_hash_map.h b/absl/container/internal/raw_hash_map.h index e35954a..8984dfa 100644 --- a/absl/container/internal/raw_hash_map.h +++ b/absl/container/internal/raw_hash_map.h
@@ -82,23 +82,24 @@ // // MovableOrVoid is an optional, extra constraint (via SFINAE) to guard // separate move-only overloads. - template <class K, bool Value, typename MovableOrVoid = void> + template <class K, bool Value, bool ExtraConstraint = true> using LifetimeBoundK = std::bool_constant< - (std::is_void_v<MovableOrVoid> || + (ExtraConstraint || !IsAllocMoveSameAsCopy<absl::remove_cvref_t<K>, Alloc>::value) && HasValue<Value, std::conditional_t< policy_trait_element_is_owner<Policy>::value, std::false_type, type_traits_internal::IsLifetimeBoundAssignment< typename Policy::key_type, K>>>::value>; - template <class V, bool Value, typename = void> + template <class V, bool Value> using LifetimeBoundV = HasValue<Value, type_traits_internal::IsLifetimeBoundAssignment< typename Policy::mapped_type, V>>; - template <class K, bool KValue, class V, bool VValue, typename... Dummy> - using LifetimeBoundKV = - std::conjunction<LifetimeBoundK<K, KValue, std::void_t<Dummy...>>, - LifetimeBoundV<V, VValue>>; + template <class K, bool KValue, class V, bool VValue, + bool ExtraConstraintK = true, bool ExtraConstraintV = true> + using LifetimeBoundKV = std::conjunction< + LifetimeBoundK<K, KValue, ExtraConstraintK && ExtraConstraintV>, + LifetimeBoundV<V, VValue>>; public: using key_type = typename Policy::key_type; @@ -131,8 +132,10 @@ typename K = key_type, class V = mapped_type, \ ABSL_INTERNAL_IF_##KValue##_NOR_##VValue( \ int = (EnableIf<LifetimeBoundKV<K, KValue, V, VValue, \ - IfRRef<int KQual>::AddPtr<K>, \ - IfRRef<int VQual>::AddPtr<V>>>()), \ + !std::is_reference_v<int KQual> || \ + !std::is_reference_v<K>, \ + !std::is_reference_v<int VQual> || \ + !std::is_reference_v<V>>>()), \ ABSL_INTERNAL_SINGLE_ARG( \ int&..., \ decltype(EnableIf<LifetimeBoundKV<K, KValue, V, VValue>>()) = \ @@ -226,7 +229,8 @@ // arguments as `std::unordered_map::try_emplace()`, namely that these // functions will not move from rvalue arguments if insertions do not happen. template < - class K = key_type, int = EnableIf<LifetimeBoundK<K, false, K*>>(), + class K = key_type, + int = EnableIf<LifetimeBoundK<K, false, !std::is_reference_v<K>>>(), class... Args, std::enable_if_t<!std::is_convertible_v<K, const_iterator>, int> = 0> std::pair<iterator, bool> try_emplace(key_arg<K>&& k, Args&&... args) @@ -237,7 +241,7 @@ template < class K = key_type, class... Args, - EnableIf<LifetimeBoundK<K, true, K*>> = 0, + EnableIf<LifetimeBoundK<K, true, !std::is_reference_v<K>>> = 0, std::enable_if_t<!std::is_convertible_v<K, const_iterator>, int> = 0> std::pair<iterator, bool> try_emplace( key_arg<K>&& k ABSL_INTERNAL_ATTRIBUTE_CAPTURED_BY_THIS, @@ -263,7 +267,8 @@ return this->template try_emplace<K, 0>(k, std::forward<Args>(args)...); } - template <class K = key_type, int = EnableIf<LifetimeBoundK<K, false, K*>>(), + template <class K = key_type, + int = EnableIf<LifetimeBoundK<K, false, !std::is_reference_v<K>>>(), class... Args> iterator try_emplace(const_iterator, key_arg<K>&& k, Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND { @@ -271,7 +276,7 @@ .first; } template <class K = key_type, class... Args, - EnableIf<LifetimeBoundK<K, true, K*>> = 0> + EnableIf<LifetimeBoundK<K, true, !std::is_reference_v<K>>> = 0> iterator try_emplace(const_iterator hint, key_arg<K>&& k ABSL_INTERNAL_ATTRIBUTE_CAPTURED_BY_THIS, Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND { @@ -315,7 +320,7 @@ } template <class K = key_type, class P = Policy, - int = EnableIf<LifetimeBoundK<K, false, K*>>()> + int = EnableIf<LifetimeBoundK<K, false, !std::is_reference_v<K>>>()> MappedReference<P> operator[](key_arg<K>&& key) ABSL_ATTRIBUTE_LIFETIME_BOUND { // It is safe to use unchecked_deref here because try_emplace @@ -325,7 +330,7 @@ try_emplace(std::forward<key_arg<K>>(key)).first)); } template <class K = key_type, class P = Policy, int&..., - EnableIf<LifetimeBoundK<K, true, K*>> = 0> + EnableIf<LifetimeBoundK<K, true, !std::is_reference_v<K>>> = 0> MappedReference<P> operator[]( key_arg<K>&& key ABSL_INTERNAL_ATTRIBUTE_CAPTURED_BY_THIS) ABSL_ATTRIBUTE_LIFETIME_BOUND { @@ -377,23 +382,15 @@ // Try to avoid using std::tuple since that slows compilation. // (example: https://godbolt.org/z/nefW93T6K) - // Simple trick to bypass instantiating a bunch of templates in cases - // where they're irrelevant. - using V = std::conditional_t< - sizeof...(Args) == 0 && - std::is_same_v<typename std::allocator_traits< - Alloc>::template rebind_alloc<char>, - std::allocator<char>>, - mapped_type, void>; - - if constexpr (std::is_trivially_move_constructible_v<V> && - std::is_trivially_destructible_v<V> && - // Check if size is a power of 2, to ensure cheap calls. - absl::has_single_bit(sizeof(mapped_type)) && - // Check if copying 1-2 registers is enough. We don't want - // (for example) 32 one-byte values to move around. - alignof(mapped_type) * 2 >= sizeof(mapped_type) && - sizeof(mapped_type) <= sizeof(std::pair<void*, size_t>)) { + if constexpr (ShouldPreferPassByValue<std::conditional_t< + sizeof...(Args) == 0 && + std::is_same_v< + typename std::allocator_traits< + Alloc>::template rebind_alloc<char>, + std::allocator<char>>, + mapped_type, + // Trick to avoid unnecessary template instantiations. + void>>::value) { // Common case: operator[] // Note: This has a different run-time performance characteristic than // the other branches, due to the usage of a temporary object. In the
diff --git a/absl/container/internal/raw_hash_set.cc b/absl/container/internal/raw_hash_set.cc index 15a80cb..3465a90 100644 --- a/absl/container/internal/raw_hash_set.cc +++ b/absl/container/internal/raw_hash_set.cc
@@ -292,9 +292,6 @@ void CommonFields::AssertNotDebugCapacityImpl() const { const HashtableCapacity cap = maybe_invalid_capacity(); - if (ABSL_PREDICT_TRUE(cap.IsValid())) { - return; - } assert(!cap.IsReentrance() && "Reentrant container access during element construction/destruction " "is not allowed."); @@ -983,6 +980,35 @@ Destruct<kSooEnabled>(c, policy, kStandardDeallocBackingArrayFn); } +void AssertHashEqConsistentImpl(CommonFields& common, const void* hash_fn, + HashSlotFn hash_slot, size_t slot_size, + bool soo_enabled, size_t hash, const void* key, + EqualToKeyFn equal) { + const size_t seed = common.seed().seed(); + + const auto assert_consistent = [&](const ctrl_t*, void* slot) { + const bool is_key_equal = equal(&common, key, slot); + if (!is_key_equal) return; + + [[maybe_unused]] const bool is_hash_equal = + hash == hash_slot(hash_fn, slot, seed); + assert((!is_key_equal || is_hash_equal) && + "eq(k1, k2) must imply that hash(k1) == hash(k2). " + "hash/eq functors are inconsistent."); + }; + + if (common.is_small()) { + assert_consistent( + /*unused*/ nullptr, + soo_enabled ? SingleSlotAddress</*kSooEnabled=*/true>(common) + : SingleSlotAddress</*kSooEnabled=*/false>(common)); + return; + } + // We only do validation for small tables so that it's constant time. + if (common.capacity() > 16) return; + IterateOverFullSlots(common, slot_size, assert_consistent); +} + namespace { // Iterates over full slots in old table, finds new positions for them and @@ -1049,12 +1075,12 @@ ABSL_ATTRIBUTE_COLD ABSL_ATTRIBUTE_NOINLINE void ReportSingleElementTableGrowthToInfozAndForceSample( CommonFields& common, const PolicyFunctions& __restrict policy, - absl::FunctionRef<size_t(size_t)> get_hash) { + void* hasher, HashForKeyFn get_hash) { HashtablezInfoHandle infoz = ForcedTrySample(policy.slot_size, policy.key_size, policy.value_size, policy.soo_capacity()); common.generate_new_seed(/*has_infoz=*/true); - ReportGrowthToInfozImpl(common, infoz, get_hash(common.seed().seed()), + ReportGrowthToInfozImpl(common, infoz, get_hash(hasher, common.seed().seed()), /*total_probe_length=*/0, /*distance_from_desired=*/0); } @@ -1071,10 +1097,10 @@ // avoid calling the hash function and seed extraction in the hot path. // `common` is first and `get_hash` is third to match argument order in callers. ABSL_ATTRIBUTE_COLD ABSL_ATTRIBUTE_NOINLINE void -ReportInsertMissToInfozAndComputeHash( - CommonFields& common, size_t probe_length, - absl::FunctionRef<size_t(size_t)> get_hash) { - common.infoz().RecordInsertMiss(get_hash(common.seed().seed()), probe_length); +ReportInsertMissToInfozAndComputeHash(CommonFields& common, size_t probe_length, + void* hasher, HashForKeyFn get_hash) { + common.infoz().RecordInsertMiss(get_hash(hasher, common.seed().seed()), + probe_length); } // Outlines cold infoz recording with computation of probe_length so callers can @@ -1751,7 +1777,7 @@ void* Grow1To3AndPrepareInsert(CommonFields& common, const PolicyFunctions& __restrict policy, - absl::FunctionRef<size_t(size_t)> get_hash) { + void* hasher, HashForKeyFn get_hash) { // TODO(b/413062340): Refactor to reuse more code with // GrowSooTableToNextCapacityAndPrepareInsert. ABSL_SWISSTABLE_ASSERT(common.capacity() == 1); @@ -1781,7 +1807,7 @@ // When we're sampled, we already have a seed. common.generate_new_seed(/*has_infoz=*/false); } - const size_t new_hash = get_hash(common.seed().seed()); + const size_t new_hash = get_hash(hasher, common.seed().seed()); h2_t new_h2 = H2(new_hash); size_t orig_hash = policy.hash_slot(policy.hash_fn(common), old_slots, common.seed().seed()); @@ -1889,7 +1915,7 @@ void* PrepareInsertSmallNonSoo(CommonFields& common, const PolicyFunctions& __restrict policy, - absl::FunctionRef<size_t(size_t)> get_hash) { + void* hasher, HashForKeyFn get_hash) { ABSL_SWISSTABLE_ASSERT(common.is_small()); ABSL_SWISSTABLE_ASSERT(!policy.soo_enabled); if (common.capacity() == 1) { @@ -1900,11 +1926,11 @@ // Call NOINLINE function to move infoz instructions out of line. if (common.has_infoz()) { ReportInsertMissToInfozAndComputeHash(common, /*probe_length=*/0, - get_hash); + hasher, get_hash); } return res; } else { - return Grow1To3AndPrepareInsert(common, policy, get_hash); + return Grow1To3AndPrepareInsert(common, policy, hasher, get_hash); } } @@ -1926,7 +1952,7 @@ PrepareInsertCommon(common); if (ABSL_PREDICT_FALSE(has_infoz)) { - ReportSingleElementTableGrowthToInfozAndForceSample(common, policy, + ReportSingleElementTableGrowthToInfozAndForceSample(common, policy, hasher, get_hash); } return new_slots; @@ -2040,14 +2066,14 @@ ABSL_ATTRIBUTE_NOINLINE void* GrowEmptySooTableToNextCapacityForceSamplingAndPrepareInsert( CommonFields& common, const PolicyFunctions& __restrict policy, - absl::FunctionRef<size_t(size_t)> get_hash) { + void* hasher, HashForKeyFn get_hash) { const size_t kNewCapacity = NextCapacity(SooCapacity()); ResizeEmptyNonAllocatedTableImpl(common, policy, kNewCapacity, /*blocked_element_count=*/0, /*force_infoz=*/true); PrepareInsertCommon(common); common.OverwriteEmptyAsFull(); - const size_t new_hash = get_hash(common.seed().seed()); + const size_t new_hash = get_hash(hasher, common.seed().seed()); // Compute before `SetCtrl*` to avoid reloading `control_`. void* res = SlotAddress(common.slot_array(kNewCapacity), SooSlotIndex(), policy.slot_size); @@ -2186,12 +2212,12 @@ template <size_t SooSlotMemcpySize, bool TransferUsesMemcpy> void* GrowSooTableToNextCapacityAndPrepareInsert( CommonFields& common, const PolicyFunctions& __restrict policy, - absl::FunctionRef<size_t(size_t)> get_hash, bool force_sampling) { + void* hasher, HashForKeyFn get_hash, bool force_sampling) { AssertSoo(common, policy); if (ABSL_PREDICT_FALSE(force_sampling)) { // The table is empty, it is only used for forced sampling of SOO tables. return GrowEmptySooTableToNextCapacityForceSamplingAndPrepareInsert( - common, policy, get_hash); + common, policy, hasher, get_hash); } ABSL_SWISSTABLE_ASSERT(common.size() == policy.soo_capacity()); static constexpr size_t kNewCapacity = NextCapacity(SooCapacity()); @@ -2214,7 +2240,7 @@ common.generate_new_seed(/*has_infoz=*/false); const h2_t soo_slot_h2 = H2(policy.hash_slot( policy.hash_fn(common), common.soo_data(), common.seed().seed())); - const size_t new_hash = get_hash(common.seed().seed()); + const size_t new_hash = get_hash(hasher, common.seed().seed()); const size_t offset = Resize1To3NewOffset(new_hash); InitializeThreeElementsControlBytes(soo_slot_h2, H2(new_hash), offset, @@ -2500,18 +2526,17 @@ // We need to instantiate ALL possible template combinations because we define // the function in the cc file. template void* GrowSooTableToNextCapacityAndPrepareInsert<0, false>( - CommonFields&, const PolicyFunctions&, absl::FunctionRef<size_t(size_t)>, - bool); + CommonFields&, const PolicyFunctions&, void*, HashForKeyFn, bool); template void* GrowSooTableToNextCapacityAndPrepareInsert< - OptimalMemcpySizeForSooSlotTransfer(1), true>( - CommonFields&, const PolicyFunctions&, absl::FunctionRef<size_t(size_t)>, - bool); + OptimalMemcpySizeForSooSlotTransfer(1), true>(CommonFields&, + const PolicyFunctions&, void*, + HashForKeyFn, bool); static_assert(VerifyOptimalMemcpySizeForSooSlotTransferRange(2, 3)); template void* GrowSooTableToNextCapacityAndPrepareInsert< - OptimalMemcpySizeForSooSlotTransfer(3), true>( - CommonFields&, const PolicyFunctions&, absl::FunctionRef<size_t(size_t)>, - bool); + OptimalMemcpySizeForSooSlotTransfer(3), true>(CommonFields&, + const PolicyFunctions&, void*, + HashForKeyFn, bool); #if UINTPTR_MAX == UINT32_MAX static_assert(MaxSooSlotSize() == 4); @@ -2519,9 +2544,9 @@ #else static_assert(VerifyOptimalMemcpySizeForSooSlotTransferRange(4, 8)); template void* GrowSooTableToNextCapacityAndPrepareInsert< - OptimalMemcpySizeForSooSlotTransfer(8), true>( - CommonFields&, const PolicyFunctions&, absl::FunctionRef<size_t(size_t)>, - bool); + OptimalMemcpySizeForSooSlotTransfer(8), true>(CommonFields&, + const PolicyFunctions&, void*, + HashForKeyFn, bool); static_assert(MaxSooSlotSize() == 8); #endif
diff --git a/absl/container/internal/raw_hash_set.h b/absl/container/internal/raw_hash_set.h index 7c01b9e..a851710 100644 --- a/absl/container/internal/raw_hash_set.h +++ b/absl/container/internal/raw_hash_set.h
@@ -1546,7 +1546,9 @@ // Asserts that the capacity is not a sentinel invalid value. void AssertNotDebugCapacity() const { if constexpr (SwisstableGenerationsOrDebugEnabled()) { - AssertNotDebugCapacityImpl(); + if (ABSL_PREDICT_FALSE(!maybe_invalid_capacity().IsValid())) { + AssertNotDebugCapacityImpl(); + } } } @@ -2174,22 +2176,24 @@ return 8; } +using HashForKeyFn = size_t (*)(void* hasher, size_t seed); + // Resizes SOO table to the NextCapacity(SooCapacity()) and prepares insert for // the given new_hash. Returns the new slot. // All possible template combinations are defined in cc file to improve // compilation time. template <size_t SooSlotMemcpySize, bool TransferUsesMemcpy> void* GrowSooTableToNextCapacityAndPrepareInsert( - CommonFields& common, const PolicyFunctions& policy, - absl::FunctionRef<size_t(size_t)> get_hash, bool force_sampling); + CommonFields& common, const PolicyFunctions& policy, void* hasher, + HashForKeyFn get_hash, bool force_sampling); // PrepareInsert for small tables (is_small()==true). // Returns the new slot. // Hash is only computed if the table is sampled or grew to large size // (is_small()==false). void* PrepareInsertSmallNonSoo(CommonFields& common, - const PolicyFunctions& policy, - absl::FunctionRef<size_t(size_t)> get_hash); + const PolicyFunctions& policy, void* hasher, + HashForKeyFn get_hash); // Resizes table with allocated slots and change the table seed. // Tables with SOO enabled must have capacity > policy.soo_capacity. @@ -2238,6 +2242,15 @@ template <bool kSooEnabled> void Destruct(CommonFields& c, const DtorPolicy& policy); +using EqualToKeyFn = bool (*)(void* set, const void* key, void* slot); + +// Asserts that hash and equal functors provided by the user are consistent, +// meaning that `eq(k1, k2)` implies `hash(k1)==hash(k2)`. +void AssertHashEqConsistentImpl(CommonFields& common, const void* hash_fn, + HashSlotFn hash_slot, size_t slot_size, + bool soo_enabled, size_t hash, const void* key, + EqualToKeyFn equal); + // Type-erased versions of raw_hash_set::erase_meta_only_{small,large}. void EraseMetaOnlySmall(CommonFields& c, bool soo_enabled, size_t slot_size); void EraseMetaOnlyLarge(CommonFields& c, size_t index, size_t slot_size); @@ -2346,13 +2359,45 @@ Alloc>::template rebind_alloc<T>>, T, void>>>; +template <class T> +using ShouldStructurallyPreferPassByValue = std::bool_constant< + // Check if size is a power of 2, to ensure cheap calls. + absl::has_single_bit(sizeof(T)) && + // Check if copying 1-2 registers is enough. We don't want (for example) 32 + // one-byte values to move around, since that sometimes results in poor + // codegen. + alignof(T) * 2 >= sizeof(T) && + sizeof(T) <= sizeof(std::pair<void*, size_t>)>; + +template <class T> +using ShouldSemanticallyPreferPassByValue = + std::bool_constant<std::is_trivially_move_constructible_v<T> && + std::is_trivially_destructible_v<T>>; + +// Checks the given type to see if it is a good candidate for accepting by value +// instead of by rvalue-reference. This includes both a semantic constraint +// (to ensure correctness), as well as a structural constraint (to ensure +// performance). +template <class T> +using ShouldPreferPassByValue = + std::bool_constant<ShouldSemanticallyPreferPassByValue<T>::value && + ShouldStructurallyPreferPassByValue<std::conditional_t< + std::is_void_v<T>, char, T>>::value>; + +// This is a 'struct' instead of 'using' in order to avoid eager evaluation for +// callers that may be passing incomplete types. +template <class T> +struct AddConstRefIfExpensive + : std::conditional<ShouldPreferPassByValue<T>::value, T, const T&> { + static_assert(std::is_same_v<remove_cvref_t<T>, T>, + "passed types must be unqualified"); +}; + template <class T, class Alloc = std::allocator<T>> -using PreferredParamType = std::conditional_t< - IsAllocMoveSameAsCopy<T, Alloc>::value, - std::conditional_t<std::is_arithmetic_v<T> || std::is_enum_v<T> || - std::is_member_pointer_v<T>, - T, const T&>, - T&&>; +using PreferredParamType = + typename std::conditional_t<IsAllocMoveSameAsCopy<T, Alloc>::value, + AddConstRefIfExpensive<T>, + std::add_rvalue_reference<T>>::type; // A SwissTable. // @@ -2428,11 +2473,6 @@ using slot_type = typename PolicyTraits::slot_type; - using IsDefaultPolicy = - std::bool_constant<std::is_same_v<Hash, typename Policy::DefaultHash> && - std::is_same_v<Eq, typename Policy::DefaultEq> && - std::is_same_v<Alloc, typename Policy::DefaultAlloc>>; - constexpr static bool kIsAbslHash = std::is_same_v<hasher, absl::Hash<key_type>> || (!HasAbslContainerHash<key_type>::value && @@ -3415,7 +3455,7 @@ AssertOnFind(key); if (is_small()) return find_small(key); prefetch_heap_block(); - return find_large(key); + return find_large<FindLargeKey<key_arg<K>>>(key); } template <class K = key_type> @@ -3570,13 +3610,14 @@ // TODO(b/289225379): consider having a helper class that has the impls for // SOO functionality. - template <class K = key_type> - ABSL_ATTRIBUTE_ALWAYS_INLINE iterator find_small(const key_arg<K>& key) { + template <class K> + ABSL_ATTRIBUTE_ALWAYS_INLINE iterator find_small(const K& key) { return empty() || !equal_to(key, single_slot()) ? end() : single_iterator(); } - template <class K = key_type> - iterator find_large(const key_arg<K>& key) { + template <class K> + iterator find_large(absl::type_identity_t<K> key) { + static_assert(std::is_same_v<K, FindLargeKey<K>>); const size_t cap = common().capacity(); ABSL_ASSUME(cap > kMaxSmallCapacity); const size_t hash = hash_of(key); @@ -3590,10 +3631,13 @@ #ifndef ABSL_HAVE_MEMORY_SANITIZER absl::PrefetchToLocalCache(slot_array + seq.offset()); #endif - for (uint32_t i : g.Match(h2)) { - const size_t offset = seq.offset(i); - if (ABSL_PREDICT_TRUE(equal_to(key, slot_array + offset))) - return iterator_at_ptr(ctrl + offset, slot_array + offset); + // Predict one iteration (common case for find-hit). + if (auto match = g.Match(h2); ABSL_PREDICT_TRUE(match)) { + do { + const size_t offset = seq.offset(*match); + if (ABSL_PREDICT_TRUE(equal_to(key, slot_array + offset))) + return iterator_at_ptr(ctrl + offset, slot_array + offset); + } while (ABSL_PREDICT_FALSE(++match)); } if (ABSL_PREDICT_TRUE(g.MaskEmpty())) return end(); seq.next(); @@ -3838,14 +3882,14 @@ } constexpr bool kUseMemcpy = PolicyTraits::transfer_uses_memcpy() && SooEnabled(); + using Hasher = HashKey<hasher, K, kIsAbslHash, kSeedShift>; + Hasher hash_key{hash_ref(), key}; slot = to_slot( GrowSooTableToNextCapacityAndPrepareInsert< kUseMemcpy ? OptimalMemcpySizeForSooSlotTransfer(sizeof(slot_type)) : 0, - kUseMemcpy>( - common(), GetPolicyFunctions(), - HashKey<hasher, K, kIsAbslHash, kSeedShift>{hash_ref(), key}, - force_sampling)); + kUseMemcpy>(common(), GetPolicyFunctions(), &hash_key, + &Hasher::GetHash, force_sampling)); return {slot, true}; } @@ -3860,14 +3904,17 @@ return {single_slot(), false}; } } - return {to_slot(PrepareInsertSmallNonSoo( - common(), GetPolicyFunctions(), - HashKey<hasher, K, kIsAbslHash, kSeedShift>{hash_ref(), key})), + using Hasher = HashKey<hasher, K, kIsAbslHash, kSeedShift>; + Hasher hash_key{hash_ref(), key}; + return {to_slot(PrepareInsertSmallNonSoo(common(), GetPolicyFunctions(), + &hash_key, &Hasher::GetHash)), true}; } template <class K> - std::pair<slot_type*, bool> find_or_prepare_insert_large(const K& key) { + std::pair<slot_type*, bool> find_or_prepare_insert_large( + absl::type_identity_t<K> key) { + static_assert(std::is_same_v<K, FindLargeKey<K>>); prefetch_heap_block(); const size_t cap = capacity(); const size_t hash = hash_of(key); @@ -3881,24 +3928,25 @@ #ifndef ABSL_HAVE_MEMORY_SANITIZER absl::PrefetchToLocalCache(slot_array + seq.offset()); #endif - for (uint32_t i : g.Match(h2)) { - slot_type* slot = slot_array + seq.offset(i); - if (ABSL_PREDICT_TRUE(equal_to(key, slot))) { - return {slot, false}; - } + // Predict one iteration (common case for insert-hit). + if (auto match = g.Match(h2); ABSL_PREDICT_TRUE(match)) { + do { + slot_type* slot = slot_array + seq.offset(*match); + if (ABSL_PREDICT_TRUE(equal_to(key, slot))) return {slot, false}; + } while (ABSL_PREDICT_FALSE(++match)); } auto mask_empty = g.MaskEmpty(); if (ABSL_PREDICT_TRUE(mask_empty)) { size_t target_group_offset = seq.offset(); - void* slot = SwisstableGenerationsEnabled() - ? PrepareInsertLargeGenerationsEnabled( - common(), GetPolicyFunctions(), hash, mask_empty, - target_group_offset, - HashKey<hasher, K, kIsAbslHash, kSeedShift>{ - hash_ref(), key}) - : PrepareInsertLarge( - common(), GetPolicyFunctions(), hash, mask_empty, - target_group_offset); + void* slot = + SwisstableGenerationsEnabled() + ? PrepareInsertLargeGenerationsEnabled( + common(), GetPolicyFunctions(), hash, mask_empty, + target_group_offset, + HashKey<hasher, absl::remove_cvref_t<K>, kIsAbslHash, + kSeedShift>{hash_ref(), key}) + : PrepareInsertLarge(common(), GetPolicyFunctions(), hash, + mask_empty, target_group_offset); return {to_slot(slot), true}; } seq.next(); @@ -3921,6 +3969,12 @@ // Asserts that the capacity is not a sentinel invalid value. void AssertNotDebugCapacity() const { common().AssertNotDebugCapacity(); } + template <class K> + static bool equal_to_key_fn(void* set, const void* key, void* slot) { + return static_cast<const raw_hash_set*>(set)->equal_to( + *static_cast<const K*>(key), to_slot(slot)); + } + // Asserts that hash and equal functors provided by the user are consistent, // meaning that `eq(k1, k2)` implies `hash(k1)==hash(k2)`. template <class K> @@ -3936,26 +3990,10 @@ std::is_same_v<key_equal, std::equal_to<key_type>>; if constexpr (kIsDebug && !kIsStringAbslHashEq && !kIsScalarAbslHashEq) { if (empty()) return; - - const size_t hash_of_arg = hash_of(key); - const auto assert_consistent = [&](const ctrl_t*, void* slot) { - const bool is_key_equal = equal_to(key, to_slot(slot)); - if (!is_key_equal) return; - - [[maybe_unused]] const bool is_hash_equal = - hash_of_arg == hash_of(to_slot(slot)); - assert((!is_key_equal || is_hash_equal) && - "eq(k1, k2) must imply that hash(k1) == hash(k2). " - "hash/eq functors are inconsistent."); - }; - - if (is_small()) { - assert_consistent(/*unused*/ nullptr, single_slot()); - return; - } - // We only do validation for small tables so that it's constant time. - if (capacity() > 16) return; - IterateOverFullSlots(common(), sizeof(slot_type), assert_consistent); + AssertHashEqConsistentImpl( + common(), get_hash_ref_fn(common()), get_hash_slot_fn(), + sizeof(slot_type), SooEnabled(), hash_of(key), &key, + &equal_to_key_fn<K>); } } @@ -3966,7 +4004,7 @@ std::pair<slot_type*, bool> find_or_prepare_insert(const K& key) { AssertOnFind(key); return is_small() ? find_or_prepare_insert_small(key) - : find_or_prepare_insert_large(key); + : find_or_prepare_insert_large<FindLargeKey<K>>(key); } // Constructs the value in the space pointed by the iterator. This only works @@ -4011,6 +4049,23 @@ private: friend struct RawHashSetTestOnlyAccess; + // For string-like keys, we convert to string_view when calling find*_large() + // operations to (a) avoid repeatedly (i) branching on SSO or (ii) calling + // strlen(), (b) share code between the string-like types, (c) avoid + // materializing string_view on the stack just to pass a reference to it. Note + // that we don't pass FindLargeKey to find*_small() to avoid unnecessarily + // constructing string_view for small empty tables. Note: we use + // type_identity_t and static_asserts to ensure that the key type in + // find*_large is FindLargeKey. (type_identity_t ensures that the template + // argument is not deduced and must be specified explicitly.) + // TODO(ezb): extend this to other types (e.g. small trivial types). + template <class K> + using FindLargeKey = std::conditional_t< + std::is_same_v<hasher, absl::container_internal::StringHash> && + std::is_same_v<key_equal, absl::container_internal::StringEq> && + std::is_convertible_v<const K&, absl::string_view>, + const absl::string_view, const K&>; + // Prefetch the heap-allocated memory region to resolve potential TLB and // cache misses. This is intended to overlap with execution of calculating the // hash for a key. @@ -4088,6 +4143,11 @@ // TODO(b/397453582): Remove support for const hasher. return const_cast<std::remove_const_t<hasher>*>(&h->hash_ref()); } + static constexpr HashSlotFn get_hash_slot_fn() { + return PolicyTraits::template get_hash_slot_fn<hasher, kIsAbslHash, + kSeedShift>(); + } + static void transfer_n_slots_fn(void* set, void* dst, void* src, size_t count) { auto* src_slot = to_slot(src); @@ -4206,8 +4266,7 @@ // for standard layout and alignof(Hash) <= alignof(CommonFields). std::is_empty_v<hasher> ? &GetRefForEmptyClass : &raw_hash_set::get_hash_ref_fn, - PolicyTraits::template get_hash_slot_fn<hasher, kIsAbslHash, - kSeedShift>(), + get_hash_slot_fn(), PolicyTraits::transfer_uses_memcpy() ? TransferNRelocatable<sizeof(slot_type)> : &raw_hash_set::transfer_n_slots_fn, @@ -4358,17 +4417,17 @@ // Extern template instantiations reduce binary size and linker input size. // Function definition is in raw_hash_set.cc. extern template void* GrowSooTableToNextCapacityAndPrepareInsert<0, false>( - CommonFields&, const PolicyFunctions&, absl::FunctionRef<size_t(size_t)>, + CommonFields&, const PolicyFunctions&, void*, HashForKeyFn, bool); extern template void* GrowSooTableToNextCapacityAndPrepareInsert<1, true>( - CommonFields&, const PolicyFunctions&, absl::FunctionRef<size_t(size_t)>, + CommonFields&, const PolicyFunctions&, void*, HashForKeyFn, bool); extern template void* GrowSooTableToNextCapacityAndPrepareInsert<4, true>( - CommonFields&, const PolicyFunctions&, absl::FunctionRef<size_t(size_t)>, + CommonFields&, const PolicyFunctions&, void*, HashForKeyFn, bool); #if UINTPTR_MAX == UINT64_MAX extern template void* GrowSooTableToNextCapacityAndPrepareInsert<8, true>( - CommonFields&, const PolicyFunctions&, absl::FunctionRef<size_t(size_t)>, + CommonFields&, const PolicyFunctions&, void*, HashForKeyFn, bool); #endif
diff --git a/absl/container/internal/raw_hash_set_test.cc b/absl/container/internal/raw_hash_set_test.cc index a4254ed..429d7cf 100644 --- a/absl/container/internal/raw_hash_set_test.cc +++ b/absl/container/internal/raw_hash_set_test.cc
@@ -3797,6 +3797,90 @@ EXPECT_TRUE((VerifyResultOf<CallCount, TransparentTable>())); } +TEST(Table, StringViewConversionCount) { + DisableSampling(); + + struct ConvertibleToStringView { + absl::string_view value; + mutable int num_conversions = 0; + + operator absl::string_view() const { // NOLINT(runtime/explicit) + ++num_conversions; + return value; + } + }; + + const auto test_table = [](auto soo) { + using Policy = ValuePolicy<absl::string_view, /*kTransferable=*/true, + /*kSoo=*/decltype(soo)::value>; + using Table = raw_hash_set<Policy, hash_default_hash<absl::string_view>, + hash_default_eq<absl::string_view>>; + Table t; + + // Empty table: no conversion to string_view. + { + ConvertibleToStringView k{"a"}; + EXPECT_EQ(t.find(k), t.end()); + EXPECT_EQ(k.num_conversions, 0); + } + { + ConvertibleToStringView k{"a"}; + t.lazy_emplace(k, [&](const auto& ctor) { + EXPECT_EQ(k.num_conversions, 0); + ctor("a"); + }); + } + + // Single-element table: 1 conversion to string_view. + ASSERT_TRUE(RawHashSetTestOnlyAccess::GetCommon(t).is_small()); + { + ConvertibleToStringView k{"a"}; + EXPECT_NE(t.find(k), t.end()); + EXPECT_EQ(k.num_conversions, 1); + } + { + ConvertibleToStringView k{"b"}; + EXPECT_EQ(t.find(k), t.end()); + EXPECT_EQ(k.num_conversions, 1); + } + { + ConvertibleToStringView k{"a"}; + EXPECT_FALSE(t.emplace(k).second); + EXPECT_EQ(k.num_conversions, 1); + } + + // Many-element table: 1 conversion to string_view. + t.insert("b"); + t.insert("c"); + ASSERT_FALSE(RawHashSetTestOnlyAccess::GetCommon(t).is_small()); + { + ConvertibleToStringView k{"a"}; + EXPECT_NE(t.find(k), t.end()); + EXPECT_EQ(k.num_conversions, 1); + } + { + ConvertibleToStringView k{"z"}; + EXPECT_EQ(t.find(k), t.end()); + EXPECT_EQ(k.num_conversions, 1); + } + { + ConvertibleToStringView k{"a"}; + EXPECT_FALSE(t.emplace(k).second); + EXPECT_EQ(k.num_conversions, 1); + } + { + ConvertibleToStringView k{"d"}; + t.lazy_emplace(k, [&](const auto& ctor) { + EXPECT_EQ(k.num_conversions, 1); + ctor("d"); + }); + } + }; + + test_table(/*soo=*/std::false_type{}); + test_table(/*soo=*/std::true_type{}); +} + TEST(Iterator, IsDefaultConstructible) { StringTable::iterator i; EXPECT_TRUE(i == StringTable::iterator());
diff --git a/absl/container/linked_hash_map.h b/absl/container/linked_hash_map.h index d04807b..60d53d3 100644 --- a/absl/container/linked_hash_map.h +++ b/absl/container/linked_hash_map.h
@@ -436,7 +436,8 @@ return LazyEmplaceInternal(key).first->second; } - template <class K = key_type, K* = nullptr> + template <class K = key_type, + std::enable_if_t<!std::is_reference_v<K>, int> = 0> mapped_type& operator[](key_arg<K>&& key) { return LazyEmplaceInternal(std::forward<key_arg<K>>(key)).first->second; } @@ -485,19 +486,22 @@ // union { int n : 1; }; // linked_hash_map<int, int> m; // m.insert_or_assign(n, n); - template <class K = key_type, class V = mapped_type, K* = nullptr, - V* = nullptr> + template <class K = key_type, class V = mapped_type, + std::enable_if_t<!std::is_reference_v<K> && !std::is_reference_v<V>, + int> = 0> std::pair<iterator, bool> insert_or_assign(key_arg<K>&& k, V&& v) { return InsertOrAssignInternal(std::forward<key_arg<K>>(k), std::forward<V>(v)); } - template <class K = key_type, class V = mapped_type, K* = nullptr> + template <class K = key_type, class V = mapped_type, + std::enable_if_t<!std::is_reference_v<K>, int> = 0> std::pair<iterator, bool> insert_or_assign(key_arg<K>&& k, const V& v) { return InsertOrAssignInternal(std::forward<key_arg<K>>(k), v); } - template <class K = key_type, class V = mapped_type, V* = nullptr> + template <class K = key_type, class V = mapped_type, + std::enable_if_t<!std::is_reference_v<V>, int> = 0> std::pair<iterator, bool> insert_or_assign(const key_arg<K>& k, V&& v) { return InsertOrAssignInternal(k, std::forward<V>(v)); } @@ -507,19 +511,22 @@ return InsertOrAssignInternal(k, v); } - template <class K = key_type, class V = mapped_type, K* = nullptr, - V* = nullptr> + template <class K = key_type, class V = mapped_type, + std::enable_if_t<!std::is_reference_v<K> && !std::is_reference_v<V>, + int> = 0> iterator insert_or_assign(const_iterator, key_arg<K>&& k, V&& v) { return insert_or_assign(std::forward<key_arg<K>>(k), std::forward<V>(v)) .first; } - template <class K = key_type, class V = mapped_type, K* = nullptr> + template <class K = key_type, class V = mapped_type, + std::enable_if_t<!std::is_reference_v<K>, int> = 0> iterator insert_or_assign(const_iterator, key_arg<K>&& k, const V& v) { return insert_or_assign(std::forward<key_arg<K>>(k), v).first; } - template <class K = key_type, class V = mapped_type, V* = nullptr> + template <class K = key_type, class V = mapped_type, + std::enable_if_t<!std::is_reference_v<V>, int> = 0> iterator insert_or_assign(const_iterator, const key_arg<K>& k, V&& v) { return insert_or_assign(k, std::forward<V>(v)).first; } @@ -540,7 +547,8 @@ return {list_iter, true}; } - template <class K = key_type, class... Args, K* = nullptr> + template <class K = key_type, class... Args, + std::enable_if_t<!std::is_reference_v<K>, int> = 0> iterator try_emplace(const_iterator, key_arg<K>&& k, Args&&... args) { return try_emplace(std::forward<key_arg<K>>(k), std::forward<Args>(args)...) .first; @@ -551,7 +559,8 @@ return emplace(std::forward<Args>(args)...).first; } - template <class K = key_type, typename... Args, K* = nullptr> + template <class K = key_type, typename... Args, + std::enable_if_t<!std::is_reference_v<K>, int> = 0> std::pair<iterator, bool> try_emplace(key_arg<K>&& key, Args&&... args) { return LazyEmplaceInternal(std::forward<key_arg<K>>(key), std::forward<Args>(args)...);
diff --git a/absl/container/linked_hash_set.h b/absl/container/linked_hash_set.h index 10ba289..18bee87 100644 --- a/absl/container/linked_hash_set.h +++ b/absl/container/linked_hash_set.h
@@ -401,7 +401,8 @@ std::pair<iterator, bool> insert(const key_arg<K>& k) { return InsertInternal(list_.end(), k); } - template <typename K = key_type, K* = nullptr> + template <typename K = key_type, + std::enable_if_t<!std::is_reference_v<K>, int> = 0> std::pair<iterator, bool> insert(key_arg<K>&& k) { return InsertInternal(list_.end(), std::move(k)); } @@ -414,11 +415,12 @@ iterator insert(const_iterator hint, const key_arg<K>& k) { return InsertInternal(hint, k).first; } - template < - typename K = key_type, K* = nullptr, - std::enable_if_t<!std::is_convertible_v<key_arg<K>&&, const_iterator> && - !std::is_convertible_v<key_arg<K>&&, iterator>, - int> = 0> + template <typename K = key_type, + std::enable_if_t< + !std::is_reference_v<K> && + !std::is_convertible_v<key_arg<K>&&, const_iterator> && + !std::is_convertible_v<key_arg<K>&&, iterator>, + int> = 0> iterator insert(const_iterator hint, key_arg<K>&& k) { return InsertInternal(hint, std::move(k)).first; }
diff --git a/absl/crc/internal/crc_x86_arm_combined.cc b/absl/crc/internal/crc_x86_arm_combined.cc index 8a2a400..95221e4 100644 --- a/absl/crc/internal/crc_x86_arm_combined.cc +++ b/absl/crc/internal/crc_x86_arm_combined.cc
@@ -593,6 +593,8 @@ // instruction, and up to kMaxStreams blocks computed with PCLMULQDQ, which // are combined in the end. static constexpr size_t kMaxStreams = 3; + // When copying, at most kMaxCopyStreams streams are used in total. + static constexpr size_t kMaxCopyStreams = 3; }; template <size_t num_crc_streams, size_t num_pclmul_streams, @@ -745,6 +747,9 @@ "Invalid number of crc streams"); static_assert(num_pclmul_streams >= 0 && num_pclmul_streams <= kMaxStreams, "Invalid number of pclmul streams"); + constexpr StreamCounts kStreams = GetStreamCounts(copy_type); + constexpr size_t kNumActiveCrcStreams = kStreams.crc; + constexpr size_t kNumActivePclmulStreams = kStreams.pclmul; const uint8_t* p = static_cast<const uint8_t*>(bytes); const uint8_t* e = p + length; char* d = static_cast<char*>(dst); @@ -779,15 +784,15 @@ } size_t bs = static_cast<size_t>(e - p) / - (num_crc_streams + num_pclmul_streams) / 64; + (kNumActiveCrcStreams + kNumActivePclmulStreams) / 64; const uint8_t* stream_start = p; const uint8_t* crc_streams[kMaxStreams]; - for (size_t i = 0; i < num_crc_streams; i++) { + for (size_t i = 0; i < kNumActiveCrcStreams; i++) { crc_streams[i] = stream_start; stream_start += bs * 64; } const uint8_t* pclmul_streams[kMaxStreams]; - for (size_t i = 0; i < num_pclmul_streams; i++) { + for (size_t i = 0; i < kNumActivePclmulStreams; i++) { pclmul_streams[i] = stream_start; stream_start += bs * 64; } @@ -796,11 +801,11 @@ uint8_t* dst_pclmul_streams[kMaxStreams]; if constexpr (copy_type != CopyType::kNone) { uint8_t* dst_stream_start = reinterpret_cast<uint8_t*>(d); - for (size_t i = 0; i < num_crc_streams; i++) { + for (size_t i = 0; i < kNumActiveCrcStreams; i++) { dst_crc_streams[i] = dst_stream_start; dst_stream_start += bs * 64; } - for (size_t i = 0; i < num_pclmul_streams; i++) { + for (size_t i = 0; i < kNumActivePclmulStreams; i++) { dst_pclmul_streams[i] = dst_stream_start; dst_stream_start += bs * 64; } @@ -812,26 +817,26 @@ V256 pclmul_v0_0[kMaxStreams]; V256 pclmul_v1_0[kMaxStreams]; - for (size_t j = 0; j < num_pclmul_streams; j++) { + for (size_t j = 0; j < kNumActivePclmulStreams; j++) { V256_LoadPairU(pclmul_streams[j], &pclmul_v0_0[j], &pclmul_v1_0[j]); } if constexpr (copy_type != CopyType::kNone) { - for (size_t j = 0; j < num_crc_streams; j++) { + for (size_t j = 0; j < kNumActiveCrcStreams; j++) { Copy64Bytes<copy_type>(dst_crc_streams[j], crc_streams[j]); dst_crc_streams[j] += 64; } - for (size_t j = 0; j < num_pclmul_streams; j++) { + for (size_t j = 0; j < kNumActivePclmulStreams; j++) { Store64Bytes<copy_type>(dst_pclmul_streams[j], pclmul_v0_0[j], pclmul_v1_0[j]); dst_pclmul_streams[j] += 64; } } - if (num_crc_streams == 1) { + if (kNumActiveCrcStreams == 1) { l64_crc[0] = Process64BytesCRC(crc_streams[0], l64_crc[0]); crc_streams[0] += 16 * 4; - } else if (num_crc_streams == 2) { + } else if (kNumActiveCrcStreams == 2) { Process64BytesCRC2Streams(crc_streams[0], crc_streams[1], l64_crc); crc_streams[0] += 16 * 4; crc_streams[1] += 16 * 4; @@ -845,21 +850,21 @@ // Align to 32 bytes for vpclmul implementation. alignas(32) V128 partialCRC[kMaxStreams][4]; - for (size_t i = 0; i < num_pclmul_streams; i++) { + for (size_t i = 0; i < kNumActivePclmulStreams; i++) { InitPclmulStream(pclmul_v0_0[i], pclmul_v1_0[i], partialCRC[i]); pclmul_streams[i] += 64; } for (size_t i = 1; i < bs; i++) { // Prefetch data for next iterations. - for (size_t j = 0; j < num_crc_streams; j++) { + for (size_t j = 0; j < kNumActiveCrcStreams; j++) { PrefetchToLocalCache(crc_streams[j] + kPrefetchHorizon); } - for (size_t j = 0; j < num_pclmul_streams; j++) { + for (size_t j = 0; j < kNumActivePclmulStreams; j++) { PrefetchToLocalCache(pclmul_streams[j] + kPrefetchHorizon); } - for (size_t j = 0; j < num_pclmul_streams; j++) { + for (size_t j = 0; j < kNumActivePclmulStreams; j++) { if constexpr (copy_type != CopyType::kNone) { ProcessPclmulStream<copy_type>(pclmul_streams[j], dst_pclmul_streams[j], partialCRC[j]); @@ -871,10 +876,10 @@ pclmul_streams[j] += 64; } - if (num_crc_streams == 1) { + if (kNumActiveCrcStreams == 1) { l64_crc[0] = Process64BytesCRC(crc_streams[0], l64_crc[0]); crc_streams[0] += 16 * 4; - } else if (num_crc_streams == 2) { + } else if (kNumActiveCrcStreams == 2) { Process64BytesCRC2Streams(crc_streams[0], crc_streams[1], l64_crc); crc_streams[0] += 16 * 4; crc_streams[1] += 16 * 4; @@ -886,7 +891,7 @@ crc_streams[2] += 16 * 4; } if constexpr (copy_type != CopyType::kNone) { - for (size_t j = 0; j < num_crc_streams; j++) { + for (size_t j = 0; j < kNumActiveCrcStreams; j++) { Copy64Bytes<copy_type>(dst_crc_streams[j], crc_streams[j] - 64); dst_crc_streams[j] += 64; } @@ -895,7 +900,7 @@ // PCLMULQDQ based streams require special final step; // CRC based don't. - for (size_t i = 0; i < num_pclmul_streams; i++) { + for (size_t i = 0; i < kNumActivePclmulStreams; i++) { l64_pclmul[i] = FinalizePclmulStream(partialCRC[i]); } @@ -903,27 +908,28 @@ static_assert(64 % (1 << kNumDroppedBits) == 0); uint32_t magic = ComputeZeroConstant(bs * 64); l64 = l64_crc[0]; - for (size_t i = 1; i < num_crc_streams; i++) { + for (size_t i = 1; i < kNumActiveCrcStreams; i++) { l64 = MultiplyWithExtraX33(static_cast<uint32_t>(l64), magic); l64 ^= l64_crc[i]; } - for (size_t i = 0; i < num_pclmul_streams; i++) { + for (size_t i = 0; i < kNumActivePclmulStreams; i++) { l64 = MultiplyWithExtraX33(static_cast<uint32_t>(l64), magic); l64 ^= l64_pclmul[i]; } // Update p and d. - if constexpr (num_pclmul_streams > 0) { - p = pclmul_streams[num_pclmul_streams - 1]; + if constexpr (kNumActivePclmulStreams > 0) { + p = pclmul_streams[kNumActivePclmulStreams - 1]; if constexpr (copy_type != CopyType::kNone) { - d = reinterpret_cast<char*>(dst_pclmul_streams[num_pclmul_streams - 1]); + d = reinterpret_cast<char*>( + dst_pclmul_streams[kNumActivePclmulStreams - 1]); size_t remaining_to_copy = static_cast<size_t>(e - p); std::memcpy(d, p, remaining_to_copy); } } else { - p = crc_streams[num_crc_streams - 1]; + p = crc_streams[kNumActiveCrcStreams - 1]; if constexpr (copy_type != CopyType::kNone) { - d = reinterpret_cast<char*>(dst_crc_streams[num_crc_streams - 1]); + d = reinterpret_cast<char*>(dst_crc_streams[kNumActiveCrcStreams - 1]); size_t remaining_to_copy = static_cast<size_t>(e - p); std::memcpy(d, p, remaining_to_copy); } @@ -966,6 +972,32 @@ } private: + struct StreamCounts { + size_t crc; + size_t pclmul; + }; + + // Returns the number of crc and pclmul streams to use. Copying adds loads and + // stores, so fewer streams are needed to saturate the dispatch width. In copy + // mode, at most kMaxCopyStreams of the configured streams are used, taken in + // the order: pclmul 1, crc 1, pclmul 2, crc 2, pclmul 3, crc 3. + static constexpr StreamCounts GetStreamCounts(CopyType copy_type) { + if (copy_type == CopyType::kNone) { + return {num_crc_streams, num_pclmul_streams}; + } + StreamCounts counts = {0, 0}; + for (size_t i = 0; i < kMaxStreams; i++) { + if (i < num_pclmul_streams && + counts.crc + counts.pclmul < kMaxCopyStreams) { + counts.pclmul++; + } + if (i < num_crc_streams && counts.crc + counts.pclmul < kMaxCopyStreams) { + counts.crc++; + } + } + return counts; + } + ABSL_INTERNAL_ATTRIBUTE_AVX ABSL_ATTRIBUTE_ALWAYS_INLINE void InitPclmulStream(V256 v0, V256 v1, V128* partialCRC) const { if constexpr (pclmul_stream_type == PclmulStreamType::NONE) { @@ -1046,9 +1078,6 @@ CRCImpl* TryNewCRC32AcceleratedX86ARMCombined() { CpuType type = GetCpuType(); switch (type) { - case CpuType::kAmdRome: - return new CRC32AcceleratedX86ARMCombinedMultipleStreams< - 3, 0, PclmulStreamType::PCLMUL>(); case CpuType::kIntelHaswell: case CpuType::kAmdNaples: return new CRC32AcceleratedX86ARMCombinedMultipleStreams< @@ -1057,13 +1086,14 @@ case CpuType::kAmdGenoa: case CpuType::kAmdSiena: case CpuType::kAmdTurin: + case CpuType::kAmdVenice: #if defined(ABSL_CRC_INTERNAL_HAVE_X86_SIMD) && \ (defined(__AVX__) || defined(ABSL_INTERNAL_CAN_FORCE_AVX)) && \ (!defined(_MSC_VER) || defined(__clang__)) // We don't have vector pclmul on arm, but this still needs to // compile. return new CRC32AcceleratedX86ARMCombinedMultipleStreams< - 3, 1, PclmulStreamType::VPCLMUL>(); + 2, 2, PclmulStreamType::VPCLMUL>(); #else return new CRC32AcceleratedX86ARMCombinedMultipleStreams< 3, 1, PclmulStreamType::PCLMUL>(); @@ -1088,6 +1118,7 @@ return new CRC32AcceleratedX86ARMCombinedMultipleStreams< 3, 2, PclmulStreamType::PCLMUL>(); // PCLMULQDQ is slow, don't use it. + case CpuType::kAmdRome: case CpuType::kIntelIvybridge: case CpuType::kIntelSandybridge: case CpuType::kIntelWestmere:
diff --git a/absl/flags/flag_benchmark.cc b/absl/flags/flag_benchmark.cc index be435d9..2a356ee 100644 --- a/absl/flags/flag_benchmark.cc +++ b/absl/flags/flag_benchmark.cc
@@ -76,8 +76,8 @@ struct UDT { UDT() = default; - UDT(const UDT&) {} - UDT& operator=(const UDT&) { return *this; } + UDT(const UDT&) = default; + UDT& operator=(const UDT&) = default; }; // Next two functions represent Abseil Flags marshalling for UDT. bool AbslParseFlag(absl::string_view, UDT*, std::string*) { return true; }
diff --git a/absl/flags/flag_test.cc b/absl/flags/flag_test.cc index 458f1b1..1d6a496 100644 --- a/absl/flags/flag_test.cc +++ b/absl/flags/flag_test.cc
@@ -1022,10 +1022,8 @@ NonTriviallyCopyableUDT(const NonTriviallyCopyableUDT& rhs) : c(rhs.c) { s_num_instance++; } - NonTriviallyCopyableUDT& operator=(const NonTriviallyCopyableUDT& rhs) { - c = rhs.c; - return *this; - } + NonTriviallyCopyableUDT& operator=(const NonTriviallyCopyableUDT& rhs) = + default; ~NonTriviallyCopyableUDT() { s_num_instance--; } static uint64_t s_num_instance;
diff --git a/absl/random/CMakeLists.txt b/absl/random/CMakeLists.txt index 914ba0f..8a31015 100644 --- a/absl/random/CMakeLists.txt +++ b/absl/random/CMakeLists.txt
@@ -873,6 +873,7 @@ ${ABSL_DEFAULT_LINKOPTS} DEPS absl::config + absl::core_headers absl::random_internal_platform absl::random_internal_randen_hwaes_impl )
diff --git a/absl/random/internal/BUILD.bazel b/absl/random/internal/BUILD.bazel index d1bf943..04f9a27 100644 --- a/absl/random/internal/BUILD.bazel +++ b/absl/random/internal/BUILD.bazel
@@ -431,6 +431,7 @@ deps = [ ":platform", "//absl/base:config", + "//absl/base:core_headers", "//absl/numeric:int128", ], )
diff --git a/absl/random/internal/nanobenchmark.cc b/absl/random/internal/nanobenchmark.cc index 0cf657b..08e7f15 100644 --- a/absl/random/internal/nanobenchmark.cc +++ b/absl/random/internal/nanobenchmark.cc
@@ -61,15 +61,6 @@ #include <time.h> // NOLINT #endif -// ABSL_RANDOM_INTERNAL_ATTRIBUTE_NEVER_INLINE prevents inlining of the method. -#if ABSL_HAVE_ATTRIBUTE(noinline) || (defined(__GNUC__) && !defined(__clang__)) -#define ABSL_RANDOM_INTERNAL_ATTRIBUTE_NEVER_INLINE __attribute__((noinline)) -#elif defined(_MSC_VER) -#define ABSL_RANDOM_INTERNAL_ATTRIBUTE_NEVER_INLINE __declspec(noinline) -#else -#define ABSL_RANDOM_INTERNAL_ATTRIBUTE_NEVER_INLINE -#endif - namespace absl { ABSL_NAMESPACE_BEGIN namespace random_internal_nanobenchmark { @@ -670,8 +661,8 @@ } // (Nearly) empty Func for measuring timer overhead/resolution. -ABSL_RANDOM_INTERNAL_ATTRIBUTE_NEVER_INLINE FuncOutput -EmptyFunc(const void* arg, const FuncInput input) { +ABSL_ATTRIBUTE_NOINLINE FuncOutput EmptyFunc(const void* arg [[maybe_unused]], + const FuncInput input) { return input; }
diff --git a/absl/random/internal/randen_detect.cc b/absl/random/internal/randen_detect.cc index 83bde75..888e36f 100644 --- a/absl/random/internal/randen_detect.cc +++ b/absl/random/internal/randen_detect.cc
@@ -243,8 +243,8 @@ // * Rely on compiler-generated target-based dispatch. // Using x86/gcc it might look something like this: // - // int __attribute__((target("aes"))) HasAes() { return 1; } - // int __attribute__((target("default"))) HasAes() { return 0; } + // __attribute__((target("aes"))) int HasAes() { return 1; } + // __attribute__((target("default"))) int HasAes() { return 0; } // // This does not work on all architecture/compiler combinations. //
diff --git a/absl/random/internal/randen_hwaes.cc b/absl/random/internal/randen_hwaes.cc index f8dd358..b632aa9 100644 --- a/absl/random/internal/randen_hwaes.cc +++ b/absl/random/internal/randen_hwaes.cc
@@ -21,6 +21,7 @@ #include <cstring> +#include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/numeric/int128.h" #include "absl/random/internal/platform.h" @@ -97,9 +98,17 @@ // NOTE: Evaluate whether we should eliminate ABSL_TARGET_CRYPTO. #if (defined(__clang__) || defined(__GNUC__)) #if defined(ABSL_ARCH_X86_64) || defined(ABSL_ARCH_X86_32) +#if ABSL_HAVE_CPP_ATTRIBUTE(gnu::target) +#define ABSL_TARGET_CRYPTO [[gnu::target("aes")]] +#else #define ABSL_TARGET_CRYPTO __attribute__((target("aes"))) +#endif #elif defined(ABSL_ARCH_PPC) +#if ABSL_HAVE_CPP_ATTRIBUTE(gnu::target) +#define ABSL_TARGET_CRYPTO [[gnu::target("crypto")]] +#else #define ABSL_TARGET_CRYPTO __attribute__((target("crypto"))) +#endif #else #define ABSL_TARGET_CRYPTO #endif @@ -125,7 +134,7 @@ using Vector128 = __vector unsigned long long; // NOLINT(runtime/int) namespace { -inline ABSL_TARGET_CRYPTO Vector128 ReverseBytes(const Vector128& v) { +ABSL_TARGET_CRYPTO inline Vector128 ReverseBytes(const Vector128& v) { // Reverses the bytes of the vector. const __vector unsigned char perm = {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; @@ -135,23 +144,23 @@ // WARNING: these load/store in native byte order. It is OK to load and then // store an unchanged vector, but interpreting the bits as a number or input // to AES will have undefined results. -inline ABSL_TARGET_CRYPTO Vector128 Vector128Load(const void* from) { +ABSL_TARGET_CRYPTO inline Vector128 Vector128Load(const void* from) { return vec_vsx_ld(0, reinterpret_cast<const Vector128*>(from)); } -inline ABSL_TARGET_CRYPTO void Vector128Store(const Vector128& v, void* to) { +ABSL_TARGET_CRYPTO inline void Vector128Store(const Vector128& v, void* to) { vec_vsx_st(v, 0, reinterpret_cast<Vector128*>(to)); } // One round of AES. "round_key" is a public constant for breaking the // symmetry of AES (ensures previously equal columns differ afterwards). -inline ABSL_TARGET_CRYPTO Vector128 AesRound(const Vector128& state, +ABSL_TARGET_CRYPTO inline Vector128 AesRound(const Vector128& state, const Vector128& round_key) { return Vector128(__builtin_crypto_vcipher(state, round_key)); } // Enables native loads in the round loop by pre-swapping. -inline ABSL_TARGET_CRYPTO void SwapEndian(absl::uint128* state) { +ABSL_TARGET_CRYPTO inline void SwapEndian(absl::uint128* state) { for (uint32_t block = 0; block < RandenTraits::kFeistelBlocks; ++block) { Vector128Store(ReverseBytes(Vector128Load(state + block)), state + block); } @@ -182,17 +191,17 @@ namespace { -inline ABSL_TARGET_CRYPTO Vector128 Vector128Load(const void* from) { +ABSL_TARGET_CRYPTO inline Vector128 Vector128Load(const void* from) { return vld1q_u8(reinterpret_cast<const uint8_t*>(from)); } -inline ABSL_TARGET_CRYPTO void Vector128Store(const Vector128& v, void* to) { +ABSL_TARGET_CRYPTO inline void Vector128Store(const Vector128& v, void* to) { vst1q_u8(reinterpret_cast<uint8_t*>(to), v); } // One round of AES. "round_key" is a public constant for breaking the // symmetry of AES (ensures previously equal columns differ afterwards). -inline ABSL_TARGET_CRYPTO Vector128 AesRound(const Vector128& state, +ABSL_TARGET_CRYPTO inline Vector128 AesRound(const Vector128& state, const Vector128& round_key) { // It is important to always use the full round function - omitting the // final MixColumns reduces security [https://eprint.iacr.org/2010/041.pdf] @@ -204,7 +213,7 @@ return vaesmcq_u8(vaeseq_u8(state, uint8x16_t{})) ^ round_key; } -inline ABSL_TARGET_CRYPTO void SwapEndian(void*) {} +ABSL_TARGET_CRYPTO inline void SwapEndian(void*) {} } // namespace @@ -232,17 +241,17 @@ __m128i data_; }; -inline ABSL_TARGET_CRYPTO Vector128 Vector128Load(const void* from) { +ABSL_TARGET_CRYPTO inline Vector128 Vector128Load(const void* from) { return Vector128(_mm_load_si128(reinterpret_cast<const __m128i*>(from))); } -inline ABSL_TARGET_CRYPTO void Vector128Store(const Vector128& v, void* to) { +ABSL_TARGET_CRYPTO inline void Vector128Store(const Vector128& v, void* to) { _mm_store_si128(reinterpret_cast<__m128i*>(to), v.data()); } // One round of AES. "round_key" is a public constant for breaking the // symmetry of AES (ensures previously equal columns differ afterwards). -inline ABSL_TARGET_CRYPTO Vector128 AesRound(const Vector128& state, +ABSL_TARGET_CRYPTO inline Vector128 AesRound(const Vector128& state, const Vector128& round_key) { // It is important to always use the full round function - omitting the // final MixColumns reduces security [https://eprint.iacr.org/2010/041.pdf] @@ -250,7 +259,7 @@ return Vector128(_mm_aesenc_si128(state.data(), round_key.data())); } -inline ABSL_TARGET_CRYPTO void SwapEndian(void*) {} +ABSL_TARGET_CRYPTO inline void SwapEndian(void*) {} } // namespace @@ -276,7 +285,7 @@ // Block shuffles applies a shuffle to the entire state between AES rounds. // Improved odd-even shuffle from "New criterion for diffusion property". -inline ABSL_TARGET_CRYPTO void BlockShuffle(absl::uint128* state) { +ABSL_TARGET_CRYPTO inline void BlockShuffle(absl::uint128* state) { static_assert(RandenTraits::kFeistelBlocks == 16, "Expecting 16 FeistelBlocks."); @@ -323,7 +332,7 @@ // per 16 bytes (vs. 10 for AES-CTR). Computing eight round functions in // parallel hides the 7-cycle AESNI latency on HSW. Note that the Feistel // XORs are 'free' (included in the second AES instruction). -inline ABSL_TARGET_CRYPTO const absl::uint128* FeistelRound( +ABSL_TARGET_CRYPTO inline const absl::uint128* FeistelRound( absl::uint128* state, const absl::uint128* ABSL_RANDOM_INTERNAL_RESTRICT keys) { static_assert(RandenTraits::kFeistelBlocks == 16, @@ -385,7 +394,7 @@ // Indistinguishable from ideal by chosen-ciphertext adversaries using less than // 2^64 queries if the round function is a PRF. This is similar to the b=8 case // of Simpira v2, but more efficient than its generic construction for b=16. -inline ABSL_TARGET_CRYPTO void Permute( +ABSL_TARGET_CRYPTO inline void Permute( absl::uint128* state, const absl::uint128* ABSL_RANDOM_INTERNAL_RESTRICT keys) { // (Successfully unrolled; the first iteration jumps into the second half) @@ -406,7 +415,7 @@ bool HasRandenHwAesImplementation() { return true; } -const void* ABSL_TARGET_CRYPTO RandenHwAes::GetKeys() { +ABSL_TARGET_CRYPTO const void* RandenHwAes::GetKeys() { // Round keys for one AES per Feistel round and branch. // The canonical implementation uses first digits of Pi. #if defined(ABSL_ARCH_PPC) @@ -417,7 +426,7 @@ } // NOLINTNEXTLINE -void ABSL_TARGET_CRYPTO RandenHwAes::Absorb(const void* seed_void, +ABSL_TARGET_CRYPTO void RandenHwAes::Absorb(const void* seed_void, void* state_void) { static_assert(RandenTraits::kCapacityBytes / sizeof(Vector128) == 1, "Unexpected Randen kCapacityBlocks"); @@ -492,7 +501,7 @@ } // NOLINTNEXTLINE -void ABSL_TARGET_CRYPTO RandenHwAes::Generate(const void* keys_void, +ABSL_TARGET_CRYPTO void RandenHwAes::Generate(const void* keys_void, void* state_void) { static_assert(RandenTraits::kCapacityBytes == sizeof(Vector128), "Capacity mismatch");
diff --git a/absl/random/internal/randen_slow.cc b/absl/random/internal/randen_slow.cc index d2b7da3..12aa4b1 100644 --- a/absl/random/internal/randen_slow.cc +++ b/absl/random/internal/randen_slow.cc
@@ -25,19 +25,6 @@ #include "absl/random/internal/platform.h" #include "absl/random/internal/randen_traits.h" -#if ABSL_HAVE_ATTRIBUTE(always_inline) || \ - (defined(__GNUC__) && !defined(__clang__)) -#define ABSL_RANDOM_INTERNAL_ATTRIBUTE_ALWAYS_INLINE \ - __attribute__((always_inline)) -#elif defined(_MSC_VER) -// We can achieve something similar to attribute((always_inline)) with MSVC by -// using the __forceinline keyword, however this is not perfect. MSVC is -// much less aggressive about inlining, and even with the __forceinline keyword. -#define ABSL_RANDOM_INTERNAL_ATTRIBUTE_ALWAYS_INLINE __forceinline -#else -#define ABSL_RANDOM_INTERNAL_ATTRIBUTE_ALWAYS_INLINE -#endif - namespace { // AES portions based on rijndael-alg-fst.c, @@ -236,22 +223,21 @@ uint32_t s[4]; }; -inline ABSL_RANDOM_INTERNAL_ATTRIBUTE_ALWAYS_INLINE Vector128 -Vector128Load(const void* from) { +ABSL_ATTRIBUTE_ALWAYS_INLINE inline Vector128 Vector128Load(const void* from) { Vector128 result; std::memcpy(result.s, from, sizeof(Vector128)); return result; } -inline ABSL_RANDOM_INTERNAL_ATTRIBUTE_ALWAYS_INLINE void Vector128Store( - const Vector128& v, void* to) { +ABSL_ATTRIBUTE_ALWAYS_INLINE inline void Vector128Store(const Vector128& v, + void* to) { std::memcpy(to, v.s, sizeof(Vector128)); } // One round of AES. "round_key" is a public constant for breaking the // symmetry of AES (ensures previously equal columns differ afterwards). -inline ABSL_RANDOM_INTERNAL_ATTRIBUTE_ALWAYS_INLINE Vector128 -AesRound(const Vector128& state, const Vector128& round_key) { +ABSL_ATTRIBUTE_ALWAYS_INLINE inline Vector128 AesRound( + const Vector128& state, const Vector128& round_key) { Vector128 result; #ifdef ABSL_IS_LITTLE_ENDIAN result.s[0] = round_key.s[0] ^ // @@ -302,8 +288,7 @@ using ::absl::random_internal::RandenTraits; // The improved Feistel block shuffle function for 16 blocks. -inline ABSL_RANDOM_INTERNAL_ATTRIBUTE_ALWAYS_INLINE void BlockShuffle( - absl::uint128* state) { +ABSL_ATTRIBUTE_ALWAYS_INLINE inline void BlockShuffle(absl::uint128* state) { static_assert(RandenTraits::kFeistelBlocks == 16, "Feistel block shuffle only works for 16 blocks."); @@ -361,9 +346,9 @@ // per 16 bytes (vs. 10 for AES-CTR). Computing eight round functions in // parallel hides the 7-cycle AESNI latency on HSW. Note that the Feistel // XORs are 'free' (included in the second AES instruction). -inline ABSL_RANDOM_INTERNAL_ATTRIBUTE_ALWAYS_INLINE const absl::uint128* -FeistelRound(absl::uint128* ABSL_RANDOM_INTERNAL_RESTRICT state, - const absl::uint128* ABSL_RANDOM_INTERNAL_RESTRICT keys) { +ABSL_ATTRIBUTE_ALWAYS_INLINE inline const absl::uint128* FeistelRound( + absl::uint128* ABSL_RANDOM_INTERNAL_RESTRICT state, + const absl::uint128* ABSL_RANDOM_INTERNAL_RESTRICT keys) { for (size_t branch = 0; branch < RandenTraits::kFeistelBlocks; branch += 4) { const Vector128 s0 = Vector128Load(state + branch); const Vector128 s1 = Vector128Load(state + branch + 1); @@ -387,7 +372,7 @@ // Indistinguishable from ideal by chosen-ciphertext adversaries using less than // 2^64 queries if the round function is a PRF. This is similar to the b=8 case // of Simpira v2, but more efficient than its generic construction for b=16. -inline ABSL_RANDOM_INTERNAL_ATTRIBUTE_ALWAYS_INLINE void Permute( +ABSL_ATTRIBUTE_ALWAYS_INLINE inline void Permute( absl::uint128* state, const absl::uint128* ABSL_RANDOM_INTERNAL_RESTRICT keys) { for (size_t round = 0; round < RandenTraits::kFeistelRounds; ++round) { @@ -397,8 +382,7 @@ } // Enables native loads in the round loop by pre-swapping. -inline ABSL_RANDOM_INTERNAL_ATTRIBUTE_ALWAYS_INLINE void SwapEndian( - absl::uint128* state) { +ABSL_ATTRIBUTE_ALWAYS_INLINE inline void SwapEndian(absl::uint128* state) { #ifdef ABSL_IS_BIG_ENDIAN for (uint32_t block = 0; block < RandenTraits::kFeistelBlocks; ++block) { uint64_t new_lo = absl::little_endian::ToHost64(
diff --git a/absl/status/internal/status_internal.h b/absl/status/internal/status_internal.h index 3bd7b1b..07a5679 100644 --- a/absl/status/internal/status_internal.h +++ b/absl/status/internal/status_internal.h
@@ -112,6 +112,11 @@ absl::FunctionRef<void(absl::string_view, const absl::Cord&)> visitor) const; + // Returns the raw payload list, or nullptr if there are none. Unlike + // `ForEachPayload`, this does not hide `kStackTraceUrl` payloads and does not + // invalidate `type_url` lifetimes in debug builds; callers must handle both. + const Payloads* absl_nullable payloads() const { return payloads_.get(); } + absl::Span<const SourceLocation> GetSourceLocations() const; void AddSourceLocation(absl::SourceLocation loc);
diff --git a/absl/status/status.cc b/absl/status/status.cc index dc7d622..bb03157 100644 --- a/absl/status/status.cc +++ b/absl/status/status.cc
@@ -18,6 +18,7 @@ #include <cstddef> #include <cstdint> #include <memory> +#include <optional> #include <ostream> #include <string> #include <type_traits> @@ -28,6 +29,7 @@ #include "absl/base/no_destructor.h" #include "absl/base/nullability.h" #include "absl/status/internal/status_internal.h" +#include "absl/strings/cord.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/types/source_location.h"
diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index 19692b3..5fe9b68 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel
@@ -1170,6 +1170,7 @@ deps = [ ":string_view", ":strings", + "//absl/algorithm:container", "//absl/base:core_headers", "//absl/base:dynamic_annotations", "//absl/container:btree",
diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index 23a32c9..1461d94 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt
@@ -401,6 +401,7 @@ COPTS ${ABSL_TEST_COPTS} DEPS + absl::algorithm_container absl::btree absl::core_headers absl::dynamic_annotations
diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index 1b60404..f9f576a 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc
@@ -15,6 +15,7 @@ #include "absl/strings/cord.h" #include <algorithm> +#include <array> #include <cassert> #include <cstddef> #include <cstdint> @@ -1203,81 +1204,6 @@ } } -} // namespace - -// A few options how this could be implemented: -// (a) Flatten the Cord and find, i.e. -// haystack.Flatten().find(needle) -// For large 'haystack' (where Cord makes sense to be used), this copies -// the whole 'haystack' and can be slow. -// (b) Use std::search, i.e. -// std::search(haystack.char_begin(), haystack.char_end(), -// needle.begin(), needle.end()) -// This avoids the copy, but compares one byte at a time, and branches a -// lot every time it has to advance. It is also not possible to use -// std::search as is, because CharIterator is only an input iterator, not a -// forward iterator. -// (c) Use string_view::find in each fragment, and specifically handle fragment -// boundaries. -// -// This currently implements option (b). -absl::Cord::CharIterator absl::Cord::FindImpl(CharIterator it, - absl::string_view needle) const { - // Ensure preconditions are met by callers first. - - // Needle must not be empty. - assert(!needle.empty()); - // Haystack must be at least as large as needle. - assert(it.chunk_iterator_.bytes_remaining_ >= needle.size()); - - // Cord is a sequence of chunks. To find `needle` we go chunk by chunk looking - // for the first char of needle, up until we have advanced `N` defined as - // `haystack.size() - needle.size()`. If we find the first char of needle at - // `P` and `P` is less than `N`, we then call `IsSubstringInCordAt` to - // see if this is the needle. If not, we advance to `P + 1` and try again. - while (it.chunk_iterator_.bytes_remaining_ >= needle.size()) { - auto haystack_chunk = Cord::ChunkRemaining(it); - assert(!haystack_chunk.empty()); - // Look for the first char of `needle` in the current chunk. - auto idx = haystack_chunk.find(needle.front()); - if (idx == absl::string_view::npos) { - // No potential match in this chunk, advance past it. - Cord::Advance(&it, haystack_chunk.size()); - continue; - } - // We found the start of a potential match in the chunk. Advance the - // iterator and haystack chunk to the match the position. - Cord::Advance(&it, idx); - // Check if there is enough haystack remaining to actually have a match. - if (it.chunk_iterator_.bytes_remaining_ < needle.size()) { - break; - } - // Check if this is `needle`. - if (IsSubstringInCordAt(it, needle)) { - return it; - } - // No match, increment the iterator for the next attempt. - Cord::Advance(&it, 1); - } - // If we got here, we did not find `needle`. - return char_end(); -} - -absl::Cord::CharIterator absl::Cord::Find(absl::string_view needle) const { - if (needle.empty()) { - return char_begin(); - } - if (needle.size() > size()) { - return char_end(); - } - if (needle.size() == size()) { - return *this == needle ? char_begin() : char_end(); - } - return FindImpl(char_begin(), needle); -} - -namespace { - // Tests whether the sequence of chunks beginning at `haystack` starts with the // sequence of chunks beginning at `needle_begin` and extending to `needle_end`. // @@ -1306,57 +1232,245 @@ // // REQUIRES: remaining `absl::Cord` starting at `position` is greater than or // equal to `needle.size()`. -bool IsSubcordInCordAt(absl::Cord::CharIterator position, - const absl::Cord& needle) { +bool IsSubstringInCordAt(absl::Cord::CharIterator position, + const absl::Cord& needle) { return IsSubcordInCordAt(position, needle.char_begin(), needle.char_end()); } +// Returns the character located `offset` bytes past `it`. +char CharacterAt(absl::Cord::CharIterator it, size_t offset) { + absl::Cord::Advance(&it, offset); + return *it; +} + +inline size_t BytesRemaining(const absl::Cord::CharIterator& it) { + return static_cast<size_t>( + absl::Cord::Distance(it, absl::Cord::CharIterator())); +} + +// Returns whether the sequence of chunks beginning at `chunk` + `idx` starts +// with the first `chunk_rem` bytes of `needle`. +// +// REQUIRES: `chunk.size() - idx >= chunk_rem`. +inline bool IsPrefixMatch(absl::string_view chunk, size_t idx, size_t chunk_rem, + absl::string_view needle) { + return chunk.substr(idx) == needle.substr(0, chunk_rem); +} + +// Returns whether the sequence of chunks beginning at `chunk` + `idx` starts +// with the first `chunk_rem` bytes of `needle`, where `needle` is a Cord. +// +// REQUIRES: `chunk.size() - idx >= chunk_rem` and `needle` is not empty. +inline bool IsPrefixMatch(absl::string_view chunk, size_t idx, size_t chunk_rem, + const absl::Cord& needle) { + auto needle_chunk = absl::Cord::ChunkRemaining(needle.char_begin()); + size_t check_len = std::min(chunk_rem, needle_chunk.size()); + return chunk.substr(idx, check_len) == needle_chunk.substr(0, check_len); +} + +// Initializes the Boyer-Moore-Horspool shift table for a string_view needle +// with a maximum scan length of `kMaxScan`. +inline void InitShift(std::array<uint16_t, 256>& shift, + absl::string_view needle, uint16_t default_shift, + size_t kMaxScan) { + shift.fill(default_shift); + const size_t m = needle.size(); + for (size_t i = kMaxScan; i > 0; --i) { + shift[static_cast<unsigned char>(needle[m - 1 - i])] = + static_cast<uint16_t>(i); + } +} + +// Initializes the Boyer-Moore-Horspool shift table for a Cord needle with a +// maximum scan length of `kMaxScan`. +inline void InitShift(std::array<uint16_t, 256>& shift, + const absl::Cord& needle, uint16_t default_shift, + size_t kMaxScan) { + shift.fill(default_shift); + auto needle_it = needle.char_begin(); + const size_t needle_size = needle.size(); + absl::Cord::Advance(&needle_it, needle_size - 1 - kMaxScan); + for (size_t i = 0; i < kMaxScan; ++i) { + shift[static_cast<unsigned char>(*needle_it)] = + static_cast<uint16_t>(kMaxScan - i); + absl::Cord::Advance(&needle_it, 1); + } +} + +template <typename Needle> +absl::Cord::CharIterator FindBMH(absl::Cord::CharIterator it, + const Needle& needle) { + const size_t needle_size = needle.size(); + const char first_needle = needle[0]; + std::array<uint16_t, 256> shift; + bool shift_initialized = false; + const size_t kMaxScan = std::min(needle_size - 1, size_t{256}); + const uint16_t default_shift = static_cast<uint16_t>(kMaxScan + 1); + const unsigned char last_needle = + static_cast<unsigned char>(needle[needle_size - 1]); + + while (BytesRemaining(it) >= needle_size) { + absl::string_view chunk = absl::Cord::ChunkRemaining(it); + auto idx = chunk.find(first_needle); + if (idx == absl::string_view::npos) { + // No potential match in this chunk, advance past it. + absl::Cord::Advance(&it, chunk.size()); + continue; + } + // Check if there is enough haystack remaining to actually have a match. + if (BytesRemaining(it) - idx < needle_size) { + break; + } + // We found the start of a potential match in the chunk. Advance the + // iterator to the match position. + absl::Cord::Advance(&it, idx); + size_t chunk_rem = chunk.size() - idx; + unsigned char last_hay; + if (chunk_rem >= needle_size) { + last_hay = static_cast<unsigned char>(chunk[idx + needle_size - 1]); + } else { + if (!IsPrefixMatch(chunk, idx, chunk_rem, needle)) { + absl::Cord::Advance(&it, 1); + continue; + } + last_hay = static_cast<unsigned char>(CharacterAt(it, needle_size - 1)); + } + // Check if the rightmost byte of the candidate window (`last_hay`) matches + // the rightmost byte of `needle` before performing a full equality check. + if (last_hay != last_needle) { + if (!shift_initialized) { + InitShift(shift, needle, default_shift, kMaxScan); + shift_initialized = true; + } + // Mismatch on the rightmost byte. Advance candidate position according + // to the Boyer-Moore-Horspool shift table. + absl::Cord::Advance(&it, shift[last_hay]); + continue; + } + // Check if this is `needle`. + if (IsSubstringInCordAt(it, needle)) { + return it; + } + if (!shift_initialized) { + InitShift(shift, needle, default_shift, kMaxScan); + shift_initialized = true; + } + last_hay = last_needle; + // Match check failed; advance candidate position according to the shift + // table. + absl::Cord::Advance(&it, shift[last_hay]); + } + // If we got here, we did not find `needle`. + return absl::Cord::CharIterator(); +} + +absl::Cord::CharIterator FindChar(absl::Cord::CharIterator it, char target) { + while (BytesRemaining(it) > 0) { + absl::string_view chunk = absl::Cord::ChunkRemaining(it); + auto idx = chunk.find(target); + if (idx != absl::string_view::npos) { + // Found target inside the current chunk. Advance the iterator to its + // position. + absl::Cord::Advance(&it, idx); + return it; + } + // Target is not inside this chunk; advance past the entire chunk. + absl::Cord::Advance(&it, chunk.size()); + } + return absl::Cord::CharIterator(); +} + } // namespace -absl::Cord::CharIterator absl::Cord::Find(const absl::Cord& needle) const { +// A few options how this could be implemented: +// (a) Flatten the Cord and find, i.e. +// haystack.Flatten().find(needle) +// For large 'haystack' (where Cord makes sense to be used), this copies +// the whole 'haystack' and can be slow. +// (b) Use std::search, i.e. +// std::search(haystack.char_begin(), haystack.char_end(), +// needle.begin(), needle.end()) +// This avoids the copy, but compares one byte at a time, and branches a +// lot every time it has to advance. It is also not possible to use +// std::search as is, because CharIterator is only an input iterator, not a +// forward iterator. +// (c) Use string_view::find in each fragment, and specifically handle fragment +// boundaries. +// +// This currently implements option (b). +template <typename T> +absl::Cord::CharIterator absl::Cord::FindImpl(CharIterator it, + const T& needle) const { + // Ensure preconditions are met by callers first. + if (needle.empty()) { return char_begin(); } - const auto needle_size = needle.size(); - if (needle_size > size()) { + if (needle.size() > size()) { return char_end(); } - if (needle_size == size()) { + if (needle.size() == size()) { return *this == needle ? char_begin() : char_end(); } - const auto needle_chunk = Cord::ChunkRemaining(needle.char_begin()); - auto haystack_it = char_begin(); - while (true) { - haystack_it = FindImpl(haystack_it, needle_chunk); - if (haystack_it == char_end() || - haystack_it.chunk_iterator_.bytes_remaining_ < needle_size) { - break; - } - // We found the first chunk of `needle` at `haystack_it` but not the entire - // subcord. Advance past the first chunk and check for the remainder. - auto haystack_advanced_it = haystack_it; - auto needle_it = needle.char_begin(); - Cord::Advance(&haystack_advanced_it, needle_chunk.size()); - Cord::Advance(&needle_it, needle_chunk.size()); - if (IsSubcordInCordAt(haystack_advanced_it, needle_it, needle.char_end())) { - return haystack_it; - } - Cord::Advance(&haystack_it, 1); - if (haystack_it.chunk_iterator_.bytes_remaining_ < needle_size) { - break; - } - if (haystack_it.chunk_iterator_.bytes_remaining_ == needle_size) { - // Special case, if there is exactly `needle_size` bytes remaining, the - // subcord is either at `haystack_it` or not at all. - if (IsSubcordInCordAt(haystack_it, needle)) { - return haystack_it; - } - break; - } + + // Needle must not be empty. + assert(!needle.empty()); + // Haystack must be at least as large as needle. + assert(it.chunk_iterator_.bytes_remaining_ >= needle.size()); + + const size_t m = needle.size(); + const char first_needle = needle[0]; + // Fast path for single-character needles. + if (m == 1) { + return FindChar(it, first_needle); } + + // Fast path for long needles. + if (m >= 256) { + return FindBMH(it, needle); + } + + // Cord is a sequence of chunks. To find `needle` we go chunk by chunk looking + // for the first char of needle, up until we have advanced `N` defined as + // `haystack.size() - needle.size()`. If we find the first char of needle at + // `P` and `P` is less than `N`, we then call `IsSubstringInCordAt` to + // see if this is the needle. If not, we advance to `P + 1` and try again. + while (it.chunk_iterator_.bytes_remaining_ >= needle.size()) { + auto haystack_chunk = Cord::ChunkRemaining(it); + assert(!haystack_chunk.empty()); + // Look for the first char of `needle` in the current chunk. + auto idx = haystack_chunk.find(first_needle); + if (idx == absl::string_view::npos) { + // No potential match in this chunk, advance past it. + Cord::Advance(&it, haystack_chunk.size()); + continue; + } + // We found the start of a potential match in the chunk. Advance the + // iterator and haystack chunk to the match the position. + Cord::Advance(&it, idx); + // Check if there is enough haystack remaining to actually have a match. + if (it.chunk_iterator_.bytes_remaining_ < needle.size()) { + break; + } + // Check if this is `needle`. + if (IsSubstringInCordAt(it, needle)) { + return it; + } + // No match, increment the iterator for the next attempt. + Cord::Advance(&it, 1); + } + // If we got here, we did not find `needle`. return char_end(); } +absl::Cord::CharIterator absl::Cord::Find(absl::string_view needle) const { + return FindImpl(char_begin(), needle); +} + +absl::Cord::CharIterator absl::Cord::Find(const absl::Cord& needle) const { + return FindImpl(char_begin(), needle); +} + bool Cord::Contains(absl::string_view rhs) const { return rhs.empty() || Find(rhs) != char_end(); }
diff --git a/absl/strings/cord.h b/absl/strings/cord.h index ef900f8..8fe8e9f 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h
@@ -1120,7 +1120,8 @@ void SetCrcCordState(crc_internal::CrcCordState state); const crc_internal::CrcCordState* absl_nullable MaybeGetCrcCordState() const; - CharIterator FindImpl(CharIterator it, absl::string_view needle) const; + template <typename T> + CharIterator FindImpl(CharIterator it, const T& needle) const; void CopyToArrayImpl(char* absl_nonnull dst) const; };
diff --git a/absl/strings/cord_test.cc b/absl/strings/cord_test.cc index 1ae1f63..aed23ef 100644 --- a/absl/strings/cord_test.cc +++ b/absl/strings/cord_test.cc
@@ -588,6 +588,482 @@ std::next(fragmented_haystack.char_begin(), 5)); } +TEST_P(CordTest, FindLinearScaling) { + // Verifies that finding a mismatching string in an adversarial haystack + // scales sub-linearly/linearly via Boyer-Moore-Horspool without quadratic + // DoS. This uses a 1MB haystack and a slightly smaller needle that should + // timeout for quadratic behavior. + constexpr size_t kSize = 256 * 1024; + std::string haystack_str(kSize, 'a'); + absl::Cord haystack = absl::MakeFragmentedCord( + {haystack_str.substr(0, kSize / 2), haystack_str.substr(kSize / 2)}); + + std::string needle_str(kSize / 2 - 1, 'a'); + needle_str.push_back('b'); + absl::Cord needle_cord = + absl::MakeFragmentedCord({needle_str.substr(0, needle_str.size() / 2), + needle_str.substr(needle_str.size() / 2)}); + + EXPECT_EQ(haystack.Find(needle_str), haystack.char_end()); + EXPECT_FALSE(haystack.Contains(needle_str)); + EXPECT_EQ(haystack.Find(needle_cord), haystack.char_end()); + EXPECT_FALSE(haystack.Contains(needle_cord)); +} + +TEST_P(CordTest, FindBMHCrossChunkMatch) { + // Test finding a large needle (M >= 256) in a fragmented Cord haystack where + // the match spans across chunk boundaries. + const size_t kChunkSize = 512; + const size_t kNeedleSize = 300; + + std::string needle_str(kNeedleSize, 'x'); + for (size_t i = 0; i < kNeedleSize; ++i) { + needle_str[i] = static_cast<char>('A' + (i % 26)); + } + + std::vector<std::string> chunks; + chunks.push_back(std::string(kChunkSize, '.')); + chunks.push_back(std::string(kChunkSize, '.')); + chunks.push_back(std::string(kChunkSize, '.')); + absl::Cord haystack = absl::MakeFragmentedCord(chunks); + + std::string flat_haystack(haystack); + flat_haystack.replace(400, kNeedleSize, needle_str); + + std::vector<std::string> modified_chunks = { + flat_haystack.substr(0, kChunkSize), + flat_haystack.substr(kChunkSize, kChunkSize), + flat_haystack.substr(2 * kChunkSize, kChunkSize)}; + haystack = absl::MakeFragmentedCord(modified_chunks); + + // Test string_view needle + auto it = haystack.Find(needle_str); + EXPECT_NE(it, haystack.char_end()); + EXPECT_EQ(std::distance(haystack.char_begin(), it), 400); + EXPECT_TRUE(haystack.Contains(needle_str)); + + // Test Cord needle + absl::Cord needle_cord = absl::MakeFragmentedCord( + {needle_str.substr(0, 150), needle_str.substr(150)}); + auto it_cord = haystack.Find(needle_cord); + EXPECT_NE(it_cord, haystack.char_end()); + EXPECT_EQ(std::distance(haystack.char_begin(), it_cord), 400); + EXPECT_TRUE(haystack.Contains(needle_cord)); +} + +TEST_P(CordTest, FindBMHChunkBoundaryPrefixMismatch) { + // Test BMH search when the first character of needle matches near the end of + // a chunk (chunk_rem < M), but the rest of the chunk prefix mismatches. + // Exercises the `chunk.substr(idx) != needle.substr(0, chunk_rem)` branch in + // FindImplBMH and FindCordBMH. + const size_t kNeedleSize = 300; + std::string needle_str(kNeedleSize, 'A'); + needle_str[0] = 'Z'; + needle_str[1] = 'B'; + + std::string chunk0(512, '.'); + chunk0[500] = 'Z'; + std::string chunk1(512, '.'); + + absl::Cord haystack = absl::MakeFragmentedCord({chunk0, chunk1}); + + EXPECT_EQ(haystack.Find(needle_str), haystack.char_end()); + EXPECT_FALSE(haystack.Contains(needle_str)); + + absl::Cord needle_cord(needle_str); + EXPECT_EQ(haystack.Find(needle_cord), haystack.char_end()); + EXPECT_FALSE(haystack.Contains(needle_cord)); +} + +TEST_P(CordTest, FindBMHCrossChunkLastByteMismatch) { + // Test BMH search when the first character and chunk prefix match near the + // end of a chunk, but the last byte across the chunk boundary mismatches. + // Exercises `last_hay != last_needle` after CharacterAt(it, m - 1) in + // FindImplBMH and FindCordBMH. + const size_t kNeedleSize = 300; + std::string needle_str(kNeedleSize, 'A'); + needle_str[0] = 'Z'; + needle_str.back() = 'Y'; + + std::string chunk0(512, '.'); + chunk0.replace(500, 12, 12, 'A'); + chunk0[500] = 'Z'; + + std::string chunk1(512, '.'); + chunk1[287] = 'W'; // Mismatches needle_str.back() ('Y') + + absl::Cord haystack = absl::MakeFragmentedCord({chunk0, chunk1}); + + EXPECT_EQ(haystack.Find(needle_str), haystack.char_end()); + EXPECT_FALSE(haystack.Contains(needle_str)); + + absl::Cord needle_cord(needle_str); + EXPECT_EQ(haystack.Find(needle_cord), haystack.char_end()); + EXPECT_FALSE(haystack.Contains(needle_cord)); +} + +TEST_P(CordTest, FindBMHCrossChunkInteriorMismatch) { + // Test BMH search when first and last characters match across chunk + // boundaries, but an interior character inside the second chunk mismatches. + // Exercises IsSubstringInCordAt / IsSubcordInCordAt returning false across + // chunks. + const size_t kNeedleSize = 300; + std::string needle_str(kNeedleSize, 'A'); + needle_str[0] = 'Z'; + needle_str.back() = 'Y'; + + std::string chunk0(512, '.'); + chunk0.replace(500, 12, 12, 'A'); + chunk0[500] = 'Z'; + + std::string chunk1(512, 'A'); + chunk1[287] = 'Y'; // Last char matches + chunk1[100] = 'W'; // Interior byte mismatches + + absl::Cord haystack = absl::MakeFragmentedCord({chunk0, chunk1}); + + EXPECT_EQ(haystack.Find(needle_str), haystack.char_end()); + EXPECT_FALSE(haystack.Contains(needle_str)); + + // Verify valid match when interior byte is restored + chunk1[100] = 'A'; + haystack = absl::MakeFragmentedCord({chunk0, chunk1}); + auto it = haystack.Find(needle_str); + EXPECT_NE(it, haystack.char_end()); + EXPECT_EQ(std::distance(haystack.char_begin(), it), 500); + EXPECT_TRUE(haystack.Contains(needle_str)); +} + +TEST_P(CordTest, FindBMHNearEndRemainingBytesBreak) { + // Test BMH search when the first character of needle matches near the end of + // haystack, but remaining bytes are less than needle size (bytes_remaining < + // M). Exercises the `idx + m > bytes_remaining` break condition in + // FindImplBMH and FindCordBMH. + const size_t kNeedleSize = 300; + std::string needle_str(kNeedleSize, 'A'); + needle_str[0] = 'Z'; + + std::string chunk0(500, '.'); + std::string chunk1(500, '.'); + chunk1[300] = 'Z'; // Offset 800 in haystack, remaining bytes = 200 (< 300) + + absl::Cord haystack = absl::MakeFragmentedCord({chunk0, chunk1}); + + EXPECT_EQ(haystack.Find(needle_str), haystack.char_end()); + EXPECT_FALSE(haystack.Contains(needle_str)); + + absl::Cord needle_cord = absl::MakeFragmentedCord( + {needle_str.substr(0, 150), needle_str.substr(150)}); + EXPECT_EQ(haystack.Find(needle_cord), haystack.char_end()); + EXPECT_FALSE(haystack.Contains(needle_cord)); +} + +TEST_P(CordTest, FindCordBMHCrossChunkInteriorMismatch) { + // Test FindCordBMH when needle is a fragmented Cord (M >= 256), last byte + // matches, but IsSubcordInCordAt returns false due to an interior mismatch + // across chunks. Exercises lazy shift table initialization after + // IsSubcordInCordAt failure in FindCordBMH. + const size_t kNeedleSize = 300; + std::string needle_str(kNeedleSize, 'A'); + needle_str[0] = 'Z'; + needle_str.back() = 'Y'; + + std::string chunk0(512, '.'); + chunk0.replace(500, 12, 12, 'A'); + chunk0[500] = 'Z'; + + std::string chunk1(512, 'A'); + chunk1[287] = 'Y'; // Last char matches + chunk1[150] = 'W'; // Interior mismatch in chunk 1 + + absl::Cord haystack = absl::MakeFragmentedCord({chunk0, chunk1}); + absl::Cord needle_cord = absl::MakeFragmentedCord( + {needle_str.substr(0, 150), needle_str.substr(150)}); + + EXPECT_EQ(haystack.Find(needle_cord), haystack.char_end()); + EXPECT_FALSE(haystack.Contains(needle_cord)); + + // Restore interior byte and verify match + chunk1[150] = 'A'; + haystack = absl::MakeFragmentedCord({chunk0, chunk1}); + auto it = haystack.Find(needle_cord); + EXPECT_NE(it, haystack.char_end()); + EXPECT_EQ(std::distance(haystack.char_begin(), it), 500); + EXPECT_TRUE(haystack.Contains(needle_cord)); +} + +TEST_P(CordTest, FindBMHNonAsciiBytes) { + // Test BMH search with high-bit non-ASCII bytes (values > 127) in Cord + // haystack and needle across chunk boundaries. + const size_t kNeedleSize = 300; + std::string needle_str(kNeedleSize, '\0'); + for (size_t i = 0; i < kNeedleSize; ++i) { + needle_str[i] = static_cast<char>(128 + (i % 128)); + } + + std::string chunk0(512, '\x7F'); + std::string chunk1(512, '\x7F'); + + // Place needle straddling chunk 0 and chunk 1 at offset 400 + std::string flat_haystack = chunk0 + chunk1; + flat_haystack.replace(400, kNeedleSize, needle_str); + + absl::Cord haystack = absl::MakeFragmentedCord( + {flat_haystack.substr(0, 512), flat_haystack.substr(512)}); + + auto it = haystack.Find(needle_str); + EXPECT_NE(it, haystack.char_end()); + EXPECT_EQ(std::distance(haystack.char_begin(), it), 400); + EXPECT_TRUE(haystack.Contains(needle_str)); + + absl::Cord needle_cord = absl::MakeFragmentedCord( + {needle_str.substr(0, 150), needle_str.substr(150)}); + auto it_cord = haystack.Find(needle_cord); + EXPECT_NE(it_cord, haystack.char_end()); + EXPECT_EQ(std::distance(haystack.char_begin(), it_cord), 400); + EXPECT_TRUE(haystack.Contains(needle_cord)); +} + +TEST_P(CordTest, FindSingleCharNeedle) { + // Test single-character string_view and Cord needles (M = 1) in fragmented + // Cord, exercising the FindChar fast path. + absl::Cord haystack = absl::MakeFragmentedCord({"hello", " ", "world"}); + + // string_view needle + auto it_sv = haystack.Find("w"); + EXPECT_NE(it_sv, haystack.char_end()); + EXPECT_EQ(std::distance(haystack.char_begin(), it_sv), 6); + EXPECT_TRUE(haystack.Contains("w")); + + EXPECT_EQ(haystack.Find("z"), haystack.char_end()); + EXPECT_FALSE(haystack.Contains("z")); + + // Cord needle + auto it_cord = haystack.Find(absl::Cord("w")); + EXPECT_NE(it_cord, haystack.char_end()); + EXPECT_EQ(std::distance(haystack.char_begin(), it_cord), 6); + EXPECT_TRUE(haystack.Contains(absl::Cord("w"))); + + EXPECT_EQ(haystack.Find(absl::Cord("z")), haystack.char_end()); + EXPECT_FALSE(haystack.Contains(absl::Cord("z"))); +} + +TEST_P(CordTest, FindStringViewNeedleSizeLargerThanHaystack) { + absl::Cord haystack("hello"); + EXPECT_EQ(haystack.Find("hello world"), haystack.char_end()); + EXPECT_FALSE(haystack.Contains("hello world")); +} + +TEST_P(CordTest, FindStringViewNeedleSizeEqualToHaystack) { + absl::Cord haystack("hello world"); + EXPECT_EQ(haystack.Find("hello world"), haystack.char_begin()); + EXPECT_TRUE(haystack.Contains("hello world")); + + EXPECT_EQ(haystack.Find("hello WOrld"), haystack.char_end()); + EXPECT_FALSE(haystack.Contains("hello WOrld")); +} + +TEST_P(CordTest, FindCordNeedleSizeEqualToHaystack) { + absl::Cord haystack("hello world"); + absl::Cord needle_match("hello world"); + absl::Cord needle_mismatch("hello WOrld"); + + EXPECT_EQ(haystack.Find(needle_match), haystack.char_begin()); + EXPECT_TRUE(haystack.Contains(needle_match)); + + EXPECT_EQ(haystack.Find(needle_mismatch), haystack.char_end()); + EXPECT_FALSE(haystack.Contains(needle_mismatch)); +} + +TEST_P(CordTest, FindImplNearEndRemainingBytesBreak) { + std::string haystack_str(50, '.'); + haystack_str[47] = 'a'; + absl::Cord haystack = absl::MakeFragmentedCord( + {haystack_str.substr(0, 25), haystack_str.substr(25)}); + + EXPECT_EQ(haystack.Find("abcde"), haystack.char_end()); + EXPECT_FALSE(haystack.Contains("abcde")); +} + +TEST_P(CordTest, FindCordNearEndRemainingBytesBreak) { + std::string haystack_str(50, '.'); + haystack_str[47] = 'a'; + absl::Cord haystack = absl::MakeFragmentedCord( + {haystack_str.substr(0, 25), haystack_str.substr(25)}); + absl::Cord needle_cord("abcde"); + + EXPECT_EQ(haystack.Find(needle_cord), haystack.char_end()); + EXPECT_FALSE(haystack.Contains(needle_cord)); +} + +TEST_P(CordTest, FindImplBMHSingleChunkInteriorMismatch) { + const size_t kNeedleSize = 300; + std::string needle_str(kNeedleSize, 'A'); + needle_str[0] = 'Z'; + needle_str.back() = 'Y'; + + std::string haystack_str(1000, 'A'); + haystack_str.replace(100, kNeedleSize, needle_str); + haystack_str[100 + 10] = 'W'; + + absl::Cord haystack(haystack_str); + EXPECT_EQ(haystack.Find(needle_str), haystack.char_end()); + EXPECT_FALSE(haystack.Contains(needle_str)); +} + +TEST_P(CordTest, FindCordBMHSingleChunkInteriorMismatch) { + const size_t kNeedleSize = 300; + std::string needle_str(kNeedleSize, 'A'); + needle_str[0] = 'Z'; + needle_str.back() = 'Y'; + + std::string haystack_str(1000, 'A'); + haystack_str.replace(100, kNeedleSize, needle_str); + haystack_str[100 + 10] = 'W'; + + absl::Cord haystack(haystack_str); + absl::Cord needle_cord(needle_str); + + EXPECT_EQ(haystack.Find(needle_cord), haystack.char_end()); + EXPECT_FALSE(haystack.Contains(needle_cord)); +} + +TEST_P(CordTest, FindBMHDuplicateCharsInNeedle) { + // Test BMH shift table calculation when needle contains duplicate characters + // at different distances from the end. Verifies that the smallest shift + // distance (rightmost occurrence) is preserved and prevents over-shifting. + const size_t kNeedleSize = 300; + std::string needle_str(kNeedleSize, 'A'); + needle_str[0] = 'Z'; + needle_str[kNeedleSize - 50] = 'Q'; + needle_str[kNeedleSize - 3] = 'Q'; + needle_str.back() = 'Y'; + + std::string haystack_str(1000, '.'); + haystack_str.replace(202, kNeedleSize, needle_str); + haystack_str[200] = 'Z'; + haystack_str[499] = 'Q'; + + absl::Cord haystack = absl::MakeFragmentedCord( + {haystack_str.substr(0, 500), haystack_str.substr(500)}); + + auto it = haystack.Find(needle_str); + EXPECT_NE(it, haystack.char_end()); + EXPECT_EQ(std::distance(haystack.char_begin(), it), 202); + EXPECT_TRUE(haystack.Contains(needle_str)); + + absl::Cord needle_cord = absl::MakeFragmentedCord( + {needle_str.substr(0, 150), needle_str.substr(150)}); + auto it_cord = haystack.Find(needle_cord); + EXPECT_NE(it_cord, haystack.char_end()); + EXPECT_EQ(std::distance(haystack.char_begin(), it_cord), 202); + EXPECT_TRUE(haystack.Contains(needle_cord)); +} + +TEST_P(CordTest, FindBMHBoundaryThreshold256) { + // Test BMH search at the exact boundary size of 256 bytes for string_view + // and Cord needles. Verifies correct shift table boundary bounds. + const size_t kNeedleSize = 256; + std::string needle_str(kNeedleSize, 'B'); + needle_str[0] = 'X'; + needle_str[kNeedleSize - 2] = 'Y'; + needle_str.back() = 'Z'; + + std::string haystack_str(1000, '.'); + haystack_str.replace(500, kNeedleSize, needle_str); + + absl::Cord haystack = absl::MakeFragmentedCord( + {haystack_str.substr(0, 400), haystack_str.substr(400)}); + + auto it = haystack.Find(needle_str); + EXPECT_NE(it, haystack.char_end()); + EXPECT_EQ(std::distance(haystack.char_begin(), it), 500); + EXPECT_TRUE(haystack.Contains(needle_str)); + + absl::Cord needle_cord = absl::MakeFragmentedCord( + {needle_str.substr(0, 100), needle_str.substr(100)}); + auto it_cord = haystack.Find(needle_cord); + EXPECT_NE(it_cord, haystack.char_end()); + EXPECT_EQ(std::distance(haystack.char_begin(), it_cord), 500); + EXPECT_TRUE(haystack.Contains(needle_cord)); +} + +TEST_P(CordTest, FindBMHMatchAtVeryEndOfCord) { + // Test BMH search when the needle is located at the very end of haystack + // (idx + m == bytes_remaining_). Verifies that boundary check uses > instead + // of >=. + const size_t kNeedleSize = 300; + std::string needle_str(kNeedleSize, 'M'); + needle_str[0] = 'S'; + needle_str.back() = 'E'; + + std::string haystack_str = std::string(500, '.') + needle_str; + absl::Cord haystack = absl::MakeFragmentedCord( + {haystack_str.substr(0, 400), haystack_str.substr(400)}); + + auto it = haystack.Find(needle_str); + EXPECT_NE(it, haystack.char_end()); + EXPECT_EQ(std::distance(haystack.char_begin(), it), 500); + EXPECT_TRUE(haystack.Contains(needle_str)); + + absl::Cord needle_cord = absl::MakeFragmentedCord( + {needle_str.substr(0, 150), needle_str.substr(150)}); + auto it_cord = haystack.Find(needle_cord); + EXPECT_NE(it_cord, haystack.char_end()); + EXPECT_EQ(std::distance(haystack.char_begin(), it_cord), 500); + EXPECT_TRUE(haystack.Contains(needle_cord)); +} + +TEST_P(CordTest, FindBMHMatchInLaterChunkAfterEmptyChunk) { + // Test BMH search when the first chunk of a fragmented Cord haystack does NOT + // contain needle[0]. Verifies that idx == npos advances past the entire first + // chunk and continues searching subsequent chunks. + const size_t kNeedleSize = 300; + std::string needle_str(kNeedleSize, 'A'); + needle_str[0] = 'Z'; + + std::string chunk0(512, '.'); + std::string chunk1 = needle_str + std::string(212, '.'); + + absl::Cord haystack = absl::MakeFragmentedCord({chunk0, chunk1}); + + auto it = haystack.Find(needle_str); + EXPECT_NE(it, haystack.char_end()); + EXPECT_EQ(std::distance(haystack.char_begin(), it), 512); + EXPECT_TRUE(haystack.Contains(needle_str)); + + absl::Cord needle_cord = absl::MakeFragmentedCord( + {needle_str.substr(0, 150), needle_str.substr(150)}); + auto it_cord = haystack.Find(needle_cord); + EXPECT_NE(it_cord, haystack.char_end()); + EXPECT_EQ(std::distance(haystack.char_begin(), it_cord), 512); + EXPECT_TRUE(haystack.Contains(needle_cord)); +} + +TEST_P(CordTest, FindShortNeedleConsecutiveCandidates) { + // Test short needle (2 <= M < 256) when consecutive positions in haystack + // start with needle.front(), where position 10 mismatches but position 11 + // matches. Verifies that failing IsSubstringInCordAt advances by exactly 1. + std::string needle = "ABZ"; + std::string haystack_str(100, '.'); + haystack_str.replace(10, 3, "ABW"); + haystack_str.replace(11, 3, "ABZ"); + + absl::Cord haystack = absl::MakeFragmentedCord( + {haystack_str.substr(0, 50), haystack_str.substr(50)}); + + auto it = haystack.Find(needle); + EXPECT_NE(it, haystack.char_end()); + EXPECT_EQ(std::distance(haystack.char_begin(), it), 11); + EXPECT_TRUE(haystack.Contains(needle)); + + absl::Cord needle_cord(needle); + auto it_cord = haystack.Find(needle_cord); + EXPECT_NE(it_cord, haystack.char_end()); + EXPECT_EQ(std::distance(haystack.char_begin(), it_cord), 11); + EXPECT_TRUE(haystack.Contains(needle_cord)); +} + TEST_P(CordTest, Subcord) { RandomEngine rng(GTEST_FLAG_GET(random_seed)); const std::string s = RandomLowercaseString(&rng, 1024);
diff --git a/absl/strings/internal/generic_printer_test.cc b/absl/strings/internal/generic_printer_test.cc index e7ecc09..47a11db 100644 --- a/absl/strings/internal/generic_printer_test.cc +++ b/absl/strings/internal/generic_printer_test.cc
@@ -330,9 +330,9 @@ TEST(GenericPrinterTest, StreamAdapter) { std::stringstream ss; static_assert( - std::is_same<std::remove_reference_t<decltype(ss << GenericPrint())>, - internal_generic_printer::GenericPrintStreamAdapter::Impl< - std::stringstream>>::value, + std::is_same_v<std::remove_reference_t<decltype(ss << GenericPrint())>, + internal_generic_printer::GenericPrintStreamAdapter::Impl< + std::stringstream>>, "expected ostream << GenericPrint() to yield adapter impl"); ss << GenericPrint() << "again, " << "back-up, " << "cue, "
diff --git a/absl/strings/internal/str_format/bind.h b/absl/strings/internal/str_format/bind.h index 1c87e1e..b5a312e 100644 --- a/absl/strings/internal/str_format/bind.h +++ b/absl/strings/internal/str_format/bind.h
@@ -22,6 +22,7 @@ #include <utility> #include "absl/base/config.h" +#include "absl/base/macros.h" #include "absl/container/inlined_vector.h" #include "absl/strings/internal/str_format/arg.h" #include "absl/strings/internal/str_format/checker.h" @@ -127,23 +128,17 @@ #ifdef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER // Honeypot overload for when the string is not constexpr. - // We use the 'unavailable' attribute to give a better compiler error than - // just 'method is deleted'. FormatSpecTemplate(...) // NOLINT - __attribute__((unavailable("Format string is not constexpr."))); + ABSL_BAD_CALL_IF(true, "Format string is not constexpr."); // Honeypot overload for when the format is constexpr and invalid. - // We use the 'unavailable' attribute to give a better compiler error than - // just 'method is deleted'. // To avoid checking the format twice, we just check that the format is // constexpr. If it is valid, then the overload below will kick in. // We add the template here to make this overload have lower priority. template <typename = void> FormatSpecTemplate(const char* s) // NOLINT - __attribute__(( - enable_if(str_format_internal::EnsureConstexpr(s), "constexpr trap"), - unavailable( - "Format specified does not match the arguments passed."))); + ABSL_BAD_CALL_IF(str_format_internal::EnsureConstexpr(s), + "Format specified does not match the arguments passed."); template <typename T = void> FormatSpecTemplate(string_view s) // NOLINT
diff --git a/absl/strings/internal/str_format/convert_test.cc b/absl/strings/internal/str_format/convert_test.cc index fc7d9ba..16fcaa9 100644 --- a/absl/strings/internal/str_format/convert_test.cc +++ b/absl/strings/internal/str_format/convert_test.cc
@@ -213,7 +213,7 @@ delete[] buf; } -void StrAppend(std::string *, const char *, ...) ABSL_PRINTF_ATTRIBUTE(2, 3); +ABSL_PRINTF_ATTRIBUTE(2, 3) void StrAppend(std::string*, const char*, ...); void StrAppend(std::string *out, const char *format, ...) { va_list ap; va_start(ap, format); @@ -221,7 +221,7 @@ va_end(ap); } -std::string StrPrint(const char *, ...) ABSL_PRINTF_ATTRIBUTE(1, 2); +ABSL_PRINTF_ATTRIBUTE(1, 2) std::string StrPrint(const char*, ...); std::string StrPrint(const char *format, ...) { va_list ap; va_start(ap, format);
diff --git a/absl/strings/str_split.h b/absl/strings/str_split.h index e86b29a..dc32af1 100644 --- a/absl/strings/str_split.h +++ b/absl/strings/str_split.h
@@ -96,7 +96,7 @@ // function: // // struct SimpleDelimiter { -// const char c_; +// char c_; // explicit SimpleDelimiter(char c) : c_(c) {} // absl::string_view Find(absl::string_view text, size_t pos) { // auto found = text.find(c_, pos); @@ -206,7 +206,7 @@ absl::string_view Find(absl::string_view text, size_t pos) const; private: - const std::string delimiters_; + std::string delimiters_; }; // ByLength @@ -237,7 +237,7 @@ absl::string_view Find(absl::string_view text, size_t pos) const; private: - const ptrdiff_t length_; + ptrdiff_t length_; }; namespace strings_internal { @@ -291,7 +291,7 @@ private: Delimiter delimiter_; - const int limit_; + int limit_; int count_; };
diff --git a/absl/strings/str_split_test.cc b/absl/strings/str_split_test.cc index 8155311..7487e00 100644 --- a/absl/strings/str_split_test.cc +++ b/absl/strings/str_split_test.cc
@@ -14,6 +14,7 @@ #include "absl/strings/str_split.h" +#include <algorithm> #include <array> #include <cstddef> #include <cstdint> @@ -32,6 +33,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/algorithm/container.h" #include "absl/base/macros.h" #include "absl/container/btree_map.h" #include "absl/container/btree_set.h" @@ -1036,6 +1038,36 @@ EXPECT_FALSE(IsFoundAt("abcd", four_char_delim, 0)); } +TEST(Split, ByAnyCharWorksWithRepeatedFind) { + auto splitter = absl::StrSplit("a,b;a", absl::ByAnyChar(",;")); + auto it = absl::c_find(splitter, "a"); + ASSERT_NE(it, splitter.end()); + // Search on from the next piece, which assigns the iterator. + it = std::find(std::next(it), splitter.end(), "a"); + ASSERT_NE(it, splitter.end()); + EXPECT_EQ(std::next(it), splitter.end()); +} + +TEST(Split, ByLengthWorksWithRepeatedFind) { + auto splitter = absl::StrSplit("aba", absl::ByLength(1)); + auto it = absl::c_find(splitter, "a"); + ASSERT_NE(it, splitter.end()); + // Search on from the next piece, which assigns the iterator. + it = std::find(std::next(it), splitter.end(), "a"); + ASSERT_NE(it, splitter.end()); + EXPECT_EQ(std::next(it), splitter.end()); +} + +TEST(Split, MaxSplitsWorksWithRepeatedFind) { + auto splitter = absl::StrSplit("a,a,b,c", absl::MaxSplits(',', 2)); + auto it = absl::c_find(splitter, "a"); + ASSERT_NE(it, splitter.end()); + // Search on from the next piece, which assigns the iterator. + it = std::find(std::next(it), splitter.end(), "a"); + ASSERT_NE(it, splitter.end()); + EXPECT_EQ(*std::next(it), "b,c"); +} + TEST(Split, WorksWithLargeStrings) { #if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ defined(ABSL_HAVE_MEMORY_SANITIZER) || defined(ABSL_HAVE_THREAD_SANITIZER)
diff --git a/absl/synchronization/internal/graphcycles.cc b/absl/synchronization/internal/graphcycles.cc index 25b51ff..7079081 100644 --- a/absl/synchronization/internal/graphcycles.cc +++ b/absl/synchronization/internal/graphcycles.cc
@@ -27,6 +27,9 @@ // implies rank[x] < rank[y]. // (2) When a new edge (x->y) is inserted, do nothing if rank[x] < rank[y]. // (3) Otherwise: adjust ranks in the neighborhood of x and y. +// +// Unlike in the paper, ranks are spaced apart, which often lets step (3) move +// just x or just y (see "Rank allocation" below). #include "absl/base/internal/low_level_alloc.h" // IWYU pragma: keep // This file is a no-op if the required LowLevelAlloc support is missing. @@ -282,7 +285,9 @@ } struct Node { - int32_t rank; // rank number assigned by Pearce-Kelly algorithm + int64_t rank; // Unique rank (see "Rank allocation") + int32_t prev_slot; // Slot with the next lower rank, or -1 if none + int32_t next_slot; // Slot with the next higher rank, or -1 if none uint32_t version; // Current version number int32_t next_hash; // Next entry in hash table bool visited; // Temporary marker used by depth-first-search @@ -348,17 +353,53 @@ } // namespace +// Rank allocation +// +// Ranks are unique int64_t values. All slots, including free slots, are kept in +// a doubly linked list sorted by rank (Node::prev_slot and Node::next_slot, +// from Rep::lowest_slot to Rep::highest_slot). Free slots keep their rank and +// their place in the list, which are reused along with the slot. New slots get +// ranks kRankStride above the highest rank. +// +// When a new edge x->y violates the rank order, InsertEdge first tries to move +// just x below y, or just y above x (see TryMoveXBelowY()): either to the +// midpoint between two ranks that are adjacent in the list, or kRankStride +// beyond the lowest or highest rank. No other slot can have such a rank, so +// ranks stay unique. +// +// kRankStride leaves room for about 32 moves, one after another, into the gap +// between two adjacent new slots. When a gap has no room left, Respace() first +// spreads out the ranks around it. Ranks beyond the lowest and highest ones +// stay within [kMinRank, kMaxRank], which leaves room for about 2^30 moves at +// each end before all slots are respaced. +static constexpr int64_t kRankStride = int64_t{1} << 32; +static constexpr int64_t kMinRank = -(int64_t{1} << 62); +static constexpr int64_t kMaxRank = int64_t{1} << 62; +// Respace() spreads out ranks until they can be at least this far apart. +static constexpr uint64_t kMinRespacedGap = uint64_t{1} << 16; +// Maximum number of in-neighbors of x (or out-neighbors of y) that +// TryMoveXBelowY() (or TryMoveYAboveX()) will examine. +static constexpr int kMaxSingleNodeDegree = 16; + struct GraphCycles::Rep { Vec<Node*> nodes_; Vec<int32_t> free_nodes_; // Indices for unused entries in nodes_ PointerMap ptrmap_; + int32_t lowest_slot = -1; // Slot with the lowest rank, or -1 if none + int32_t highest_slot = -1; // Slot with the highest rank, or -1 if none + int64_t rank_stride = kRankStride; // See TestOnlySetRankStride() // Temporary state. Vec<int32_t> deltaf_; // Results of forward DFS Vec<int32_t> deltab_; // Results of backward DFS Vec<int32_t> list_; // All nodes to reprocess - Vec<int32_t> merged_; // Rank values to assign to list_ entries Vec<int32_t> stack_; // Emulates recursion stack for depth-first searches + // Used by Reorder(): the slots to reorder, sorted by their current ranks, and + // the rank, prev_slot and next_slot of each. + Vec<int32_t> sorted_slots; + Vec<int64_t> sorted_ranks; + Vec<int32_t> old_prev_slots; + Vec<int32_t> old_next_slots; Rep() : ptrmap_(&nodes_) {} }; @@ -368,6 +409,119 @@ return (n->version == NodeVersion(id)) ? n : nullptr; } +static Node* NodeAt(GraphCycles::Rep* r, int32_t slot) { + return r->nodes_[static_cast<uint32_t>(slot)]; +} + +// Makes next_slot follow prev_slot in the rank list. prev_slot == -1 makes +// next_slot the lowest slot, and next_slot == -1 makes prev_slot the highest. +static void Link(GraphCycles::Rep* r, int32_t prev_slot, int32_t next_slot) { + if (prev_slot == -1) { + r->lowest_slot = next_slot; + } else { + NodeAt(r, prev_slot)->next_slot = next_slot; + } + if (next_slot == -1) { + r->highest_slot = prev_slot; + } else { + NodeAt(r, next_slot)->prev_slot = prev_slot; + } +} + +// Removes slot from the rank list. +static void Unlink(GraphCycles::Rep* r, int32_t slot) { + Link(r, NodeAt(r, slot)->prev_slot, NodeAt(r, slot)->next_slot); +} + +// Returns upper - lower, for lower <= upper, without overflow. +static uint64_t RankDistance(int64_t lower, int64_t upper) { + return static_cast<uint64_t>(upper) - static_cast<uint64_t>(lower); +} + +// Gives the run of adjacent slots from first_slot to last_slot in the rank list +// evenly spaced ranks, keeping their order. First grows the run on both sides +// until its slots can be at least kMinRespacedGap apart between the ranks just +// outside it (kMinRank and kMaxRank beyond the ends of the list), or until it +// covers the whole list. The whole list is spaced across +// [kMinRank / 2, kMaxRank / 2], which leaves room beyond both of its ends. +static void Respace(GraphCycles::Rep* r, int32_t first_slot, + int32_t last_slot) { + uint64_t count = 1; + for (int32_t slot = first_slot; slot != last_slot; + slot = NodeAt(r, slot)->next_slot) { + count++; + } + int64_t lower; + int64_t upper; + while (true) { + const int32_t before_slot = NodeAt(r, first_slot)->prev_slot; + const int32_t after_slot = NodeAt(r, last_slot)->next_slot; + if (before_slot == -1 && after_slot == -1) { + lower = kMinRank / 2; + upper = kMaxRank / 2; + break; + } + lower = before_slot == -1 ? kMinRank : NodeAt(r, before_slot)->rank; + upper = after_slot == -1 ? kMaxRank : NodeAt(r, after_slot)->rank; + if (RankDistance(lower, upper) / (count + 1) >= kMinRespacedGap) break; + // Double the size of the run, as far as the ends of the list allow. + for (const uint64_t target = 2 * count; count < target;) { + const int32_t prev_slot = NodeAt(r, first_slot)->prev_slot; + const int32_t next_slot = NodeAt(r, last_slot)->next_slot; + if (prev_slot == -1 && next_slot == -1) break; + if (prev_slot != -1) { + first_slot = prev_slot; + count++; + } + if (next_slot != -1) { + last_slot = next_slot; + count++; + } + } + } + const uint64_t step = RankDistance(lower, upper) / (count + 1); + int64_t rank = lower; + for (int32_t slot = first_slot;; slot = NodeAt(r, slot)->next_slot) { + rank += static_cast<int64_t>(step); + NodeAt(r, slot)->rank = rank; + if (slot == last_slot) return; + } +} + +// Links slot, which is not in the rank list, between prev_slot and next_slot, +// which are adjacent in it (-1 beyond its ends), with a rank between theirs: +// rank_stride (kRankStride by default) beyond prev_slot or next_slot at the +// ends of the list, and halfway between them otherwise. If there is no room +// for such a rank, respaces ranks first. +static void LinkBetween(GraphCycles::Rep* r, int32_t slot, int32_t prev_slot, + int32_t next_slot) { + int64_t rank; + if (prev_slot == -1 && next_slot == -1) { + rank = 0; + } else if (prev_slot == -1) { + if (NodeAt(r, next_slot)->rank < kMinRank + r->rank_stride) { + Respace(r, r->lowest_slot, r->highest_slot); + } + rank = NodeAt(r, next_slot)->rank - r->rank_stride; + } else if (next_slot == -1) { + if (NodeAt(r, prev_slot)->rank > kMaxRank - r->rank_stride) { + Respace(r, r->lowest_slot, r->highest_slot); + } + rank = NodeAt(r, prev_slot)->rank + r->rank_stride; + } else { + const Node* prev_node = NodeAt(r, prev_slot); + const Node* next_node = NodeAt(r, next_slot); + if (RankDistance(prev_node->rank, next_node->rank) < 2) { + Respace(r, prev_slot, next_slot); + } + const uint64_t gap = RankDistance(prev_node->rank, next_node->rank); + rank = prev_node->rank + static_cast<int64_t>(gap / 2); + } + NodeAt(r, slot)->rank = rank; + Link(r, prev_slot, slot); + Link(r, slot, next_slot); +} + void GraphCycles::TestOnlyAddNodes(uint32_t n) { uint32_t old_size = rep_->nodes_.size(); rep_->nodes_.resize(n); @@ -376,6 +530,13 @@ } } +void GraphCycles::TestOnlySetRankStride(int64_t stride) { + // After Respace() of the whole list, there is room for a stride of 2^61 + // beyond each end. + ABSL_RAW_CHECK(stride >= 1 && stride <= kMaxRank / 2, "Bad rank stride"); + rep_->rank_stride = stride; +} + GraphCycles::GraphCycles() { InitArenaIfNecessary(); rep_ = new (base_internal::LowLevelAlloc::AllocWithArena(sizeof(Rep), arena)) @@ -396,7 +557,6 @@ bool GraphCycles::CheckInvariants() const { Rep* r = rep_; - NodeSet ranks; // Set of ranks seen so far. for (uint32_t x = 0; x < r->nodes_.size(); x++) { Node* nx = r->nodes_[x]; void* ptr = base_internal::UnhidePtr<void>(nx->masked_ptr); @@ -407,19 +567,44 @@ if (nx->visited) { ABSL_RAW_LOG(FATAL, "Did not clear visited marker on node %" PRIu32, x); } - if (!ranks.insert(nx->rank)) { - ABSL_RAW_LOG(FATAL, "Duplicate occurrence of rank %" PRId32, nx->rank); - } HASH_FOR_EACH(y, nx->out) { Node* ny = r->nodes_[static_cast<uint32_t>(y)]; if (nx->rank >= ny->rank) { ABSL_RAW_LOG(FATAL, "Edge %" PRIu32 " ->%" PRId32 - " has bad rank assignment %" PRId32 "->%" PRId32, + " has bad rank assignment %" PRId64 "->%" PRId64, x, y, nx->rank, ny->rank); } } } + // The rank list holds each slot once, in increasing rank order, so ranks are + // unique. + const uint32_t num_slots = r->nodes_.size(); + uint32_t num_listed = 0; + int32_t prev_slot = -1; + for (int32_t slot = r->lowest_slot; slot != -1; + slot = NodeAt(r, slot)->next_slot) { + if (NodeAt(r, slot)->prev_slot != prev_slot) { + ABSL_RAW_LOG(FATAL, + "Slot %" PRId32 " has prev_slot %" PRId32 + " instead of %" PRId32, + slot, NodeAt(r, slot)->prev_slot, prev_slot); + } + if (prev_slot != -1 && + NodeAt(r, prev_slot)->rank >= NodeAt(r, slot)->rank) { + ABSL_RAW_LOG( + FATAL, "Rank of slot %" PRId32 " is not above rank of slot %" PRId32, + slot, prev_slot); + } + if (++num_listed > num_slots) { + ABSL_RAW_LOG(FATAL, "Rank list has more entries than there are slots"); + } + prev_slot = slot; + } + if (prev_slot != r->highest_slot || num_listed != num_slots) { + ABSL_RAW_LOG(FATAL, "Rank list has %" PRIu32 " of %" PRIu32 " slots", + num_listed, num_slots); + } return true; } @@ -428,29 +613,29 @@ if (i != -1) { return MakeId(i, rep_->nodes_[static_cast<uint32_t>(i)]->version); } else if (rep_->free_nodes_.empty()) { + const int32_t slot = static_cast<int32_t>(rep_->nodes_.size()); Node* n = new (base_internal::LowLevelAlloc::AllocWithArena(sizeof(Node), arena)) Node; n->version = 1; // Avoid 0 since it is used by InvalidGraphId() n->visited = false; - n->rank = static_cast<int32_t>(rep_->nodes_.size()); n->masked_ptr = base_internal::HidePtr(ptr); n->nstack = 0; n->priority = 0; rep_->nodes_.push_back(n); - rep_->ptrmap_.Add(ptr, n->rank); - return MakeId(n->rank, n->version); + LinkBetween(rep_, slot, rep_->highest_slot, /*next_slot=*/-1); + rep_->ptrmap_.Add(ptr, slot); + return MakeId(slot, n->version); } else { - // Preserve preceding rank since the set of ranks in use must be - // a permutation of [0,rep_->nodes_.size()-1]. - int32_t r = rep_->free_nodes_.back(); + // The slot keeps its rank (see "Rank allocation"). + int32_t slot = rep_->free_nodes_.back(); rep_->free_nodes_.pop_back(); - Node* n = rep_->nodes_[static_cast<uint32_t>(r)]; + Node* n = rep_->nodes_[static_cast<uint32_t>(slot)]; n->masked_ptr = base_internal::HidePtr(ptr); n->nstack = 0; n->priority = 0; - rep_->ptrmap_.Add(ptr, r); - return MakeId(r, n->version); + rep_->ptrmap_.Add(ptr, slot); + return MakeId(slot, n->version); } } @@ -502,13 +687,65 @@ } } -static bool ForwardDFS(GraphCycles::Rep* r, int32_t n, int32_t upper_bound); -static void BackwardDFS(GraphCycles::Rep* r, int32_t n, int32_t lower_bound); +static bool ForwardDFS(GraphCycles::Rep* r, int32_t n, int64_t upper_bound); +static void BackwardDFS(GraphCycles::Rep* r, int32_t n, int64_t lower_bound); static void Reorder(GraphCycles::Rep* r); static void Sort(const Vec<Node*>&, Vec<int32_t>* delta); static void MoveToList(GraphCycles::Rep* r, Vec<int32_t>* src, Vec<int32_t>* dst); +// TryMoveXBelowY() and TryMoveYAboveX() are called by InsertEdge(x, y) when +// rank(x) > rank(y). They try to restore the rank order by moving only x below +// y, or only y above x, which is much cheaper than the Pearce-Kelly search and +// reorder. +// +// x can move below y if all of x's in-neighbors rank below y. This also shows +// that the new edge does not close a cycle: every node reachable from y ranks +// at or above y, so a path from y to x would have to enter x from such a node. +// Similarly, y can move above x if all of y's out-neighbors rank above x. +// +// Each returns false, changing nothing, if the move is not possible or if +// there are more than kMaxSingleNodeDegree neighbors to check. +static bool TryMoveXBelowY(GraphCycles::Rep* r, int32_t x, int32_t y) { + Node* nx = NodeAt(r, x); + Node* ny = NodeAt(r, y); + int degree = 0; + HASH_FOR_EACH(u, nx->in) { + if (++degree > kMaxSingleNodeDegree) return false; + if (NodeAt(r, u)->rank >= ny->rank) return false; + } + Unlink(r, x); + if (degree == 0) { + // Move x below all other nodes. + LinkBetween(r, x, /*prev_slot=*/-1, r->lowest_slot); + } else { + // Move x right below y. Its in-neighbors rank below y, so they rank no + // higher than the node right below y. + LinkBetween(r, x, ny->prev_slot, y); + } + return true; +} + +static bool TryMoveYAboveX(GraphCycles::Rep* r, int32_t x, int32_t y) { + Node* nx = NodeAt(r, x); + Node* ny = NodeAt(r, y); + int degree = 0; + HASH_FOR_EACH(w, ny->out) { + if (++degree > kMaxSingleNodeDegree) return false; + if (NodeAt(r, w)->rank <= nx->rank) return false; + } + Unlink(r, y); + if (degree == 0) { + // Move y above all other nodes. + LinkBetween(r, y, r->highest_slot, /*next_slot=*/-1); + } else { + // Move y right above x. Its out-neighbors rank above x, so they rank no + // lower than the node right above x. + LinkBetween(r, y, x, nx->next_slot); + } + return true; +} + bool GraphCycles::InsertEdge(GraphId idx, GraphId idy) { Rep* r = rep_; const int32_t x = NodeIndex(idx); @@ -530,6 +767,9 @@ return true; } + // Try moving just x or just y. + if (TryMoveXBelowY(r, x, y) || TryMoveYAboveX(r, x, y)) return true; + // Current rank assignments are incompatible with the new edge. Recompute. // We only need to consider nodes that fall in the range [ny->rank,nx->rank]. if (!ForwardDFS(r, y, nx->rank)) { @@ -548,7 +788,7 @@ return true; } -static bool ForwardDFS(GraphCycles::Rep* r, int32_t n, int32_t upper_bound) { +static bool ForwardDFS(GraphCycles::Rep* r, int32_t n, int64_t upper_bound) { // Avoid recursion since stack space might be limited. // We instead keep a stack of nodes to visit. r->deltaf_.clear(); @@ -576,7 +816,7 @@ return true; } -static void BackwardDFS(GraphCycles::Rep* r, int32_t n, int32_t lower_bound) { +static void BackwardDFS(GraphCycles::Rep* r, int32_t n, int64_t lower_bound) { r->deltab_.clear(); r->stack_.clear(); r->stack_.push_back(n); @@ -602,19 +842,48 @@ Sort(r->nodes_, &r->deltab_); Sort(r->nodes_, &r->deltaf_); + // The slots to reorder, sorted by their current ranks. In their new order + // (list_), they take over the same ranks and places in the rank list. + const uint32_t count = r->deltab_.size() + r->deltaf_.size(); + r->sorted_slots.resize(count); + std::merge(r->deltab_.begin(), r->deltab_.end(), r->deltaf_.begin(), + r->deltaf_.end(), r->sorted_slots.begin(), + [r](int32_t a, int32_t b) { + return NodeAt(r, a)->rank < NodeAt(r, b)->rank; + }); + // Adds contents of delta lists to list_ (backwards deltas first). r->list_.clear(); MoveToList(r, &r->deltab_, &r->list_); MoveToList(r, &r->deltaf_, &r->list_); - // Produce sorted list of all ranks that will be reassigned. - r->merged_.resize(r->deltab_.size() + r->deltaf_.size()); - std::merge(r->deltab_.begin(), r->deltab_.end(), r->deltaf_.begin(), - r->deltaf_.end(), r->merged_.begin()); + // Record the rank, prev_slot and next_slot of each place before changing + // them. + r->sorted_ranks.resize(count); + r->old_prev_slots.resize(count); + r->old_next_slots.resize(count); + for (uint32_t i = 0; i < count; i++) { + const Node* p = NodeAt(r, r->sorted_slots[i]); + r->sorted_ranks[i] = p->rank; + r->old_prev_slots[i] = p->prev_slot; + r->old_next_slots[i] = p->next_slot; + } - // Assign the ranks in order to the collected list. - for (uint32_t i = 0; i < r->list_.size(); i++) { - r->nodes_[static_cast<uint32_t>(r->list_[i])]->rank = r->merged_[i]; + // Assign the ranks and places in order to the collected list. A prev_slot + // (or next_slot) that is also being reordered must be the previous (or next) + // place in sorted_slots, so link to the slot that now takes that place + // instead. + for (uint32_t i = 0; i < count; i++) { + const int32_t slot = r->list_[i]; + NodeAt(r, slot)->rank = r->sorted_ranks[i]; + if (i > 0 && r->old_prev_slots[i] == r->sorted_slots[i - 1]) { + Link(r, r->list_[i - 1], slot); + } else { + Link(r, r->old_prev_slots[i], slot); + } + if (i + 1 == count || r->old_next_slots[i] != r->sorted_slots[i + 1]) { + Link(r, slot, r->old_next_slots[i]); + } } } @@ -633,10 +902,7 @@ static void MoveToList(GraphCycles::Rep* r, Vec<int32_t>* src, Vec<int32_t>* dst) { - for (auto& v : *src) { - int32_t w = v; - // Replace v entry with its rank - v = r->nodes_[static_cast<uint32_t>(w)]->rank; + for (int32_t w : *src) { // Prepare for future DFS calls r->nodes_[static_cast<uint32_t>(w)]->visited = false; dst->push_back(w);
diff --git a/absl/synchronization/internal/graphcycles.h b/absl/synchronization/internal/graphcycles.h index da06504..27eaa6c 100644 --- a/absl/synchronization/internal/graphcycles.h +++ b/absl/synchronization/internal/graphcycles.h
@@ -131,6 +131,12 @@ // very full. void TestOnlyAddNodes(uint32_t n); + // Test-only method to set how far apart the ranks of new nodes are, and how + // far below (above) all other nodes a node is moved. The default is 2^32. A + // stride of up to 2^61 makes the ranks beyond the lowest and highest ones run + // out after a few new nodes or moves. + void TestOnlySetRankStride(int64_t stride); + // ---------------------------------------------------- struct Rep; private:
diff --git a/absl/synchronization/internal/graphcycles_benchmark.cc b/absl/synchronization/internal/graphcycles_benchmark.cc index 9012801..9d11c38 100644 --- a/absl/synchronization/internal/graphcycles_benchmark.cc +++ b/absl/synchronization/internal/graphcycles_benchmark.cc
@@ -40,4 +40,65 @@ } BENCHMARK(BM_StressTest)->Range(2048, 1048576); +using absl::synchronization_internal::GraphCycles; +using absl::synchronization_internal::GraphId; + +// Returns a node for a new pointer value, next_ptr, and increments next_ptr. +GraphId NewNode(GraphCycles& g, uintptr_t& next_ptr) { + return g.GetId(reinterpret_cast<void*>(next_ptr++)); +} + +// Adds a chain of n new nodes after node. +void AddChain(GraphCycles& g, GraphId node, int n, uintptr_t& next_ptr) { + for (int i = 0; i < n; i++) { + const GraphId next = NewNode(g, next_ptr); + ABSL_RAW_CHECK(g.InsertEdge(node, next), ""); + node = next; + } +} + +// Inserts edges from new nodes to an old node that has a chain of +// state.range(0) descendants. New nodes rank above old ones, so each edge +// violates the rank order. Deadlock detection sees this pattern whenever a +// newly created Mutex is held while an older one is acquired. +void BM_NewNodeToOldNode(benchmark::State& state) { + const int num_descendants = static_cast<int>(state.range(0)); + constexpr int kNewNodes = 10000; + while (state.KeepRunningBatch(kNewNodes)) { + GraphCycles g; + uintptr_t next_ptr = 1; + const GraphId old_node = NewNode(g, next_ptr); + AddChain(g, old_node, num_descendants, next_ptr); + for (int i = 0; i < kNewNodes; i++) { + const GraphId new_node = NewNode(g, next_ptr); + ABSL_RAW_CHECK(g.InsertEdge(new_node, old_node), ""); + } + } +} +BENCHMARK(BM_NewNodeToOldNode)->Arg(256); + +// Inserts edges u -> new node -> y, where u -> y are old nodes and y has a +// chain of state.range(0) descendants. Each new node must move between u and y, +// so these moves all go into the same gap. Deadlock detection sees this +// pattern when newly created Mutexes are each acquired while one Mutex is held, +// and then held while another one is acquired. +void BM_NewNodesBetweenOldNodes(benchmark::State& state) { + const int num_descendants = static_cast<int>(state.range(0)); + constexpr int kNewNodes = 10000; + while (state.KeepRunningBatch(kNewNodes)) { + GraphCycles g; + uintptr_t next_ptr = 1; + const GraphId u = NewNode(g, next_ptr); + const GraphId y = NewNode(g, next_ptr); + ABSL_RAW_CHECK(g.InsertEdge(u, y), ""); + AddChain(g, y, num_descendants, next_ptr); + for (int i = 0; i < kNewNodes; i++) { + const GraphId new_node = NewNode(g, next_ptr); + ABSL_RAW_CHECK(g.InsertEdge(u, new_node), ""); + ABSL_RAW_CHECK(g.InsertEdge(new_node, y), ""); + } + } +} +BENCHMARK(BM_NewNodesBetweenOldNodes)->Arg(256); + } // namespace
diff --git a/absl/synchronization/internal/graphcycles_test.cc b/absl/synchronization/internal/graphcycles_test.cc index d4ba0f6..b1a6daf 100644 --- a/absl/synchronization/internal/graphcycles_test.cc +++ b/absl/synchronization/internal/graphcycles_test.cc
@@ -483,6 +483,167 @@ graph_cycles.RemoveNode(reinterpret_cast<void*>(buf)); } +// Returns ids for n new nodes. A new GraphCycles ranks nodes in creation order, +// so InsertEdge(ids[i], ids[j]) with i > j violates the rank order and makes +// InsertEdge reorder nodes. +static std::vector<GraphId> NewNodes(GraphCycles* g, size_t n) { + std::vector<GraphId> ids(n); + for (size_t i = 0; i < n; ++i) { + ids[i] = g->GetId(reinterpret_cast<void*>(static_cast<uintptr_t>(i + 1))); + } + return ids; +} + +// Each group of edges below violates the rank order in a different way. The +// comments say how InsertEdge is expected to restore the order. +TEST(GraphCycles, RankOrderRepairs) { + GraphCycles g; + std::vector<GraphId> n = NewNodes(&g, 16); + + // x has no in-neighbors: move x below all nodes. + ASSERT_TRUE(g.InsertEdge(n[1], n[0])); + + // x's in-neighbors rank below y: move x into the gap below y. + ASSERT_TRUE(g.InsertEdge(n[2], n[4])); + ASSERT_TRUE(g.InsertEdge(n[4], n[3])); + + // y has no out-neighbors: move y above all nodes. + ASSERT_TRUE(g.InsertEdge(n[6], n[7])); + ASSERT_TRUE(g.InsertEdge(n[7], n[5])); + + // y's out-neighbors rank above x: move y into the gap above x. + ASSERT_TRUE(g.InsertEdge(n[8], n[11])); + ASSERT_TRUE(g.InsertEdge(n[9], n[10])); + ASSERT_TRUE(g.InsertEdge(n[10], n[8])); + + // Neither x nor y can move alone: Pearce-Kelly reorder. + ASSERT_TRUE(g.InsertEdge(n[12], n[13])); + ASSERT_TRUE(g.InsertEdge(n[14], n[15])); + ASSERT_TRUE(g.InsertEdge(n[15], n[12])); + CheckInvariants(g); + + // Edges that would close a cycle are still rejected. + EXPECT_FALSE(g.InsertEdge(n[0], n[1])); + EXPECT_FALSE(g.InsertEdge(n[3], n[2])); + EXPECT_FALSE(g.InsertEdge(n[5], n[6])); + EXPECT_FALSE(g.InsertEdge(n[8], n[9])); + EXPECT_FALSE(g.InsertEdge(n[13], n[14])); + CheckInvariants(g); +} + +// InsertEdge(x, y) does not move x if x has too many in-neighbors to check, +// even if moving it would restore the rank order. Here y cannot move either, so +// InsertEdge falls back to the Pearce-Kelly reorder. +TEST(GraphCycles, TooManyNeighborsToMove) { + GraphCycles g; + std::vector<GraphId> n = NewNodes(&g, 41); + + // x is 40, which has 17 in-neighbors. y is 30, which cannot move above x + // because of its out-neighbor 31. + for (size_t i = 0; i <= 16; ++i) { + ASSERT_TRUE(g.InsertEdge(n[i], n[40])); + } + ASSERT_TRUE(g.InsertEdge(n[30], n[31])); + ASSERT_TRUE(g.InsertEdge(n[40], n[30])); + CheckInvariants(g); + + EXPECT_FALSE(g.InsertEdge(n[30], n[0])); + CheckInvariants(g); +} + +// Each move of a node right below the same node halves the gap below it. Check +// that InsertEdge keeps working when that gap runs out and the ranks around it +// are respaced. +TEST(GraphCycles, RespacesCrowdedGapBelow) { + GraphCycles g; + std::vector<GraphId> n = NewNodes(&g, 2000); + + // Each n[i] has in-neighbor 50, which ranks below 51, so each n[i] -> n[51] + // edge below moves n[i] right below 51. + for (size_t i = 100; i < 2000; ++i) { + ASSERT_TRUE(g.InsertEdge(n[50], n[i])); + ASSERT_TRUE(g.InsertEdge(n[i], n[51])); + if (i % 100 == 0) CheckInvariants(g); + } + CheckInvariants(g); + + EXPECT_FALSE(g.InsertEdge(n[51], n[50])); + EXPECT_FALSE(g.InsertEdge(n[51], n[100])); + EXPECT_FALSE(g.InsertEdge(n[1999], n[50])); + CheckInvariants(g); +} + +// Same, for moves of a node right above the same node. +TEST(GraphCycles, RespacesCrowdedGapAbove) { + GraphCycles g; + constexpr size_t kNumMoved = 1000; + std::vector<GraphId> n = NewNodes(&g, 2 * kNumMoved + 18); + + // x ranks above each n[i], i < kNumMoved, and has too many in-neighbors to + // move. n[i] has an out-neighbor that ranks above x, so each x -> n[i] edge + // below moves n[i] right above x. + const GraphId x = n[kNumMoved + 17]; + for (size_t j = 0; j <= 16; ++j) { + ASSERT_TRUE(g.InsertEdge(n[kNumMoved + j], x)); + } + for (size_t i = 0; i < kNumMoved; ++i) { + ASSERT_TRUE(g.InsertEdge(n[i], n[kNumMoved + 18 + i])); + ASSERT_TRUE(g.InsertEdge(x, n[i])); + if (i % 100 == 0) CheckInvariants(g); + } + CheckInvariants(g); + + EXPECT_FALSE(g.InsertEdge(n[0], x)); + EXPECT_FALSE(g.InsertEdge(n[kNumMoved + 18], x)); + EXPECT_FALSE(g.InsertEdge(n[kNumMoved - 1], n[kNumMoved])); + CheckInvariants(g); +} + +// With a rank stride this large, the ranks beyond the lowest and highest ones +// run out after a few new nodes, or a few moves of a node below or above all +// other nodes, and then all ranks are respaced. Check that the rank order stays +// valid. +TEST(GraphCycles, RanksRunOut) { + GraphCycles g; + g.TestOnlySetRankStride(int64_t{1} << 60); + std::vector<GraphId> n = NewNodes(&g, 50); + for (size_t i = 0; i < 9; ++i) { + ASSERT_TRUE(g.InsertEdge(n[i], n[i + 1])); + } + CheckInvariants(g); + const GraphId a = n[10]; + const GraphId b = n[11]; + + // a and b have no in-neighbors, so each InsertEdge call below moves one of + // them below all other nodes. + for (int i = 0; i < 1000; ++i) { + ASSERT_TRUE(g.InsertEdge(b, a)); + g.RemoveEdge(b, a); + ASSERT_TRUE(g.InsertEdge(a, b)); + g.RemoveEdge(a, b); + } + CheckInvariants(g); + + // Now a and b have too many in-neighbors to move, so each InsertEdge call + // below moves the other one above all other nodes. + for (size_t i = 12; i < 50; ++i) { + ASSERT_TRUE(g.InsertEdge(n[i], i % 2 == 0 ? a : b)); + } + for (int i = 0; i < 1000; ++i) { + ASSERT_TRUE(g.InsertEdge(b, a)); + g.RemoveEdge(b, a); + ASSERT_TRUE(g.InsertEdge(a, b)); + g.RemoveEdge(a, b); + } + CheckInvariants(g); + + EXPECT_TRUE(g.IsReachable(n[0], n[9])); + EXPECT_FALSE(g.InsertEdge(n[9], n[0])); + ASSERT_TRUE(g.InsertEdge(n[9], a)); + EXPECT_FALSE(g.InsertEdge(a, n[0])); + CheckInvariants(g); +} + } // namespace synchronization_internal ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/time/civil_time.cc b/absl/time/civil_time.cc index a704247..76dbcaf 100644 --- a/absl/time/civil_time.cc +++ b/absl/time/civil_time.cc
@@ -16,6 +16,7 @@ #include <cerrno> #include <cstdlib> +#include <limits> #include <ostream> #include <string> @@ -68,7 +69,15 @@ // end-of-year rollover) can carry into the year. The other fields are taken // from `cs`, so the same carry must be applied to the original year; // otherwise the reconstructed value would use the wrong (un-carried) year. - const civil_year_t year = y + (cs.year() - normalized_year); + // `cs.year()` and `normalized_year` both lie in the normalized range, so + // the carry is small, but applying it to `y` can overflow civil_year_t when + // `y` is at the extreme representable year; reject that rather than wrap. + const civil_year_t carry = cs.year() - normalized_year; + if ((carry > 0 && y > (std::numeric_limits<civil_year_t>::max)() - carry) || + (carry < 0 && y < (std::numeric_limits<civil_year_t>::min)() - carry)) { + return false; + } + const civil_year_t year = y + carry; *c = CivilT(year, cs.month(), cs.day(), cs.hour(), cs.minute(), cs.second()); return true;
diff --git a/absl/time/civil_time_test.cc b/absl/time/civil_time_test.cc index 3f03fc1..53f52a5 100644 --- a/absl/time/civil_time_test.cc +++ b/absl/time/civil_time_test.cc
@@ -873,6 +873,19 @@ EXPECT_EQ(absl::CivilSecond(0, 1, 1, 0, 0, 0), ss); } +TEST(CivilTime, ParseYearCarryDoesNotOverflow) { + // A ":60" leap second on the last second of the maximum representable year + // carries into the year, which would be civil_year_t(max) + 1 and is not + // representable, so the parse must fail cleanly rather than overflow. + absl::CivilSecond ss; + EXPECT_FALSE(absl::ParseCivilTime("9223372036854775807-12-31T23:59:60", &ss)) + << ss; + // The same year without a carry remains valid and round-trips. + EXPECT_TRUE(absl::ParseCivilTime("9223372036854775807-12-31T23:59:59", &ss)) + << ss; + EXPECT_EQ("9223372036854775807-12-31T23:59:59", absl::FormatCivilTime(ss)); +} + TEST(CivilTime, AbslStringify) { static_assert(absl::HasAbslStringify<absl::CivilSecond>::value); static_assert(absl::HasAbslStringify<absl::CivilMinute>::value);
diff --git a/absl/time/internal/cctz/src/cctz_benchmark.cc b/absl/time/internal/cctz/src/cctz_benchmark.cc index 17ff576..9457114 100644 --- a/absl/time/internal/cctz/src/cctz_benchmark.cc +++ b/absl/time/internal/cctz/src/cctz_benchmark.cc
@@ -262,7 +262,7 @@ while (state.KeepRunning()) { std::swap(t, t2); t += 1; -#if defined(_WIN32) || defined(_WIN64) +#if defined(_WIN32) benchmark::DoNotOptimize(localtime_s(&tm, &t)); #else benchmark::DoNotOptimize(localtime_r(&t, &tm)); @@ -287,7 +287,7 @@ struct tm tm; while (state.KeepRunning()) { t += 1; -#if defined(_WIN32) || defined(_WIN64) +#if defined(_WIN32) benchmark::DoNotOptimize(gmtime_s(&tm, &t)); #else benchmark::DoNotOptimize(gmtime_r(&t, &tm));
diff --git a/absl/time/internal/cctz/src/time_zone_format.cc b/absl/time/internal/cctz/src/time_zone_format.cc index ee4341d..c2839ea 100644 --- a/absl/time/internal/cctz/src/time_zone_format.cc +++ b/absl/time/internal/cctz/src/time_zone_format.cc
@@ -198,7 +198,7 @@ // as the result of a failed load_time_zone(). char sign = '+'; if (offset < 0) { - offset = -offset; // bounded by 24h so no overflow + offset = -offset; // bounded by -25h so no overflow sign = '-'; } const int seconds = offset % 60; @@ -219,7 +219,7 @@ ep = Format02d(ep, minutes); if (sep != '\0') *--ep = sep; } - ep = Format02d(ep, hours); + ep = Format02d(ep, hours); // bounded by 26h so fits in two digits *--ep = sign; return ep; }
diff --git a/absl/time/internal/cctz/src/time_zone_format_test.cc b/absl/time/internal/cctz/src/time_zone_format_test.cc index cbaf09a..da3ff50 100644 --- a/absl/time/internal/cctz/src/time_zone_format_test.cc +++ b/absl/time/internal/cctz/src/time_zone_format_test.cc
@@ -1998,7 +1998,7 @@ EXPECT_EQ(in, out); // RFC1123_full includes %z } -#if defined(_WIN32) || defined(_WIN64) +#if defined(_WIN32) // Initial investigations indicate the %c does not roundtrip on Windows. // TODO: Figure out what is going on here (perhaps a locale problem). #elif defined(__EMSCRIPTEN__)
diff --git a/absl/time/internal/cctz/src/time_zone_info.cc b/absl/time/internal/cctz/src/time_zone_info.cc index 9d385b2..c04b680 100644 --- a/absl/time/internal/cctz/src/time_zone_info.cc +++ b/absl/time/internal/cctz/src/time_zone_info.cc
@@ -790,8 +790,8 @@ for (std::size_t i = 0; i != hdr.typecnt; ++i) { transition_types_[i].utc_offset = static_cast<std::int_least32_t>(Decode32(bp)); - if (transition_types_[i].utc_offset >= kSecsPerDay || - transition_types_[i].utc_offset <= -kSecsPerDay) + if (transition_types_[i].utc_offset >= 26 * 60 * 60 || + transition_types_[i].utc_offset <= -25 * 60 * 60) return false; bp += 4; transition_types_[i].is_dst = (Decode8(bp++) != 0);
diff --git a/absl/time/internal/cctz/src/time_zone_libc.cc b/absl/time/internal/cctz/src/time_zone_libc.cc index 362da1a..4e2680a 100644 --- a/absl/time/internal/cctz/src/time_zone_libc.cc +++ b/absl/time/internal/cctz/src/time_zone_libc.cc
@@ -40,11 +40,11 @@ namespace { -#if defined(_WIN32) || defined(_WIN64) +#if defined(_WIN32) // Uses the globals: '_timezone', '_dstbias' and '_tzname'. -auto tm_gmtoff(const std::tm& tm) -> decltype(_timezone + _dstbias) { +auto tm_gmtoff(const std::tm& tm) -> decltype(-_timezone + -_dstbias) { const bool is_dst = tm.tm_isdst > 0; - return _timezone + (is_dst ? _dstbias : 0); + return -_timezone + (is_dst ? -_dstbias : 0); } auto tm_zone(const std::tm& tm) -> decltype(_tzname[0]) { const bool is_dst = tm.tm_isdst > 0; @@ -52,9 +52,9 @@ } #elif defined(__sun) || defined(_AIX) // Uses the globals: 'timezone', 'altzone' and 'tzname'. -auto tm_gmtoff(const std::tm& tm) -> decltype(timezone) { +auto tm_gmtoff(const std::tm& tm) -> decltype(-timezone) { const bool is_dst = tm.tm_isdst > 0; - return is_dst ? altzone : timezone; + return is_dst ? -altzone : -timezone; } auto tm_zone(const std::tm& tm) -> decltype(tzname[0]) { const bool is_dst = tm.tm_isdst > 0; @@ -63,9 +63,9 @@ #elif defined(__native_client__) || defined(__myriad2__) || \ defined(__EMSCRIPTEN__) // Uses the globals: '_timezone' and 'tzname'. -auto tm_gmtoff(const std::tm& tm) -> decltype(_timezone + 0) { +auto tm_gmtoff(const std::tm& tm) -> decltype(-_timezone + 0) { const bool is_dst = tm.tm_isdst > 0; - return _timezone + (is_dst ? 60 * 60 : 0); + return -_timezone + (is_dst ? 60 * 60 : 0); } auto tm_zone(const std::tm& tm) -> decltype(tzname[0]) { const bool is_dst = tm.tm_isdst > 0; @@ -73,14 +73,38 @@ } #elif defined(__VXWORKS__) // Uses the globals: 'timezone' and 'tzname'. -auto tm_gmtoff(const std::tm& tm) -> decltype(timezone + 0) { +auto tm_gmtoff(const std::tm& tm) -> decltype(-timezone + 0) { const bool is_dst = tm.tm_isdst > 0; - return timezone + (is_dst ? 60 * 60 : 0); + return -timezone + (is_dst ? 60 * 60 : 0); } auto tm_zone(const std::tm& tm) -> decltype(tzname[0]) { const bool is_dst = tm.tm_isdst > 0; return tzname[is_dst]; } +#elif defined(__NEWLIB__) +// The struct std::tm extension fields are named by __TM_GMTOFF and __TM_ZONE, +// either of which the configuration may leave undefined. Where it does, fall +// back on the globals: '_timezone' and 'tzname'. +#if defined(__TM_GMTOFF) +auto tm_gmtoff(const std::tm& tm) -> decltype(tm.__TM_GMTOFF) { + return tm.__TM_GMTOFF; +} +#else +auto tm_gmtoff(const std::tm& tm) -> decltype(-_timezone + 0) { + const bool is_dst = tm.tm_isdst > 0; + return -_timezone + (is_dst ? 60 * 60 : 0); +} +#endif +#if defined(__TM_ZONE) +auto tm_zone(const std::tm& tm) -> decltype(tm.__TM_ZONE) { + return tm.__TM_ZONE; +} +#else +auto tm_zone(const std::tm& tm) -> decltype(tzname[0]) { + const bool is_dst = tm.tm_isdst > 0; + return tzname[is_dst]; +} +#endif #else // Adapt to different spellings of the struct std::tm extension fields. #if defined(tm_gmtoff) @@ -121,7 +145,7 @@ using tm_gmtoff_t = decltype(tm_gmtoff(std::tm{})); inline std::tm* gm_time(const std::time_t* timep, std::tm* result) { -#if defined(_WIN32) || defined(_WIN64) +#if defined(_WIN32) return gmtime_s(result, timep) ? nullptr : result; #else return gmtime_r(timep, result); @@ -129,7 +153,7 @@ } inline std::tm* local_time(const std::time_t* timep, std::tm* result) { -#if defined(_WIN32) || defined(_WIN64) +#if defined(_WIN32) return localtime_s(result, timep) ? nullptr : result; #else return localtime_r(timep, result);
diff --git a/absl/time/internal/cctz/src/time_zone_lookup_test.cc b/absl/time/internal/cctz/src/time_zone_lookup_test.cc index f53ac03..ab9a432 100644 --- a/absl/time/internal/cctz/src/time_zone_lookup_test.cc +++ b/absl/time/internal/cctz/src/time_zone_lookup_test.cc
@@ -470,7 +470,7 @@ if (sizeof(std::time_t) >= 8) { // Checks that "tm_year + 1900", as used by the "libc" implementation, // can produce year values beyond the range on an int without overflow. -#if defined(_WIN32) || defined(_WIN64) +#if defined(_WIN32) // localtime_s() and gmtime_s() don't believe in years outside [1970:3000]. #else const time_zone cut = LoadZone("libc:UTC"); @@ -1087,6 +1087,22 @@ return std::unique_ptr<ZoneInfoSource>(new StringZoneInfoSource( MakeExtendedTzif(0, -5 * 3600, "EST", "EST5EDT,M3.2.0,M11.1.0"))); } + if (name == "test:ExtendedLargeOffset") { + // A file-supplied UTC offset of 25h is more than a day from UTC but still + // within the (-25h < utoff < 26h) range RFC 9636 allows, so the reader + // must accept it. + return std::unique_ptr<ZoneInfoSource>( + new StringZoneInfoSource(MakeExtendedTzif( + 0, 25 * 3600, std::string{"EST", 4}, "EST5EDT,M3.2.0,M11.1.0"))); + } + if (name == "test:MinUtcOffset") { + // The minimum 32-bit UTC offset is far outside the (-25h < utoff < 26h) + // range RFC 9636 allows, and is the one offset whose negation overflows + // in FormatOffset(), so the reader must reject it. + const std::int_fast32_t min_offset = -2147483647 - 1; + return std::unique_ptr<ZoneInfoSource>(new StringZoneInfoSource( + MakeExtendedTzif(0, min_offset, std::string{"X", 2}, ""))); + } return fallback(name); } @@ -1129,6 +1145,31 @@ cctz_extension::zone_info_source_factory = prev_factory; } +// A file-supplied UTC offset more than a day from UTC is still valid per +// RFC 9636 (which allows -25h < utoff < 26h), so loading must succeed. +TEST(TimeZoneEdgeCase, ExtendedLargeOffset) { + auto prev_factory = cctz_extension::zone_info_source_factory; + cctz_extension::zone_info_source_factory = ExtendedTestFactory; + + time_zone tz; + EXPECT_TRUE(load_time_zone("test:ExtendedLargeOffset", &tz)); + + cctz_extension::zone_info_source_factory = prev_factory; +} + +// A file-supplied UTC offset outside the range RFC 9636 allows (which is +// -25h < utoff < 26h) would break the fixed-width offset formatting, so +// loading must fail. +TEST(TimeZoneEdgeCase, MinUtcOffset) { + auto prev_factory = cctz_extension::zone_info_source_factory; + cctz_extension::zone_info_source_factory = ExtendedTestFactory; + + time_zone tz; + EXPECT_FALSE(load_time_zone("test:MinUtcOffset", &tz)); + + cctz_extension::zone_info_source_factory = prev_factory; +} + // Looking up the maximum time in an extended zone must fold back through the // 400-year cycle without overflowing when BreakTime() computes the shift. TEST(TimeZoneEdgeCase, ExtendedFarFuture) {
diff --git a/absl/time/internal/cctz/testdata/version b/absl/time/internal/cctz/testdata/version index b62e268..2c2d640 100644 --- a/absl/time/internal/cctz/testdata/version +++ b/absl/time/internal/cctz/testdata/version
@@ -1 +1 @@ -2026d +2026e
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Rainy_River b/absl/time/internal/cctz/testdata/zoneinfo/America/Rainy_River index 7e646d1..0ca1509 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Rainy_River +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Rainy_River Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Winnipeg b/absl/time/internal/cctz/testdata/zoneinfo/America/Winnipeg index 7e646d1..0ca1509 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Winnipeg +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Winnipeg Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Canada/Central b/absl/time/internal/cctz/testdata/zoneinfo/Canada/Central index 7e646d1..0ca1509 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Canada/Central +++ b/absl/time/internal/cctz/testdata/zoneinfo/Canada/Central Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Eire b/absl/time/internal/cctz/testdata/zoneinfo/Eire index 17d2b15..df91cd5 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Eire +++ b/absl/time/internal/cctz/testdata/zoneinfo/Eire Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Dublin b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Dublin index 17d2b15..df91cd5 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Dublin +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Dublin Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab b/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab index 0c6a15c..1aed9b5 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab +++ b/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab
@@ -107,7 +107,7 @@ CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas) CA,BS +4339-07923 America/Toronto Eastern - ON & QC (most areas) CA +6344-06828 America/Iqaluit Eastern - NU (most areas) -CA +4953-09709 America/Winnipeg Central - ON (west), Manitoba +CA +4953-09709 America/Winnipeg EST - Manitoba, ON (northwest) CA +744144-0944945 America/Resolute Central - NU (Resolute) CA +624900-0920459 America/Rankin_Inlet Central - NU (central) CA +5024-10439 America/Regina CST - SK (most areas)
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab b/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab index 1c9b44b..bd3a278 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab +++ b/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab
@@ -80,7 +80,10 @@ XX -2709-10926 Pacific/Easter Easter Island # # -06/-05 - CST/CDT (North America DST) -XX +415100-0873900 America/Chicago Central (CST/CDT) - US & Canada; Mexico near US border +XX +415100-0873900 America/Chicago Central (CST/CDT) - US; Mexico near US border; central Nunavut +# +# -06/-05 - CST/CDT (North America DST) until 2026-11-01 02:00; then EST +XX +4953-09709 America/Winnipeg Eastern Standard (EST) - Manitoba, northwestern Ontario # # -05 XX -1203-07703 America/Lima eastern South America
diff --git a/ci/cmake_install_test.sh b/ci/cmake_install_test.sh index 5e3e07b..d3bc5d0 100755 --- a/ci/cmake_install_test.sh +++ b/ci/cmake_install_test.sh
@@ -44,7 +44,7 @@ --rm \ ${DOCKER_EXTRA_ARGS:-} \ ${DOCKER_CONTAINER} \ - /bin/bash -c "cmake /abseil-cpp-ro && make -j$(nproc) && make install" + /bin/sh -c "cmake /abseil-cpp-ro && make -j$(nproc) && make install" # Verify that a more complicated project works. for link_type in ${LINK_TYPE}; do @@ -60,5 +60,5 @@ --rm \ ${DOCKER_EXTRA_ARGS:-} \ ${DOCKER_CONTAINER} \ - /bin/bash -c "cp -r /abseil-cpp-ro/* . && CMake/install_test_project/test.sh" + /bin/sh -c "cp -r /abseil-cpp-ro/* . && CMake/install_test_project/test.sh" done
diff --git a/ci/linux_arm_clang-latest_libcxx_bazel.sh b/ci/linux_arm_clang-latest_libcxx_bazel.sh index 24d5e48..01c5610 100755 --- a/ci/linux_arm_clang-latest_libcxx_bazel.sh +++ b/ci/linux_arm_clang-latest_libcxx_bazel.sh
@@ -33,7 +33,7 @@ fi if [[ -z ${EXCEPTIONS_MODE:-} ]]; then - EXCEPTIONS_MODE="-fno-exceptions -fexceptions" + EXCEPTIONS_MODE="-fno-exceptions" fi source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh" @@ -44,13 +44,13 @@ docker run "${DOCKER_CONTAINER}" /opt/llvm/bin/clang -v docker run "${DOCKER_CONTAINER}" cat /root/cached_bazel_versions -# USE_BAZEL_CACHE=1 only works on Kokoro. +# Bazel cache only works on Kokoro. # Without access to the credentials this won't work. -if [[ ${USE_BAZEL_CACHE:-0} -ne 0 ]]; then +if [[ ${KOKORO_KEYSTORE_DIR:-} ]]; then DOCKER_EXTRA_ARGS="--mount type=bind,source=${KOKORO_KEYSTORE_DIR},target=/keystore,readonly ${DOCKER_EXTRA_ARGS:-}" # Bazel doesn't track changes to tools outside of the workspace # (e.g. /usr/bin/gcc), so by appending the docker container to the - # remote_http_cache url, we make changes to the container part of + # remote_cache url, we make changes to the container part of # the cache key. Hashing the key is to make it shorter and url-safe. container_key=$(echo ${DOCKER_CONTAINER} | sha256sum | head -c 16) BAZEL_EXTRA_ARGS="--remote_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}" @@ -81,7 +81,7 @@ --rm \ ${DOCKER_EXTRA_ARGS:-} \ ${DOCKER_CONTAINER} \ - /bin/bash --login -c " + /bin/sh -c " cp -r /abseil-cpp-ro/* /abseil-cpp/ if [ -n \"${ALTERNATE_OPTIONS:-}\" ]; then cp ${ALTERNATE_OPTIONS:-} absl/base/options.h || exit 1
diff --git a/ci/linux_clang-latest_libcxx_asan_bazel.sh b/ci/linux_clang-latest_libcxx_asan_bazel.sh index b8d2b5f..d31f608 100755 --- a/ci/linux_clang-latest_libcxx_asan_bazel.sh +++ b/ci/linux_clang-latest_libcxx_asan_bazel.sh
@@ -29,11 +29,11 @@ fi if [[ -z ${COMPILATION_MODE:-} ]]; then - COMPILATION_MODE="fastbuild opt" + COMPILATION_MODE="fastbuild" fi if [[ -z ${EXCEPTIONS_MODE:-} ]]; then - EXCEPTIONS_MODE="-fno-exceptions -fexceptions" + EXCEPTIONS_MODE="-fno-exceptions" fi source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh" @@ -44,24 +44,24 @@ docker run "${DOCKER_CONTAINER}" /opt/llvm/bin/clang -v docker run "${DOCKER_CONTAINER}" cat /root/cached_bazel_versions -# USE_BAZEL_CACHE=1 only works on Kokoro. +# Bazel cache only works on Kokoro. # Without access to the credentials this won't work. -if [[ ${USE_BAZEL_CACHE:-0} -ne 0 ]]; then +if [[ ${KOKORO_KEYSTORE_DIR:-} ]]; then DOCKER_EXTRA_ARGS="--mount type=bind,source=${KOKORO_KEYSTORE_DIR},target=/keystore,readonly ${DOCKER_EXTRA_ARGS:-}" # Bazel doesn't track changes to tools outside of the workspace # (e.g. /usr/bin/gcc), so by appending the docker container to the - # remote_http_cache url, we make changes to the container part of + # remote_cache url, we make changes to the container part of # the cache key. Hashing the key is to make it shorter and url-safe. container_key=$(echo ${DOCKER_CONTAINER} | sha256sum | head -c 16) BAZEL_EXTRA_ARGS="--remote_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}" fi -# Avoid depending on external sites like GitHub by checking --distdir for -# external dependencies first. -# https://docs.bazel.build/versions/master/guide.html#distdir -if [[ ${KOKORO_GFILE_DIR:-} ]] && [[ -d "${KOKORO_GFILE_DIR}/distdir" ]]; then - DOCKER_EXTRA_ARGS="--mount type=bind,source=${KOKORO_GFILE_DIR}/distdir,target=/distdir,readonly ${DOCKER_EXTRA_ARGS:-}" - BAZEL_EXTRA_ARGS="--distdir=/distdir ${BAZEL_EXTRA_ARGS:-}" +# Use Bazel Vendor mode to reduce reliance on external dependencies. +# See https://bazel.build/external/vendor and the Dockerfile for +# an explaination of how this works. +if [[ ${KOKORO_GFILE_DIR:-} ]] && [[ -f "${KOKORO_GFILE_DIR}/distdir/abseil-cpp_vendor.tar.gz" ]]; then + DOCKER_EXTRA_ARGS="--mount type=bind,source=${KOKORO_GFILE_DIR}/distdir,target=/distdir,readonly --env=BAZEL_VENDOR_ARCHIVE=/distdir/abseil-cpp_vendor.tar.gz ${DOCKER_EXTRA_ARGS:-}" + BAZEL_EXTRA_ARGS="--vendor_dir=/abseil-cpp_vendor ${BAZEL_EXTRA_ARGS:-}" fi # https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html#available-checks @@ -83,7 +83,7 @@ --rm \ ${DOCKER_EXTRA_ARGS:-} \ ${DOCKER_CONTAINER} \ - /bin/bash --login -c " + /bin/sh -c " /usr/local/bin/bazel test ... \ --compilation_mode=\"${compilation_mode}\" \ --copt=\"${exceptions_mode}\" \
diff --git a/ci/linux_clang-latest_libcxx_bazel.sh b/ci/linux_clang-latest_libcxx_bazel.sh index 99e92b9..830dd14 100755 --- a/ci/linux_clang-latest_libcxx_bazel.sh +++ b/ci/linux_clang-latest_libcxx_bazel.sh
@@ -33,7 +33,7 @@ fi if [[ -z ${EXCEPTIONS_MODE:-} ]]; then - EXCEPTIONS_MODE="-fno-exceptions -fexceptions" + EXCEPTIONS_MODE="-fno-exceptions" fi source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh" @@ -44,13 +44,13 @@ docker run "${DOCKER_CONTAINER}" /opt/llvm/bin/clang -v docker run "${DOCKER_CONTAINER}" cat /root/cached_bazel_versions -# USE_BAZEL_CACHE=1 only works on Kokoro. +# Bazel cache only works on Kokoro. # Without access to the credentials this won't work. -if [[ ${USE_BAZEL_CACHE:-0} -ne 0 ]]; then +if [[ ${KOKORO_KEYSTORE_DIR:-} ]]; then DOCKER_EXTRA_ARGS="--mount type=bind,source=${KOKORO_KEYSTORE_DIR},target=/keystore,readonly ${DOCKER_EXTRA_ARGS:-}" # Bazel doesn't track changes to tools outside of the workspace # (e.g. /usr/bin/gcc), so by appending the docker container to the - # remote_http_cache url, we make changes to the container part of + # remote_cache url, we make changes to the container part of # the cache key. Hashing the key is to make it shorter and url-safe. container_key=$(echo ${DOCKER_CONTAINER} | sha256sum | head -c 16) BAZEL_EXTRA_ARGS="--remote_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}" @@ -81,7 +81,7 @@ --rm \ ${DOCKER_EXTRA_ARGS:-} \ ${DOCKER_CONTAINER} \ - /bin/bash --login -c " + /bin/sh -c " cp -r /abseil-cpp-ro/* /abseil-cpp/ if [ -n \"${ALTERNATE_OPTIONS:-}\" ]; then cp ${ALTERNATE_OPTIONS:-} absl/base/options.h || exit 1
diff --git a/ci/linux_clang-latest_libcxx_tsan_bazel.sh b/ci/linux_clang-latest_libcxx_tsan_bazel.sh index 56c3c41..240dbea 100755 --- a/ci/linux_clang-latest_libcxx_tsan_bazel.sh +++ b/ci/linux_clang-latest_libcxx_tsan_bazel.sh
@@ -29,11 +29,11 @@ fi if [[ -z ${COMPILATION_MODE:-} ]]; then - COMPILATION_MODE="fastbuild opt" + COMPILATION_MODE="fastbuild" fi if [[ -z ${EXCEPTIONS_MODE:-} ]]; then - EXCEPTIONS_MODE="-fno-exceptions -fexceptions" + EXCEPTIONS_MODE="-fno-exceptions" fi source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh" @@ -44,13 +44,13 @@ docker run "${DOCKER_CONTAINER}" /opt/llvm/bin/clang -v docker run "${DOCKER_CONTAINER}" cat /root/cached_bazel_versions -# USE_BAZEL_CACHE=1 only works on Kokoro. +# Bazel cache only works on Kokoro. # Without access to the credentials this won't work. -if [[ ${USE_BAZEL_CACHE:-0} -ne 0 ]]; then +if [[ ${KOKORO_KEYSTORE_DIR:-} ]]; then DOCKER_EXTRA_ARGS="--mount type=bind,source=${KOKORO_KEYSTORE_DIR},target=/keystore,readonly ${DOCKER_EXTRA_ARGS:-}" # Bazel doesn't track changes to tools outside of the workspace # (e.g. /usr/bin/gcc), so by appending the docker container to the - # remote_http_cache url, we make changes to the container part of + # remote_cache url, we make changes to the container part of # the cache key. Hashing the key is to make it shorter and url-safe. container_key=$(echo ${DOCKER_CONTAINER} | sha256sum | head -c 16) BAZEL_EXTRA_ARGS="--remote_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}" @@ -80,7 +80,7 @@ --rm \ ${DOCKER_EXTRA_ARGS:-} \ ${DOCKER_CONTAINER} \ - /bin/bash --login -c " + /bin/sh -c " /usr/local/bin/bazel test ... \ --build_tag_filters=\"-notsan\" \ --compilation_mode=\"${compilation_mode}\" \
diff --git a/ci/linux_clang-latest_libstdcxx_bazel.sh b/ci/linux_clang-latest_libstdcxx_bazel.sh index 2fc8210..fe15f84 100755 --- a/ci/linux_clang-latest_libstdcxx_bazel.sh +++ b/ci/linux_clang-latest_libstdcxx_bazel.sh
@@ -33,7 +33,7 @@ fi if [[ -z ${EXCEPTIONS_MODE:-} ]]; then - EXCEPTIONS_MODE="-fno-exceptions -fexceptions" + EXCEPTIONS_MODE="-fno-exceptions" fi source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh" @@ -45,13 +45,13 @@ docker run "${DOCKER_CONTAINER}" /opt/llvm/bin/clang -v docker run "${DOCKER_CONTAINER}" cat /root/cached_bazel_versions -# USE_BAZEL_CACHE=1 only works on Kokoro. +# Bazel cache only works on Kokoro. # Without access to the credentials this won't work. -if [[ ${USE_BAZEL_CACHE:-0} -ne 0 ]]; then +if [[ ${KOKORO_KEYSTORE_DIR:-} ]]; then DOCKER_EXTRA_ARGS="--mount type=bind,source=${KOKORO_KEYSTORE_DIR},target=/keystore,readonly ${DOCKER_EXTRA_ARGS:-}" # Bazel doesn't track changes to tools outside of the workspace # (e.g. /usr/bin/gcc), so by appending the docker container to the - # remote_http_cache url, we make changes to the container part of + # remote_cache url, we make changes to the container part of # the cache key. Hashing the key is to make it shorter and url-safe. container_key=$(echo ${DOCKER_CONTAINER} | sha256sum | head -c 16) BAZEL_EXTRA_ARGS="--remote_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}" @@ -79,7 +79,7 @@ --rm \ ${DOCKER_EXTRA_ARGS:-} \ ${DOCKER_CONTAINER} \ - /bin/bash --login -c " + /bin/sh -c " /usr/local/bin/bazel test ... \ --compilation_mode=\"${compilation_mode}\" \ --copt=\"--gcc-toolchain=/usr/local\" \
diff --git a/ci/linux_gcc-floor_libstdcxx_bazel.sh b/ci/linux_gcc-floor_libstdcxx_bazel.sh index 765b784..f6f59cb 100755 --- a/ci/linux_gcc-floor_libstdcxx_bazel.sh +++ b/ci/linux_gcc-floor_libstdcxx_bazel.sh
@@ -33,7 +33,7 @@ fi if [[ -z ${EXCEPTIONS_MODE:-} ]]; then - EXCEPTIONS_MODE="-fno-exceptions -fexceptions" + EXCEPTIONS_MODE="-fno-exceptions" fi source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh" @@ -44,16 +44,16 @@ docker run "${DOCKER_CONTAINER}" /opt/gcc-10/bin/gcc -v docker run "${DOCKER_CONTAINER}" cat /root/cached_bazel_versions -# USE_BAZEL_CACHE=1 only works on Kokoro. +# Bazel cache only works on Kokoro. # Without access to the credentials this won't work. -if [[ ${USE_BAZEL_CACHE:-0} -ne 0 ]]; then - DOCKER_EXTRA_ARGS="--volume=${KOKORO_KEYSTORE_DIR}:/keystore:ro ${DOCKER_EXTRA_ARGS:-}" +if [[ ${KOKORO_KEYSTORE_DIR:-} ]]; then + DOCKER_EXTRA_ARGS="--mount type=bind,source=${KOKORO_KEYSTORE_DIR},target=/keystore,readonly ${DOCKER_EXTRA_ARGS:-}" # Bazel doesn't track changes to tools outside of the workspace # (e.g. /usr/bin/gcc), so by appending the docker container to the - # remote_http_cache url, we make changes to the container part of + # remote_cache url, we make changes to the container part of # the cache key. Hashing the key is to make it shorter and url-safe. container_key=$(echo ${DOCKER_CONTAINER} | sha256sum | head -c 16) - BAZEL_EXTRA_ARGS="--remote_http_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}" + BAZEL_EXTRA_ARGS="--remote_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}" fi # Use Bazel Vendor mode to reduce reliance on external dependencies. @@ -73,13 +73,13 @@ --env="CC=/opt/gcc-10/bin/gcc" \ --env="BAZEL_CXXOPTS=-std=${std}" \ --env="BAZEL_LINKOPTS=-L/opt/gcc-10/lib64:-Wl,-rpath=/opt/gcc-10/lib64" \ - --volume="${ABSEIL_ROOT}:/abseil-cpp:ro" \ + --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp,readonly \ --workdir=/abseil-cpp \ --cap-add=SYS_PTRACE \ --rm \ ${DOCKER_EXTRA_ARGS:-} \ ${DOCKER_CONTAINER} \ - /bin/bash --login -c " + /bin/sh -c " /usr/local/bin/bazel test ... \ --compilation_mode=\"${compilation_mode}\" \ --copt=\"${exceptions_mode}\" \
diff --git a/ci/linux_gcc-latest_libstdcxx_bazel.sh b/ci/linux_gcc-latest_libstdcxx_bazel.sh index 509aeb9..51d3c9c 100755 --- a/ci/linux_gcc-latest_libstdcxx_bazel.sh +++ b/ci/linux_gcc-latest_libstdcxx_bazel.sh
@@ -33,7 +33,7 @@ fi if [[ -z ${EXCEPTIONS_MODE:-} ]]; then - EXCEPTIONS_MODE="-fno-exceptions -fexceptions" + EXCEPTIONS_MODE="-fno-exceptions" fi source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh" @@ -44,13 +44,13 @@ docker run "${DOCKER_CONTAINER}" /usr/local/bin/gcc -v docker run "${DOCKER_CONTAINER}" cat /root/cached_bazel_versions -# USE_BAZEL_CACHE=1 only works on Kokoro. +# Bazel cache only works on Kokoro. # Without access to the credentials this won't work. -if [[ ${USE_BAZEL_CACHE:-0} -ne 0 ]]; then +if [[ ${KOKORO_KEYSTORE_DIR:-} ]]; then DOCKER_EXTRA_ARGS="--mount type=bind,source=${KOKORO_KEYSTORE_DIR},target=/keystore,readonly ${DOCKER_EXTRA_ARGS:-}" # Bazel doesn't track changes to tools outside of the workspace # (e.g. /usr/bin/gcc), so by appending the docker container to the - # remote_http_cache url, we make changes to the container part of + # remote_cache url, we make changes to the container part of # the cache key. Hashing the key is to make it shorter and url-safe. container_key=$(echo ${DOCKER_CONTAINER} | sha256sum | head -c 16) BAZEL_EXTRA_ARGS="--remote_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}" @@ -79,7 +79,7 @@ --rm \ ${DOCKER_EXTRA_ARGS:-} \ ${DOCKER_CONTAINER} \ - /bin/bash --login -c " + /bin/sh -c " cp -r /abseil-cpp-ro/* /abseil-cpp/ if [ -n \"${ALTERNATE_OPTIONS:-}\" ]; then cp ${ALTERNATE_OPTIONS:-} absl/base/options.h || exit 1
diff --git a/ci/linux_gcc-latest_libstdcxx_cmake.sh b/ci/linux_gcc-latest_libstdcxx_cmake.sh index 2f69bba..4463882 100755 --- a/ci/linux_gcc-latest_libstdcxx_cmake.sh +++ b/ci/linux_gcc-latest_libstdcxx_cmake.sh
@@ -28,8 +28,8 @@ DOCKER_EXTRA_ARGS="--mount type=bind,source=${KOKORO_GFILE_DIR}/distdir,target=/distdir,readonly ${DOCKER_EXTRA_ARGS:-}" fi -if [[ -z ${ABSL_CMAKE_CXX_STANDARDS:-} ]]; then - ABSL_CMAKE_CXX_STANDARDS="17 20" +if [[ -z ${STD:-} ]]; then + STD="17" fi if [[ -z ${ABSL_CMAKE_BUILD_TYPES:-} ]]; then @@ -47,7 +47,7 @@ source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh" readonly DOCKER_CONTAINER=${LINUX_GCC_LATEST_CONTAINER} -for std in ${ABSL_CMAKE_CXX_STANDARDS}; do +for std in ${STD}; do for compilation_mode in ${ABSL_CMAKE_BUILD_TYPES}; do for build_shared in ${ABSL_CMAKE_BUILD_SHARED}; do if [[ $build_shared == "OFF" ]]; then @@ -67,14 +67,14 @@ -e CXXFLAGS="-Werror" \ ${DOCKER_EXTRA_ARGS:-} \ "${DOCKER_CONTAINER}" \ - /bin/bash -c " + /bin/sh -c " cmake /abseil-cpp \ - -DABSL_GOOGLETEST_DOWNLOAD_URL=${ABSL_GOOGLETEST_DOWNLOAD_URL} \ - -DBUILD_SHARED_LIBS=${build_shared} \ + -DABSL_GOOGLETEST_DOWNLOAD_URL=\"${ABSL_GOOGLETEST_DOWNLOAD_URL}\" \ + -DBUILD_SHARED_LIBS=\"${build_shared}\" \ -DABSL_BUILD_TESTING=ON \ - -DCMAKE_BUILD_TYPE=${compilation_mode} \ - -DCMAKE_CXX_STANDARD=${std} \ - -DABSL_BUILD_MONOLITHIC_SHARED_LIBS=${monolithic_shared} \ + -DCMAKE_BUILD_TYPE=\"${compilation_mode}\" \ + -DCMAKE_CXX_STANDARD=\"${std}\" \ + -DABSL_BUILD_MONOLITHIC_SHARED_LIBS=\"${monolithic_shared}\" \ -DCMAKE_MODULE_LINKER_FLAGS=\"-Wl,--no-undefined\" && \ make -j$(nproc) && \ TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo \
diff --git a/ci/linux_gcc_alpine_cmake.sh b/ci/linux_gcc_alpine_cmake.sh index 1d64a95..20393e9 100755 --- a/ci/linux_gcc_alpine_cmake.sh +++ b/ci/linux_gcc_alpine_cmake.sh
@@ -28,16 +28,16 @@ DOCKER_EXTRA_ARGS="--mount type=bind,source=${KOKORO_GFILE_DIR}/distdir,target=/distdir,readonly ${DOCKER_EXTRA_ARGS:-}" fi -if [[ -z ${ABSL_CMAKE_CXX_STANDARDS:-} ]]; then - ABSL_CMAKE_CXX_STANDARDS="17" +if [[ -z ${STD:-} ]]; then + STD="17" fi if [[ -z ${ABSL_CMAKE_BUILD_TYPES:-} ]]; then - ABSL_CMAKE_BUILD_TYPES="Debug Release" + ABSL_CMAKE_BUILD_TYPES="Debug" fi if [[ -z ${ABSL_CMAKE_BUILD_SHARED:-} ]]; then - ABSL_CMAKE_BUILD_SHARED="OFF ON" + ABSL_CMAKE_BUILD_SHARED="OFF" fi if [[ -z ${ABSL_CMAKE_BUILD_MONOLITHIC_SHARED_LIBS:-} ]]; then @@ -47,7 +47,7 @@ source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh" readonly DOCKER_CONTAINER=${LINUX_ALPINE_CONTAINER} -for std in ${ABSL_CMAKE_CXX_STANDARDS}; do +for std in ${STD}; do for compilation_mode in ${ABSL_CMAKE_BUILD_TYPES}; do for build_shared in ${ABSL_CMAKE_BUILD_SHARED}; do if [[ $build_shared == "OFF" ]]; then @@ -69,12 +69,12 @@ "${DOCKER_CONTAINER}" \ /bin/sh -c " cmake /abseil-cpp \ - -DABSL_GOOGLETEST_DOWNLOAD_URL=${ABSL_GOOGLETEST_DOWNLOAD_URL} \ - -DBUILD_SHARED_LIBS=${build_shared} \ + -DABSL_GOOGLETEST_DOWNLOAD_URL=\"${ABSL_GOOGLETEST_DOWNLOAD_URL}\" \ + -DBUILD_SHARED_LIBS=\"${build_shared}\" \ -DABSL_BUILD_TESTING=ON \ - -DCMAKE_BUILD_TYPE=${compilation_mode} \ - -DCMAKE_CXX_STANDARD=${std} \ - -DABSL_BUILD_MONOLITHIC_SHARED_LIBS=${monolithic_shared} \ + -DCMAKE_BUILD_TYPE=\"${compilation_mode}\" \ + -DCMAKE_CXX_STANDARD=\"${std}\" \ + -DABSL_BUILD_MONOLITHIC_SHARED_LIBS=\"${monolithic_shared}\" \ -DCMAKE_MODULE_LINKER_FLAGS=\"-Wl,--no-undefined\" && \ make -j$(nproc) && \ TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo \
diff --git a/ci/macos_xcode_cmake.sh b/ci/macos_xcode_cmake.sh index 7a6ccd5..47b9e03 100755 --- a/ci/macos_xcode_cmake.sh +++ b/ci/macos_xcode_cmake.sh
@@ -63,12 +63,12 @@ # TODO(absl-team): Enable -Werror once all warnings are fixed. time cmake ${ABSEIL_ROOT} \ -GXcode \ - -DBUILD_SHARED_LIBS=${build_shared} \ + -DBUILD_SHARED_LIBS="${build_shared}" \ -DABSL_BUILD_TESTING=ON \ - -DCMAKE_BUILD_TYPE=${compilation_mode} \ + -DCMAKE_BUILD_TYPE="${compilation_mode}" \ -DCMAKE_CXX_STANDARD=17 \ -DCMAKE_MODULE_LINKER_FLAGS="-Wl,--no-undefined" \ - -DABSL_BUILD_MONOLITHIC_SHARED_LIBS=${monolithic_shared} \ + -DABSL_BUILD_MONOLITHIC_SHARED_LIBS="${monolithic_shared}" \ -DABSL_GOOGLETEST_DOWNLOAD_URL="${ABSL_GOOGLETEST_DOWNLOAD_URL}" time cmake --build . time TZDIR=${ABSEIL_ROOT}/absl/time/internal/cctz/testdata/zoneinfo \
diff --git a/ci/windows_clangcl_bazel.bat b/ci/windows_clangcl_bazel.bat index 42be0f7..e196d3b 100755 --- a/ci/windows_clangcl_bazel.bat +++ b/ci/windows_clangcl_bazel.bat
@@ -32,15 +32,15 @@ :: Set the standard version, [c++17|c++20|c++latest] :: https://msdn.microsoft.com/en-us/library/mt490614.aspx :: The default is c++17 if not set on command line. -IF "%STD%"=="" SET STD=c++17 +IF NOT DEFINED STD SET STD=c++17 :: Set the compilation_mode (fastbuild|opt|dbg) :: https://docs.bazel.build/versions/master/user-manual.html#flag--compilation_mode :: The default is fastbuild -IF "%COMPILATION_MODE%"=="" SET COMPILATION_MODE=fastbuild +IF NOT DEFINED COMPILATION_MODE SET COMPILATION_MODE=fastbuild :: Copy the alternate option file, if specified. -IF NOT "%ALTERNATE_OPTIONS%"=="" copy %ALTERNATE_OPTIONS% absl\base\options.h +IF DEFINED ALTERNATE_OPTIONS copy %ALTERNATE_OPTIONS% absl\base\options.h :: To upgrade Bazel, first download a new binary from :: https://github.com/bazelbuild/bazel/releases and copy it to @@ -49,11 +49,11 @@ :: TODO(absl-team): Remove -Wno-microsoft-cast %KOKORO_GFILE_DIR%\bazel-9.1.0-windows-x86_64.exe ^ test ... ^ - --compilation_mode=%COMPILATION_MODE% ^ + --compilation_mode="%COMPILATION_MODE%" ^ --compiler=clang-cl ^ --copt=/WX ^ --copt=-Wno-microsoft-cast ^ - --cxxopt=/std:%STD% ^ + --cxxopt="/std:%STD%" ^ --define=absl=1 ^ --enable_bzlmod=true ^ --extra_execution_platforms=//:x64_windows-clang-cl ^
diff --git a/ci/windows_msvc_bazel.bat b/ci/windows_msvc_bazel.bat index a4bd3d7..4cb0870 100755 --- a/ci/windows_msvc_bazel.bat +++ b/ci/windows_msvc_bazel.bat
@@ -29,24 +29,24 @@ :: Set the standard version, [c++17|c++latest] :: https://msdn.microsoft.com/en-us/library/mt490614.aspx :: The default is c++17 if not set on command line. -IF "%STD%"=="" SET STD=c++17 +IF NOT DEFINED STD SET STD=c++17 :: Set the compilation_mode (fastbuild|opt|dbg) :: https://docs.bazel.build/versions/master/user-manual.html#flag--compilation_mode :: The default is fastbuild -IF "%COMPILATION_MODE%"=="" SET COMPILATION_MODE=fastbuild +IF NOT DEFINED COMPILATION_MODE SET COMPILATION_MODE=fastbuild :: Copy the alternate option file, if specified. -IF NOT "%ALTERNATE_OPTIONS%"=="" copy %ALTERNATE_OPTIONS% absl\base\options.h +IF DEFINED ALTERNATE_OPTIONS copy %ALTERNATE_OPTIONS% absl\base\options.h :: To upgrade Bazel, first download a new binary from :: https://github.com/bazelbuild/bazel/releases and copy it to :: /google/data/rw/teams/absl/kokoro/windows. "%KOKORO_GFILE_DIR%\bazel-9.1.0-windows-x86_64.exe" ^ test ... ^ - --compilation_mode=%COMPILATION_MODE% ^ + --compilation_mode="%COMPILATION_MODE%" ^ --copt=/WX ^ - --cxxopt=/std:%STD% ^ + --cxxopt="/std:%STD%" ^ --define=absl=1 ^ --enable_bzlmod=true ^ --keep_going ^
diff --git a/ci/windows_msvc_cmake.bat b/ci/windows_msvc_cmake.bat index 6fbc324..a35dcec 100755 --- a/ci/windows_msvc_cmake.bat +++ b/ci/windows_msvc_cmake.bat
@@ -40,6 +40,17 @@ SET CMAKE_BUILD_PARALLEL_LEVEL=16 SET CTEST_PARALLEL_LEVEL=16 +:: Set the standard version, [c++17|c++latest] +:: https://msdn.microsoft.com/en-us/library/mt490614.aspx +:: The default is c++17 if not set on command line. +IF NOT DEFINED STD SET STD=17 + +IF NOT DEFINED ABSL_CMAKE_BUILD_SHARED SET ABSL_CMAKE_BUILD_SHARED=OFF + +IF NOT DEFINED ABSL_CMAKE_BUILD_TYPE SET ABSL_CMAKE_BUILD_TYPE=Debug + +IF NOT DEFINED ABSL_CMAKE_GENERATOR SET "ABSL_CMAKE_GENERATOR=Visual Studio 17 2022" + :: Change directory to the root of the project. CD %~dp0\.. if %errorlevel% neq 0 EXIT /B 1 @@ -53,9 +64,9 @@ %CMAKE_BIN% ^ -DABSL_BUILD_TESTING=ON ^ - -DABSL_GOOGLETEST_DOWNLOAD_URL=%ABSL_GOOGLETEST_DOWNLOAD_URL% ^ - -DBUILD_SHARED_LIBS=%ABSL_CMAKE_BUILD_SHARED% ^ - -DCMAKE_CXX_STANDARD=%ABSL_CMAKE_CXX_STANDARD% ^ + -DABSL_GOOGLETEST_DOWNLOAD_URL="%ABSL_GOOGLETEST_DOWNLOAD_URL%" ^ + -DBUILD_SHARED_LIBS="%ABSL_CMAKE_BUILD_SHARED%" ^ + -DCMAKE_CXX_STANDARD="%STD%" ^ -G "%ABSL_CMAKE_GENERATOR%" ^ .. IF %errorlevel% neq 0 EXIT /B 1