From 3c814105108680997d0821077694f663693b5382 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Fri, 14 Feb 2020 09:41:25 -0800 Subject: Export of internal Abseil changes -- 97faa5fdfa4cd5d7a74cd9332cddd8a7c1e67b89 by Abseil Team : Internal changes PiperOrigin-RevId: 295164378 -- 74990f100b3f4172c770ef8c76c05c8e99febdde by Xiaoyi Zhang : Release `absl::Cord`. PiperOrigin-RevId: 295161959 -- 6018c57f43c45c31dc1a61c0cd75fa2aa9be8dab by Gennadiy Rozental : Introduce independent notion of FlagStaticTypeID. This change separates static flag value type identification from the type specific "vtable" with all the operations specific to value type. This change allows us to do the following: * We can move most of "vtable" implementation from handle header, which will become public soon, into implementation details of Abseil Flag. * We can combine back marshalling ops and general ops into a single vtable routine. They were split previously to facilitate type identification without requiring marshalling routines to be exposed in header. * We do not need to store two vtable pointers. We can now store only one. The static type id can be deduced on request. Overall we are saving 24 bytes per flag according to size_tester run. PiperOrigin-RevId: 295149687 -- 986b78e9ba571aa85154e70bda4580edd45bb7bf by Abseil Team : Update internal comments. PiperOrigin-RevId: 295030681 -- 825412b29fd6015027bbc3e5f802706eee0d2837 by Matthew Brown : Change str_format_internal::ConversionChar to an enum (from a struct-wrapped enum). PiperOrigin-RevId: 294987462 -- f9f88d91809d2cc33fc129df70fa93e7a2c35c69 by Derek Mauro : Use more precise wording in the question on live-at-head PiperOrigin-RevId: 294957679 GitOrigin-RevId: 97faa5fdfa4cd5d7a74cd9332cddd8a7c1e67b89 Change-Id: I081e70d148ffac7296d65e2a2f775f643eaf70bf --- absl/strings/cord.h | 1121 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1121 insertions(+) create mode 100644 absl/strings/cord.h (limited to 'absl/strings/cord.h') diff --git a/absl/strings/cord.h b/absl/strings/cord.h new file mode 100644 index 00000000..40566cba --- /dev/null +++ b/absl/strings/cord.h @@ -0,0 +1,1121 @@ +// Copyright 2020 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// A Cord is a sequence of characters with some unusual access propreties. +// A Cord supports efficient insertions and deletions at the start and end of +// the byte sequence, but random access reads are slower, and random access +// modifications are not supported by the API. Cord also provides cheap copies +// (using a copy-on-write strategy) and cheap substring operations. +// +// Thread safety +// ------------- +// Cord has the same thread-safety properties as many other types like +// std::string, std::vector<>, int, etc -- it is thread-compatible. In +// particular, if no thread may call a non-const method, then it is safe to +// concurrently call const methods. Copying a Cord produces a new instance that +// can be used concurrently with the original in arbitrary ways. +// +// Implementation is similar to the "Ropes" described in: +// Ropes: An alternative to strings +// Hans J. Boehm, Russ Atkinson, Michael Plass +// Software Practice and Experience, December 1995 + +#ifndef ABSL_STRINGS_CORD_H_ +#define ABSL_STRINGS_CORD_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/base/internal/endian.h" +#include "absl/base/internal/invoke.h" +#include "absl/base/internal/per_thread_tls.h" +#include "absl/base/macros.h" +#include "absl/base/port.h" +#include "absl/container/inlined_vector.h" +#include "absl/functional/function_ref.h" +#include "absl/meta/type_traits.h" +#include "absl/strings/internal/cord_internal.h" +#include "absl/strings/internal/resize_uninitialized.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +class Cord; +class CordTestPeer; +template +Cord MakeCordFromExternal(absl::string_view, Releaser&&); +void CopyCordToString(const Cord& src, std::string* dst); +namespace hash_internal { +template +H HashFragmentedCord(H, const Cord&); +} + +// A Cord is a sequence of characters. +class Cord { + private: + template + using EnableIfString = + absl::enable_if_t::value, int>; + + public: + // -------------------------------------------------------------------- + // Constructors, destructors and helper factories + + // Create an empty cord + constexpr Cord() noexcept; + + // Cord is copyable and efficiently movable. + // The moved-from state is valid but unspecified. + Cord(const Cord& src); + Cord(Cord&& src) noexcept; + Cord& operator=(const Cord& x); + Cord& operator=(Cord&& x) noexcept; + + // Create a cord out of "src". This constructor is explicit on + // purpose so that people do not get automatic type conversions. + explicit Cord(absl::string_view src); + Cord& operator=(absl::string_view src); + + // These are templated to avoid ambiguities for types that are convertible to + // both `absl::string_view` and `std::string`, such as `const char*`. + // + // Note that these functions reserve the right to reuse the `string&&`'s + // memory and that they will do so in the future. + template = 0> + explicit Cord(T&& src) : Cord(absl::string_view(src)) {} + template = 0> + Cord& operator=(T&& src); + + // Destroy the cord + ~Cord() { + if (contents_.is_tree()) DestroyCordSlow(); + } + + // Creates a Cord that takes ownership of external memory. The contents of + // `data` are not copied. + // + // This function takes a callable that is invoked when all Cords are + // finished with `data`. The data must remain live and unchanging until the + // releaser is called. The requirements for the releaser are that it: + // * is move constructible, + // * supports `void operator()(absl::string_view) const`, + // * does not have alignment requirement greater than what is guaranteed by + // ::operator new. This is dictated by alignof(std::max_align_t) before + // C++17 and __STDCPP_DEFAULT_NEW_ALIGNMENT__ if compiling with C++17 or + // it is supported by the implementation. + // + // Example: + // + // Cord MakeCord(BlockPool* pool) { + // Block* block = pool->NewBlock(); + // FillBlock(block); + // return absl::MakeCordFromExternal( + // block->ToStringView(), + // [pool, block](absl::string_view /*ignored*/) { + // pool->FreeBlock(block); + // }); + // } + // + // WARNING: It's likely a bug if your releaser doesn't do anything. + // For example, consider the following: + // + // void Foo(const char* buffer, int len) { + // auto c = absl::MakeCordFromExternal(absl::string_view(buffer, len), + // [](absl::string_view) {}); + // + // // BUG: If Bar() copies its cord for any reason, including keeping a + // // substring of it, the lifetime of buffer might be extended beyond + // // when Foo() returns. + // Bar(c); + // } + template + friend Cord MakeCordFromExternal(absl::string_view data, Releaser&& releaser); + + // -------------------------------------------------------------------- + // Mutations + + void Clear(); + + void Append(const Cord& src); + void Append(Cord&& src); + void Append(absl::string_view src); + template = 0> + void Append(T&& src); + + void Prepend(const Cord& src); + void Prepend(absl::string_view src); + template = 0> + void Prepend(T&& src); + + void RemovePrefix(size_t n); + void RemoveSuffix(size_t n); + + // Returns a new cord representing the subrange [pos, pos + new_size) of + // *this. If pos >= size(), the result is empty(). If + // (pos + new_size) >= size(), the result is the subrange [pos, size()). + Cord Subcord(size_t pos, size_t new_size) const; + + friend void swap(Cord& x, Cord& y) noexcept; + + // -------------------------------------------------------------------- + // Accessors + + size_t size() const; + bool empty() const; + + // Returns the approximate number of bytes pinned by this Cord. Note that + // Cords that share memory could each be "charged" independently for the same + // shared memory. + size_t EstimatedMemoryUsage() const; + + // -------------------------------------------------------------------- + // Comparators + + // Compares 'this' Cord with rhs. This function and its relatives + // treat Cords as sequences of unsigned bytes. The comparison is a + // straightforward lexicographic comparison. Return value: + // -1 'this' Cord is smaller + // 0 two Cords are equal + // 1 'this' Cord is larger + int Compare(absl::string_view rhs) const; + int Compare(const Cord& rhs) const; + + // Does 'this' cord start/end with rhs + bool StartsWith(const Cord& rhs) const; + bool StartsWith(absl::string_view rhs) const; + bool EndsWith(absl::string_view rhs) const; + bool EndsWith(const Cord& rhs) const; + + // -------------------------------------------------------------------- + // Conversion to other types + + explicit operator std::string() const; + + // Copies the contents from `src` to `*dst`. + // + // This function optimizes the case of reusing the destination std::string since it + // can reuse previously allocated capacity. However, this function does not + // guarantee that pointers previously returned by `dst->data()` remain valid + // even if `*dst` had enough capacity to hold `src`. If `*dst` is a new + // object, prefer to simply use the conversion operator to `std::string`. + friend void CopyCordToString(const Cord& src, std::string* dst); + + // -------------------------------------------------------------------- + // Iteration + + class CharIterator; + + // Type for iterating over the chunks of a `Cord`. See comments for + // `Cord::chunk_begin()`, `Cord::chunk_end()` and `Cord::Chunks()` below for + // preferred usage. + // + // Additional notes: + // * The `string_view` returned by dereferencing a valid, non-`end()` + // iterator is guaranteed to be non-empty. + // * A `ChunkIterator` object is invalidated after any non-const + // operation on the `Cord` object over which it iterates. + // * Two `ChunkIterator` objects can be equality compared if and only if + // they remain valid and iterate over the same `Cord`. + // * This is a proxy iterator. This means the `string_view` returned by the + // iterator does not live inside the Cord, and its lifetime is limited to + // the lifetime of the iterator itself. To help prevent issues, + // `ChunkIterator::reference` is not a true reference type and is + // equivalent to `value_type`. + // * The iterator keeps state that can grow for `Cord`s that contain many + // nodes and are imbalanced due to sharing. Prefer to pass this type by + // const reference instead of by value. + class ChunkIterator { + public: + using iterator_category = std::input_iterator_tag; + using value_type = absl::string_view; + using difference_type = ptrdiff_t; + using pointer = const value_type*; + using reference = value_type; + + ChunkIterator() = default; + + ChunkIterator& operator++(); + ChunkIterator operator++(int); + bool operator==(const ChunkIterator& other) const; + bool operator!=(const ChunkIterator& other) const; + reference operator*() const; + pointer operator->() const; + + friend class Cord; + friend class CharIterator; + + private: + // Constructs a `begin()` iterator from `cord`. + explicit ChunkIterator(const Cord* cord); + + // Removes `n` bytes from `current_chunk_`. Expects `n` to be smaller than + // `current_chunk_.size()`. + void RemoveChunkPrefix(size_t n); + Cord AdvanceAndReadBytes(size_t n); + void AdvanceBytes(size_t n); + // Iterates `n` bytes, where `n` is expected to be greater than or equal to + // `current_chunk_.size()`. + void AdvanceBytesSlowPath(size_t n); + + // A view into bytes of the current `CordRep`. It may only be a view to a + // suffix of bytes if this is being used by `CharIterator`. + absl::string_view current_chunk_; + // The current leaf, or `nullptr` if the iterator points to short data. + // If the current chunk is a substring node, current_leaf_ points to the + // underlying flat or external node. + absl::cord_internal::CordRep* current_leaf_ = nullptr; + // The number of bytes left in the `Cord` over which we are iterating. + size_t bytes_remaining_ = 0; + absl::InlinedVector + stack_of_right_children_; + }; + + // Returns an iterator to the first chunk of the `Cord`. + // + // This is useful for getting a `ChunkIterator` outside the context of a + // range-based for-loop (in which case see `Cord::Chunks()` below). + // + // Example: + // + // absl::Cord::ChunkIterator FindAsChunk(const absl::Cord& c, + // absl::string_view s) { + // return std::find(c.chunk_begin(), c.chunk_end(), s); + // } + ChunkIterator chunk_begin() const; + // Returns an iterator one increment past the last chunk of the `Cord`. + ChunkIterator chunk_end() const; + + // Convenience wrapper over `Cord::chunk_begin()` and `Cord::chunk_end()` to + // enable range-based for-loop iteration over `Cord` chunks. + // + // Prefer to use `Cord::Chunks()` below instead of constructing this directly. + class ChunkRange { + public: + explicit ChunkRange(const Cord* cord) : cord_(cord) {} + + ChunkIterator begin() const; + ChunkIterator end() const; + + private: + const Cord* cord_; + }; + + // Returns a range for iterating over the chunks of a `Cord` with a + // range-based for-loop. + // + // Example: + // + // void ProcessChunks(const Cord& cord) { + // for (absl::string_view chunk : cord.Chunks()) { ... } + // } + // + // Note that the ordinary caveats of temporary lifetime extension apply: + // + // void Process() { + // for (absl::string_view chunk : CordFactory().Chunks()) { + // // The temporary Cord returned by CordFactory has been destroyed! + // } + // } + ChunkRange Chunks() const; + + // Type for iterating over the characters of a `Cord`. See comments for + // `Cord::char_begin()`, `Cord::char_end()` and `Cord::Chars()` below for + // preferred usage. + // + // Additional notes: + // * A `CharIterator` object is invalidated after any non-const + // operation on the `Cord` object over which it iterates. + // * Two `CharIterator` objects can be equality compared if and only if + // they remain valid and iterate over the same `Cord`. + // * The iterator keeps state that can grow for `Cord`s that contain many + // nodes and are imbalanced due to sharing. Prefer to pass this type by + // const reference instead of by value. + // * This type cannot be a forward iterator because a `Cord` can reuse + // sections of memory. This violates the requirement that if dereferencing + // two iterators returns the same object, the iterators must compare + // equal. + class CharIterator { + public: + using iterator_category = std::input_iterator_tag; + using value_type = char; + using difference_type = ptrdiff_t; + using pointer = const char*; + using reference = const char&; + + CharIterator() = default; + + CharIterator& operator++(); + CharIterator operator++(int); + bool operator==(const CharIterator& other) const; + bool operator!=(const CharIterator& other) const; + reference operator*() const; + pointer operator->() const; + + friend Cord; + + private: + explicit CharIterator(const Cord* cord) : chunk_iterator_(cord) {} + + ChunkIterator chunk_iterator_; + }; + + // Advances `*it` by `n_bytes` and returns the bytes passed as a `Cord`. + // + // `n_bytes` must be less than or equal to the number of bytes remaining for + // iteration. Otherwise the behavior is undefined. It is valid to pass + // `char_end()` and 0. + static Cord AdvanceAndRead(CharIterator* it, size_t n_bytes); + + // Advances `*it` by `n_bytes`. + // + // `n_bytes` must be less than or equal to the number of bytes remaining for + // iteration. Otherwise the behavior is undefined. It is valid to pass + // `char_end()` and 0. + static void Advance(CharIterator* it, size_t n_bytes); + + // Returns the longest contiguous view starting at the iterator's position. + // + // `it` must be dereferenceable. + static absl::string_view ChunkRemaining(const CharIterator& it); + + // Returns an iterator to the first character of the `Cord`. + CharIterator char_begin() const; + // Returns an iterator to one past the last character of the `Cord`. + CharIterator char_end() const; + + // Convenience wrapper over `Cord::char_begin()` and `Cord::char_end()` to + // enable range-based for-loop iterator over the characters of a `Cord`. + // + // Prefer to use `Cord::Chars()` below instead of constructing this directly. + class CharRange { + public: + explicit CharRange(const Cord* cord) : cord_(cord) {} + + CharIterator begin() const; + CharIterator end() const; + + private: + const Cord* cord_; + }; + + // Returns a range for iterating over the characters of a `Cord` with a + // range-based for-loop. + // + // Example: + // + // void ProcessCord(const Cord& cord) { + // for (char c : cord.Chars()) { ... } + // } + // + // Note that the ordinary caveats of temporary lifetime extension apply: + // + // void Process() { + // for (char c : CordFactory().Chars()) { + // // The temporary Cord returned by CordFactory has been destroyed! + // } + // } + CharRange Chars() const; + + // -------------------------------------------------------------------- + // Miscellaneous + + // Get the "i"th character of 'this' and return it. + // NOTE: This routine is reasonably efficient. It is roughly + // logarithmic in the number of nodes that make up the cord. Still, + // if you need to iterate over the contents of a cord, you should + // use a CharIterator/CordIterator rather than call operator[] or Get() + // repeatedly in a loop. + // + // REQUIRES: 0 <= i < size() + char operator[](size_t i) const; + + // Flattens the cord into a single array and returns a view of the data. + // + // If the cord was already flat, the contents are not modified. + absl::string_view Flatten(); + + private: + friend class CordTestPeer; + template + friend H absl::hash_internal::HashFragmentedCord(H, const Cord&); + friend bool operator==(const Cord& lhs, const Cord& rhs); + friend bool operator==(const Cord& lhs, absl::string_view rhs); + + // Call the provided function once for each cord chunk, in order. Unlike + // Chunks(), this API will not allocate memory. + void ForEachChunk(absl::FunctionRef) const; + + // Allocates new contiguous storage for the contents of the cord. This is + // called by Flatten() when the cord was not already flat. + absl::string_view FlattenSlowPath(); + + // Actual cord contents are hidden inside the following simple + // class so that we can isolate the bulk of cord.cc from changes + // to the representation. + // + // InlineRep holds either either a tree pointer, or an array of kMaxInline + // bytes. + class InlineRep { + public: + static const unsigned char kMaxInline = 15; + static_assert(kMaxInline >= sizeof(absl::cord_internal::CordRep*), ""); + // Tag byte & kMaxInline means we are storing a pointer. + static const unsigned char kTreeFlag = 1 << 4; + // Tag byte & kProfiledFlag means we are profiling the Cord. + static const unsigned char kProfiledFlag = 1 << 5; + + constexpr InlineRep() : data_{} {} + InlineRep(const InlineRep& src); + InlineRep(InlineRep&& src); + InlineRep& operator=(const InlineRep& src); + InlineRep& operator=(InlineRep&& src) noexcept; + + void Swap(InlineRep* rhs); + bool empty() const; + size_t size() const; + const char* data() const; // Returns nullptr if holding pointer + void set_data(const char* data, size_t n, + bool nullify_tail); // Discards pointer, if any + char* set_data(size_t n); // Write data to the result + // Returns nullptr if holding bytes + absl::cord_internal::CordRep* tree() const; + // Discards old pointer, if any + void set_tree(absl::cord_internal::CordRep* rep); + // Replaces a tree with a new root. This is faster than set_tree, but it + // should only be used when it's clear that the old rep was a tree. + void replace_tree(absl::cord_internal::CordRep* rep); + // Returns non-null iff was holding a pointer + absl::cord_internal::CordRep* clear(); + // Convert to pointer if necessary + absl::cord_internal::CordRep* force_tree(size_t extra_hint); + void reduce_size(size_t n); // REQUIRES: holding data + void remove_prefix(size_t n); // REQUIRES: holding data + void AppendArray(const char* src_data, size_t src_size); + absl::string_view FindFlatStartPiece() const; + void AppendTree(absl::cord_internal::CordRep* tree); + void PrependTree(absl::cord_internal::CordRep* tree); + void GetAppendRegion(char** region, size_t* size, size_t max_length); + void GetAppendRegion(char** region, size_t* size); + bool IsSame(const InlineRep& other) const { + return memcmp(data_, other.data_, sizeof(data_)) == 0; + } + int BitwiseCompare(const InlineRep& other) const { + uint64_t x, y; + // Use memcpy to avoid anti-aliasing issues. + memcpy(&x, data_, sizeof(x)); + memcpy(&y, other.data_, sizeof(y)); + if (x == y) { + memcpy(&x, data_ + 8, sizeof(x)); + memcpy(&y, other.data_ + 8, sizeof(y)); + if (x == y) return 0; + } + return absl::big_endian::FromHost64(x) < absl::big_endian::FromHost64(y) + ? -1 + : 1; + } + void CopyTo(std::string* dst) const { + // memcpy is much faster when operating on a known size. On most supported + // platforms, the small std::string optimization is large enough that resizing + // to 15 bytes does not cause a memory allocation. + absl::strings_internal::STLStringResizeUninitialized(dst, + sizeof(data_) - 1); + memcpy(&(*dst)[0], data_, sizeof(data_) - 1); + // erase is faster than resize because the logic for memory allocation is + // not needed. + dst->erase(data_[kMaxInline]); + } + + // Copies the inline contents into `dst`. Assumes the cord is not empty. + void CopyToArray(char* dst) const; + + bool is_tree() const { return data_[kMaxInline] > kMaxInline; } + + private: + friend class Cord; + + void AssignSlow(const InlineRep& src); + // Unrefs the tree, stops profiling, and zeroes the contents + void ClearSlow(); + + // If the data has length <= kMaxInline, we store it in data_[0..len-1], + // and store the length in data_[kMaxInline]. Else we store it in a tree + // and store a pointer to that tree in data_[0..sizeof(CordRep*)-1]. + alignas(absl::cord_internal::CordRep*) char data_[kMaxInline + 1]; + }; + InlineRep contents_; + + // Helper for MemoryUsage() + static size_t MemoryUsageAux(const absl::cord_internal::CordRep* rep); + + // Helper for GetFlat() + static bool GetFlatAux(absl::cord_internal::CordRep* rep, + absl::string_view* fragment); + + // Helper for ForEachChunk() + static void ForEachChunkAux( + absl::cord_internal::CordRep* rep, + absl::FunctionRef callback); + + // The destructor for non-empty Cords. + void DestroyCordSlow(); + + // Out-of-line implementation of slower parts of logic. + void CopyToArraySlowPath(char* dst) const; + int CompareSlowPath(absl::string_view rhs, size_t compared_size, + size_t size_to_compare) const; + int CompareSlowPath(const Cord& rhs, size_t compared_size, + size_t size_to_compare) const; + bool EqualsImpl(absl::string_view rhs, size_t size_to_compare) const; + bool EqualsImpl(const Cord& rhs, size_t size_to_compare) const; + int CompareImpl(const Cord& rhs) const; + + template + friend ResultType GenericCompare(const Cord& lhs, const RHS& rhs, + size_t size_to_compare); + static absl::string_view GetFirstChunk(const Cord& c); + static absl::string_view GetFirstChunk(absl::string_view sv); + + // Returns a new reference to contents_.tree(), or steals an existing + // reference if called on an rvalue. + absl::cord_internal::CordRep* TakeRep() const&; + absl::cord_internal::CordRep* TakeRep() &&; + + // Helper for Append() + template + void AppendImpl(C&& src); +}; + +ABSL_NAMESPACE_END +} // namespace absl + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// allow a Cord to be logged +extern std::ostream& operator<<(std::ostream& out, const Cord& cord); + +// ------------------------------------------------------------------ +// Internal details follow. Clients should ignore. + +namespace cord_internal { + +// Fast implementation of memmove for up to 15 bytes. This implementation is +// safe for overlapping regions. If nullify_tail is true, the destination is +// padded with '\0' up to 16 bytes. +inline void SmallMemmove(char* dst, const char* src, size_t n, + bool nullify_tail = false) { + if (n >= 8) { + assert(n <= 16); + uint64_t buf1; + uint64_t buf2; + memcpy(&buf1, src, 8); + memcpy(&buf2, src + n - 8, 8); + if (nullify_tail) { + memset(dst + 8, 0, 8); + } + memcpy(dst, &buf1, 8); + memcpy(dst + n - 8, &buf2, 8); + } else if (n >= 4) { + uint32_t buf1; + uint32_t buf2; + memcpy(&buf1, src, 4); + memcpy(&buf2, src + n - 4, 4); + if (nullify_tail) { + memset(dst + 4, 0, 4); + memset(dst + 8, 0, 8); + } + memcpy(dst, &buf1, 4); + memcpy(dst + n - 4, &buf2, 4); + } else { + if (n != 0) { + dst[0] = src[0]; + dst[n / 2] = src[n / 2]; + dst[n - 1] = src[n - 1]; + } + if (nullify_tail) { + memset(dst + 8, 0, 8); + memset(dst + n, 0, 8); + } + } +} + +struct ExternalRepReleaserPair { + CordRep* rep; + void* releaser_address; +}; + +// Allocates a new external `CordRep` and returns a pointer to it and a pointer +// to `releaser_size` bytes where the desired releaser can be constructed. +// Expects `data` to be non-empty. +ExternalRepReleaserPair NewExternalWithUninitializedReleaser( + absl::string_view data, ExternalReleaserInvoker invoker, + size_t releaser_size); + +// Creates a new `CordRep` that owns `data` and `releaser` and returns a pointer +// to it, or `nullptr` if `data` was empty. +template +// NOLINTNEXTLINE - suppress clang-tidy raw pointer return. +CordRep* NewExternalRep(absl::string_view data, Releaser&& releaser) { + static_assert( +#if defined(__STDCPP_DEFAULT_NEW_ALIGNMENT__) + alignof(Releaser) <= __STDCPP_DEFAULT_NEW_ALIGNMENT__, +#else + alignof(Releaser) <= alignof(max_align_t), +#endif + "Releasers with alignment requirement greater than what is returned by " + "default `::operator new()` are not supported."); + + using ReleaserType = absl::decay_t; + if (data.empty()) { + // Never create empty external nodes. + ::absl::base_internal::Invoke( + ReleaserType(std::forward(releaser)), data); + return nullptr; + } + + auto releaser_invoker = [](void* type_erased_releaser, absl::string_view d) { + auto* my_releaser = static_cast(type_erased_releaser); + ::absl::base_internal::Invoke(std::move(*my_releaser), d); + my_releaser->~ReleaserType(); + return sizeof(Releaser); + }; + + ExternalRepReleaserPair external = NewExternalWithUninitializedReleaser( + data, releaser_invoker, sizeof(releaser)); + ::new (external.releaser_address) + ReleaserType(std::forward(releaser)); + return external.rep; +} + +// Overload for function reference types that dispatches using a function +// pointer because there are no `alignof()` or `sizeof()` a function reference. +// NOLINTNEXTLINE - suppress clang-tidy raw pointer return. +inline CordRep* NewExternalRep(absl::string_view data, + void (&releaser)(absl::string_view)) { + return NewExternalRep(data, &releaser); +} + +} // namespace cord_internal + +template +Cord MakeCordFromExternal(absl::string_view data, Releaser&& releaser) { + Cord cord; + cord.contents_.set_tree(::absl::cord_internal::NewExternalRep( + data, std::forward(releaser))); + return cord; +} + +inline Cord::InlineRep::InlineRep(const Cord::InlineRep& src) { + cord_internal::SmallMemmove(data_, src.data_, sizeof(data_)); +} + +inline Cord::InlineRep::InlineRep(Cord::InlineRep&& src) { + memcpy(data_, src.data_, sizeof(data_)); + memset(src.data_, 0, sizeof(data_)); +} + +inline Cord::InlineRep& Cord::InlineRep::operator=(const Cord::InlineRep& src) { + if (this == &src) { + return *this; + } + if (!is_tree() && !src.is_tree()) { + cord_internal::SmallMemmove(data_, src.data_, sizeof(data_)); + return *this; + } + AssignSlow(src); + return *this; +} + +inline Cord::InlineRep& Cord::InlineRep::operator=( + Cord::InlineRep&& src) noexcept { + if (is_tree()) { + ClearSlow(); + } + memcpy(data_, src.data_, sizeof(data_)); + memset(src.data_, 0, sizeof(data_)); + return *this; +} + +inline void Cord::InlineRep::Swap(Cord::InlineRep* rhs) { + if (rhs == this) { + return; + } + + Cord::InlineRep tmp; + cord_internal::SmallMemmove(tmp.data_, data_, sizeof(data_)); + cord_internal::SmallMemmove(data_, rhs->data_, sizeof(data_)); + cord_internal::SmallMemmove(rhs->data_, tmp.data_, sizeof(data_)); +} + +inline const char* Cord::InlineRep::data() const { + return is_tree() ? nullptr : data_; +} + +inline absl::cord_internal::CordRep* Cord::InlineRep::tree() const { + if (is_tree()) { + absl::cord_internal::CordRep* rep; + memcpy(&rep, data_, sizeof(rep)); + return rep; + } else { + return nullptr; + } +} + +inline bool Cord::InlineRep::empty() const { return data_[kMaxInline] == 0; } + +inline size_t Cord::InlineRep::size() const { + const char tag = data_[kMaxInline]; + if (tag <= kMaxInline) return tag; + return static_cast(tree()->length); +} + +inline void Cord::InlineRep::set_tree(absl::cord_internal::CordRep* rep) { + if (rep == nullptr) { + memset(data_, 0, sizeof(data_)); + } else { + bool was_tree = is_tree(); + memcpy(data_, &rep, sizeof(rep)); + memset(data_ + sizeof(rep), 0, sizeof(data_) - sizeof(rep) - 1); + if (!was_tree) { + data_[kMaxInline] = kTreeFlag; + } + } +} + +inline void Cord::InlineRep::replace_tree(absl::cord_internal::CordRep* rep) { + ABSL_ASSERT(is_tree()); + if (ABSL_PREDICT_FALSE(rep == nullptr)) { + set_tree(rep); + return; + } + memcpy(data_, &rep, sizeof(rep)); + memset(data_ + sizeof(rep), 0, sizeof(data_) - sizeof(rep) - 1); +} + +inline absl::cord_internal::CordRep* Cord::InlineRep::clear() { + const char tag = data_[kMaxInline]; + absl::cord_internal::CordRep* result = nullptr; + if (tag > kMaxInline) { + memcpy(&result, data_, sizeof(result)); + } + memset(data_, 0, sizeof(data_)); // Clear the cord + return result; +} + +inline void Cord::InlineRep::CopyToArray(char* dst) const { + assert(!is_tree()); + size_t n = data_[kMaxInline]; + assert(n != 0); + cord_internal::SmallMemmove(dst, data_, n); +} + +constexpr inline Cord::Cord() noexcept {} + +inline Cord& Cord::operator=(const Cord& x) { + contents_ = x.contents_; + return *this; +} + +inline Cord::Cord(Cord&& src) noexcept : contents_(std::move(src.contents_)) {} + +inline Cord& Cord::operator=(Cord&& x) noexcept { + contents_ = std::move(x.contents_); + return *this; +} + +template > +inline Cord& Cord::operator=(T&& src) { + *this = absl::string_view(src); + return *this; +} + +inline size_t Cord::size() const { + // Length is 1st field in str.rep_ + return contents_.size(); +} + +inline bool Cord::empty() const { return contents_.empty(); } + +inline size_t Cord::EstimatedMemoryUsage() const { + size_t result = sizeof(Cord); + if (const absl::cord_internal::CordRep* rep = contents_.tree()) { + result += MemoryUsageAux(rep); + } + return result; +} + +inline absl::string_view Cord::Flatten() { + absl::cord_internal::CordRep* rep = contents_.tree(); + if (rep == nullptr) { + return absl::string_view(contents_.data(), contents_.size()); + } else { + absl::string_view already_flat_contents; + if (GetFlatAux(rep, &already_flat_contents)) { + return already_flat_contents; + } + } + return FlattenSlowPath(); +} + +inline void Cord::Append(absl::string_view src) { + contents_.AppendArray(src.data(), src.size()); +} + +template > +inline void Cord::Append(T&& src) { + // Note that this function reserves the right to reuse the `string&&`'s + // memory and that it will do so in the future. + Append(absl::string_view(src)); +} + +template > +inline void Cord::Prepend(T&& src) { + // Note that this function reserves the right to reuse the `string&&`'s + // memory and that it will do so in the future. + Prepend(absl::string_view(src)); +} + +inline int Cord::Compare(const Cord& rhs) const { + if (!contents_.is_tree() && !rhs.contents_.is_tree()) { + return contents_.BitwiseCompare(rhs.contents_); + } + + return CompareImpl(rhs); +} + +// Does 'this' cord start/end with rhs +inline bool Cord::StartsWith(const Cord& rhs) const { + if (contents_.IsSame(rhs.contents_)) return true; + size_t rhs_size = rhs.size(); + if (size() < rhs_size) return false; + return EqualsImpl(rhs, rhs_size); +} + +inline bool Cord::StartsWith(absl::string_view rhs) const { + size_t rhs_size = rhs.size(); + if (size() < rhs_size) return false; + return EqualsImpl(rhs, rhs_size); +} + +inline Cord::ChunkIterator::ChunkIterator(const Cord* cord) + : bytes_remaining_(cord->size()) { + if (cord->empty()) return; + if (cord->contents_.is_tree()) { + stack_of_right_children_.push_back(cord->contents_.tree()); + operator++(); + } else { + current_chunk_ = absl::string_view(cord->contents_.data(), cord->size()); + } +} + +inline Cord::ChunkIterator Cord::ChunkIterator::operator++(int) { + ChunkIterator tmp(*this); + operator++(); + return tmp; +} + +inline bool Cord::ChunkIterator::operator==(const ChunkIterator& other) const { + return bytes_remaining_ == other.bytes_remaining_; +} + +inline bool Cord::ChunkIterator::operator!=(const ChunkIterator& other) const { + return !(*this == other); +} + +inline Cord::ChunkIterator::reference Cord::ChunkIterator::operator*() const { + assert(bytes_remaining_ != 0); + return current_chunk_; +} + +inline Cord::ChunkIterator::pointer Cord::ChunkIterator::operator->() const { + assert(bytes_remaining_ != 0); + return ¤t_chunk_; +} + +inline void Cord::ChunkIterator::RemoveChunkPrefix(size_t n) { + assert(n < current_chunk_.size()); + current_chunk_.remove_prefix(n); + bytes_remaining_ -= n; +} + +inline void Cord::ChunkIterator::AdvanceBytes(size_t n) { + if (ABSL_PREDICT_TRUE(n < current_chunk_.size())) { + RemoveChunkPrefix(n); + } else if (n != 0) { + AdvanceBytesSlowPath(n); + } +} + +inline Cord::ChunkIterator Cord::chunk_begin() const { + return ChunkIterator(this); +} + +inline Cord::ChunkIterator Cord::chunk_end() const { return ChunkIterator(); } + +inline Cord::ChunkIterator Cord::ChunkRange::begin() const { + return cord_->chunk_begin(); +} + +inline Cord::ChunkIterator Cord::ChunkRange::end() const { + return cord_->chunk_end(); +} + +inline Cord::ChunkRange Cord::Chunks() const { return ChunkRange(this); } + +inline Cord::CharIterator& Cord::CharIterator::operator++() { + if (ABSL_PREDICT_TRUE(chunk_iterator_->size() > 1)) { + chunk_iterator_.RemoveChunkPrefix(1); + } else { + ++chunk_iterator_; + } + return *this; +} + +inline Cord::CharIterator Cord::CharIterator::operator++(int) { + CharIterator tmp(*this); + operator++(); + return tmp; +} + +inline bool Cord::CharIterator::operator==(const CharIterator& other) const { + return chunk_iterator_ == other.chunk_iterator_; +} + +inline bool Cord::CharIterator::operator!=(const CharIterator& other) const { + return !(*this == other); +} + +inline Cord::CharIterator::reference Cord::CharIterator::operator*() const { + return *chunk_iterator_->data(); +} + +inline Cord::CharIterator::pointer Cord::CharIterator::operator->() const { + return chunk_iterator_->data(); +} + +inline Cord Cord::AdvanceAndRead(CharIterator* it, size_t n_bytes) { + assert(it != nullptr); + return it->chunk_iterator_.AdvanceAndReadBytes(n_bytes); +} + +inline void Cord::Advance(CharIterator* it, size_t n_bytes) { + assert(it != nullptr); + it->chunk_iterator_.AdvanceBytes(n_bytes); +} + +inline absl::string_view Cord::ChunkRemaining(const CharIterator& it) { + return *it.chunk_iterator_; +} + +inline Cord::CharIterator Cord::char_begin() const { + return CharIterator(this); +} + +inline Cord::CharIterator Cord::char_end() const { return CharIterator(); } + +inline Cord::CharIterator Cord::CharRange::begin() const { + return cord_->char_begin(); +} + +inline Cord::CharIterator Cord::CharRange::end() const { + return cord_->char_end(); +} + +inline Cord::CharRange Cord::Chars() const { return CharRange(this); } + +inline void Cord::ForEachChunk( + absl::FunctionRef callback) const { + absl::cord_internal::CordRep* rep = contents_.tree(); + if (rep == nullptr) { + callback(absl::string_view(contents_.data(), contents_.size())); + } else { + return ForEachChunkAux(rep, callback); + } +} + +// Nonmember Cord-to-Cord relational operarators. +inline bool operator==(const Cord& lhs, const Cord& rhs) { + if (lhs.contents_.IsSame(rhs.contents_)) return true; + size_t rhs_size = rhs.size(); + if (lhs.size() != rhs_size) return false; + return lhs.EqualsImpl(rhs, rhs_size); +} + +inline bool operator!=(const Cord& x, const Cord& y) { return !(x == y); } +inline bool operator<(const Cord& x, const Cord& y) { + return x.Compare(y) < 0; +} +inline bool operator>(const Cord& x, const Cord& y) { + return x.Compare(y) > 0; +} +inline bool operator<=(const Cord& x, const Cord& y) { + return x.Compare(y) <= 0; +} +inline bool operator>=(const Cord& x, const Cord& y) { + return x.Compare(y) >= 0; +} + +// Nonmember Cord-to-absl::string_view relational operators. +// +// Due to implicit conversions, these also enable comparisons of Cord with +// with std::string, ::string, and const char*. +inline bool operator==(const Cord& lhs, absl::string_view rhs) { + size_t lhs_size = lhs.size(); + size_t rhs_size = rhs.size(); + if (lhs_size != rhs_size) return false; + return lhs.EqualsImpl(rhs, rhs_size); +} + +inline bool operator==(absl::string_view x, const Cord& y) { return y == x; } +inline bool operator!=(const Cord& x, absl::string_view y) { return !(x == y); } +inline bool operator!=(absl::string_view x, const Cord& y) { return !(x == y); } +inline bool operator<(const Cord& x, absl::string_view y) { + return x.Compare(y) < 0; +} +inline bool operator<(absl::string_view x, const Cord& y) { + return y.Compare(x) > 0; +} +inline bool operator>(const Cord& x, absl::string_view y) { return y < x; } +inline bool operator>(absl::string_view x, const Cord& y) { return y < x; } +inline bool operator<=(const Cord& x, absl::string_view y) { return !(y < x); } +inline bool operator<=(absl::string_view x, const Cord& y) { return !(y < x); } +inline bool operator>=(const Cord& x, absl::string_view y) { return !(x < y); } +inline bool operator>=(absl::string_view x, const Cord& y) { return !(x < y); } + +// Overload of swap for Cord. The use of non-const references is +// required. :( +inline void swap(Cord& x, Cord& y) noexcept { y.contents_.Swap(&x.contents_); } + +// Some internals exposed to test code. +namespace strings_internal { +class CordTestAccess { + public: + static size_t FlatOverhead(); + static size_t MaxFlatLength(); + static size_t SizeofCordRepConcat(); + static size_t SizeofCordRepExternal(); + static size_t SizeofCordRepSubstring(); + static size_t FlatTagToLength(uint8_t tag); + static uint8_t LengthToTag(size_t s); +}; +} // namespace strings_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_CORD_H_ -- cgit v1.2.3 From b19ba96766db08b1f32605cb4424a0e7ea0c7584 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Tue, 3 Mar 2020 11:22:10 -0800 Subject: Export of internal Abseil changes -- a3e58c1870a9626039f4d178d2d599319bd9f8a8 by Matt Kulukundis : Allow MakeCordFromExternal to take a zero arg releaser. PiperOrigin-RevId: 298650274 -- 01897c4a9bb99f3dc329a794019498ad345ddebd by Samuel Benzaquen : Reduce library bloat for absl::Flag by moving the definition of base virtual functions to a .cc file. This removes the duplicate symbols in user translation units and has the side effect of moving the vtable definition too (re key function) PiperOrigin-RevId: 298617920 -- 190f0d3782c63aed01046886d7fbc1be5bca2de9 by Derek Mauro : Import GitHub #596: Unbreak stacktrace code for UWP apps PiperOrigin-RevId: 298600834 -- cd5cf6f8c87b35b85a9584e94da2a99057345b73 by Gennadiy Rozental : Use union of heap allocated pointer, one word atomic and two word atomic to represent flags value. Any type T, which is trivially copy-able and with with sizeof(T) <= 8, will be stored in atomic int64_t. Any type T, which is trivially copy-able and with with 8 < sizeof(T) <= 16, will be stored in atomic AlignedTwoWords. We also introducing value storage type to distinguish these cases. PiperOrigin-RevId: 298497200 -- f8fe7bd53bfed601f002f521e34ab4bc083fc28b by Matthew Brown : Ensure a deep copy and proper equality on absl::Status::ErasePayload PiperOrigin-RevId: 298482742 -- a5c9ccddf4b04f444e3f7e27dbc14faf1fcb5373 by Gennadiy Rozental : Change ChunkIterator implementation to use fixed capacity collection of CordRep*. We can now assume that depth never exceeds 91. That makes comparison operator exception safe. I've tested that with this CL we do not observe an overhead of chunk_end. Compiler optimized this iterator completely. PiperOrigin-RevId: 298458472 -- 327ea5e8910bc388b03389c730763f9823abfce5 by Abseil Team : Minor cleanups in b-tree code: - Rename some variables: fix issues of different param names between definition/declaration, move away from `x` as a default meaningless variable name. - Make init_leaf/init_internal be non-static methods (they already take the node as the first parameter). - In internal_emplace/try_shrink, update root/rightmost the same way as in insert_unique/insert_multi. - Replace a TODO with a comment. PiperOrigin-RevId: 298432836 -- 8020ce9ec8558ee712d9733ae3d660ac1d3ffe1a by Abseil Team : Guard against unnecessary copy in case the buffer is empty. This is important in cases were the user is explicitly tuning their chunks to match PiecewiseChunkSize(). PiperOrigin-RevId: 298366044 -- 89324441d1c0c697c90ba7d8fc63639805fcaa9d by Abseil Team : Internal change PiperOrigin-RevId: 298219363 GitOrigin-RevId: a3e58c1870a9626039f4d178d2d599319bd9f8a8 Change-Id: I28dffc684b6fd0292b94807b88ec6664d5d0e183 --- absl/base/log_severity_test.cc | 2 +- absl/container/btree_benchmark.cc | 24 +-- absl/container/btree_map.h | 4 +- absl/container/btree_set.h | 4 +- absl/container/btree_test.cc | 50 ++--- absl/container/internal/btree.h | 214 ++++++++++---------- absl/container/internal/btree_container.h | 42 ++-- absl/debugging/internal/stacktrace_win32-inl.inc | 12 +- absl/flags/BUILD.bazel | 4 + absl/flags/CMakeLists.txt | 3 + absl/flags/flag.h | 1 - absl/flags/flag_benchmark.cc | 8 + absl/flags/flag_test.cc | 143 ++++++++++---- absl/flags/internal/commandlineflag.cc | 30 +++ absl/flags/internal/commandlineflag.h | 6 +- absl/flags/internal/flag.cc | 107 +++++++--- absl/flags/internal/flag.h | 208 +++++++++---------- absl/hash/internal/hash.h | 15 +- absl/random/internal/BUILD.bazel | 1 + absl/status/status.cc | 8 + absl/status/status_test.cc | 57 ++++++ absl/strings/cord.cc | 242 +++++++++++------------ absl/strings/cord.h | 100 +++++++++- absl/strings/cord_test.cc | 56 ++++++ 24 files changed, 841 insertions(+), 500 deletions(-) create mode 100644 absl/flags/internal/commandlineflag.cc (limited to 'absl/strings/cord.h') diff --git a/absl/base/log_severity_test.cc b/absl/base/log_severity_test.cc index 2302aa12..2c6872b0 100644 --- a/absl/base/log_severity_test.cc +++ b/absl/base/log_severity_test.cc @@ -53,7 +53,7 @@ TEST(StreamTest, Works) { } static_assert( - absl::flags_internal::IsAtomicFlagTypeTrait::value, + absl::flags_internal::FlagUseOneWordStorage::value, "Flags of type absl::LogSeverity ought to be lock-free."); using ParseFlagFromOutOfRangeIntegerTest = TestWithParam; diff --git a/absl/container/btree_benchmark.cc b/absl/container/btree_benchmark.cc index 4af92f9f..420cfa0d 100644 --- a/absl/container/btree_benchmark.cc +++ b/absl/container/btree_benchmark.cc @@ -538,19 +538,19 @@ struct BigType { BigType() : BigType(0) {} explicit BigType(int x) { std::iota(values.begin(), values.end(), x); } - void Copy(const BigType& x) { - for (int i = 0; i < Size && i < Copies; ++i) values[i] = x.values[i]; + void Copy(const BigType& other) { + for (int i = 0; i < Size && i < Copies; ++i) values[i] = other.values[i]; // If Copies > Size, do extra copies. for (int i = Size, idx = 0; i < Copies; ++i) { - int64_t tmp = x.values[idx]; + int64_t tmp = other.values[idx]; benchmark::DoNotOptimize(tmp); idx = idx + 1 == Size ? 0 : idx + 1; } } - BigType(const BigType& x) { Copy(x); } - BigType& operator=(const BigType& x) { - Copy(x); + BigType(const BigType& other) { Copy(other); } + BigType& operator=(const BigType& other) { + Copy(other); return *this; } @@ -641,14 +641,14 @@ struct BigTypePtr { explicit BigTypePtr(int x) { ptr = absl::make_unique>(x); } - BigTypePtr(const BigTypePtr& x) { - ptr = absl::make_unique>(*x.ptr); + BigTypePtr(const BigTypePtr& other) { + ptr = absl::make_unique>(*other.ptr); } - BigTypePtr(BigTypePtr&& x) noexcept = default; - BigTypePtr& operator=(const BigTypePtr& x) { - ptr = absl::make_unique>(*x.ptr); + BigTypePtr(BigTypePtr&& other) noexcept = default; + BigTypePtr& operator=(const BigTypePtr& other) { + ptr = absl::make_unique>(*other.ptr); } - BigTypePtr& operator=(BigTypePtr&& x) noexcept = default; + BigTypePtr& operator=(BigTypePtr&& other) noexcept = default; bool operator<(const BigTypePtr& other) const { return *ptr < *other.ptr; } bool operator==(const BigTypePtr& other) const { return *ptr == *other.ptr; } diff --git a/absl/container/btree_map.h b/absl/container/btree_map.h index d23f4ee5..bb450ead 100644 --- a/absl/container/btree_map.h +++ b/absl/container/btree_map.h @@ -318,7 +318,7 @@ class btree_map // Extracts the element at the indicated position and returns a node handle // owning that extracted data. // - // template node_type extract(const K& x): + // template node_type extract(const K& k): // // Extracts the element with the key matching the passed key value and // returns a node handle owning that extracted data. If the `btree_map` @@ -645,7 +645,7 @@ class btree_multimap // Extracts the element at the indicated position and returns a node handle // owning that extracted data. // - // template node_type extract(const K& x): + // template node_type extract(const K& k): // // Extracts the element with the key matching the passed key value and // returns a node handle owning that extracted data. If the `btree_multimap` diff --git a/absl/container/btree_set.h b/absl/container/btree_set.h index 127fb940..d3e78866 100644 --- a/absl/container/btree_set.h +++ b/absl/container/btree_set.h @@ -263,7 +263,7 @@ class btree_set // Extracts the element at the indicated position and returns a node handle // owning that extracted data. // - // template node_type extract(const K& x): + // template node_type extract(const K& k): // // Extracts the element with the key matching the passed key value and // returns a node handle owning that extracted data. If the `btree_set` @@ -567,7 +567,7 @@ class btree_multiset // Extracts the element at the indicated position and returns a node handle // owning that extracted data. // - // template node_type extract(const K& x): + // template node_type extract(const K& k): // // Extracts the element with the key matching the passed key value and // returns a node handle owning that extracted data. If the `btree_multiset` diff --git a/absl/container/btree_test.cc b/absl/container/btree_test.cc index 9edf38f9..ce12e819 100644 --- a/absl/container/btree_test.cc +++ b/absl/container/btree_test.cc @@ -89,8 +89,8 @@ class base_checker { public: base_checker() : const_tree_(tree_) {} - base_checker(const base_checker &x) - : tree_(x.tree_), const_tree_(tree_), checker_(x.checker_) {} + base_checker(const base_checker &other) + : tree_(other.tree_), const_tree_(tree_), checker_(other.checker_) {} template base_checker(InputIterator b, InputIterator e) : tree_(b, e), const_tree_(tree_), checker_(b, e) {} @@ -124,11 +124,11 @@ class base_checker { } return tree_iter; } - void value_check(const value_type &x) { + void value_check(const value_type &v) { typename KeyOfValue::type key_of_value; - const key_type &key = key_of_value(x); - CheckPairEquals(*find(key), x); + const key_type &key = key_of_value(v); + CheckPairEquals(*find(key), v); lower_bound(key); upper_bound(key); equal_range(key); @@ -187,9 +187,9 @@ class base_checker { return res; } - base_checker &operator=(const base_checker &x) { - tree_ = x.tree_; - checker_ = x.checker_; + base_checker &operator=(const base_checker &other) { + tree_ = other.tree_; + checker_ = other.checker_; return *this; } @@ -250,9 +250,9 @@ class base_checker { tree_.clear(); checker_.clear(); } - void swap(base_checker &x) { - tree_.swap(x.tree_); - checker_.swap(x.checker_); + void swap(base_checker &other) { + tree_.swap(other.tree_); + checker_.swap(other.checker_); } void verify() const { @@ -323,28 +323,28 @@ class unique_checker : public base_checker { public: unique_checker() : super_type() {} - unique_checker(const unique_checker &x) : super_type(x) {} + unique_checker(const unique_checker &other) : super_type(other) {} template unique_checker(InputIterator b, InputIterator e) : super_type(b, e) {} unique_checker &operator=(const unique_checker &) = default; // Insertion routines. - std::pair insert(const value_type &x) { + std::pair insert(const value_type &v) { int size = this->tree_.size(); std::pair checker_res = - this->checker_.insert(x); - std::pair tree_res = this->tree_.insert(x); + this->checker_.insert(v); + std::pair tree_res = this->tree_.insert(v); CheckPairEquals(*tree_res.first, *checker_res.first); EXPECT_EQ(tree_res.second, checker_res.second); EXPECT_EQ(this->tree_.size(), this->checker_.size()); EXPECT_EQ(this->tree_.size(), size + tree_res.second); return tree_res; } - iterator insert(iterator position, const value_type &x) { + iterator insert(iterator position, const value_type &v) { int size = this->tree_.size(); std::pair checker_res = - this->checker_.insert(x); - iterator tree_res = this->tree_.insert(position, x); + this->checker_.insert(v); + iterator tree_res = this->tree_.insert(position, v); CheckPairEquals(*tree_res, *checker_res.first); EXPECT_EQ(this->tree_.size(), this->checker_.size()); EXPECT_EQ(this->tree_.size(), size + checker_res.second); @@ -371,25 +371,25 @@ class multi_checker : public base_checker { public: multi_checker() : super_type() {} - multi_checker(const multi_checker &x) : super_type(x) {} + multi_checker(const multi_checker &other) : super_type(other) {} template multi_checker(InputIterator b, InputIterator e) : super_type(b, e) {} multi_checker &operator=(const multi_checker &) = default; // Insertion routines. - iterator insert(const value_type &x) { + iterator insert(const value_type &v) { int size = this->tree_.size(); - auto checker_res = this->checker_.insert(x); - iterator tree_res = this->tree_.insert(x); + auto checker_res = this->checker_.insert(v); + iterator tree_res = this->tree_.insert(v); CheckPairEquals(*tree_res, *checker_res); EXPECT_EQ(this->tree_.size(), this->checker_.size()); EXPECT_EQ(this->tree_.size(), size + 1); return tree_res; } - iterator insert(iterator position, const value_type &x) { + iterator insert(iterator position, const value_type &v) { int size = this->tree_.size(); - auto checker_res = this->checker_.insert(x); - iterator tree_res = this->tree_.insert(position, x); + auto checker_res = this->checker_.insert(v); + iterator tree_res = this->tree_.insert(position, v); CheckPairEquals(*tree_res, *checker_res); EXPECT_EQ(this->tree_.size(), this->checker_.size()); EXPECT_EQ(this->tree_.size(), size + 1); diff --git a/absl/container/internal/btree.h b/absl/container/internal/btree.h index fd5c0e7a..2a5c7314 100644 --- a/absl/container/internal/btree.h +++ b/absl/container/internal/btree.h @@ -252,9 +252,9 @@ struct map_params : common_paramssecond; } }; @@ -315,8 +315,8 @@ struct set_params : common_params struct upper_bound_adapter { explicit upper_bound_adapter(const Compare &c) : comp(c) {} - template - bool operator()(const K &a, const LK &b) const { + template + bool operator()(const K1 &a, const K2 &b) const { // Returns true when a is not greater than b. return !compare_internal::compare_result_as_less_than(comp(b, a)); } @@ -736,32 +736,28 @@ class btree_node { // Merges a node with its right sibling, moving all of the values and the // delimiting key in the parent node onto itself. - void merge(btree_node *sibling, allocator_type *alloc); + void merge(btree_node *src, allocator_type *alloc); - // Swap the contents of "this" and "src". - void swap(btree_node *src, allocator_type *alloc); + // Swaps the contents of `this` and `other`. + void swap(btree_node *other, allocator_type *alloc); // Node allocation/deletion routines. - static btree_node *init_leaf(btree_node *n, btree_node *parent, - int max_count) { - n->set_parent(parent); - n->set_position(0); - n->set_start(0); - n->set_finish(0); - n->set_max_count(max_count); + void init_leaf(btree_node *parent, int max_count) { + set_parent(parent); + set_position(0); + set_start(0); + set_finish(0); + set_max_count(max_count); absl::container_internal::SanitizerPoisonMemoryRegion( - n->start_slot(), max_count * sizeof(slot_type)); - return n; + start_slot(), max_count * sizeof(slot_type)); } - static btree_node *init_internal(btree_node *n, btree_node *parent) { - init_leaf(n, parent, kNodeValues); + void init_internal(btree_node *parent) { + init_leaf(parent, kNodeValues); // Set `max_count` to a sentinel value to indicate that this node is // internal. - n->set_max_count(kInternalNodeMaxCount); + set_max_count(kInternalNodeMaxCount); absl::container_internal::SanitizerPoisonMemoryRegion( - &n->mutable_child(n->start()), - (kNodeValues + 1) * sizeof(btree_node *)); - return n; + &mutable_child(start()), (kNodeValues + 1) * sizeof(btree_node *)); } void destroy(allocator_type *alloc) { for (int i = start(); i < finish(); ++i) { @@ -787,13 +783,13 @@ class btree_node { } // Move n values starting at value i in this node into the values starting at - // value j in node x. + // value j in dest_node. void uninitialized_move_n(const size_type n, const size_type i, - const size_type j, btree_node *x, + const size_type j, btree_node *dest_node, allocator_type *alloc) { absl::container_internal::SanitizerUnpoisonMemoryRegion( - x->slot(j), n * sizeof(slot_type)); - for (slot_type *src = slot(i), *end = src + n, *dest = x->slot(j); + dest_node->slot(j), n * sizeof(slot_type)); + for (slot_type *src = slot(i), *end = src + n, *dest = dest_node->slot(j); src != end; ++src, ++dest) { params_type::construct(alloc, dest, src); } @@ -856,8 +852,8 @@ struct btree_iterator { std::is_same, iterator>::value && std::is_same::value, int> = 0> - btree_iterator(const btree_iterator &x) // NOLINT - : node(x.node), position(x.position) {} + btree_iterator(const btree_iterator &other) // NOLINT + : node(other.node), position(other.position) {} private: // This SFINAE allows explicit conversions from const_iterator to @@ -869,8 +865,8 @@ struct btree_iterator { std::is_same, const_iterator>::value && std::is_same::value, int> = 0> - explicit btree_iterator(const btree_iterator &x) - : node(const_cast(x.node)), position(x.position) {} + explicit btree_iterator(const btree_iterator &other) + : node(const_cast(other.node)), position(other.position) {} // Increment/decrement the iterator. void increment() { @@ -890,11 +886,11 @@ struct btree_iterator { void decrement_slow(); public: - bool operator==(const const_iterator &x) const { - return node == x.node && position == x.position; + bool operator==(const const_iterator &other) const { + return node == other.node && position == other.position; } - bool operator!=(const const_iterator &x) const { - return node != x.node || position != x.position; + bool operator!=(const const_iterator &other) const { + return node != other.node || position != other.position; } // Accessors for the key/value the iterator is pointing at. @@ -942,7 +938,8 @@ struct btree_iterator { // The node in the tree the iterator is pointing at. Node *node; // The position within the node of the tree the iterator is pointing at. - // TODO(ezb): make this a field_type + // NOTE: this is an int rather than a field_type because iterators can point + // to invalid positions (such as -1) in certain circumstances. int position; }; @@ -994,9 +991,9 @@ class btree { node_stats(size_type l, size_type i) : leaf_nodes(l), internal_nodes(i) {} - node_stats &operator+=(const node_stats &x) { - leaf_nodes += x.leaf_nodes; - internal_nodes += x.internal_nodes; + node_stats &operator+=(const node_stats &other) { + leaf_nodes += other.leaf_nodes; + internal_nodes += other.internal_nodes; return *this; } @@ -1028,15 +1025,15 @@ class btree { private: // For use in copy_or_move_values_in_order. - const value_type &maybe_move_from_iterator(const_iterator x) { return *x; } - value_type &&maybe_move_from_iterator(iterator x) { return std::move(*x); } + const value_type &maybe_move_from_iterator(const_iterator it) { return *it; } + value_type &&maybe_move_from_iterator(iterator it) { return std::move(*it); } // Copies or moves (depending on the template parameter) the values in - // x into this btree in their order in x. This btree must be empty before this - // method is called. This method is used in copy construction, copy - // assignment, and move assignment. + // other into this btree in their order in other. This btree must be empty + // before this method is called. This method is used in copy construction, + // copy assignment, and move assignment. template - void copy_or_move_values_in_order(Btree *x); + void copy_or_move_values_in_order(Btree *other); // Validates that various assumptions/requirements are true at compile time. constexpr static bool static_assert_validation(); @@ -1044,12 +1041,12 @@ class btree { public: btree(const key_compare &comp, const allocator_type &alloc); - btree(const btree &x); - btree(btree &&x) noexcept - : root_(std::move(x.root_)), - rightmost_(absl::exchange(x.rightmost_, EmptyNode())), - size_(absl::exchange(x.size_, 0)) { - x.mutable_root() = EmptyNode(); + btree(const btree &other); + btree(btree &&other) noexcept + : root_(std::move(other.root_)), + rightmost_(absl::exchange(other.rightmost_, EmptyNode())), + size_(absl::exchange(other.size_, 0)) { + other.mutable_root() = EmptyNode(); } ~btree() { @@ -1059,9 +1056,9 @@ class btree { clear(); } - // Assign the contents of x to *this. - btree &operator=(const btree &x); - btree &operator=(btree &&x) noexcept; + // Assign the contents of other to *this. + btree &operator=(const btree &other); + btree &operator=(btree &&other) noexcept; iterator begin() { return iterator(leftmost()); } const_iterator begin() const { return const_iterator(leftmost()); } @@ -1204,15 +1201,15 @@ class btree { // Clear the btree, deleting all of the values it contains. void clear(); - // Swap the contents of *this and x. - void swap(btree &x); + // Swaps the contents of `this` and `other`. + void swap(btree &other); const key_compare &key_comp() const noexcept { return root_.template get<0>(); } - template - bool compare_keys(const K &x, const LK &y) const { - return compare_internal::compare_result_as_less_than(key_comp()(x, y)); + template + bool compare_keys(const K1 &a, const K2 &b) const { + return compare_internal::compare_result_as_less_than(key_comp()(a, b)); } value_compare value_comp() const { return value_compare(key_comp()); } @@ -1322,16 +1319,19 @@ class btree { // Node creation/deletion routines. node_type *new_internal_node(node_type *parent) { - node_type *p = allocate(node_type::InternalSize()); - return node_type::init_internal(p, parent); + node_type *n = allocate(node_type::InternalSize()); + n->init_internal(parent); + return n; } node_type *new_leaf_node(node_type *parent) { - node_type *p = allocate(node_type::LeafSize()); - return node_type::init_leaf(p, parent, kNodeValues); + node_type *n = allocate(node_type::LeafSize()); + n->init_leaf(parent, kNodeValues); + return n; } node_type *new_leaf_root_node(const int max_count) { - node_type *p = allocate(node_type::LeafSize(max_count)); - return node_type::init_leaf(p, p, max_count); + node_type *n = allocate(node_type::LeafSize(max_count)); + n->init_leaf(/*parent=*/n, max_count); + return n; } // Deletion helper routines. @@ -1715,12 +1715,12 @@ void btree_node

::merge(btree_node *src, allocator_type *alloc) { } template -void btree_node

::swap(btree_node *x, allocator_type *alloc) { +void btree_node

::swap(btree_node *other, allocator_type *alloc) { using std::swap; - assert(leaf() == x->leaf()); + assert(leaf() == other->leaf()); // Determine which is the smaller/larger node. - btree_node *smaller = this, *larger = x; + btree_node *smaller = this, *larger = other; if (smaller->count() > larger->count()) { swap(smaller, larger); } @@ -1759,7 +1759,7 @@ void btree_node

::swap(btree_node *x, allocator_type *alloc) { // Swap the `finish`s. // TODO(ezb): with floating storage, will also need to swap starts. - swap(mutable_finish(), x->mutable_finish()); + swap(mutable_finish(), other->mutable_finish()); } //// @@ -1814,7 +1814,7 @@ void btree_iterator::decrement_slow() { // btree methods template template -void btree

::copy_or_move_values_in_order(Btree *x) { +void btree

::copy_or_move_values_in_order(Btree *other) { static_assert(std::is_same::value || std::is_same::value, "Btree type must be same or const."); @@ -1822,11 +1822,11 @@ void btree

::copy_or_move_values_in_order(Btree *x) { // We can avoid key comparisons because we know the order of the // values is the same order we'll store them in. - auto iter = x->begin(); - if (iter == x->end()) return; + auto iter = other->begin(); + if (iter == other->end()) return; insert_multi(maybe_move_from_iterator(iter)); ++iter; - for (; iter != x->end(); ++iter) { + for (; iter != other->end(); ++iter) { // If the btree is not empty, we can just insert the new value at the end // of the tree. internal_emplace(end(), maybe_move_from_iterator(iter)); @@ -1869,8 +1869,9 @@ btree

::btree(const key_compare &comp, const allocator_type &alloc) : root_(comp, alloc, EmptyNode()), rightmost_(EmptyNode()), size_(0) {} template -btree

::btree(const btree &x) : btree(x.key_comp(), x.allocator()) { - copy_or_move_values_in_order(&x); +btree

::btree(const btree &other) + : btree(other.key_comp(), other.allocator()) { + copy_or_move_values_in_order(&other); } template @@ -1977,46 +1978,47 @@ void btree

::insert_iterator_multi(InputIterator b, InputIterator e) { } template -auto btree

::operator=(const btree &x) -> btree & { - if (this != &x) { +auto btree

::operator=(const btree &other) -> btree & { + if (this != &other) { clear(); - *mutable_key_comp() = x.key_comp(); + *mutable_key_comp() = other.key_comp(); if (absl::allocator_traits< allocator_type>::propagate_on_container_copy_assignment::value) { - *mutable_allocator() = x.allocator(); + *mutable_allocator() = other.allocator(); } - copy_or_move_values_in_order(&x); + copy_or_move_values_in_order(&other); } return *this; } template -auto btree

::operator=(btree &&x) noexcept -> btree & { - if (this != &x) { +auto btree

::operator=(btree &&other) noexcept -> btree & { + if (this != &other) { clear(); using std::swap; if (absl::allocator_traits< allocator_type>::propagate_on_container_copy_assignment::value) { // Note: `root_` also contains the allocator and the key comparator. - swap(root_, x.root_); - swap(rightmost_, x.rightmost_); - swap(size_, x.size_); + swap(root_, other.root_); + swap(rightmost_, other.rightmost_); + swap(size_, other.size_); } else { - if (allocator() == x.allocator()) { - swap(mutable_root(), x.mutable_root()); - swap(*mutable_key_comp(), *x.mutable_key_comp()); - swap(rightmost_, x.rightmost_); - swap(size_, x.size_); + if (allocator() == other.allocator()) { + swap(mutable_root(), other.mutable_root()); + swap(*mutable_key_comp(), *other.mutable_key_comp()); + swap(rightmost_, other.rightmost_); + swap(size_, other.size_); } else { // We aren't allowed to propagate the allocator and the allocator is // different so we can't take over its memory. We must move each element - // individually. We need both `x` and `this` to have `x`s key comparator - // while moving the values so we can't swap the key comparators. - *mutable_key_comp() = x.key_comp(); - copy_or_move_values_in_order(&x); + // individually. We need both `other` and `this` to have `other`s key + // comparator while moving the values so we can't swap the key + // comparators. + *mutable_key_comp() = other.key_comp(); + copy_or_move_values_in_order(&other); } } } @@ -2215,20 +2217,20 @@ void btree

::clear() { } template -void btree

::swap(btree &x) { +void btree

::swap(btree &other) { using std::swap; if (absl::allocator_traits< allocator_type>::propagate_on_container_swap::value) { // Note: `root_` also contains the allocator and the key comparator. - swap(root_, x.root_); + swap(root_, other.root_); } else { // It's undefined behavior if the allocators are unequal here. - assert(allocator() == x.allocator()); - swap(mutable_root(), x.mutable_root()); - swap(*mutable_key_comp(), *x.mutable_key_comp()); + assert(allocator() == other.allocator()); + swap(mutable_root(), other.mutable_root()); + swap(*mutable_key_comp(), *other.mutable_key_comp()); } - swap(rightmost_, x.rightmost_); - swap(size_, x.size_); + swap(rightmost_, other.rightmost_); + swap(size_, other.size_); } template @@ -2417,8 +2419,7 @@ void btree

::try_shrink() { if (root()->leaf()) { assert(size() == 0); delete_leaf_node(root()); - mutable_root() = EmptyNode(); - rightmost_ = EmptyNode(); + mutable_root() = rightmost_ = EmptyNode(); } else { node_type *child = root()->start_child(); child->make_root(); @@ -2463,8 +2464,7 @@ inline auto btree

::internal_emplace(iterator iter, Args &&... args) new_leaf_root_node((std::min)(kNodeValues, 2 * max_count)); iter.node->swap(root(), mutable_allocator()); delete_leaf_node(root()); - mutable_root() = iter.node; - rightmost_ = iter.node; + mutable_root() = rightmost_ = iter.node; } else { rebalance_or_split(&iter); } diff --git a/absl/container/internal/btree_container.h b/absl/container/internal/btree_container.h index f2e4c3a5..734c90ef 100644 --- a/absl/container/internal/btree_container.h +++ b/absl/container/internal/btree_container.h @@ -68,10 +68,10 @@ class btree_container { explicit btree_container(const key_compare &comp, const allocator_type &alloc = allocator_type()) : tree_(comp, alloc) {} - btree_container(const btree_container &x) = default; - btree_container(btree_container &&x) noexcept = default; - btree_container &operator=(const btree_container &x) = default; - btree_container &operator=(btree_container &&x) noexcept( + btree_container(const btree_container &other) = default; + btree_container(btree_container &&other) noexcept = default; + btree_container &operator=(const btree_container &other) = default; + btree_container &operator=(btree_container &&other) noexcept( std::is_nothrow_move_assignable::value) = default; // Iterator routines. @@ -154,7 +154,7 @@ class btree_container { public: // Utility routines. void clear() { tree_.clear(); } - void swap(btree_container &x) { tree_.swap(x.tree_); } + void swap(btree_container &other) { tree_.swap(other.tree_); } void verify() const { tree_.verify(); } // Size routines. @@ -257,26 +257,26 @@ class btree_set_container : public btree_container { } // Insertion routines. - std::pair insert(const value_type &x) { - return this->tree_.insert_unique(params_type::key(x), x); + std::pair insert(const value_type &v) { + return this->tree_.insert_unique(params_type::key(v), v); } - std::pair insert(value_type &&x) { - return this->tree_.insert_unique(params_type::key(x), std::move(x)); + std::pair insert(value_type &&v) { + return this->tree_.insert_unique(params_type::key(v), std::move(v)); } template std::pair emplace(Args &&... args) { init_type v(std::forward(args)...); return this->tree_.insert_unique(params_type::key(v), std::move(v)); } - iterator insert(const_iterator position, const value_type &x) { + iterator insert(const_iterator position, const value_type &v) { return this->tree_ - .insert_hint_unique(iterator(position), params_type::key(x), x) + .insert_hint_unique(iterator(position), params_type::key(v), v) .first; } - iterator insert(const_iterator position, value_type &&x) { + iterator insert(const_iterator position, value_type &&v) { return this->tree_ - .insert_hint_unique(iterator(position), params_type::key(x), - std::move(x)) + .insert_hint_unique(iterator(position), params_type::key(v), + std::move(v)) .first; } template @@ -562,15 +562,15 @@ class btree_multiset_container : public btree_container { } // Insertion routines. - iterator insert(const value_type &x) { return this->tree_.insert_multi(x); } - iterator insert(value_type &&x) { - return this->tree_.insert_multi(std::move(x)); + iterator insert(const value_type &v) { return this->tree_.insert_multi(v); } + iterator insert(value_type &&v) { + return this->tree_.insert_multi(std::move(v)); } - iterator insert(const_iterator position, const value_type &x) { - return this->tree_.insert_hint_multi(iterator(position), x); + iterator insert(const_iterator position, const value_type &v) { + return this->tree_.insert_hint_multi(iterator(position), v); } - iterator insert(const_iterator position, value_type &&x) { - return this->tree_.insert_hint_multi(iterator(position), std::move(x)); + iterator insert(const_iterator position, value_type &&v) { + return this->tree_.insert_hint_multi(iterator(position), std::move(v)); } template void insert(InputIterator b, InputIterator e) { diff --git a/absl/debugging/internal/stacktrace_win32-inl.inc b/absl/debugging/internal/stacktrace_win32-inl.inc index af4578a5..1c666c8b 100644 --- a/absl/debugging/internal/stacktrace_win32-inl.inc +++ b/absl/debugging/internal/stacktrace_win32-inl.inc @@ -46,9 +46,9 @@ typedef USHORT NTAPI RtlCaptureStackBackTrace_Function( OUT PVOID *backtrace, OUT PULONG backtrace_hash); -// It is not possible to load RtlCaptureStackBackTrace at static init time in -// UWP. CaptureStackBackTrace is the public version of RtlCaptureStackBackTrace -#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) && \ +// It is not possible to load RtlCaptureStackBackTrace at static init time in +// UWP. CaptureStackBackTrace is the public version of RtlCaptureStackBackTrace +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) && \ !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) static RtlCaptureStackBackTrace_Function* const RtlCaptureStackBackTrace_fn = &::CaptureStackBackTrace; @@ -56,9 +56,9 @@ static RtlCaptureStackBackTrace_Function* const RtlCaptureStackBackTrace_fn = // Load the function we need at static init time, where we don't have // to worry about someone else holding the loader's lock. static RtlCaptureStackBackTrace_Function* const RtlCaptureStackBackTrace_fn = - (RtlCaptureStackBackTrace_Function*) - GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlCaptureStackBackTrace"); -#endif // WINAPI_PARTITION_APP && !WINAPI_PARTITION_DESKTOP + (RtlCaptureStackBackTrace_Function*)GetProcAddress( + GetModuleHandleA("ntdll.dll"), "RtlCaptureStackBackTrace"); +#endif // WINAPI_PARTITION_APP && !WINAPI_PARTITION_DESKTOP template static int UnwindImpl(void** result, int* sizes, int max_depth, int skip_count, diff --git a/absl/flags/BUILD.bazel b/absl/flags/BUILD.bazel index cdb4e7e8..9166f74c 100644 --- a/absl/flags/BUILD.bazel +++ b/absl/flags/BUILD.bazel @@ -45,6 +45,7 @@ cc_library( "//absl/base:config", "//absl/base:core_headers", "//absl/memory", + "//absl/meta:type_traits", "//absl/strings", "//absl/synchronization", ], @@ -130,6 +131,9 @@ cc_library( cc_library( name = "handle", + srcs = [ + "internal/commandlineflag.cc", + ], hdrs = [ "internal/commandlineflag.h", ], diff --git a/absl/flags/CMakeLists.txt b/absl/flags/CMakeLists.txt index 1d25f0de..01cf09b1 100644 --- a/absl/flags/CMakeLists.txt +++ b/absl/flags/CMakeLists.txt @@ -33,6 +33,7 @@ absl_cc_library( absl::flags_handle absl::flags_registry absl::synchronization + absl::meta PUBLIC ) @@ -117,6 +118,8 @@ absl_cc_library( absl_cc_library( NAME flags_handle + SRCS + "internal/commandlineflag.cc" HDRS "internal/commandlineflag.h" COPTS diff --git a/absl/flags/flag.h b/absl/flags/flag.h index cff02c1f..bb917654 100644 --- a/absl/flags/flag.h +++ b/absl/flags/flag.h @@ -148,7 +148,6 @@ class Flag { return GetImpl()->template IsOfType(); } T Get() const { return GetImpl()->Get(); } - bool AtomicGet(T* v) const { return GetImpl()->AtomicGet(v); } void Set(const T& v) { GetImpl()->Set(v); } void SetCallback(const flags_internal::FlagCallbackFunc mutation_callback) { GetImpl()->SetCallback(mutation_callback); diff --git a/absl/flags/flag_benchmark.cc b/absl/flags/flag_benchmark.cc index 87f73170..ff95bb5d 100644 --- a/absl/flags/flag_benchmark.cc +++ b/absl/flags/flag_benchmark.cc @@ -109,3 +109,11 @@ namespace { BENCHMARKED_TYPES(BM_GetFlag) } // namespace + +#define InvokeGetFlag(T) \ + T AbslInvokeGetFlag##T() { return absl::GetFlag(FLAGS_##T##_flag); } \ + int odr##T = (benchmark::DoNotOptimize(AbslInvokeGetFlag##T), 1); + +BENCHMARKED_TYPES(InvokeGetFlag) + +// To veiw disassembly use: gdb ${BINARY} -batch -ex "disassemble /s $FUNC" diff --git a/absl/flags/flag_test.cc b/absl/flags/flag_test.cc index 4984d284..1e01b49c 100644 --- a/absl/flags/flag_test.cc +++ b/absl/flags/flag_test.cc @@ -49,28 +49,6 @@ void* TestMakeDflt() { } void TestCallback() {} -template -bool TestConstructionFor() { - constexpr flags::FlagHelpArg help_arg{flags::FlagHelpMsg("literal help"), - flags::FlagHelpKind::kLiteral}; - constexpr flags::Flag f1("f1", "file", help_arg, &TestMakeDflt); - EXPECT_EQ(f1.Name(), "f1"); - EXPECT_EQ(f1.Help(), "literal help"); - EXPECT_EQ(f1.Filename(), "file"); - - ABSL_CONST_INIT static flags::Flag f2( - "f2", "file", - {flags::FlagHelpMsg(&TestHelpMsg), flags::FlagHelpKind::kGenFunc}, - &TestMakeDflt); - flags::FlagRegistrar(&f2).OnUpdate(TestCallback); - - EXPECT_EQ(f2.Name(), "f2"); - EXPECT_EQ(f2.Help(), "dynamic help"); - EXPECT_EQ(f2.Filename(), "file"); - - return true; -} - struct UDT { UDT() = default; UDT(const UDT&) = default; @@ -98,19 +76,103 @@ class FlagTest : public testing::Test { } }; +struct S1 { + S1() = default; + S1(const S1&) = default; + int32_t f1; + int64_t f2; +}; + +struct S2 { + S2() = default; + S2(const S2&) = default; + int64_t f1; + double f2; +}; + +TEST_F(FlagTest, Traits) { + EXPECT_EQ(flags::FlagValue::Kind(), + flags::FlagValueStorageKind::kOneWordAtomic); + EXPECT_EQ(flags::FlagValue::Kind(), + flags::FlagValueStorageKind::kOneWordAtomic); + EXPECT_EQ(flags::FlagValue::Kind(), + flags::FlagValueStorageKind::kOneWordAtomic); + EXPECT_EQ(flags::FlagValue::Kind(), + flags::FlagValueStorageKind::kOneWordAtomic); + +#if defined(ABSL_FLAGS_INTERNAL_ATOMIC_DOUBLE_WORD) + EXPECT_EQ(flags::FlagValue::Kind(), + flags::FlagValueStorageKind::kTwoWordsAtomic); + EXPECT_EQ(flags::FlagValue::Kind(), + flags::FlagValueStorageKind::kTwoWordsAtomic); +#else + EXPECT_EQ(flags::FlagValue::Kind(), + flags::FlagValueStorageKind::kHeapAllocated); + EXPECT_EQ(flags::FlagValue::Kind(), + flags::FlagValueStorageKind::kHeapAllocated); +#endif + + EXPECT_EQ(flags::FlagValue::Kind(), + flags::FlagValueStorageKind::kHeapAllocated); + EXPECT_EQ(flags::FlagValue::Kind>(), + flags::FlagValueStorageKind::kHeapAllocated); +} + +// -------------------------------------------------------------------- + +constexpr flags::FlagHelpArg help_arg{flags::FlagHelpMsg("literal help"), + flags::FlagHelpKind::kLiteral}; + +using String = std::string; + +#define DEFINE_CONSTRUCTED_FLAG(T) \ + constexpr flags::Flag f1##T("f1", "file", help_arg, &TestMakeDflt); \ + ABSL_CONST_INIT flags::Flag f2##T( \ + "f2", "file", \ + {flags::FlagHelpMsg(&TestHelpMsg), flags::FlagHelpKind::kGenFunc}, \ + &TestMakeDflt) + +#define TEST_CONSTRUCTED_FLAG(T) TestConstructionFor(f1##T, &f2##T); + +DEFINE_CONSTRUCTED_FLAG(bool); +DEFINE_CONSTRUCTED_FLAG(int16_t); +DEFINE_CONSTRUCTED_FLAG(uint16_t); +DEFINE_CONSTRUCTED_FLAG(int32_t); +DEFINE_CONSTRUCTED_FLAG(uint32_t); +DEFINE_CONSTRUCTED_FLAG(int64_t); +DEFINE_CONSTRUCTED_FLAG(uint64_t); +DEFINE_CONSTRUCTED_FLAG(float); +DEFINE_CONSTRUCTED_FLAG(double); +DEFINE_CONSTRUCTED_FLAG(String); +DEFINE_CONSTRUCTED_FLAG(UDT); + +template +bool TestConstructionFor(const flags::Flag& f1, flags::Flag* f2) { + EXPECT_EQ(f1.Name(), "f1"); + EXPECT_EQ(f1.Help(), "literal help"); + EXPECT_EQ(f1.Filename(), "file"); + + flags::FlagRegistrar(f2).OnUpdate(TestCallback); + + EXPECT_EQ(f2->Name(), "f2"); + EXPECT_EQ(f2->Help(), "dynamic help"); + EXPECT_EQ(f2->Filename(), "file"); + + return true; +} + TEST_F(FlagTest, TestConstruction) { - TestConstructionFor(); - TestConstructionFor(); - TestConstructionFor(); - TestConstructionFor(); - TestConstructionFor(); - TestConstructionFor(); - TestConstructionFor(); - TestConstructionFor(); - TestConstructionFor(); - TestConstructionFor(); - - TestConstructionFor(); + TEST_CONSTRUCTED_FLAG(bool); + TEST_CONSTRUCTED_FLAG(int16_t); + TEST_CONSTRUCTED_FLAG(uint16_t); + TEST_CONSTRUCTED_FLAG(int32_t); + TEST_CONSTRUCTED_FLAG(uint32_t); + TEST_CONSTRUCTED_FLAG(int64_t); + TEST_CONSTRUCTED_FLAG(uint64_t); + TEST_CONSTRUCTED_FLAG(float); + TEST_CONSTRUCTED_FLAG(double); + TEST_CONSTRUCTED_FLAG(String); + TEST_CONSTRUCTED_FLAG(UDT); } // -------------------------------------------------------------------- @@ -391,17 +453,18 @@ TEST_F(FlagTest, TestCustomUDT) { using FlagDeathTest = FlagTest; TEST_F(FlagDeathTest, TestTypeMismatchValidations) { - EXPECT_DEBUG_DEATH( - static_cast(absl::GetFlag(FLAGS_mistyped_int_flag)), - "Flag 'mistyped_int_flag' is defined as one type and declared " - "as another"); - EXPECT_DEATH(absl::SetFlag(&FLAGS_mistyped_int_flag, 1), +#if !defined(NDEBUG) + EXPECT_DEATH(static_cast(absl::GetFlag(FLAGS_mistyped_int_flag)), "Flag 'mistyped_int_flag' is defined as one type and declared " "as another"); - EXPECT_DEATH(static_cast(absl::GetFlag(FLAGS_mistyped_string_flag)), "Flag 'mistyped_string_flag' is defined as one type and " "declared as another"); +#endif + + EXPECT_DEATH(absl::SetFlag(&FLAGS_mistyped_int_flag, 1), + "Flag 'mistyped_int_flag' is defined as one type and declared " + "as another"); EXPECT_DEATH( absl::SetFlag(&FLAGS_mistyped_string_flag, std::vector{}), "Flag 'mistyped_string_flag' is defined as one type and declared as " diff --git a/absl/flags/internal/commandlineflag.cc b/absl/flags/internal/commandlineflag.cc new file mode 100644 index 00000000..90765a3e --- /dev/null +++ b/absl/flags/internal/commandlineflag.cc @@ -0,0 +1,30 @@ +// +// Copyright 2020 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/internal/commandlineflag.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace flags_internal { + +FlagStateInterface::~FlagStateInterface() {} + +bool CommandLineFlag::IsRetired() const { return false; } +bool CommandLineFlag::IsAbseilFlag() const { return true; } + +} // namespace flags_internal +ABSL_NAMESPACE_END +} // namespace absl + diff --git a/absl/flags/internal/commandlineflag.h b/absl/flags/internal/commandlineflag.h index 6363c661..e91ddde6 100644 --- a/absl/flags/internal/commandlineflag.h +++ b/absl/flags/internal/commandlineflag.h @@ -77,7 +77,7 @@ enum ValueSource { // of a flag produced this flag state from method CommandLineFlag::SaveState(). class FlagStateInterface { public: - virtual ~FlagStateInterface() {} + virtual ~FlagStateInterface(); // Restores the flag originated this object to the saved state. virtual void Restore() const = 0; @@ -146,9 +146,9 @@ class CommandLineFlag { // Returns help message associated with this flag. virtual std::string Help() const = 0; // Returns true iff this object corresponds to retired flag. - virtual bool IsRetired() const { return false; } + virtual bool IsRetired() const; // Returns true iff this is a handle to an Abseil Flag. - virtual bool IsAbseilFlag() const { return true; } + virtual bool IsAbseilFlag() const; // Returns id of the flag's value type. virtual FlagStaticTypeId TypeId() const = 0; virtual bool IsModified() const = 0; diff --git a/absl/flags/internal/flag.cc b/absl/flags/internal/flag.cc index 5a921e28..a944e16e 100644 --- a/absl/flags/internal/flag.cc +++ b/absl/flags/internal/flag.cc @@ -77,19 +77,33 @@ class MutexRelock { void FlagImpl::Init() { new (&data_guard_) absl::Mutex; - absl::MutexLock lock(reinterpret_cast(&data_guard_)); - - value_.dynamic = MakeInitValue().release(); - StoreAtomic(); + // At this point the default_value_ always points to gen_func. + std::unique_ptr init_value( + (*default_value_.gen_func)(), DynValueDeleter{op_}); + switch (ValueStorageKind()) { + case FlagValueStorageKind::kHeapAllocated: + value_.dynamic = init_value.release(); + break; + case FlagValueStorageKind::kOneWordAtomic: { + int64_t atomic_value; + std::memcpy(&atomic_value, init_value.get(), Sizeof(op_)); + value_.one_word_atomic.store(atomic_value, std::memory_order_release); + break; + } + case FlagValueStorageKind::kTwoWordsAtomic: { + AlignedTwoWords atomic_value{0, 0}; + std::memcpy(&atomic_value, init_value.get(), Sizeof(op_)); + value_.two_words_atomic.store(atomic_value, std::memory_order_release); + break; + } + } } -// Ensures that the lazily initialized data is initialized, -// and returns pointer to the mutex guarding flags data. absl::Mutex* FlagImpl::DataGuard() const { absl::call_once(const_cast(this)->init_control_, &FlagImpl::Init, const_cast(this)); - // data_guard_ is initialized. + // data_guard_ is initialized inside Init. return reinterpret_cast(&data_guard_); } @@ -129,8 +143,24 @@ std::unique_ptr FlagImpl::MakeInitValue() const { } void FlagImpl::StoreValue(const void* src) { - flags_internal::Copy(op_, src, value_.dynamic); - StoreAtomic(); + switch (ValueStorageKind()) { + case FlagValueStorageKind::kHeapAllocated: + Copy(op_, src, value_.dynamic); + break; + case FlagValueStorageKind::kOneWordAtomic: { + int64_t one_word_val; + std::memcpy(&one_word_val, src, Sizeof(op_)); + value_.one_word_atomic.store(one_word_val, std::memory_order_release); + break; + } + case FlagValueStorageKind::kTwoWordsAtomic: { + AlignedTwoWords two_words_val{0, 0}; + std::memcpy(&two_words_val, src, Sizeof(op_)); + value_.two_words_atomic.store(two_words_val, std::memory_order_release); + break; + } + } + modified_ = true; ++counter_; InvokeCallback(); @@ -165,9 +195,25 @@ std::string FlagImpl::DefaultValue() const { } std::string FlagImpl::CurrentValue() const { - absl::MutexLock l(DataGuard()); + DataGuard(); // Make sure flag initialized + switch (ValueStorageKind()) { + case FlagValueStorageKind::kHeapAllocated: { + absl::MutexLock l(DataGuard()); + return flags_internal::Unparse(op_, value_.dynamic); + } + case FlagValueStorageKind::kOneWordAtomic: { + const auto one_word_val = + value_.one_word_atomic.load(std::memory_order_acquire); + return flags_internal::Unparse(op_, &one_word_val); + } + case FlagValueStorageKind::kTwoWordsAtomic: { + const auto two_words_val = + value_.two_words_atomic.load(std::memory_order_acquire); + return flags_internal::Unparse(op_, &two_words_val); + } + } - return flags_internal::Unparse(op_, value_.dynamic); + return ""; } void FlagImpl::SetCallback(const FlagCallbackFunc mutation_callback) { @@ -244,26 +290,27 @@ std::unique_ptr FlagImpl::TryParse( } void FlagImpl::Read(void* dst) const { - absl::ReaderMutexLock l(DataGuard()); + DataGuard(); // Make sure flag initialized + switch (ValueStorageKind()) { + case FlagValueStorageKind::kHeapAllocated: { + absl::MutexLock l(DataGuard()); - flags_internal::CopyConstruct(op_, value_.dynamic, dst); -} - -void FlagImpl::StoreAtomic() { - size_t data_size = flags_internal::Sizeof(op_); - - if (data_size <= sizeof(int64_t)) { - int64_t t = 0; - std::memcpy(&t, value_.dynamic, data_size); - value_.atomics.small_atomic.store(t, std::memory_order_release); - } -#if defined(ABSL_FLAGS_INTERNAL_ATOMIC_DOUBLE_WORD) - else if (data_size <= sizeof(FlagsInternalTwoWordsType)) { - FlagsInternalTwoWordsType t{0, 0}; - std::memcpy(&t, value_.dynamic, data_size); - value_.atomics.big_atomic.store(t, std::memory_order_release); + flags_internal::CopyConstruct(op_, value_.dynamic, dst); + break; + } + case FlagValueStorageKind::kOneWordAtomic: { + const auto one_word_val = + value_.one_word_atomic.load(std::memory_order_acquire); + std::memcpy(dst, &one_word_val, Sizeof(op_)); + break; + } + case FlagValueStorageKind::kTwoWordsAtomic: { + const auto two_words_val = + value_.two_words_atomic.load(std::memory_order_acquire); + std::memcpy(dst, &two_words_val, Sizeof(op_)); + break; + } } -#endif } void FlagImpl::Write(const void* src) { @@ -339,7 +386,7 @@ bool FlagImpl::SetFromString(absl::string_view value, FlagSettingMode set_mode, } if (!modified_) { - // Need to set both default value *and* current, in this case + // Need to set both default value *and* current, in this case. StoreValue(default_value_.dynamic_value); modified_ = false; } diff --git a/absl/flags/internal/flag.h b/absl/flags/internal/flag.h index 35a148cf..307b7377 100644 --- a/absl/flags/internal/flag.h +++ b/absl/flags/internal/flag.h @@ -31,6 +31,7 @@ #include "absl/flags/internal/commandlineflag.h" #include "absl/flags/internal/registry.h" #include "absl/memory/memory.h" +#include "absl/meta/type_traits.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/synchronization/mutex.h" @@ -249,95 +250,66 @@ enum class FlagDefaultKind : uint8_t { kDynamicValue = 0, kGenFunc = 1 }; /////////////////////////////////////////////////////////////////////////////// // Flag current value auxiliary structs. -// The minimum atomic size we believe to generate lock free code, i.e. all -// trivially copyable types not bigger this size generate lock free code. -static constexpr int kMinLockFreeAtomicSize = 8; +constexpr int64_t UninitializedFlagValue() { return 0xababababababababll; } -// The same as kMinLockFreeAtomicSize but maximum atomic size. As double words -// might use two registers, we want to dispatch the logic for them. -#if defined(ABSL_FLAGS_INTERNAL_ATOMIC_DOUBLE_WORD) -static constexpr int kMaxLockFreeAtomicSize = 16; -#else -static constexpr int kMaxLockFreeAtomicSize = 8; -#endif - -// We can use atomic in cases when it fits in the register, trivially copyable -// in order to make memcpy operations. template -struct IsAtomicFlagTypeTrait { - static constexpr bool value = - (sizeof(T) <= kMaxLockFreeAtomicSize && - type_traits_internal::is_trivially_copyable::value); -}; +using FlagUseOneWordStorage = std::integral_constant< + bool, absl::type_traits_internal::is_trivially_copyable::value && + (sizeof(T) <= 8)>; +#if defined(ABSL_FLAGS_INTERNAL_ATOMIC_DOUBLE_WORD) // Clang does not always produce cmpxchg16b instruction when alignment of a 16 // bytes type is not 16. -struct alignas(16) FlagsInternalTwoWordsType { +struct alignas(16) AlignedTwoWords { int64_t first; int64_t second; }; -constexpr bool operator==(const FlagsInternalTwoWordsType& that, - const FlagsInternalTwoWordsType& other) { - return that.first == other.first && that.second == other.second; -} -constexpr bool operator!=(const FlagsInternalTwoWordsType& that, - const FlagsInternalTwoWordsType& other) { - return !(that == other); -} - -constexpr int64_t SmallAtomicInit() { return 0xababababababababll; } - -template -struct BestAtomicType { - using type = int64_t; - static constexpr int64_t AtomicInit() { return SmallAtomicInit(); } +template +using FlagUseTwoWordsStorage = std::integral_constant< + bool, absl::type_traits_internal::is_trivially_copyable::value && + (sizeof(T) > 8) && (sizeof(T) <= 16)>; +#else +// This is actually unused and only here to avoid ifdefs in other palces. +struct AlignedTwoWords { + constexpr AlignedTwoWords() = default; + constexpr AlignedTwoWords(int64_t, int64_t) {} }; +// This trait should be type dependent, otherwise SFINAE below will fail template -struct BestAtomicType< - T, typename std::enable_if<(kMinLockFreeAtomicSize < sizeof(T) && - sizeof(T) <= kMaxLockFreeAtomicSize), - void>::type> { - using type = FlagsInternalTwoWordsType; - static constexpr FlagsInternalTwoWordsType AtomicInit() { - return {SmallAtomicInit(), SmallAtomicInit()}; - } +using FlagUseTwoWordsStorage = + std::integral_constant; +#endif + +template +using FlagUseHeapStorage = + std::integral_constant::value && + !FlagUseTwoWordsStorage::value>; + +enum class FlagValueStorageKind : uint8_t { + kHeapAllocated = 0, + kOneWordAtomic = 1, + kTwoWordsAtomic = 2 }; -struct FlagValue { - // Heap allocated value. - void* dynamic = nullptr; - // For some types, a copy of the current value is kept in an atomically - // accessible field. - union Atomics { - // Using small atomic for small types. - std::atomic small_atomic; - template ::type> - int64_t load() const { - return small_atomic.load(std::memory_order_acquire); - } +union FlagValue { + constexpr explicit FlagValue(int64_t v) : one_word_atomic(v) {} -#if defined(ABSL_FLAGS_INTERNAL_ATOMIC_DOUBLE_WORD) - // Using big atomics for big types. - std::atomic big_atomic; - template ::type> - FlagsInternalTwoWordsType load() const { - return big_atomic.load(std::memory_order_acquire); - } - constexpr Atomics() - : big_atomic{FlagsInternalTwoWordsType{SmallAtomicInit(), - SmallAtomicInit()}} {} -#else - constexpr Atomics() : small_atomic{SmallAtomicInit()} {} -#endif - }; - Atomics atomics{}; + template + static constexpr FlagValueStorageKind Kind() { + return FlagUseHeapStorage::value + ? FlagValueStorageKind::kHeapAllocated + : FlagUseOneWordStorage::value + ? FlagValueStorageKind::kOneWordAtomic + : FlagUseTwoWordsStorage::value + ? FlagValueStorageKind::kTwoWordsAtomic + : FlagValueStorageKind::kHeapAllocated; + } + + void* dynamic; + std::atomic one_word_atomic; + std::atomic two_words_atomic; }; /////////////////////////////////////////////////////////////////////////////// @@ -369,18 +341,21 @@ struct DynValueDeleter { class FlagImpl { public: constexpr FlagImpl(const char* name, const char* filename, FlagOpFn op, - FlagHelpArg help, FlagDfltGenFunc default_value_gen) + FlagHelpArg help, FlagValueStorageKind value_kind, + FlagDfltGenFunc default_value_gen) : name_(name), filename_(filename), op_(op), help_(help.source), help_source_kind_(static_cast(help.kind)), + value_storage_kind_(static_cast(value_kind)), def_kind_(static_cast(FlagDefaultKind::kGenFunc)), modified_(false), on_command_line_(false), counter_(0), callback_(nullptr), default_value_(default_value_gen), + value_(flags_internal::UninitializedFlagValue()), data_guard_{} {} // Constant access methods @@ -393,34 +368,29 @@ class FlagImpl { std::string CurrentValue() const ABSL_LOCKS_EXCLUDED(*DataGuard()); void Read(void* dst) const ABSL_LOCKS_EXCLUDED(*DataGuard()); - template ::value, int>::type = 0> + template ::value, + int>::type = 0> void Get(T* dst) const { - AssertValidType(&flags_internal::FlagStaticTypeIdGen); Read(dst); } - // Overload for `GetFlag()` for types that support lock-free reads. - template ::value, + template ::value, int>::type = 0> void Get(T* dst) const { - // For flags of types which can be accessed "atomically" we want to avoid - // slowing down flag value access due to type validation. That's why - // this validation is hidden behind !NDEBUG -#ifndef NDEBUG - AssertValidType(&flags_internal::FlagStaticTypeIdGen); -#endif - using U = flags_internal::BestAtomicType; - typename U::type r = value_.atomics.template load(); - if (r != U::AtomicInit()) { - std::memcpy(static_cast(dst), &r, sizeof(T)); - } else { - Read(dst); + int64_t one_word_val = + value_.one_word_atomic.load(std::memory_order_acquire); + if (ABSL_PREDICT_FALSE(one_word_val == UninitializedFlagValue())) { + DataGuard(); // Make sure flag initialized + one_word_val = value_.one_word_atomic.load(std::memory_order_acquire); } + std::memcpy(dst, static_cast(&one_word_val), sizeof(T)); } - template - void Set(const T& src) { - AssertValidType(&flags_internal::FlagStaticTypeIdGen); - Write(&src); + template ::value, int>::type = 0> + void Get(T* dst) const { + DataGuard(); // Make sure flag initialized + const auto two_words_val = + value_.two_words_atomic.load(std::memory_order_acquire); + std::memcpy(dst, &two_words_val, sizeof(T)); } // Mutating access methods @@ -428,9 +398,6 @@ class FlagImpl { bool SetFromString(absl::string_view value, FlagSettingMode set_mode, ValueSource source, std::string* err) ABSL_LOCKS_EXCLUDED(*DataGuard()); - // If possible, updates copy of the Flag's value that is stored in an - // atomic word. - void StoreAtomic() ABSL_EXCLUSIVE_LOCKS_REQUIRED(*DataGuard()); // Interfaces to operate on callbacks. void SetCallback(const FlagCallbackFunc mutation_callback) @@ -456,6 +423,14 @@ class FlagImpl { bool ValidateInputValue(absl::string_view value) const ABSL_LOCKS_EXCLUDED(*DataGuard()); + // Used in read/write operations to validate source/target has correct type. + // For example if flag is declared as absl::Flag FLAGS_foo, a call to + // absl::GetFlag(FLAGS_foo) validates that the type of FLAGS_foo is indeed + // int. To do that we pass the "assumed" type id (which is deduced from type + // int) as an argument `op`, which is in turn is validated against the type id + // stored in flag object by flag definition statement. + void AssertValidType(FlagStaticTypeId type_id) const; + private: // Ensures that `data_guard_` is initialized and returns it. absl::Mutex* DataGuard() const ABSL_LOCK_RETURNED((absl::Mutex*)&data_guard_); @@ -475,17 +450,13 @@ class FlagImpl { FlagHelpKind HelpSourceKind() const { return static_cast(help_source_kind_); } + FlagValueStorageKind ValueStorageKind() const { + return static_cast(value_storage_kind_); + } FlagDefaultKind DefaultKind() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(*DataGuard()) { return static_cast(def_kind_); } - // Used in read/write operations to validate source/target has correct type. - // For example if flag is declared as absl::Flag FLAGS_foo, a call to - // absl::GetFlag(FLAGS_foo) validates that the type of FLAGS_foo is indeed - // int. To do that we pass the "assumed" type id (which is deduced from type - // int) as an argument `op`, which is in turn is validated against the type id - // stored in flag object by flag definition statement. - void AssertValidType(FlagStaticTypeId type_id) const; // Immutable flag's state. @@ -499,6 +470,8 @@ class FlagImpl { const FlagHelpMsg help_; // Indicates if help message was supplied as literal or generator func. const uint8_t help_source_kind_ : 1; + // Kind of storage this flag is using for the flag's value. + const uint8_t value_storage_kind_ : 2; // ------------------------------------------------------------------------ // The bytes containing the const bitfields must not be shared with bytes @@ -530,8 +503,13 @@ class FlagImpl { // value specified in ABSL_FLAG or pointer to the dynamically set default // value via SetCommandLineOptionWithMode. def_kind_ is used to distinguish // these two cases. - FlagDefaultSrc default_value_ ABSL_GUARDED_BY(*DataGuard()); - // Current Flag Value + FlagDefaultSrc default_value_; + + // Atomically mutable flag's state + + // Flag's value. This can be either the atomically stored small value or + // pointer to the heap allocated dynamic value. value_storage_kind_ is used + // to distinguish these cases. FlagValue value_; // This is reserved space for an absl::Mutex to guard flag data. It will be @@ -553,7 +531,8 @@ class Flag final : public flags_internal::CommandLineFlag { public: constexpr Flag(const char* name, const char* filename, const FlagHelpArg help, const FlagDfltGenFunc default_value_gen) - : impl_(name, filename, &FlagOps, help, default_value_gen) {} + : impl_(name, filename, &FlagOps, help, FlagValue::Kind(), + default_value_gen) {} T Get() const { // See implementation notes in CommandLineFlag::Get(). @@ -564,10 +543,17 @@ class Flag final : public flags_internal::CommandLineFlag { }; U u; +#if !defined(NDEBUG) + impl_.AssertValidType(&flags_internal::FlagStaticTypeIdGen); +#endif + impl_.Get(&u.value); return std::move(u.value); } - void Set(const T& v) { impl_.Set(v); } + void Set(const T& v) { + impl_.AssertValidType(&flags_internal::FlagStaticTypeIdGen); + impl_.Write(&v); + } void SetCallback(const FlagCallbackFunc mutation_callback) { impl_.SetCallback(mutation_callback); } @@ -619,7 +605,7 @@ class Flag final : public flags_internal::CommandLineFlag { }; template -inline void FlagState::Restore() const { +void FlagState::Restore() const { if (flag_->RestoreState(*this)) { ABSL_INTERNAL_LOG(INFO, absl::StrCat("Restore saved value of ", flag_->Name(), diff --git a/absl/hash/internal/hash.h b/absl/hash/internal/hash.h index ae7a60cd..1cc2c5e5 100644 --- a/absl/hash/internal/hash.h +++ b/absl/hash/internal/hash.h @@ -955,12 +955,15 @@ H PiecewiseCombiner::add_buffer(H state, const unsigned char* data, return state; } - // Complete the buffer and hash it - const size_t bytes_needed = PiecewiseChunkSize() - position_; - memcpy(buf_ + position_, data, bytes_needed); - state = H::combine_contiguous(std::move(state), buf_, PiecewiseChunkSize()); - data += bytes_needed; - size -= bytes_needed; + // If the buffer is partially filled we need to complete the buffer + // and hash it. + if (position_ != 0) { + const size_t bytes_needed = PiecewiseChunkSize() - position_; + memcpy(buf_ + position_, data, bytes_needed); + state = H::combine_contiguous(std::move(state), buf_, PiecewiseChunkSize()); + data += bytes_needed; + size -= bytes_needed; + } // Hash whatever chunks we can without copying while (size >= PiecewiseChunkSize()) { diff --git a/absl/random/internal/BUILD.bazel b/absl/random/internal/BUILD.bazel index d7ad4efe..1c9dabb5 100644 --- a/absl/random/internal/BUILD.bazel +++ b/absl/random/internal/BUILD.bazel @@ -705,6 +705,7 @@ cc_test( cc_test( name = "randen_benchmarks", size = "medium", + timeout = "long", srcs = ["randen_benchmarks.cc"], copts = ABSL_TEST_COPTS + ABSL_RANDOM_RANDEN_COPTS, flaky = 1, diff --git a/absl/status/status.cc b/absl/status/status.cc index bbc1895e..df3b740f 100644 --- a/absl/status/status.cc +++ b/absl/status/status.cc @@ -147,7 +147,15 @@ void Status::SetPayload(absl::string_view type_url, absl::Cord payload) { bool Status::ErasePayload(absl::string_view type_url) { int index = status_internal::FindPayloadIndexByUrl(GetPayloads(), type_url); if (index != -1) { + PrepareToModify(); GetPayloads()->erase(GetPayloads()->begin() + index); + if (GetPayloads()->empty() && message().empty()) { + // Special case: If this can be represented inlined, it MUST be + // inlined (EqualsSlow depends on this behavior). + StatusCode c = static_cast(raw_code()); + Unref(rep_); + rep_ = CodeToInlinedRep(c); + } return true; } diff --git a/absl/status/status_test.cc b/absl/status/status_test.cc index 7cc65e45..ca9488ad 100644 --- a/absl/status/status_test.cc +++ b/absl/status/status_test.cc @@ -204,6 +204,25 @@ TEST(Status, TestComparePayloads) { EXPECT_EQ(bad_status1, bad_status2); } +TEST(Status, TestComparePayloadsAfterErase) { + absl::Status payload_status(absl::StatusCode::kInternal, ""); + payload_status.SetPayload(kUrl1, absl::Cord(kPayload1)); + payload_status.SetPayload(kUrl2, absl::Cord(kPayload2)); + + absl::Status empty_status(absl::StatusCode::kInternal, ""); + + // Different payloads, not equal + EXPECT_NE(payload_status, empty_status); + EXPECT_TRUE(payload_status.ErasePayload(kUrl1)); + + // Still Different payloads, still not equal. + EXPECT_NE(payload_status, empty_status); + EXPECT_TRUE(payload_status.ErasePayload(kUrl2)); + + // Both empty payloads, should be equal + EXPECT_EQ(payload_status, empty_status); +} + PayloadsVec AllVisitedPayloads(const absl::Status& s) { PayloadsVec result; @@ -261,6 +280,36 @@ TEST(Status, ToString) { HasSubstr("[bar='\\xff']"))); } +absl::Status EraseAndReturn(const absl::Status& base) { + absl::Status copy = base; + EXPECT_TRUE(copy.ErasePayload(kUrl1)); + return copy; +} + +TEST(Status, CopyOnWriteForErasePayload) { + { + absl::Status base(absl::StatusCode::kInvalidArgument, "fail"); + base.SetPayload(kUrl1, absl::Cord(kPayload1)); + EXPECT_TRUE(base.GetPayload(kUrl1).has_value()); + absl::Status copy = EraseAndReturn(base); + EXPECT_TRUE(base.GetPayload(kUrl1).has_value()); + EXPECT_FALSE(copy.GetPayload(kUrl1).has_value()); + } + { + absl::Status base(absl::StatusCode::kInvalidArgument, "fail"); + base.SetPayload(kUrl1, absl::Cord(kPayload1)); + absl::Status copy = base; + + EXPECT_TRUE(base.GetPayload(kUrl1).has_value()); + EXPECT_TRUE(copy.GetPayload(kUrl1).has_value()); + + EXPECT_TRUE(base.ErasePayload(kUrl1)); + + EXPECT_FALSE(base.GetPayload(kUrl1).has_value()); + EXPECT_TRUE(copy.GetPayload(kUrl1).has_value()); + } +} + TEST(Status, CopyConstructor) { { absl::Status status; @@ -300,6 +349,14 @@ TEST(Status, CopyAssignment) { } } +TEST(Status, CopyAssignmentIsNotRef) { + const absl::Status status_orig(absl::StatusCode::kInvalidArgument, "message"); + absl::Status status_copy = status_orig; + EXPECT_EQ(status_orig, status_copy); + status_copy.SetPayload(kUrl1, absl::Cord(kPayload1)); + EXPECT_NE(status_orig, status_copy); +} + TEST(Status, MoveConstructor) { { absl::Status status; diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index 5cc68539..9b32b3cc 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc @@ -30,7 +30,6 @@ #include "absl/base/internal/raw_logging.h" #include "absl/base/port.h" #include "absl/container/fixed_array.h" -#include "absl/container/inlined_vector.h" #include "absl/strings/escaping.h" #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/resize_uninitialized.h" @@ -132,6 +131,14 @@ inline const CordRepExternal* CordRep::external() const { return static_cast(this); } +using CordTreeConstPath = CordTreePath; + +// This type is used to store the list of pending nodes during re-balancing. +// Its maximum size is 2 * MaxCordDepth() because the tree has a maximum +// possible depth of MaxCordDepth() and every concat node along a tree path +// could theoretically be split during rebalancing. +using RebalancingStack = CordTreePath; + } // namespace cord_internal static const size_t kFlatOverhead = offsetof(CordRep, data); @@ -180,8 +187,8 @@ static constexpr size_t TagToLength(uint8_t tag) { // Enforce that kMaxFlatSize maps to a well-known exact tag value. static_assert(TagToAllocatedSize(224) == kMaxFlatSize, "Bad tag logic"); -constexpr uint64_t Fibonacci(unsigned char n, uint64_t a = 0, uint64_t b = 1) { - return n == 0 ? a : Fibonacci(n - 1, b, a + b); +constexpr uint64_t Fibonacci(uint8_t n, uint64_t a = 0, uint64_t b = 1) { + return n == 0 ? a : n == 1 ? b : Fibonacci(n - 1, b, a + b); } static_assert(Fibonacci(63) == 6557470319842, @@ -189,89 +196,68 @@ static_assert(Fibonacci(63) == 6557470319842, // Minimum length required for a given depth tree -- a tree is considered // balanced if -// length(t) >= min_length[depth(t)] -// The root node depth is allowed to become twice as large to reduce rebalancing -// for larger strings (see IsRootBalanced). -static constexpr uint64_t min_length[] = { - Fibonacci(2), - Fibonacci(3), - Fibonacci(4), - Fibonacci(5), - Fibonacci(6), - Fibonacci(7), - Fibonacci(8), - Fibonacci(9), - Fibonacci(10), - Fibonacci(11), - Fibonacci(12), - Fibonacci(13), - Fibonacci(14), - Fibonacci(15), - Fibonacci(16), - Fibonacci(17), - Fibonacci(18), - Fibonacci(19), - Fibonacci(20), - Fibonacci(21), - Fibonacci(22), - Fibonacci(23), - Fibonacci(24), - Fibonacci(25), - Fibonacci(26), - Fibonacci(27), - Fibonacci(28), - Fibonacci(29), - Fibonacci(30), - Fibonacci(31), - Fibonacci(32), - Fibonacci(33), - Fibonacci(34), - Fibonacci(35), - Fibonacci(36), - Fibonacci(37), - Fibonacci(38), - Fibonacci(39), - Fibonacci(40), - Fibonacci(41), - Fibonacci(42), - Fibonacci(43), - Fibonacci(44), - Fibonacci(45), - Fibonacci(46), - Fibonacci(47), - 0xffffffffffffffffull, // Avoid overflow -}; - -static const int kMinLengthSize = ABSL_ARRAYSIZE(min_length); - -// The inlined size to use with absl::InlinedVector. -// -// Note: The InlinedVectors in this file (and in cord.h) do not need to use -// the same value for their inlined size. The fact that they do is historical. -// It may be desirable for each to use a different inlined size optimized for -// that InlinedVector's usage. -// -// TODO(jgm): Benchmark to see if there's a more optimal value than 47 for -// the inlined vector size (47 exists for backward compatibility). -static const int kInlinedVectorSize = 47; - -static inline bool IsRootBalanced(CordRep* node) { - if (node->tag != CONCAT) { - return true; - } else if (node->concat()->depth() <= 15) { - return true; - } else if (node->concat()->depth() > kMinLengthSize) { - return false; - } else { - // Allow depth to become twice as large as implied by fibonacci rule to - // reduce rebalancing for larger strings. - return (node->length >= min_length[node->concat()->depth() / 2]); - } +// length(t) >= kMinLength[depth(t)] +// The node depth is allowed to become larger to reduce rebalancing +// for larger strings (see ShouldRebalance). +constexpr uint64_t kMinLength[] = { + Fibonacci(2), Fibonacci(3), Fibonacci(4), Fibonacci(5), Fibonacci(6), + Fibonacci(7), Fibonacci(8), Fibonacci(9), Fibonacci(10), Fibonacci(11), + Fibonacci(12), Fibonacci(13), Fibonacci(14), Fibonacci(15), Fibonacci(16), + Fibonacci(17), Fibonacci(18), Fibonacci(19), Fibonacci(20), Fibonacci(21), + Fibonacci(22), Fibonacci(23), Fibonacci(24), Fibonacci(25), Fibonacci(26), + Fibonacci(27), Fibonacci(28), Fibonacci(29), Fibonacci(30), Fibonacci(31), + Fibonacci(32), Fibonacci(33), Fibonacci(34), Fibonacci(35), Fibonacci(36), + Fibonacci(37), Fibonacci(38), Fibonacci(39), Fibonacci(40), Fibonacci(41), + Fibonacci(42), Fibonacci(43), Fibonacci(44), Fibonacci(45), Fibonacci(46), + Fibonacci(47), Fibonacci(48), Fibonacci(49), Fibonacci(50), Fibonacci(51), + Fibonacci(52), Fibonacci(53), Fibonacci(54), Fibonacci(55), Fibonacci(56), + Fibonacci(57), Fibonacci(58), Fibonacci(59), Fibonacci(60), Fibonacci(61), + Fibonacci(62), Fibonacci(63), Fibonacci(64), Fibonacci(65), Fibonacci(66), + Fibonacci(67), Fibonacci(68), Fibonacci(69), Fibonacci(70), Fibonacci(71), + Fibonacci(72), Fibonacci(73), Fibonacci(74), Fibonacci(75), Fibonacci(76), + Fibonacci(77), Fibonacci(78), Fibonacci(79), Fibonacci(80), Fibonacci(81), + Fibonacci(82), Fibonacci(83), Fibonacci(84), Fibonacci(85), Fibonacci(86), + Fibonacci(87), Fibonacci(88), Fibonacci(89), Fibonacci(90), Fibonacci(91), + Fibonacci(92), Fibonacci(93)}; + +static_assert(sizeof(kMinLength) / sizeof(uint64_t) == + (cord_internal::MaxCordDepth() + 1), + "Not enough elements in kMinLength array to cover all the " + "supported Cord depth(s)"); + +inline bool ShouldRebalance(const CordRep* node) { + if (node->tag != CONCAT) return false; + + size_t node_depth = node->concat()->depth(); + + if (node_depth <= 15) return false; + + // Rebalancing Cords is expensive, so we reduce how often rebalancing occurs + // by allowing shallow Cords to have twice the depth that the Fibonacci rule + // would otherwise imply. Deep Cords need to follow the rule more closely, + // however to ensure algorithm correctness. We implement this with linear + // interpolation. Cords of depth 16 are treated as though they have a depth + // of 16 * 1/2, and Cords of depth MaxCordDepth() interpolate to + // MaxCordDepth() * 1. + return node->length < + kMinLength[(node_depth * (cord_internal::MaxCordDepth() - 16)) / + (2 * cord_internal::MaxCordDepth() - 16 - node_depth)]; +} + +// Unlike root balancing condition this one is part of the re-balancing +// algorithm and has to be always matching against right depth for +// algorithm to be correct. +inline bool IsNodeBalanced(const CordRep* node) { + if (node->tag != CONCAT) return true; + + size_t node_depth = node->concat()->depth(); + + return node->length >= kMinLength[node_depth]; } static CordRep* Rebalance(CordRep* node); -static void DumpNode(CordRep* rep, bool include_data, std::ostream* os); -static bool VerifyNode(CordRep* root, CordRep* start_node, +static void DumpNode(const CordRep* rep, bool include_data, std::ostream* os); +static bool VerifyNode(const CordRep* root, const CordRep* start_node, bool full_validation); static inline CordRep* VerifyTree(CordRep* node) { @@ -318,7 +304,8 @@ __attribute__((preserve_most)) static void UnrefInternal(CordRep* rep) { assert(rep != nullptr); - absl::InlinedVector pending; + cord_internal::RebalancingStack pending; + while (true) { if (rep->tag == CONCAT) { CordRepConcat* rep_concat = rep->concat(); @@ -400,6 +387,11 @@ static void SetConcatChildren(CordRepConcat* concat, CordRep* left, concat->length = left->length + right->length; concat->set_depth(1 + std::max(Depth(left), Depth(right))); + + ABSL_INTERNAL_CHECK(concat->depth() <= cord_internal::MaxCordDepth(), + "Cord depth exceeds max"); + ABSL_INTERNAL_CHECK(concat->length >= left->length, "Cord is too long"); + ABSL_INTERNAL_CHECK(concat->length >= right->length, "Cord is too long"); } // Create a concatenation of the specified nodes. @@ -425,7 +417,7 @@ static CordRep* RawConcat(CordRep* left, CordRep* right) { static CordRep* Concat(CordRep* left, CordRep* right) { CordRep* rep = RawConcat(left, right); - if (rep != nullptr && !IsRootBalanced(rep)) { + if (rep != nullptr && ShouldRebalance(rep)) { rep = Rebalance(rep); } return VerifyTree(rep); @@ -916,7 +908,7 @@ void Cord::Prepend(absl::string_view src) { static CordRep* RemovePrefixFrom(CordRep* node, size_t n) { if (n >= node->length) return nullptr; if (n == 0) return Ref(node); - absl::InlinedVector rhs_stack; + cord_internal::CordTreeMutablePath rhs_stack; while (node->tag == CONCAT) { assert(n <= node->length); @@ -957,7 +949,7 @@ static CordRep* RemovePrefixFrom(CordRep* node, size_t n) { static CordRep* RemoveSuffixFrom(CordRep* node, size_t n) { if (n >= node->length) return nullptr; if (n == 0) return Ref(node); - absl::InlinedVector lhs_stack; + absl::cord_internal::CordTreeMutablePath lhs_stack; bool inplace_ok = node->refcount.IsOne(); while (node->tag == CONCAT) { @@ -1028,6 +1020,7 @@ void Cord::RemoveSuffix(size_t n) { // Work item for NewSubRange(). struct SubRange { + SubRange() = default; SubRange(CordRep* a_node, size_t a_pos, size_t a_n) : node(a_node), pos(a_pos), n(a_n) {} CordRep* node; // nullptr means concat last 2 results. @@ -1036,8 +1029,11 @@ struct SubRange { }; static CordRep* NewSubRange(CordRep* node, size_t pos, size_t n) { - absl::InlinedVector results; - absl::InlinedVector todo; + cord_internal::CordTreeMutablePath results; + // The algorithm below in worst case scenario adds up to 3 nodes to the `todo` + // list, but we also pop one out on every cycle. If original tree has depth d + // todo list can grew up to 2*d in size. + cord_internal::CordTreePath todo; todo.push_back(SubRange(node, pos, n)); do { const SubRange& sr = todo.back(); @@ -1074,7 +1070,7 @@ static CordRep* NewSubRange(CordRep* node, size_t pos, size_t n) { } } while (!todo.empty()); assert(results.size() == 1); - return results[0]; + return results.back(); } Cord Cord::Subcord(size_t pos, size_t new_size) const { @@ -1113,11 +1109,12 @@ Cord Cord::Subcord(size_t pos, size_t new_size) const { class CordForest { public: - explicit CordForest(size_t length) - : root_length_(length), trees_(kMinLengthSize, nullptr) {} + explicit CordForest(size_t length) : root_length_(length), trees_({}) {} void Build(CordRep* cord_root) { - std::vector pending = {cord_root}; + // We are adding up to two nodes to the `pending` list, but we also popping + // one, so the size of `pending` will never exceed `MaxCordDepth()`. + cord_internal::CordTreeMutablePath pending(cord_root); while (!pending.empty()) { CordRep* node = pending.back(); @@ -1129,21 +1126,20 @@ class CordForest { } CordRepConcat* concat_node = node->concat(); - if (concat_node->depth() >= kMinLengthSize || - concat_node->length < min_length[concat_node->depth()]) { - pending.push_back(concat_node->right); - pending.push_back(concat_node->left); - - if (concat_node->refcount.IsOne()) { - concat_node->left = concat_freelist_; - concat_freelist_ = concat_node; - } else { - Ref(concat_node->right); - Ref(concat_node->left); - Unref(concat_node); - } - } else { + if (IsNodeBalanced(concat_node)) { AddNode(node); + continue; + } + pending.push_back(concat_node->right); + pending.push_back(concat_node->left); + + if (concat_node->refcount.IsOne()) { + concat_node->left = concat_freelist_; + concat_freelist_ = concat_node; + } else { + Ref(concat_node->right); + Ref(concat_node->left); + Unref(concat_node); } } } @@ -1175,7 +1171,7 @@ class CordForest { // Collect together everything with which we will merge node int i = 0; - for (; node->length > min_length[i + 1]; ++i) { + for (; node->length > kMinLength[i + 1]; ++i) { auto& tree_at_i = trees_[i]; if (tree_at_i == nullptr) continue; @@ -1186,7 +1182,7 @@ class CordForest { sum = AppendNode(node, sum); // Insert sum into appropriate place in the forest - for (; sum->length >= min_length[i]; ++i) { + for (; sum->length >= kMinLength[i]; ++i) { auto& tree_at_i = trees_[i]; if (tree_at_i == nullptr) continue; @@ -1194,7 +1190,7 @@ class CordForest { tree_at_i = nullptr; } - // min_length[0] == 1, which means sum->length >= min_length[0] + // kMinLength[0] == 1, which means sum->length >= kMinLength[0] assert(i > 0); trees_[i - 1] = sum; } @@ -1227,9 +1223,7 @@ class CordForest { } size_t root_length_; - - // use an inlined vector instead of a flat array to get bounds checking - absl::InlinedVector trees_; + std::array trees_; // List of concat nodes we can re-use for Cord balancing. CordRepConcat* concat_freelist_ = nullptr; @@ -1841,18 +1835,18 @@ absl::string_view Cord::FlattenSlowPath() { } } -static void DumpNode(CordRep* rep, bool include_data, std::ostream* os) { +static void DumpNode(const CordRep* rep, bool include_data, std::ostream* os) { const int kIndentStep = 1; int indent = 0; - absl::InlinedVector stack; - absl::InlinedVector indents; + cord_internal::CordTreeConstPath stack; + cord_internal::CordTreePath indents; for (;;) { *os << std::setw(3) << rep->refcount.Get(); *os << " " << std::setw(7) << rep->length; *os << " ["; - if (include_data) *os << static_cast(rep); + if (include_data) *os << static_cast(rep); *os << "]"; - *os << " " << (IsRootBalanced(rep) ? 'b' : 'u'); + *os << " " << (IsNodeBalanced(rep) ? 'b' : 'u'); *os << " " << std::setw(indent) << ""; if (rep->tag == CONCAT) { *os << "CONCAT depth=" << Depth(rep) << "\n"; @@ -1873,7 +1867,7 @@ static void DumpNode(CordRep* rep, bool include_data, std::ostream* os) { } else { *os << "FLAT cap=" << TagToLength(rep->tag) << " ["; if (include_data) - *os << absl::CEscape(std::string(rep->data, rep->length)); + *os << absl::CEscape(absl::string_view(rep->data, rep->length)); *os << "]\n"; } if (stack.empty()) break; @@ -1886,19 +1880,19 @@ static void DumpNode(CordRep* rep, bool include_data, std::ostream* os) { ABSL_INTERNAL_CHECK(indents.empty(), ""); } -static std::string ReportError(CordRep* root, CordRep* node) { +static std::string ReportError(const CordRep* root, const CordRep* node) { std::ostringstream buf; buf << "Error at node " << node << " in:"; DumpNode(root, true, &buf); return buf.str(); } -static bool VerifyNode(CordRep* root, CordRep* start_node, +static bool VerifyNode(const CordRep* root, const CordRep* start_node, bool full_validation) { - absl::InlinedVector worklist; + cord_internal::CordTreeConstPath worklist; worklist.push_back(start_node); do { - CordRep* node = worklist.back(); + const CordRep* node = worklist.back(); worklist.pop_back(); ABSL_INTERNAL_CHECK(node != nullptr, ReportError(root, node)); @@ -1948,7 +1942,7 @@ static bool VerifyNode(CordRep* root, CordRep* start_node, // Iterate over the tree. cur_node is never a leaf node and leaf nodes will // never be appended to tree_stack. This reduces overhead from manipulating // tree_stack. - absl::InlinedVector tree_stack; + cord_internal::CordTreeConstPath tree_stack; const CordRep* cur_node = rep; while (true) { const CordRep* next_node = nullptr; diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 40566cba..68a7e52f 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -41,13 +41,13 @@ #include #include #include +#include #include "absl/base/internal/endian.h" #include "absl/base/internal/invoke.h" #include "absl/base/internal/per_thread_tls.h" #include "absl/base/macros.h" #include "absl/base/port.h" -#include "absl/container/inlined_vector.h" #include "absl/functional/function_ref.h" #include "absl/meta/type_traits.h" #include "absl/strings/internal/cord_internal.h" @@ -66,6 +66,73 @@ template H HashFragmentedCord(H, const Cord&); } +namespace cord_internal { + +// It's expensive to keep a tree perfectly balanced, so instead we keep trees +// approximately balanced. A tree node N of depth D(N) that contains a string +// of L(N) characters is considered balanced if L >= Fibonacci(D + 2). +// The "+ 2" is used to ensure that every leaf node contains at least one +// character. Here we presume that +// Fibonacci(0) = 0 +// Fibonacci(1) = 1 +// Fibonacci(2) = 1 +// Fibonacci(3) = 2 +// ... +// +// Fibonacci numbers are convenient because it means when two balanced trees of +// the same depth are made the children of a new node, the resulting tree is +// guaranteed to also be balanced: +// +// +// L(left) >= Fibonacci(D(left) + 2) +// L(right) >= Fibonacci(D(right) + 2) +// +// L(left) + L(right) >= Fibonacci(D(left) + 2) + Fibonacci(D(right) + 2) +// L(left) + L(right) == L(new_tree) +// +// L(new_tree) >= 2 * Fibonacci(D(child) + 2) +// D(child) == D(new_tree) - 1 +// +// L(new_tree) >= 2 * Fibonacci(D(new_tree) + 1) +// 2 * Fibonacci(N) >= Fibonacci(N + 1) +// +// L(new_tree) >= Fibonacci(D(new_tree) + 2) +// +// +// The 93rd Fibonacci number is the largest Fibonacci number that can be +// represented in 64 bits, so the size of a balanced Cord of depth 92 is too big +// for an unsigned 64 bit integer to hold. Therefore we can safely assume that +// the maximum depth of a Cord is 91. +constexpr size_t MaxCordDepth() { return 91; } + +// This class models fixed max size stack of CordRep pointers. +// The elements are being pushed back and popped from the back. +template +class CordTreePath { + public: + CordTreePath() {} + explicit CordTreePath(CordRepPtr root) { push_back(root); } + + bool empty() const { return size_ == 0; } + size_t size() const { return size_; } + void clear() { size_ = 0; } + + CordRepPtr back() { return data_[size_ - 1]; } + + void pop_back() { + --size_; + assert(size_ < N); + } + void push_back(CordRepPtr elem) { data_[size_++] = elem; } + + private: + CordRepPtr data_[N]; + size_t size_ = 0; +}; + +using CordTreeMutablePath = CordTreePath; +} // namespace cord_internal + // A Cord is a sequence of characters. class Cord { private: @@ -114,7 +181,8 @@ class Cord { // finished with `data`. The data must remain live and unchanging until the // releaser is called. The requirements for the releaser are that it: // * is move constructible, - // * supports `void operator()(absl::string_view) const`, + // * supports `void operator()(absl::string_view) const` or + // `void operator()() const`, // * does not have alignment requirement greater than what is guaranteed by // ::operator new. This is dictated by alignof(std::max_align_t) before // C++17 and __STDCPP_DEFAULT_NEW_ALIGNMENT__ if compiling with C++17 or @@ -127,8 +195,8 @@ class Cord { // FillBlock(block); // return absl::MakeCordFromExternal( // block->ToStringView(), - // [pool, block](absl::string_view /*ignored*/) { - // pool->FreeBlock(block); + // [pool, block](absl::string_view v) { + // pool->FreeBlock(block, v); // }); // } // @@ -282,8 +350,7 @@ class Cord { absl::cord_internal::CordRep* current_leaf_ = nullptr; // The number of bytes left in the `Cord` over which we are iterating. size_t bytes_remaining_ = 0; - absl::InlinedVector - stack_of_right_children_; + absl::cord_internal::CordTreeMutablePath stack_of_right_children_; }; // Returns an iterator to the first chunk of the `Cord`. @@ -667,6 +734,21 @@ ExternalRepReleaserPair NewExternalWithUninitializedReleaser( absl::string_view data, ExternalReleaserInvoker invoker, size_t releaser_size); +struct Rank1 {}; +struct Rank0 : Rank1 {}; + +template > +void InvokeReleaser(Rank0, Releaser&& releaser, absl::string_view data) { + ::absl::base_internal::Invoke(std::forward(releaser), data); +} + +template > +void InvokeReleaser(Rank1, Releaser&& releaser, absl::string_view) { + ::absl::base_internal::Invoke(std::forward(releaser)); +} + // Creates a new `CordRep` that owns `data` and `releaser` and returns a pointer // to it, or `nullptr` if `data` was empty. template @@ -684,14 +766,14 @@ CordRep* NewExternalRep(absl::string_view data, Releaser&& releaser) { using ReleaserType = absl::decay_t; if (data.empty()) { // Never create empty external nodes. - ::absl::base_internal::Invoke( - ReleaserType(std::forward(releaser)), data); + InvokeReleaser(Rank0{}, ReleaserType(std::forward(releaser)), + data); return nullptr; } auto releaser_invoker = [](void* type_erased_releaser, absl::string_view d) { auto* my_releaser = static_cast(type_erased_releaser); - ::absl::base_internal::Invoke(std::move(*my_releaser), d); + InvokeReleaser(Rank0{}, std::move(*my_releaser), d); my_releaser->~ReleaserType(); return sizeof(Releaser); }; diff --git a/absl/strings/cord_test.cc b/absl/strings/cord_test.cc index 434f3a24..68515cbf 100644 --- a/absl/strings/cord_test.cc +++ b/absl/strings/cord_test.cc @@ -1032,6 +1032,19 @@ TEST(ConstructFromExternal, MoveOnlyReleaser) { EXPECT_TRUE(invoked); } +TEST(ConstructFromExternal, NoArgLambda) { + bool invoked = false; + (void)absl::MakeCordFromExternal("dummy", [&invoked]() { invoked = true; }); + EXPECT_TRUE(invoked); +} + +TEST(ConstructFromExternal, StringViewArgLambda) { + bool invoked = false; + (void)absl::MakeCordFromExternal( + "dummy", [&invoked](absl::string_view) { invoked = true; }); + EXPECT_TRUE(invoked); +} + TEST(ConstructFromExternal, NonTrivialReleaserDestructor) { struct Releaser { explicit Releaser(bool* destroyed) : destroyed(destroyed) {} @@ -1346,6 +1359,49 @@ TEST(CordChunkIterator, Operations) { VerifyChunkIterator(subcords, 128); } +TEST(CordChunkIterator, MaxLengthFullTree) { + absl::Cord cord; + size_t size = 1; + AddExternalMemory("x", &cord); + EXPECT_EQ(cord.size(), size); + + for (int i = 0; i < 63; ++i) { + cord.Prepend(absl::Cord(cord)); + size <<= 1; + + EXPECT_EQ(cord.size(), size); + + auto chunk_it = cord.chunk_begin(); + EXPECT_EQ(*chunk_it, "x"); + } + + EXPECT_DEATH_IF_SUPPORTED( + (cord.Prepend(absl::Cord(cord)), *cord.chunk_begin()), + "Cord is too long"); +} + +TEST(CordChunkIterator, MaxDepth) { + // By reusing nodes, it's possible in pathological cases to build a Cord that + // exceeds both the maximum permissible length and depth. In this case, the + // violation of the maximum depth is reported. + absl::Cord left_child; + AddExternalMemory("x", &left_child); + absl::Cord root = left_child; + + for (int i = 0; i < 91; ++i) { + size_t new_size = left_child.size() + root.size(); + root.Prepend(left_child); + EXPECT_EQ(root.size(), new_size); + + auto chunk_it = root.chunk_begin(); + EXPECT_EQ(*chunk_it, "x"); + + std::swap(left_child, root); + } + + EXPECT_DEATH_IF_SUPPORTED(root.Prepend(left_child), "Cord depth exceeds max"); +} + TEST(CordCharIterator, Traits) { static_assert(std::is_copy_constructible::value, ""); -- cgit v1.2.3 From cf3a1998e9d41709d4141e2f13375993cba1130e Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Thu, 5 Mar 2020 08:37:17 -0800 Subject: Export of internal Abseil changes -- 44ccc0320ffaa2106ba3c6393b5a40c3b4f7b901 by Abseil Team : Clarify span iterator documentation. PiperOrigin-RevId: 299110584 -- 80d016d8026b8d6904aa0ff2d5e1c3ae27f129bb by Greg Falcon : Add Cord::TryFlat(). PiperOrigin-RevId: 298889772 -- da6900203f1e4131d5693cbca157b6dba099bbed by Greg Falcon : clang-format cord_test.cc. PiperOrigin-RevId: 298851425 GitOrigin-RevId: 44ccc0320ffaa2106ba3c6393b5a40c3b4f7b901 Change-Id: Ia5394f6fbb473d206726fdd48a00eb07a6acad6a --- absl/strings/BUILD.bazel | 1 + absl/strings/CMakeLists.txt | 1 + absl/strings/cord.h | 19 +++++++++++- absl/strings/cord_test.cc | 71 ++++++++++++++++++++++++++++++++++++--------- absl/types/span.h | 34 +++++++++++++++------- 5 files changed, 101 insertions(+), 25 deletions(-) (limited to 'absl/strings/cord.h') diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index b950ec76..e72db82c 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -285,6 +285,7 @@ cc_library( "//absl/container:inlined_vector", "//absl/functional:function_ref", "//absl/meta:type_traits", + "//absl/types:optional", ], ) diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index cebc5928..0757a9cb 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt @@ -546,6 +546,7 @@ absl_cc_library( absl::fixed_array absl::function_ref absl::inlined_vector + absl::optional absl::raw_logging_internal absl::type_traits PUBLIC diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 68a7e52f..29ed7f75 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -53,6 +53,7 @@ #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/resize_uninitialized.h" #include "absl/strings/string_view.h" +#include "absl/types/optional.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -512,6 +513,10 @@ class Cord { // REQUIRES: 0 <= i < size() char operator[](size_t i) const; + // If this cord's representation is a single flat array, return a + // string_view referencing that array. Otherwise return nullopt. + absl::optional TryFlat() const; + // Flattens the cord into a single array and returns a view of the data. // // If the cord was already flat, the contents are not modified. @@ -630,7 +635,7 @@ class Cord { // Helper for MemoryUsage() static size_t MemoryUsageAux(const absl::cord_internal::CordRep* rep); - // Helper for GetFlat() + // Helper for GetFlat() and TryFlat() static bool GetFlatAux(absl::cord_internal::CordRep* rep, absl::string_view* fragment); @@ -942,6 +947,18 @@ inline size_t Cord::EstimatedMemoryUsage() const { return result; } +inline absl::optional Cord::TryFlat() const { + absl::cord_internal::CordRep* rep = contents_.tree(); + if (rep == nullptr) { + return absl::string_view(contents_.data(), contents_.size()); + } + absl::string_view fragment; + if (GetFlatAux(rep, &fragment)) { + return fragment; + } + return absl::nullopt; +} + inline absl::string_view Cord::Flatten() { absl::cord_internal::CordRep* rep = contents_.tree(); if (rep == nullptr) { diff --git a/absl/strings/cord_test.cc b/absl/strings/cord_test.cc index 68515cbf..a683cc4b 100644 --- a/absl/strings/cord_test.cc +++ b/absl/strings/cord_test.cc @@ -70,9 +70,8 @@ static std::string RandomLowercaseString(RandomEngine* rng) { static std::string RandomLowercaseString(RandomEngine* rng, size_t length) { std::string result(length, '\0'); std::uniform_int_distribution chars('a', 'z'); - std::generate(result.begin(), result.end(), [&]() { - return static_cast(chars(*rng)); - }); + std::generate(result.begin(), result.end(), + [&]() { return static_cast(chars(*rng)); }); return result; } @@ -424,6 +423,50 @@ TEST(Cord, CopyToString) { "copying ", "to ", "a ", "string."})); } +TEST(TryFlat, Empty) { + absl::Cord c; + EXPECT_EQ(c.TryFlat(), ""); +} + +TEST(TryFlat, Flat) { + absl::Cord c("hello"); + EXPECT_EQ(c.TryFlat(), "hello"); +} + +TEST(TryFlat, SubstrInlined) { + absl::Cord c("hello"); + c.RemovePrefix(1); + EXPECT_EQ(c.TryFlat(), "ello"); +} + +TEST(TryFlat, SubstrFlat) { + absl::Cord c("longer than 15 bytes"); + c.RemovePrefix(1); + EXPECT_EQ(c.TryFlat(), "onger than 15 bytes"); +} + +TEST(TryFlat, Concat) { + absl::Cord c = absl::MakeFragmentedCord({"hel", "lo"}); + EXPECT_EQ(c.TryFlat(), absl::nullopt); +} + +TEST(TryFlat, External) { + absl::Cord c = absl::MakeCordFromExternal("hell", [](absl::string_view) {}); + EXPECT_EQ(c.TryFlat(), "hell"); +} + +TEST(TryFlat, SubstrExternal) { + absl::Cord c = absl::MakeCordFromExternal("hell", [](absl::string_view) {}); + c.RemovePrefix(1); + EXPECT_EQ(c.TryFlat(), "ell"); +} + +TEST(TryFlat, SubstrConcat) { + absl::Cord c = absl::MakeFragmentedCord({"hello", " world"}); + c.RemovePrefix(1); + EXPECT_EQ(c.TryFlat(), absl::nullopt); +} + static bool IsFlat(const absl::Cord& c) { return c.chunk_begin() == c.chunk_end() || ++c.chunk_begin() == c.chunk_end(); } @@ -511,24 +554,24 @@ TEST(Cord, MultipleLengths) { for (size_t i = 0; i < d.size(); i++) { std::string a = d.data(i); - { // Construct from Cord + { // Construct from Cord absl::Cord tmp(a); absl::Cord x(tmp); EXPECT_EQ(a, std::string(x)) << "'" << a << "'"; } - { // Construct from absl::string_view + { // Construct from absl::string_view absl::Cord x(a); EXPECT_EQ(a, std::string(x)) << "'" << a << "'"; } - { // Append cord to self + { // Append cord to self absl::Cord self(a); self.Append(self); EXPECT_EQ(a + a, std::string(self)) << "'" << a << "' + '" << a << "'"; } - { // Prepend cord to self + { // Prepend cord to self absl::Cord self(a); self.Prepend(self); EXPECT_EQ(a + a, std::string(self)) << "'" << a << "' + '" << a << "'"; @@ -538,40 +581,40 @@ TEST(Cord, MultipleLengths) { for (size_t j = 0; j < d.size(); j++) { std::string b = d.data(j); - { // CopyFrom Cord + { // CopyFrom Cord absl::Cord x(a); absl::Cord y(b); x = y; EXPECT_EQ(b, std::string(x)) << "'" << a << "' + '" << b << "'"; } - { // CopyFrom absl::string_view + { // CopyFrom absl::string_view absl::Cord x(a); x = b; EXPECT_EQ(b, std::string(x)) << "'" << a << "' + '" << b << "'"; } - { // Cord::Append(Cord) + { // Cord::Append(Cord) absl::Cord x(a); absl::Cord y(b); x.Append(y); EXPECT_EQ(a + b, std::string(x)) << "'" << a << "' + '" << b << "'"; } - { // Cord::Append(absl::string_view) + { // Cord::Append(absl::string_view) absl::Cord x(a); x.Append(b); EXPECT_EQ(a + b, std::string(x)) << "'" << a << "' + '" << b << "'"; } - { // Cord::Prepend(Cord) + { // Cord::Prepend(Cord) absl::Cord x(a); absl::Cord y(b); x.Prepend(y); EXPECT_EQ(b + a, std::string(x)) << "'" << b << "' + '" << a << "'"; } - { // Cord::Prepend(absl::string_view) + { // Cord::Prepend(absl::string_view) absl::Cord x(a); x.Prepend(b); EXPECT_EQ(b + a, std::string(x)) << "'" << b << "' + '" << a << "'"; @@ -1089,7 +1132,7 @@ TEST(ConstructFromExternal, ReferenceQualifierOverloads) { } TEST(ExternalMemory, BasicUsage) { - static const char* strings[] = { "", "hello", "there" }; + static const char* strings[] = {"", "hello", "there"}; for (const char* str : strings) { absl::Cord dst("(prefix)"); AddExternalMemory(str, &dst); diff --git a/absl/types/span.h b/absl/types/span.h index 25347c63..21cda34b 100644 --- a/absl/types/span.h +++ b/absl/types/span.h @@ -292,60 +292,74 @@ class Span { // Span::front() // - // Returns a reference to the first element of this span. + // Returns a reference to the first element of this span. The span must not + // be empty. constexpr reference front() const noexcept { return ABSL_ASSERT(size() > 0), *data(); } // Span::back() // - // Returns a reference to the last element of this span. + // Returns a reference to the last element of this span. The span must not + // be empty. constexpr reference back() const noexcept { return ABSL_ASSERT(size() > 0), *(data() + size() - 1); } // Span::begin() // - // Returns an iterator to the first element of this span. + // Returns an iterator pointing to the first element of this span, or `end()` + // if the span is empty. constexpr iterator begin() const noexcept { return data(); } // Span::cbegin() // - // Returns a const iterator to the first element of this span. + // Returns a const iterator pointing to the first element of this span, or + // `end()` if the span is empty. constexpr const_iterator cbegin() const noexcept { return begin(); } // Span::end() // - // Returns an iterator to the last element of this span. + // Returns an iterator pointing just beyond the last element at the + // end of this span. This iterator acts as a placeholder; attempting to + // access it results in undefined behavior. constexpr iterator end() const noexcept { return data() + size(); } // Span::cend() // - // Returns a const iterator to the last element of this span. + // Returns a const iterator pointing just beyond the last element at the + // end of this span. This iterator acts as a placeholder; attempting to + // access it results in undefined behavior. constexpr const_iterator cend() const noexcept { return end(); } // Span::rbegin() // - // Returns a reverse iterator starting at the last element of this span. + // Returns a reverse iterator pointing to the last element at the end of this + // span, or `rend()` if the span is empty. constexpr reverse_iterator rbegin() const noexcept { return reverse_iterator(end()); } // Span::crbegin() // - // Returns a reverse const iterator starting at the last element of this span. + // Returns a const reverse iterator pointing to the last element at the end of + // this span, or `crend()` if the span is empty. constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); } // Span::rend() // - // Returns a reverse iterator starting at the first element of this span. + // Returns a reverse iterator pointing just before the first element + // at the beginning of this span. This pointer acts as a placeholder; + // attempting to access its element results in undefined behavior. constexpr reverse_iterator rend() const noexcept { return reverse_iterator(begin()); } // Span::crend() // - // Returns a reverse iterator starting at the first element of this span. + // Returns a reverse const iterator pointing just before the first element + // at the beginning of this span. This pointer acts as a placeholder; + // attempting to access its element results in undefined behavior. constexpr const_reverse_iterator crend() const noexcept { return rend(); } // Span mutations -- cgit v1.2.3 From a877af1f294be0866eab2676effd46687acb3b11 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Tue, 10 Mar 2020 09:28:06 -0700 Subject: Export of internal Abseil changes -- ea0cfebeb69b25bec343652bbe1a203f5476c51a by Mark Barolak : Change "std::string" to "string" in places where a "std::" qualification was incorrectly inserted by automation. PiperOrigin-RevId: 300108520 GitOrigin-RevId: ea0cfebeb69b25bec343652bbe1a203f5476c51a Change-Id: Ie3621e63a6ebad67b9fe56a3ebe33e1d50dac602 --- CMakeLists.txt | 2 +- absl/container/btree_test.cc | 4 +- absl/container/inlined_vector_benchmark.cc | 2 +- absl/container/inlined_vector_test.cc | 6 +-- absl/container/internal/btree.h | 2 +- absl/container/internal/compressed_tuple_test.cc | 4 +- absl/container/internal/raw_hash_set_test.cc | 6 +-- absl/debugging/failure_signal_handler.h | 2 +- absl/debugging/internal/demangle.cc | 6 +-- absl/debugging/leak_check_fail_test.cc | 4 +- absl/debugging/leak_check_test.cc | 6 +-- absl/debugging/symbolize_elf.inc | 2 +- absl/flags/internal/commandlineflag.h | 6 +-- absl/flags/internal/flag.cc | 2 +- absl/flags/internal/flag.h | 2 +- absl/flags/internal/usage.cc | 8 ++-- absl/flags/marshalling.cc | 2 +- absl/flags/parse.cc | 6 +-- absl/flags/usage_config.h | 2 +- absl/hash/hash_test.cc | 4 +- absl/random/bernoulli_distribution_test.cc | 4 +- absl/random/internal/nanobenchmark.cc | 2 +- absl/random/uniform_int_distribution_test.cc | 2 +- absl/status/status.cc | 2 +- absl/status/status.h | 4 +- absl/strings/charconv.cc | 4 +- absl/strings/charconv_benchmark.cc | 2 +- absl/strings/cord.h | 4 +- absl/strings/cord_test.cc | 8 ++-- absl/strings/escaping.cc | 18 ++++----- absl/strings/escaping_test.cc | 6 +-- absl/strings/internal/char_map.h | 2 +- absl/strings/internal/charconv_bigint.cc | 2 +- absl/strings/internal/charconv_bigint.h | 4 +- absl/strings/internal/charconv_parse.cc | 4 +- absl/strings/internal/charconv_parse_test.cc | 2 +- absl/strings/internal/numbers_test_common.h | 2 +- absl/strings/internal/str_format/bind.h | 4 +- absl/strings/internal/str_format/parser.h | 4 +- absl/strings/numbers_test.cc | 4 +- absl/strings/str_cat.h | 2 +- absl/strings/str_cat_benchmark.cc | 2 +- absl/strings/str_cat_test.cc | 6 +-- absl/strings/str_format_test.cc | 4 +- absl/strings/str_join_test.cc | 8 ++-- absl/strings/str_replace_benchmark.cc | 2 +- absl/strings/str_replace_test.cc | 12 +++--- absl/strings/str_split.cc | 4 +- absl/strings/str_split_test.cc | 32 +++++++-------- absl/strings/string_view.h | 14 +++---- absl/strings/string_view_test.cc | 28 ++++++------- absl/strings/substitute.cc | 10 ++--- absl/strings/substitute.h | 46 +++++++++++----------- absl/strings/substitute_test.cc | 8 ++-- absl/synchronization/mutex.cc | 2 +- absl/time/civil_time.cc | 6 +-- absl/time/duration.cc | 4 +- absl/time/format_test.cc | 6 +-- absl/time/internal/cctz/include/cctz/time_zone.h | 2 +- .../internal/cctz/include/cctz/zone_info_source.h | 2 +- absl/time/internal/cctz/src/time_zone_format.cc | 4 +- .../internal/cctz/src/time_zone_format_test.cc | 2 +- absl/time/internal/cctz/src/time_zone_impl.h | 2 +- absl/time/internal/cctz/src/time_zone_info.cc | 2 +- .../internal/cctz/src/time_zone_lookup_test.cc | 2 +- absl/time/internal/cctz/src/tzfile.h | 4 +- absl/time/time_zone_test.cc | 2 +- absl/types/variant_test.cc | 2 +- 68 files changed, 191 insertions(+), 191 deletions(-) (limited to 'absl/strings/cord.h') diff --git a/CMakeLists.txt b/CMakeLists.txt index 74b5cd9d..e94dcd3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ cmake_policy(SET CMP0025 NEW) # if command can use IN_LIST cmake_policy(SET CMP0057 NEW) -# Project version variables are the empty std::string if version is unspecified +# Project version variables are the empty string if version is unspecified cmake_policy(SET CMP0048 NEW) project(absl CXX) diff --git a/absl/container/btree_test.cc b/absl/container/btree_test.cc index da8e7082..7ccdf6a1 100644 --- a/absl/container/btree_test.cc +++ b/absl/container/btree_test.cc @@ -2132,11 +2132,11 @@ TEST(Btree, UserProvidedKeyCompareToComparators) { TEST(Btree, TryEmplaceBasicTest) { absl::btree_map m; - // Should construct a std::string from the literal. + // Should construct a string from the literal. m.try_emplace(1, "one"); EXPECT_EQ(1, m.size()); - // Try other std::string constructors and const lvalue key. + // Try other string constructors and const lvalue key. const int key(42); m.try_emplace(key, 3, 'a'); m.try_emplace(2, std::string("two")); diff --git a/absl/container/inlined_vector_benchmark.cc b/absl/container/inlined_vector_benchmark.cc index 3f2b4ed2..b8dafe93 100644 --- a/absl/container/inlined_vector_benchmark.cc +++ b/absl/container/inlined_vector_benchmark.cc @@ -83,7 +83,7 @@ int GetNonShortStringOptimizationSize() { } ABSL_RAW_LOG( FATAL, - "Failed to find a std::string larger than the short std::string optimization"); + "Failed to find a string larger than the short string optimization"); return -1; } diff --git a/absl/container/inlined_vector_test.cc b/absl/container/inlined_vector_test.cc index 2c9b0d0e..5965eac7 100644 --- a/absl/container/inlined_vector_test.cc +++ b/absl/container/inlined_vector_test.cc @@ -780,7 +780,7 @@ TEST(IntVec, Reserve) { TEST(StringVec, SelfRefPushBack) { std::vector std_v; absl::InlinedVector v; - const std::string s = "A quite long std::string to ensure heap."; + const std::string s = "A quite long string to ensure heap."; std_v.push_back(s); v.push_back(s); for (int i = 0; i < 20; ++i) { @@ -795,7 +795,7 @@ TEST(StringVec, SelfRefPushBack) { TEST(StringVec, SelfRefPushBackWithMove) { std::vector std_v; absl::InlinedVector v; - const std::string s = "A quite long std::string to ensure heap."; + const std::string s = "A quite long string to ensure heap."; std_v.push_back(s); v.push_back(s); for (int i = 0; i < 20; ++i) { @@ -808,7 +808,7 @@ TEST(StringVec, SelfRefPushBackWithMove) { } TEST(StringVec, SelfMove) { - const std::string s = "A quite long std::string to ensure heap."; + const std::string s = "A quite long string to ensure heap."; for (int len = 0; len < 20; len++) { SCOPED_TRACE(len); absl::InlinedVector v; diff --git a/absl/container/internal/btree.h b/absl/container/internal/btree.h index 301c3656..d986f81e 100644 --- a/absl/container/internal/btree.h +++ b/absl/container/internal/btree.h @@ -186,7 +186,7 @@ struct key_compare_to_adapter> { template struct common_params { - // If Compare is a common comparator for a std::string-like type, then we adapt it + // If Compare is a common comparator for a string-like type, then we adapt it // to use heterogeneous lookup and to be a key-compare-to comparator. using key_compare = typename key_compare_to_adapter::type; // A type which indicates if we have a key-compare-to functor or a plain old diff --git a/absl/container/internal/compressed_tuple_test.cc b/absl/container/internal/compressed_tuple_test.cc index 1dae12db..62a7483e 100644 --- a/absl/container/internal/compressed_tuple_test.cc +++ b/absl/container/internal/compressed_tuple_test.cc @@ -277,11 +277,11 @@ TEST(CompressedTupleTest, Nested) { TEST(CompressedTupleTest, Reference) { int i = 7; - std::string s = "Very long std::string that goes in the heap"; + std::string s = "Very long string that goes in the heap"; CompressedTuple x(i, i, s, s); // Sanity check. We should have not moved from `s` - EXPECT_EQ(s, "Very long std::string that goes in the heap"); + EXPECT_EQ(s, "Very long string that goes in the heap"); EXPECT_EQ(x.get<0>(), x.get<1>()); EXPECT_NE(&x.get<0>(), &x.get<1>()); diff --git a/absl/container/internal/raw_hash_set_test.cc b/absl/container/internal/raw_hash_set_test.cc index a96ae68a..2fc85591 100644 --- a/absl/container/internal/raw_hash_set_test.cc +++ b/absl/container/internal/raw_hash_set_test.cc @@ -1666,9 +1666,9 @@ TEST(Nodes, EmptyNodeType) { } TEST(Nodes, ExtractInsert) { - constexpr char k0[] = "Very long std::string zero."; - constexpr char k1[] = "Very long std::string one."; - constexpr char k2[] = "Very long std::string two."; + constexpr char k0[] = "Very long string zero."; + constexpr char k1[] = "Very long string one."; + constexpr char k2[] = "Very long string two."; StringTable t = {{k0, ""}, {k1, ""}, {k2, ""}}; EXPECT_THAT(t, UnorderedElementsAre(Pair(k0, ""), Pair(k1, ""), Pair(k2, ""))); diff --git a/absl/debugging/failure_signal_handler.h b/absl/debugging/failure_signal_handler.h index f5a83962..0c0f585d 100644 --- a/absl/debugging/failure_signal_handler.h +++ b/absl/debugging/failure_signal_handler.h @@ -88,7 +88,7 @@ struct FailureSignalHandlerOptions { bool call_previous_handler = false; // If non-null, indicates a pointer to a callback function that will be called - // upon failure, with a std::string argument containing failure data. This function + // upon failure, with a string argument containing failure data. This function // may be used as a hook to write failure data to a secondary location, such // as a log file. This function may also be called with null data, as a hint // to flush any buffered data before the program may be terminated. Consider diff --git a/absl/debugging/internal/demangle.cc b/absl/debugging/internal/demangle.cc index fc615c3f..fc262e50 100644 --- a/absl/debugging/internal/demangle.cc +++ b/absl/debugging/internal/demangle.cc @@ -151,7 +151,7 @@ static const AbbrevPair kSubstitutionList[] = { // frame, so every byte counts. typedef struct { int mangled_idx; // Cursor of mangled name. - int out_cur_idx; // Cursor of output std::string. + int out_cur_idx; // Cursor of output string. int prev_name_idx; // For constructors/destructors. signed int prev_name_length : 16; // For constructors/destructors. signed int nest_level : 15; // For nested names. @@ -172,8 +172,8 @@ static_assert(sizeof(ParseState) == 4 * sizeof(int), // Only one copy of this exists for each call to Demangle, so the size of this // struct is nearly inconsequential. typedef struct { - const char *mangled_begin; // Beginning of input std::string. - char *out; // Beginning of output std::string. + const char *mangled_begin; // Beginning of input string. + char *out; // Beginning of output string. int out_end_idx; // One past last allowed output character. int recursion_depth; // For stack exhaustion prevention. int steps; // Cap how much work we'll do, regardless of depth. diff --git a/absl/debugging/leak_check_fail_test.cc b/absl/debugging/leak_check_fail_test.cc index 2887ceab..c49b81a9 100644 --- a/absl/debugging/leak_check_fail_test.cc +++ b/absl/debugging/leak_check_fail_test.cc @@ -25,7 +25,7 @@ TEST(LeakCheckTest, LeakMemory) { // failed exit code. char* foo = strdup("lsan should complain about this leaked string"); - ABSL_RAW_LOG(INFO, "Should detect leaked std::string %s", foo); + ABSL_RAW_LOG(INFO, "Should detect leaked string %s", foo); } TEST(LeakCheckTest, LeakMemoryAfterDisablerScope) { @@ -34,7 +34,7 @@ TEST(LeakCheckTest, LeakMemoryAfterDisablerScope) { // failed exit code. { absl::LeakCheckDisabler disabler; } char* foo = strdup("lsan should also complain about this leaked string"); - ABSL_RAW_LOG(INFO, "Re-enabled leak detection.Should detect leaked std::string %s", + ABSL_RAW_LOG(INFO, "Re-enabled leak detection.Should detect leaked string %s", foo); } diff --git a/absl/debugging/leak_check_test.cc b/absl/debugging/leak_check_test.cc index 93a7edd2..b5cc4874 100644 --- a/absl/debugging/leak_check_test.cc +++ b/absl/debugging/leak_check_test.cc @@ -30,13 +30,13 @@ TEST(LeakCheckTest, DetectLeakSanitizer) { TEST(LeakCheckTest, IgnoreLeakSuppressesLeakedMemoryErrors) { auto foo = absl::IgnoreLeak(new std::string("some ignored leaked string")); - ABSL_RAW_LOG(INFO, "Ignoring leaked std::string %s", foo->c_str()); + ABSL_RAW_LOG(INFO, "Ignoring leaked string %s", foo->c_str()); } TEST(LeakCheckTest, LeakCheckDisablerIgnoresLeak) { absl::LeakCheckDisabler disabler; - auto foo = new std::string("some std::string leaked while checks are disabled"); - ABSL_RAW_LOG(INFO, "Ignoring leaked std::string %s", foo->c_str()); + auto foo = new std::string("some string leaked while checks are disabled"); + ABSL_RAW_LOG(INFO, "Ignoring leaked string %s", foo->c_str()); } } // namespace diff --git a/absl/debugging/symbolize_elf.inc b/absl/debugging/symbolize_elf.inc index c371635f..fe1d36ee 100644 --- a/absl/debugging/symbolize_elf.inc +++ b/absl/debugging/symbolize_elf.inc @@ -1402,7 +1402,7 @@ bool RegisterFileMappingHint(const void *start, const void *end, uint64_t offset if (g_num_file_mapping_hints >= kMaxFileMappingHints) { ret = false; } else { - // TODO(ckennelly): Move this into a std::string copy routine. + // TODO(ckennelly): Move this into a string copy routine. int len = strlen(filename); char *dst = static_cast( base_internal::LowLevelAlloc::AllocWithArena(len + 1, SigSafeArena())); diff --git a/absl/flags/internal/commandlineflag.h b/absl/flags/internal/commandlineflag.h index e91ddde6..9a740d57 100644 --- a/absl/flags/internal/commandlineflag.h +++ b/absl/flags/internal/commandlineflag.h @@ -141,7 +141,7 @@ class CommandLineFlag { // Returns name of the file where this flag is defined. virtual std::string Filename() const = 0; // Returns name of the flag's value type for some built-in types or empty - // std::string. + // string. virtual absl::string_view Typename() const = 0; // Returns help message associated with this flag. virtual std::string Help() const = 0; @@ -163,7 +163,7 @@ class CommandLineFlag { // or nullptr if flag does not support saving and restoring a state. virtual std::unique_ptr SaveState() = 0; - // Sets the value of the flag based on specified std::string `value`. If the flag + // Sets the value of the flag based on specified string `value`. If the flag // was successfully set to new value, it returns true. Otherwise, sets `error` // to indicate the error, leaves the flag unchanged, and returns false. There // are three ways to set the flag's value: @@ -176,7 +176,7 @@ class CommandLineFlag { flags_internal::ValueSource source, std::string* error) = 0; - // Checks that flags default value can be converted to std::string and back to the + // Checks that flags default value can be converted to string and back to the // flag's value type. virtual void CheckDefaultValueParsingRoundtrip() const = 0; diff --git a/absl/flags/internal/flag.cc b/absl/flags/internal/flag.cc index a944e16e..a12fe7c5 100644 --- a/absl/flags/internal/flag.cc +++ b/absl/flags/internal/flag.cc @@ -408,7 +408,7 @@ void FlagImpl::CheckDefaultValueParsingRoundtrip() const { ABSL_INTERNAL_LOG( FATAL, absl::StrCat("Flag ", Name(), " (from ", Filename(), - "): std::string form of default value '", v, + "): string form of default value '", v, "' could not be parsed; error=", error)); } diff --git a/absl/flags/internal/flag.h b/absl/flags/internal/flag.h index 307b7377..344e31f6 100644 --- a/absl/flags/internal/flag.h +++ b/absl/flags/internal/flag.h @@ -439,7 +439,7 @@ class FlagImpl { ABSL_EXCLUSIVE_LOCKS_REQUIRED(*DataGuard()); // Flag initialization called via absl::call_once. void Init(); - // Attempts to parse supplied `value` std::string. If parsing is successful, + // Attempts to parse supplied `value` string. If parsing is successful, // returns new value. Otherwise returns nullptr. std::unique_ptr TryParse(absl::string_view value, std::string* err) const diff --git a/absl/flags/internal/usage.cc b/absl/flags/internal/usage.cc index ff907161..a9a5cba9 100644 --- a/absl/flags/internal/usage.cc +++ b/absl/flags/internal/usage.cc @@ -134,14 +134,14 @@ class FlagHelpPrettyPrinter { first_line_(true) {} void Write(absl::string_view str, bool wrap_line = false) { - // Empty std::string - do nothing. + // Empty string - do nothing. if (str.empty()) return; std::vector tokens; if (wrap_line) { for (auto line : absl::StrSplit(str, absl::ByAnyChar("\n\r"))) { if (!tokens.empty()) { - // Keep line separators in the input std::string. + // Keep line separators in the input string. tokens.push_back("\n"); } for (auto token : @@ -156,13 +156,13 @@ class FlagHelpPrettyPrinter { for (auto token : tokens) { bool new_line = (line_len_ == 0); - // Respect line separators in the input std::string. + // Respect line separators in the input string. if (token == "\n") { EndLine(); continue; } - // Write the token, ending the std::string first if necessary/possible. + // Write the token, ending the string first if necessary/possible. if (!new_line && (line_len_ + token.size() >= max_line_len_)) { EndLine(); new_line = true; diff --git a/absl/flags/marshalling.cc b/absl/flags/marshalling.cc index 6f2ddda8..09baae88 100644 --- a/absl/flags/marshalling.cc +++ b/absl/flags/marshalling.cc @@ -172,7 +172,7 @@ std::string Unparse(long long v) { return absl::StrCat(v); } std::string Unparse(unsigned long long v) { return absl::StrCat(v); } template std::string UnparseFloatingPointVal(T v) { - // digits10 is guaranteed to roundtrip correctly in std::string -> value -> std::string + // digits10 is guaranteed to roundtrip correctly in string -> value -> string // conversions, but may not be enough to represent all the values correctly. std::string digit10_str = absl::StrFormat("%.*g", std::numeric_limits::digits10, v); diff --git a/absl/flags/parse.cc b/absl/flags/parse.cc index 812e4981..af5fb12d 100644 --- a/absl/flags/parse.cc +++ b/absl/flags/parse.cc @@ -533,10 +533,10 @@ std::tuple DeduceFlagValue(const CommandLineFlag& flag, curr_list->PopFront(); value = curr_list->Front(); - // Heuristic to detect the case where someone treats a std::string arg + // Heuristic to detect the case where someone treats a string arg // like a bool or just forgets to pass a value: // --my_string_var --foo=bar - // We look for a flag of std::string type, whose value begins with a + // We look for a flag of string type, whose value begins with a // dash and corresponds to known flag or standalone --. if (!value.empty() && value[0] == '-' && flag.IsOfType()) { auto maybe_flag_name = std::get<0>(SplitNameAndValue(value.substr(1))); @@ -646,7 +646,7 @@ std::vector ParseCommandLineImpl(int argc, char* argv[], // 60. Split the current argument on '=' to figure out the argument // name and value. If flag name is empty it means we've got "--". value - // can be empty either if there were no '=' in argument std::string at all or + // can be empty either if there were no '=' in argument string at all or // an argument looked like "--foo=". In a latter case is_empty_value is // true. absl::string_view flag_name; diff --git a/absl/flags/usage_config.h b/absl/flags/usage_config.h index 0ed7e1b4..96eecea2 100644 --- a/absl/flags/usage_config.h +++ b/absl/flags/usage_config.h @@ -90,7 +90,7 @@ struct FlagsUsageConfig { // program output. flags_internal::FlagKindFilter contains_helppackage_flags; - // Generates std::string containing program version. This is the std::string reported + // Generates string containing program version. This is the string reported // when user specifies --version in a command line. std::function version_string; diff --git a/absl/hash/hash_test.cc b/absl/hash/hash_test.cc index e55e0ca9..5e6a8b18 100644 --- a/absl/hash/hash_test.cc +++ b/absl/hash/hash_test.cc @@ -316,7 +316,7 @@ TEST(HashValueTest, Strings) { t(std::string(huge)), t(absl::string_view(huge)), // t(FlatCord(huge)), t(FragmentedCord(huge))))); - // Make sure that hashing a `const char*` does not use its std::string-value. + // Make sure that hashing a `const char*` does not use its string-value. EXPECT_NE(SpyHash(static_cast("ABC")), SpyHash(absl::string_view("ABC"))); } @@ -512,7 +512,7 @@ TEST(HashValueTest, CombinePiecewiseBuffer) { SCOPED_TRACE(big_buffer_size); std::string big_buffer; for (int i = 0; i < big_buffer_size; ++i) { - // Arbitrary std::string + // Arbitrary string big_buffer.push_back(32 + (i * (i / 3)) % 64); } auto big_buffer_hash = hash(PiecewiseHashTester(big_buffer)); diff --git a/absl/random/bernoulli_distribution_test.cc b/absl/random/bernoulli_distribution_test.cc index f2c3b99c..5581af50 100644 --- a/absl/random/bernoulli_distribution_test.cc +++ b/absl/random/bernoulli_distribution_test.cc @@ -131,7 +131,7 @@ TEST(BernoulliTest, StabilityTest) { 0x275b0dc7e0a18acfull, 0x36cebe0d2653682eull, 0x0361e9b23861596bull, }); - // Generate a std::string of '0' and '1' for the distribution output. + // Generate a string of '0' and '1' for the distribution output. auto generate = [&urbg](absl::bernoulli_distribution& dist) { std::string output; output.reserve(36); @@ -176,7 +176,7 @@ TEST(BernoulliTest, StabilityTest2) { 0xECDD4775619F1510ull, 0x13CCA830EB61BD96ull, 0x0334FE1EAA0363CFull, 0xB5735C904C70A239ull, 0xD59E9E0BCBAADE14ull, 0xEECC86BC60622CA7ull}); - // Generate a std::string of '0' and '1' for the distribution output. + // Generate a string of '0' and '1' for the distribution output. auto generate = [&urbg](absl::bernoulli_distribution& dist) { std::string output; output.reserve(13); diff --git a/absl/random/internal/nanobenchmark.cc b/absl/random/internal/nanobenchmark.cc index 8fee77fc..c9181813 100644 --- a/absl/random/internal/nanobenchmark.cc +++ b/absl/random/internal/nanobenchmark.cc @@ -101,7 +101,7 @@ std::string BrandString() { char brand_string[49]; uint32_t abcd[4]; - // Check if brand std::string is supported (it is on all reasonable Intel/AMD) + // Check if brand string is supported (it is on all reasonable Intel/AMD) Cpuid(0x80000000U, 0, abcd); if (abcd[0] < 0x80000004U) { return std::string(); diff --git a/absl/random/uniform_int_distribution_test.cc b/absl/random/uniform_int_distribution_test.cc index aacff88d..69537603 100644 --- a/absl/random/uniform_int_distribution_test.cc +++ b/absl/random/uniform_int_distribution_test.cc @@ -123,7 +123,7 @@ TYPED_TEST(UniformIntDistributionTest, ViolatesPreconditionsDeathTest) { absl::uniform_int_distribution dist(10, 1); auto x = dist(gen); - // Any value will generate a non-empty std::string. + // Any value will generate a non-empty string. EXPECT_FALSE(absl::StrCat(+x).empty()) << x; #endif // NDEBUG } diff --git a/absl/status/status.cc b/absl/status/status.cc index 52ecc0ef..6d57a6be 100644 --- a/absl/status/status.cc +++ b/absl/status/status.cc @@ -177,7 +177,7 @@ void Status::ForEachPayload( visitor(elem.type_url, elem.payload); #else // In debug mode invalidate the type url to prevent users from relying on - // this std::string lifetime. + // this string lifetime. // NOLINTNEXTLINE intentional extra conversion to force temporary. visitor(std::string(elem.type_url), elem.payload); diff --git a/absl/status/status.h b/absl/status/status.h index 9706d4ba..67ff988f 100644 --- a/absl/status/status.h +++ b/absl/status/status.h @@ -122,7 +122,7 @@ class ABSL_MUST_USE_RESULT Status final { // Returns the error message. Note: prefer ToString() for debug logging. // This message rarely describes the error code. It is not unusual for the - // error message to be the empty std::string. + // error message to be the empty string. absl::string_view message() const; friend bool operator==(const Status&, const Status&); @@ -231,7 +231,7 @@ class ABSL_MUST_USE_RESULT Status final { static uintptr_t PointerToRep(status_internal::StatusRep* r); static status_internal::StatusRep* RepToPointer(uintptr_t r); - // Returns std::string for non-ok Status. + // Returns string for non-ok Status. std::string ToStringSlow() const; // Status supports two different representations. diff --git a/absl/strings/charconv.cc b/absl/strings/charconv.cc index bdba768d..3613a652 100644 --- a/absl/strings/charconv.cc +++ b/absl/strings/charconv.cc @@ -619,10 +619,10 @@ from_chars_result FromCharsImpl(const char* first, const char* last, // Either we failed to parse a hex float after the "0x", or we read // "0xinf" or "0xnan" which we don't want to match. // - // However, a std::string that begins with "0x" also begins with "0", which + // However, a string that begins with "0x" also begins with "0", which // is normally a valid match for the number zero. So we want these // strings to match zero unless fmt_flags is `scientific`. (This flag - // means an exponent is required, which the std::string "0" does not have.) + // means an exponent is required, which the string "0" does not have.) if (fmt_flags == chars_format::scientific) { result.ec = std::errc::invalid_argument; } else { diff --git a/absl/strings/charconv_benchmark.cc b/absl/strings/charconv_benchmark.cc index 644b2abd..e8c7371d 100644 --- a/absl/strings/charconv_benchmark.cc +++ b/absl/strings/charconv_benchmark.cc @@ -132,7 +132,7 @@ BENCHMARK(BM_Absl_HugeMantissa); std::string MakeHardCase(int length) { // The number 1.1521...e-297 is exactly halfway between 12345 * 2**-1000 and // the next larger representable number. The digits of this number are in - // the std::string below. + // the string below. const std::string digits = "1." "152113937042223790993097181572444900347587985074226836242307364987727724" diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 29ed7f75..3941f19c 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -278,7 +278,7 @@ class Cord { // Copies the contents from `src` to `*dst`. // - // This function optimizes the case of reusing the destination std::string since it + // This function optimizes the case of reusing the destination string since it // can reuse previously allocated capacity. However, this function does not // guarantee that pointers previously returned by `dst->data()` remain valid // even if `*dst` had enough capacity to hold `src`. If `*dst` is a new @@ -603,7 +603,7 @@ class Cord { } void CopyTo(std::string* dst) const { // memcpy is much faster when operating on a known size. On most supported - // platforms, the small std::string optimization is large enough that resizing + // platforms, the small string optimization is large enough that resizing // to 15 bytes does not cause a memory allocation. absl::strings_internal::STLStringResizeUninitialized(dst, sizeof(data_) - 1); diff --git a/absl/strings/cord_test.cc b/absl/strings/cord_test.cc index a683cc4b..d6e091f8 100644 --- a/absl/strings/cord_test.cc +++ b/absl/strings/cord_test.cc @@ -174,7 +174,7 @@ TEST(Cord, AllFlatSizes) { using absl::strings_internal::CordTestAccess; for (size_t s = 0; s < CordTestAccess::MaxFlatLength(); s++) { - // Make a std::string of length s. + // Make a string of length s. std::string src; while (src.size() < s) { src.push_back('a' + (src.size() % 26)); @@ -409,7 +409,7 @@ static void VerifyCopyToString(const absl::Cord& cord) { if (cord.size() <= kInitialLength) { EXPECT_EQ(has_initial_contents.data(), address_before_copy) - << "CopyCordToString allocated new std::string storage; " + << "CopyCordToString allocated new string storage; " "has_initial_contents = \"" << has_initial_contents << "\""; } @@ -856,7 +856,7 @@ TEST(Cord, CompareAfterAssign) { } // Test CompareTo() and ComparePrefix() against string and substring -// comparison methods from std::basic_string. +// comparison methods from basic_string. static void TestCompare(const absl::Cord& c, const absl::Cord& d, RandomEngine* rng) { typedef std::basic_string ustring; @@ -912,7 +912,7 @@ void CompareOperators() { EXPECT_TRUE(a == a); // For pointer type (i.e. `const char*`), operator== compares the address - // instead of the std::string, so `a == const char*("a")` isn't necessarily true. + // instead of the string, so `a == const char*("a")` isn't necessarily true. EXPECT_TRUE(std::is_pointer::value || a == T1("a")); EXPECT_TRUE(std::is_pointer::value || a == T2("a")); EXPECT_FALSE(a == b); diff --git a/absl/strings/escaping.cc b/absl/strings/escaping.cc index 7adc1b65..9fceeef0 100644 --- a/absl/strings/escaping.cc +++ b/absl/strings/escaping.cc @@ -450,7 +450,7 @@ bool Base64UnescapeInternal(const char* src_param, size_t szsrc, char* dest, // The GET_INPUT macro gets the next input character, skipping // over any whitespace, and stopping when we reach the end of the - // std::string or when we read any non-data character. The arguments are + // string or when we read any non-data character. The arguments are // an arbitrary identifier (used as a label for goto) and the number // of data bytes that must remain in the input to avoid aborting the // loop. @@ -473,18 +473,18 @@ bool Base64UnescapeInternal(const char* src_param, size_t szsrc, char* dest, if (dest) { // This loop consumes 4 input bytes and produces 3 output bytes // per iteration. We can't know at the start that there is enough - // data left in the std::string for a full iteration, so the loop may + // data left in the string for a full iteration, so the loop may // break out in the middle; if so 'state' will be set to the // number of input bytes read. while (szsrc >= 4) { // We'll start by optimistically assuming that the next four - // bytes of the std::string (src[0..3]) are four good data bytes + // bytes of the string (src[0..3]) are four good data bytes // (that is, no nulls, whitespace, padding chars, or illegal // chars). We need to test src[0..2] for nulls individually // before constructing temp to preserve the property that we - // never read past a null in the std::string (no matter how long - // szsrc claims the std::string is). + // never read past a null in the string (no matter how long + // szsrc claims the string is). if (!src[0] || !src[1] || !src[2] || ((temp = ((unsigned(unbase64[src[0]]) << 18) | @@ -509,7 +509,7 @@ bool Base64UnescapeInternal(const char* src_param, size_t szsrc, char* dest, temp = (temp << 6) | decode; } else { // We really did have four good data bytes, so advance four - // characters in the std::string. + // characters in the string. szsrc -= 4; src += 4; @@ -644,7 +644,7 @@ bool Base64UnescapeInternal(const char* src_param, size_t szsrc, char* dest, state); } - // The remainder of the std::string should be all whitespace, mixed with + // The remainder of the string should be all whitespace, mixed with // exactly 0 equals signs, or exactly 'expected_equals' equals // signs. (Always accepting 0 equals signs is an Abseil extension // not covered in the RFC, as is accepting dot as the pad character.) @@ -771,7 +771,7 @@ constexpr char kWebSafeBase64Chars[] = template bool Base64UnescapeInternal(const char* src, size_t slen, String* dest, const signed char* unbase64) { - // Determine the size of the output std::string. Base64 encodes every 3 bytes into + // Determine the size of the output string. Base64 encodes every 3 bytes into // 4 characters. any leftover chars are added directly for good measure. // This is documented in the base64 RFC: http://tools.ietf.org/html/rfc3548 const size_t dest_len = 3 * (slen / 4) + (slen % 4); @@ -779,7 +779,7 @@ bool Base64UnescapeInternal(const char* src, size_t slen, String* dest, strings_internal::STLStringResizeUninitialized(dest, dest_len); // We are getting the destination buffer by getting the beginning of the - // std::string and converting it into a char *. + // string and converting it into a char *. size_t len; const bool ok = Base64UnescapeInternal(src, slen, &(*dest)[0], dest_len, unbase64, &len); diff --git a/absl/strings/escaping_test.cc b/absl/strings/escaping_test.cc index 1967975b..45671a0e 100644 --- a/absl/strings/escaping_test.cc +++ b/absl/strings/escaping_test.cc @@ -300,7 +300,7 @@ static struct { absl::string_view plaintext; absl::string_view cyphertext; } const base64_tests[] = { - // Empty std::string. + // Empty string. {{"", 0}, {"", 0}}, {{nullptr, 0}, {"", 0}}, // if length is zero, plaintext ptr must be ignored! @@ -586,7 +586,7 @@ void TestEscapeAndUnescape() { EXPECT_EQ(encoded, websafe); EXPECT_EQ(absl::WebSafeBase64Escape(tc.plaintext), websafe); - // Let's try the std::string version of the decoder + // Let's try the string version of the decoder decoded = "this junk should be ignored"; EXPECT_TRUE(absl::WebSafeBase64Unescape(websafe, &decoded)); EXPECT_EQ(decoded, tc.plaintext); @@ -625,7 +625,7 @@ TEST(Base64, DISABLED_HugeData) { std::string escaped; absl::Base64Escape(huge, &escaped); - // Generates the std::string that should match a base64 encoded "xxx..." std::string. + // Generates the string that should match a base64 encoded "xxx..." string. // "xxx" in base64 is "eHh4". std::string expected_encoding; expected_encoding.reserve(kSize / 3 * 4); diff --git a/absl/strings/internal/char_map.h b/absl/strings/internal/char_map.h index a76e6036..61484de0 100644 --- a/absl/strings/internal/char_map.h +++ b/absl/strings/internal/char_map.h @@ -72,7 +72,7 @@ class Charmap { CharMaskForWord(x, 2), CharMaskForWord(x, 3)); } - // Containing all the chars in the C-std::string 's'. + // Containing all the chars in the C-string 's'. // Note that this is expensively recursive because of the C++11 constexpr // formulation. Use only in constexpr initializers. static constexpr Charmap FromString(const char* s) { diff --git a/absl/strings/internal/charconv_bigint.cc b/absl/strings/internal/charconv_bigint.cc index 66f33e72..ebf8c079 100644 --- a/absl/strings/internal/charconv_bigint.cc +++ b/absl/strings/internal/charconv_bigint.cc @@ -208,7 +208,7 @@ int BigUnsigned::ReadDigits(const char* begin, const char* end, ++dropped_digits; } if (begin < end && *std::prev(end) == '.') { - // If the std::string ends in '.', either before or after dropping zeroes, then + // If the string ends in '.', either before or after dropping zeroes, then // drop the decimal point and look for more digits to drop. dropped_digits = 0; --end; diff --git a/absl/strings/internal/charconv_bigint.h b/absl/strings/internal/charconv_bigint.h index 999e9ae3..8f702976 100644 --- a/absl/strings/internal/charconv_bigint.h +++ b/absl/strings/internal/charconv_bigint.h @@ -66,7 +66,7 @@ class BigUnsigned { static_cast(v >> 32)} {} // Constructs a BigUnsigned from the given string_view containing a decimal - // value. If the input std::string is not a decimal integer, constructs a 0 + // value. If the input string is not a decimal integer, constructs a 0 // instead. explicit BigUnsigned(absl::string_view sv) : size_(0), words_{} { // Check for valid input, returning a 0 otherwise. This is reasonable @@ -210,7 +210,7 @@ class BigUnsigned { return words_[index]; } - // Returns this integer as a decimal std::string. This is not used in the decimal- + // Returns this integer as a decimal string. This is not used in the decimal- // to-binary conversion; it is intended to aid in testing. std::string ToString() const; diff --git a/absl/strings/internal/charconv_parse.cc b/absl/strings/internal/charconv_parse.cc index d9a57a78..fd6d9480 100644 --- a/absl/strings/internal/charconv_parse.cc +++ b/absl/strings/internal/charconv_parse.cc @@ -302,7 +302,7 @@ bool ParseInfinityOrNan(const char* begin, const char* end, switch (*begin) { case 'i': case 'I': { - // An infinity std::string consists of the characters "inf" or "infinity", + // An infinity string consists of the characters "inf" or "infinity", // case insensitive. if (strings_internal::memcasecmp(begin + 1, "nf", 2) != 0) { return false; @@ -326,7 +326,7 @@ bool ParseInfinityOrNan(const char* begin, const char* end, } out->type = strings_internal::FloatType::kNan; out->end = begin + 3; - // NaN is allowed to be followed by a parenthesized std::string, consisting of + // NaN is allowed to be followed by a parenthesized string, consisting of // only the characters [a-zA-Z0-9_]. Match that if it's present. begin += 3; if (begin < end && *begin == '(') { diff --git a/absl/strings/internal/charconv_parse_test.cc b/absl/strings/internal/charconv_parse_test.cc index 9511c987..bc2d1118 100644 --- a/absl/strings/internal/charconv_parse_test.cc +++ b/absl/strings/internal/charconv_parse_test.cc @@ -63,7 +63,7 @@ void ExpectParsedFloat(std::string s, absl::chars_format format_flags, } const std::string::size_type expected_characters_matched = s.find('$'); ABSL_RAW_CHECK(expected_characters_matched != std::string::npos, - "Input std::string must contain $"); + "Input string must contain $"); s.replace(expected_characters_matched, 1, ""); ParsedFloat parsed = diff --git a/absl/strings/internal/numbers_test_common.h b/absl/strings/internal/numbers_test_common.h index 1a1e50c4..eaa88a88 100644 --- a/absl/strings/internal/numbers_test_common.h +++ b/absl/strings/internal/numbers_test_common.h @@ -170,7 +170,7 @@ inline const std::array& strtouint64_test_cases() { {"0x1234", true, 16, 0x1234}, - // Base-10 std::string version. + // Base-10 string version. {"1234", true, 0, 1234}, {nullptr, false, 0, 0}, }}; diff --git a/absl/strings/internal/str_format/bind.h b/absl/strings/internal/str_format/bind.h index cf41b197..ee4475e0 100644 --- a/absl/strings/internal/str_format/bind.h +++ b/absl/strings/internal/str_format/bind.h @@ -76,11 +76,11 @@ class FormatSpecTemplate public: #ifdef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER - // Honeypot overload for when the std::string is not constexpr. + // 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 std::string is not constexpr."))); + __attribute__((unavailable("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 diff --git a/absl/strings/internal/str_format/parser.h b/absl/strings/internal/str_format/parser.h index 45c90d1d..7d966517 100644 --- a/absl/strings/internal/str_format/parser.h +++ b/absl/strings/internal/str_format/parser.h @@ -143,7 +143,7 @@ bool ParseFormatString(string_view src, Consumer consumer) { auto tag = GetTagForChar(percent[1]); if (tag.is_conv()) { if (ABSL_PREDICT_FALSE(next_arg < 0)) { - // This indicates an error in the format std::string. + // This indicates an error in the format string. // The only way to get `next_arg < 0` here is to have a positional // argument first which sets next_arg to -1 and then a non-positional // argument. @@ -287,7 +287,7 @@ class ExtendedParsedFormat : public str_format_internal::ParsedFormatBase { #ifdef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER __attribute__(( enable_if(str_format_internal::EnsureConstexpr(format), - "Format std::string is not constexpr."), + "Format string is not constexpr."), enable_if(str_format_internal::ValidFormatImpl(format), "Format specified does not match the template arguments."))) #endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER diff --git a/absl/strings/numbers_test.cc b/absl/strings/numbers_test.cc index 68229b15..bd4e1162 100644 --- a/absl/strings/numbers_test.cc +++ b/absl/strings/numbers_test.cc @@ -481,7 +481,7 @@ TEST(stringtest, safe_strto32_base) { EXPECT_TRUE(safe_strto32_base(std::string("0x1234"), &value, 16)); EXPECT_EQ(0x1234, value); - // Base-10 std::string version. + // Base-10 string version. EXPECT_TRUE(safe_strto32_base("1234", &value, 10)); EXPECT_EQ(1234, value); } @@ -622,7 +622,7 @@ TEST(stringtest, safe_strto64_base) { EXPECT_TRUE(safe_strto64_base(std::string("0x1234"), &value, 16)); EXPECT_EQ(0x1234, value); - // Base-10 std::string version. + // Base-10 string version. EXPECT_TRUE(safe_strto64_base("1234", &value, 10)); EXPECT_EQ(1234, value); } diff --git a/absl/strings/str_cat.h b/absl/strings/str_cat.h index 292fa235..a8a85c73 100644 --- a/absl/strings/str_cat.h +++ b/absl/strings/str_cat.h @@ -253,7 +253,7 @@ class AlphaNum { const std::basic_string, Allocator>& str) : piece_(str) {} - // Use std::string literals ":" instead of character literals ':'. + // Use string literals ":" instead of character literals ':'. AlphaNum(char c) = delete; // NOLINT(runtime/explicit) AlphaNum(const AlphaNum&) = delete; diff --git a/absl/strings/str_cat_benchmark.cc b/absl/strings/str_cat_benchmark.cc index 14c63b3f..ee4ad112 100644 --- a/absl/strings/str_cat_benchmark.cc +++ b/absl/strings/str_cat_benchmark.cc @@ -23,7 +23,7 @@ namespace { const char kStringOne[] = "Once Upon A Time, "; -const char kStringTwo[] = "There was a std::string benchmark"; +const char kStringTwo[] = "There was a string benchmark"; // We want to include negative numbers in the benchmark, so this function // is used to count 0, 1, -1, 2, -2, 3, -3, ... diff --git a/absl/strings/str_cat_test.cc b/absl/strings/str_cat_test.cc index be39880b..f3770dc0 100644 --- a/absl/strings/str_cat_test.cc +++ b/absl/strings/str_cat_test.cc @@ -162,7 +162,7 @@ TEST(StrCat, Basics) { EXPECT_EQ(result, "12345678910, 10987654321!"); std::string one = - "1"; // Actually, it's the size of this std::string that we want; a + "1"; // Actually, it's the size of this string that we want; a // 64-bit build distinguishes between size_t and uint64_t, // even though they're both unsigned 64-bit values. result = absl::StrCat("And a ", one.size(), " and a ", @@ -375,7 +375,7 @@ TEST(StrAppend, Basics) { EXPECT_EQ(result.substr(old_size), "12345678910, 10987654321!"); std::string one = - "1"; // Actually, it's the size of this std::string that we want; a + "1"; // Actually, it's the size of this string that we want; a // 64-bit build distinguishes between size_t and uint64_t, // even though they're both unsigned 64-bit values. old_size = result.size(); @@ -463,7 +463,7 @@ TEST(StrAppend, CornerCases) { } TEST(StrAppend, CornerCasesNonEmptyAppend) { - for (std::string result : {"hello", "a std::string too long to fit in the SSO"}) { + for (std::string result : {"hello", "a string too long to fit in the SSO"}) { const std::string expected = result; absl::StrAppend(&result, ""); EXPECT_EQ(result, expected); diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc index acbdbf4a..554dca72 100644 --- a/absl/strings/str_format_test.cc +++ b/absl/strings/str_format_test.cc @@ -242,7 +242,7 @@ class TempFile { std::FILE* file() const { return file_; } - // Read the file into a std::string. + // Read the file into a string. std::string ReadFile() { std::fseek(file_, 0, SEEK_END); int size = std::ftell(file_); @@ -345,7 +345,7 @@ TEST(StrFormat, BehavesAsDocumented) { EXPECT_EQ(StrFormat("%c", int{'a'}), "a"); EXPECT_EQ(StrFormat("%c", long{'a'}), "a"); // NOLINT EXPECT_EQ(StrFormat("%c", uint64_t{'a'}), "a"); - // "s" - std::string Eg: "C" -> "C", std::string("C++") -> "C++" + // "s" - string Eg: "C" -> "C", std::string("C++") -> "C++" // Formats std::string, char*, string_view, and Cord. EXPECT_EQ(StrFormat("%s", "C"), "C"); EXPECT_EQ(StrFormat("%s", std::string("C++")), "C++"); diff --git a/absl/strings/str_join_test.cc b/absl/strings/str_join_test.cc index 921d9c2b..2be6256e 100644 --- a/absl/strings/str_join_test.cc +++ b/absl/strings/str_join_test.cc @@ -134,26 +134,26 @@ TEST(StrJoin, APIExamples) { // { - // Empty range yields an empty std::string. + // Empty range yields an empty string. std::vector v; EXPECT_EQ("", absl::StrJoin(v, "-")); } { - // A range of 1 element gives a std::string with that element but no + // A range of 1 element gives a string with that element but no // separator. std::vector v = {"foo"}; EXPECT_EQ("foo", absl::StrJoin(v, "-")); } { - // A range with a single empty std::string element + // A range with a single empty string element std::vector v = {""}; EXPECT_EQ("", absl::StrJoin(v, "-")); } { - // A range with 2 elements, one of which is an empty std::string + // A range with 2 elements, one of which is an empty string std::vector v = {"a", ""}; EXPECT_EQ("a-", absl::StrJoin(v, "-")); } diff --git a/absl/strings/str_replace_benchmark.cc b/absl/strings/str_replace_benchmark.cc index 95b2dc10..01331da2 100644 --- a/absl/strings/str_replace_benchmark.cc +++ b/absl/strings/str_replace_benchmark.cc @@ -62,7 +62,7 @@ void SetUpStrings() { } } // big_string->resize(50); - // OK, we've set up the std::string, now let's set up expectations - first by + // OK, we've set up the string, now let's set up expectations - first by // just replacing "the" with "box" after_replacing_the = new std::string(*big_string); for (size_t pos = 0; diff --git a/absl/strings/str_replace_test.cc b/absl/strings/str_replace_test.cc index 1ca23aff..9d8c7f75 100644 --- a/absl/strings/str_replace_test.cc +++ b/absl/strings/str_replace_test.cc @@ -25,7 +25,7 @@ TEST(StrReplaceAll, OneReplacement) { std::string s; - // Empty std::string. + // Empty string. s = absl::StrReplaceAll(s, {{"", ""}}); EXPECT_EQ(s, ""); s = absl::StrReplaceAll(s, {{"x", ""}}); @@ -47,7 +47,7 @@ TEST(StrReplaceAll, OneReplacement) { s = absl::StrReplaceAll("abc", {{"xyz", "123"}}); EXPECT_EQ(s, "abc"); - // Replace entire std::string. + // Replace entire string. s = absl::StrReplaceAll("abc", {{"abc", "xyz"}}); EXPECT_EQ(s, "xyz"); @@ -88,7 +88,7 @@ TEST(StrReplaceAll, OneReplacement) { TEST(StrReplaceAll, ManyReplacements) { std::string s; - // Empty std::string. + // Empty string. s = absl::StrReplaceAll("", {{"", ""}, {"x", ""}, {"", "y"}, {"x", "y"}}); EXPECT_EQ(s, ""); @@ -96,7 +96,7 @@ TEST(StrReplaceAll, ManyReplacements) { s = absl::StrReplaceAll("abc", {{"", ""}, {"", "y"}, {"x", ""}}); EXPECT_EQ(s, "abc"); - // Replace entire std::string, one char at a time + // Replace entire string, one char at a time s = absl::StrReplaceAll("abc", {{"a", "x"}, {"b", "y"}, {"c", "z"}}); EXPECT_EQ(s, "xyz"); s = absl::StrReplaceAll("zxy", {{"z", "x"}, {"x", "y"}, {"y", "z"}}); @@ -264,7 +264,7 @@ TEST(StrReplaceAll, Inplace) { std::string s; int reps; - // Empty std::string. + // Empty string. s = ""; reps = absl::StrReplaceAll({{"", ""}, {"x", ""}, {"", "y"}, {"x", "y"}}, &s); EXPECT_EQ(reps, 0); @@ -276,7 +276,7 @@ TEST(StrReplaceAll, Inplace) { EXPECT_EQ(reps, 0); EXPECT_EQ(s, "abc"); - // Replace entire std::string, one char at a time + // Replace entire string, one char at a time s = "abc"; reps = absl::StrReplaceAll({{"a", "x"}, {"b", "y"}, {"c", "z"}}, &s); EXPECT_EQ(reps, 3); diff --git a/absl/strings/str_split.cc b/absl/strings/str_split.cc index d0f86669..e08c26b6 100644 --- a/absl/strings/str_split.cc +++ b/absl/strings/str_split.cc @@ -42,7 +42,7 @@ absl::string_view GenericFind(absl::string_view text, absl::string_view delimiter, size_t pos, FindPolicy find_policy) { if (delimiter.empty() && text.length() > 0) { - // Special case for empty std::string delimiters: always return a zero-length + // Special case for empty string delimiters: always return a zero-length // absl::string_view referring to the item at position 1 past pos. return absl::string_view(text.data() + pos + 1, 0); } @@ -127,7 +127,7 @@ absl::string_view ByLength::Find(absl::string_view text, size_t pos) const { pos = std::min(pos, text.size()); // truncate `pos` absl::string_view substr = text.substr(pos); - // If the std::string is shorter than the chunk size we say we + // If the string is shorter than the chunk size we say we // "can't find the delimiter" so this will be the last chunk. if (substr.length() <= static_cast(length_)) return absl::string_view(text.data() + text.size(), 0); diff --git a/absl/strings/str_split_test.cc b/absl/strings/str_split_test.cc index 02f27bc4..67f62a78 100644 --- a/absl/strings/str_split_test.cc +++ b/absl/strings/str_split_test.cc @@ -71,7 +71,7 @@ TEST(Split, TraitsTest) { // namespaces just like callers will need to use. TEST(Split, APIExamples) { { - // Passes std::string delimiter. Assumes the default of ByString. + // Passes string delimiter. Assumes the default of ByString. std::vector v = absl::StrSplit("a,b,c", ","); // NOLINT EXPECT_THAT(v, ElementsAre("a", "b", "c")); @@ -97,7 +97,7 @@ TEST(Split, APIExamples) { } { - // Uses the Literal std::string "=>" as the delimiter. + // Uses the Literal string "=>" as the delimiter. const std::vector v = absl::StrSplit("a=>b=>c", "=>"); EXPECT_THAT(v, ElementsAre("a", "b", "c")); } @@ -121,17 +121,17 @@ TEST(Split, APIExamples) { } { - // Splits the input std::string into individual characters by using an empty - // std::string as the delimiter. + // Splits the input string into individual characters by using an empty + // string as the delimiter. std::vector v = absl::StrSplit("abc", ""); EXPECT_THAT(v, ElementsAre("a", "b", "c")); } { - // Splits std::string data with embedded NUL characters, using NUL as the + // Splits string data with embedded NUL characters, using NUL as the // delimiter. A simple delimiter of "\0" doesn't work because strlen() will - // say that's the empty std::string when constructing the absl::string_view - // delimiter. Instead, a non-empty std::string containing NUL can be used as the + // say that's the empty string when constructing the absl::string_view + // delimiter. Instead, a non-empty string containing NUL can be used as the // delimiter. std::string embedded_nulls("a\0b\0c", 5); std::string null_delim("\0", 1); @@ -436,7 +436,7 @@ TEST(Splitter, ConversionOperator) { // less-than, equal-to, and more-than 2 strings. TEST(Splitter, ToPair) { { - // Empty std::string + // Empty string std::pair p = absl::StrSplit("", ','); EXPECT_EQ("", p.first); EXPECT_EQ("", p.second); @@ -565,7 +565,7 @@ TEST(Split, AcceptsCertainTemporaries) { TEST(Split, Temporary) { // Use a std::string longer than the SSO length, so that when the temporary is - // destroyed, if the splitter keeps a reference to the std::string's contents, + // destroyed, if the splitter keeps a reference to the string's contents, // it'll reference freed memory instead of just dead on-stack memory. const char input[] = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u"; EXPECT_LT(sizeof(std::string), ABSL_ARRAYSIZE(input)) @@ -651,14 +651,14 @@ TEST(Split, UTF8) { // Tests splitting utf8 strings and utf8 delimiters. std::string utf8_string = u8"\u03BA\u1F79\u03C3\u03BC\u03B5"; { - // A utf8 input std::string with an ascii delimiter. + // A utf8 input string with an ascii delimiter. std::string to_split = "a," + utf8_string; std::vector v = absl::StrSplit(to_split, ','); EXPECT_THAT(v, ElementsAre("a", utf8_string)); } { - // A utf8 input std::string and a utf8 delimiter. + // A utf8 input string and a utf8 delimiter. std::string to_split = "a," + utf8_string + ",b"; std::string unicode_delimiter = "," + utf8_string + ","; std::vector v = @@ -667,7 +667,7 @@ TEST(Split, UTF8) { } { - // A utf8 input std::string and ByAnyChar with ascii chars. + // A utf8 input string and ByAnyChar with ascii chars. std::vector v = absl::StrSplit(u8"Foo h\u00E4llo th\u4E1Ere", absl::ByAnyChar(" \t")); EXPECT_THAT(v, ElementsAre("Foo", u8"h\u00E4llo", u8"th\u4E1Ere")); @@ -814,10 +814,10 @@ TEST(Delimiter, ByString) { ByString comma_string(","); TestComma(comma_string); - // The first occurrence of empty std::string ("") in a std::string is at position 0. + // The first occurrence of empty string ("") in a string is at position 0. // There is a test below that demonstrates this for absl::string_view::find(). // If the ByString delimiter returned position 0 for this, there would - // be an infinite loop in the SplitIterator code. To avoid this, empty std::string + // be an infinite loop in the SplitIterator code. To avoid this, empty string // is a special case in that it always returns the item at position 1. absl::string_view abc("abc"); EXPECT_EQ(0, abc.find("")); // "" is found at position 0 @@ -876,7 +876,7 @@ TEST(Delimiter, ByAnyChar) { EXPECT_FALSE(IsFoundAt("=", two_delims, -1)); // ByAnyChar behaves just like ByString when given a delimiter of empty - // std::string. That is, it always returns a zero-length absl::string_view + // string. That is, it always returns a zero-length absl::string_view // referring to the item at position 1, not position 0. ByAnyChar empty(""); EXPECT_FALSE(IsFoundAt("", empty, 0)); @@ -913,7 +913,7 @@ TEST(Split, WorksWithLargeStrings) { std::vector v = absl::StrSplit(s, '-'); EXPECT_EQ(2, v.size()); // The first element will contain 2G of 'x's. - // testing::StartsWith is too slow with a 2G std::string. + // testing::StartsWith is too slow with a 2G string. EXPECT_EQ('x', v[0][0]); EXPECT_EQ('x', v[0][1]); EXPECT_EQ('x', v[0][3]); diff --git a/absl/strings/string_view.h b/absl/strings/string_view.h index 55e80d62..b314bc34 100644 --- a/absl/strings/string_view.h +++ b/absl/strings/string_view.h @@ -319,7 +319,7 @@ class string_view { // stored elsewhere). Note that `string_view::data()` may contain embedded nul // characters, but the returned buffer may or may not be NUL-terminated; // therefore, do not pass `data()` to a routine that expects a NUL-terminated - // std::string. + // string. constexpr const_pointer data() const noexcept { return ptr_; } // Modifiers @@ -327,7 +327,7 @@ class string_view { // string_view::remove_prefix() // // Removes the first `n` characters from the `string_view`. Note that the - // underlying std::string is not changed, only the view. + // underlying string is not changed, only the view. void remove_prefix(size_type n) { assert(n <= length_); ptr_ += n; @@ -337,7 +337,7 @@ class string_view { // string_view::remove_suffix() // // Removes the last `n` characters from the `string_view`. Note that the - // underlying std::string is not changed, only the view. + // underlying string is not changed, only the view. void remove_suffix(size_type n) { assert(n <= length_); length_ -= n; @@ -394,7 +394,7 @@ class string_view { // // Performs a lexicographical comparison between the `string_view` and // another `absl::string_view`, returning -1 if `this` is less than, 0 if - // `this` is equal to, and 1 if `this` is greater than the passed std::string + // `this` is equal to, and 1 if `this` is greater than the passed string // view. Note that in the case of data equality, a further comparison is made // on the respective sizes of the two `string_view`s to determine which is // smaller, equal, or greater. @@ -420,17 +420,17 @@ class string_view { } // Overload of `string_view::compare()` for comparing a `string_view` and a - // a different C-style std::string `s`. + // a different C-style string `s`. int compare(const char* s) const { return compare(string_view(s)); } // Overload of `string_view::compare()` for comparing a substring of the - // `string_view` and a different std::string C-style std::string `s`. + // `string_view` and a different string C-style string `s`. int compare(size_type pos1, size_type count1, const char* s) const { return substr(pos1, count1).compare(string_view(s)); } // Overload of `string_view::compare()` for comparing a substring of the - // `string_view` and a substring of a different C-style std::string `s`. + // `string_view` and a substring of a different C-style string `s`. int compare(size_type pos1, size_type count1, const char* s, size_type count2) const { return substr(pos1, count1).compare(string_view(s, count2)); diff --git a/absl/strings/string_view_test.cc b/absl/strings/string_view_test.cc index cb6a758f..6ba06144 100644 --- a/absl/strings/string_view_test.cc +++ b/absl/strings/string_view_test.cc @@ -410,7 +410,7 @@ TEST(StringViewTest, STL2) { EXPECT_EQ(a.find(e, 17), 17); absl::string_view g("xx not found bb"); EXPECT_EQ(a.find(g), absl::string_view::npos); - // empty std::string nonsense + // empty string nonsense EXPECT_EQ(d.find(b), absl::string_view::npos); EXPECT_EQ(e.find(b), absl::string_view::npos); EXPECT_EQ(d.find(b, 4), absl::string_view::npos); @@ -438,7 +438,7 @@ TEST(StringViewTest, STL2) { EXPECT_EQ(g.find('o', 4), 4); EXPECT_EQ(g.find('o', 5), 8); EXPECT_EQ(a.find('b', 5), absl::string_view::npos); - // empty std::string nonsense + // empty string nonsense EXPECT_EQ(d.find('\0'), absl::string_view::npos); EXPECT_EQ(e.find('\0'), absl::string_view::npos); EXPECT_EQ(d.find('\0', 4), absl::string_view::npos); @@ -465,7 +465,7 @@ TEST(StringViewTest, STL2) { EXPECT_EQ(e.rfind(b), absl::string_view::npos); EXPECT_EQ(d.rfind(b, 4), absl::string_view::npos); EXPECT_EQ(e.rfind(b, 7), absl::string_view::npos); - // empty std::string nonsense + // empty string nonsense EXPECT_EQ(d.rfind(d, 4), std::string().rfind(std::string())); EXPECT_EQ(e.rfind(d, 7), std::string().rfind(std::string())); EXPECT_EQ(d.rfind(e, 4), std::string().rfind(std::string())); @@ -484,7 +484,7 @@ TEST(StringViewTest, STL2) { EXPECT_EQ(f.rfind('\0', 12), 3); EXPECT_EQ(f.rfind('3'), 2); EXPECT_EQ(f.rfind('5'), 5); - // empty std::string nonsense + // empty string nonsense EXPECT_EQ(d.rfind('o'), absl::string_view::npos); EXPECT_EQ(e.rfind('o'), absl::string_view::npos); EXPECT_EQ(d.rfind('o', 4), absl::string_view::npos); @@ -520,7 +520,7 @@ TEST(StringViewTest, STL2FindFirst) { EXPECT_EQ(g.find_first_of(c), 0); EXPECT_EQ(a.find_first_of(f), absl::string_view::npos); EXPECT_EQ(f.find_first_of(a), absl::string_view::npos); - // empty std::string nonsense + // empty string nonsense EXPECT_EQ(a.find_first_of(d), absl::string_view::npos); EXPECT_EQ(a.find_first_of(e), absl::string_view::npos); EXPECT_EQ(d.find_first_of(b), absl::string_view::npos); @@ -538,7 +538,7 @@ TEST(StringViewTest, STL2FindFirst) { EXPECT_EQ(a.find_first_not_of(f), 0); EXPECT_EQ(a.find_first_not_of(d), 0); EXPECT_EQ(a.find_first_not_of(e), 0); - // empty std::string nonsense + // empty string nonsense EXPECT_EQ(a.find_first_not_of(d), 0); EXPECT_EQ(a.find_first_not_of(e), 0); EXPECT_EQ(a.find_first_not_of(d, 1), 1); @@ -566,7 +566,7 @@ TEST(StringViewTest, STL2FindFirst) { EXPECT_EQ(f.find_first_not_of('\0'), 0); EXPECT_EQ(f.find_first_not_of('\0', 3), 4); EXPECT_EQ(f.find_first_not_of('\0', 2), 2); - // empty std::string nonsense + // empty string nonsense EXPECT_EQ(d.find_first_not_of('x'), absl::string_view::npos); EXPECT_EQ(e.find_first_not_of('x'), absl::string_view::npos); EXPECT_EQ(d.find_first_not_of('\0'), absl::string_view::npos); @@ -606,7 +606,7 @@ TEST(StringViewTest, STL2FindLast) { EXPECT_EQ(f.find_last_of(i, 5), 5); EXPECT_EQ(f.find_last_of(i, 6), 6); EXPECT_EQ(f.find_last_of(a, 4), absl::string_view::npos); - // empty std::string nonsense + // empty string nonsense EXPECT_EQ(f.find_last_of(d), absl::string_view::npos); EXPECT_EQ(f.find_last_of(e), absl::string_view::npos); EXPECT_EQ(f.find_last_of(d, 4), absl::string_view::npos); @@ -632,7 +632,7 @@ TEST(StringViewTest, STL2FindLast) { EXPECT_EQ(a.find_last_not_of(c, 24), 22); EXPECT_EQ(a.find_last_not_of(b, 3), 3); EXPECT_EQ(a.find_last_not_of(b, 2), absl::string_view::npos); - // empty std::string nonsense + // empty string nonsense EXPECT_EQ(f.find_last_not_of(d), f.size()-1); EXPECT_EQ(f.find_last_not_of(e), f.size()-1); EXPECT_EQ(f.find_last_not_of(d, 4), 4); @@ -656,7 +656,7 @@ TEST(StringViewTest, STL2FindLast) { EXPECT_EQ(h.find_last_not_of('x', 2), 2); EXPECT_EQ(h.find_last_not_of('=', 2), absl::string_view::npos); EXPECT_EQ(b.find_last_not_of('b', 1), 0); - // empty std::string nonsense + // empty string nonsense EXPECT_EQ(d.find_last_not_of('x'), absl::string_view::npos); EXPECT_EQ(e.find_last_not_of('x'), absl::string_view::npos); EXPECT_EQ(d.find_last_not_of('\0'), absl::string_view::npos); @@ -678,7 +678,7 @@ TEST(StringViewTest, STL2Substr) { EXPECT_EQ(a.substr(23, 99), c); EXPECT_EQ(a.substr(0), a); EXPECT_EQ(a.substr(3, 2), "de"); - // empty std::string nonsense + // empty string nonsense EXPECT_EQ(d.substr(0, 99), e); // use of npos EXPECT_EQ(a.substr(0, absl::string_view::npos), a); @@ -859,7 +859,7 @@ TEST(StringViewTest, NULLInput) { EXPECT_EQ(s.size(), 0); // .ToString() on a absl::string_view with nullptr should produce the empty - // std::string. + // string. EXPECT_EQ("", std::string(s)); #endif // ABSL_HAVE_STRING_VIEW_FROM_NULLPTR } @@ -977,7 +977,7 @@ TEST(StringViewTest, ConstexprCompiles) { #if defined(ABSL_USES_STD_STRING_VIEW) // In libstdc++ (as of 7.2), `std::string_view::string_view(const char*)` - // calls `std::char_traits::length(const char*)` to get the std::string + // calls `std::char_traits::length(const char*)` to get the string // length, but it is not marked constexpr yet. See GCC bug: // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78156 // Also, there is a LWG issue that adds constexpr to length() which was just @@ -1180,7 +1180,7 @@ TEST(FindOneCharTest, EdgeCases) { TEST(HugeStringView, TwoPointTwoGB) { if (sizeof(size_t) <= 4 || RunningOnValgrind()) return; - // Try a huge std::string piece. + // Try a huge string piece. const size_t size = size_t{2200} * 1000 * 1000; std::string s(size, 'a'); absl::string_view sp(s); diff --git a/absl/strings/substitute.cc b/absl/strings/substitute.cc index 5b69a3ef..1f3c7409 100644 --- a/absl/strings/substitute.cc +++ b/absl/strings/substitute.cc @@ -36,7 +36,7 @@ void SubstituteAndAppendArray(std::string* output, absl::string_view format, if (i + 1 >= format.size()) { #ifndef NDEBUG ABSL_RAW_LOG(FATAL, - "Invalid absl::Substitute() format std::string: \"%s\".", + "Invalid absl::Substitute() format string: \"%s\".", absl::CEscape(format).c_str()); #endif return; @@ -46,8 +46,8 @@ void SubstituteAndAppendArray(std::string* output, absl::string_view format, #ifndef NDEBUG ABSL_RAW_LOG( FATAL, - "Invalid absl::Substitute() format std::string: asked for \"$" - "%d\", but only %d args were given. Full format std::string was: " + "Invalid absl::Substitute() format string: asked for \"$" + "%d\", but only %d args were given. Full format string was: " "\"%s\".", index, static_cast(num_args), absl::CEscape(format).c_str()); #endif @@ -61,7 +61,7 @@ void SubstituteAndAppendArray(std::string* output, absl::string_view format, } else { #ifndef NDEBUG ABSL_RAW_LOG(FATAL, - "Invalid absl::Substitute() format std::string: \"%s\".", + "Invalid absl::Substitute() format string: \"%s\".", absl::CEscape(format).c_str()); #endif return; @@ -73,7 +73,7 @@ void SubstituteAndAppendArray(std::string* output, absl::string_view format, if (size == 0) return; - // Build the std::string. + // Build the string. size_t original_size = output->size(); strings_internal::STLStringResizeUninitialized(output, original_size + size); char* target = &(*output)[original_size]; diff --git a/absl/strings/substitute.h b/absl/strings/substitute.h index 4d0984d3..e7b4c1e6 100644 --- a/absl/strings/substitute.h +++ b/absl/strings/substitute.h @@ -99,7 +99,7 @@ namespace substitute_internal { // This class has implicit constructors. class Arg { public: - // Overloads for std::string-y things + // Overloads for string-y things // // Explicitly overload `const char*` so the compiler doesn't cast to `bool`. Arg(const char* value) // NOLINT(runtime/explicit) @@ -360,13 +360,13 @@ inline void SubstituteAndAppend( void SubstituteAndAppend(std::string* output, const char* format) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 0, "There were no substitution arguments " - "but this format std::string has a $[0-9] in it"); + "but this format string has a $[0-9] in it"); void SubstituteAndAppend(std::string* output, const char* format, const substitute_internal::Arg& a0) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 1, "There was 1 substitution argument given, but " - "this format std::string is either missing its $0, or " + "this format string is either missing its $0, or " "contains one of $1-$9"); void SubstituteAndAppend(std::string* output, const char* format, @@ -374,7 +374,7 @@ void SubstituteAndAppend(std::string* output, const char* format, const substitute_internal::Arg& a1) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 3, "There were 2 substitution arguments given, but " - "this format std::string is either missing its $0/$1, or " + "this format string is either missing its $0/$1, or " "contains one of $2-$9"); void SubstituteAndAppend(std::string* output, const char* format, @@ -383,7 +383,7 @@ void SubstituteAndAppend(std::string* output, const char* format, const substitute_internal::Arg& a2) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 7, "There were 3 substitution arguments given, but " - "this format std::string is either missing its $0/$1/$2, or " + "this format string is either missing its $0/$1/$2, or " "contains one of $3-$9"); void SubstituteAndAppend(std::string* output, const char* format, @@ -393,7 +393,7 @@ void SubstituteAndAppend(std::string* output, const char* format, const substitute_internal::Arg& a3) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 15, "There were 4 substitution arguments given, but " - "this format std::string is either missing its $0-$3, or " + "this format string is either missing its $0-$3, or " "contains one of $4-$9"); void SubstituteAndAppend(std::string* output, const char* format, @@ -404,7 +404,7 @@ void SubstituteAndAppend(std::string* output, const char* format, const substitute_internal::Arg& a4) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 31, "There were 5 substitution arguments given, but " - "this format std::string is either missing its $0-$4, or " + "this format string is either missing its $0-$4, or " "contains one of $5-$9"); void SubstituteAndAppend(std::string* output, const char* format, @@ -416,7 +416,7 @@ void SubstituteAndAppend(std::string* output, const char* format, const substitute_internal::Arg& a5) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 63, "There were 6 substitution arguments given, but " - "this format std::string is either missing its $0-$5, or " + "this format string is either missing its $0-$5, or " "contains one of $6-$9"); void SubstituteAndAppend( @@ -426,7 +426,7 @@ void SubstituteAndAppend( const substitute_internal::Arg& a5, const substitute_internal::Arg& a6) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 127, "There were 7 substitution arguments given, but " - "this format std::string is either missing its $0-$6, or " + "this format string is either missing its $0-$6, or " "contains one of $7-$9"); void SubstituteAndAppend( @@ -437,7 +437,7 @@ void SubstituteAndAppend( const substitute_internal::Arg& a7) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 255, "There were 8 substitution arguments given, but " - "this format std::string is either missing its $0-$7, or " + "this format string is either missing its $0-$7, or " "contains one of $8-$9"); void SubstituteAndAppend( @@ -449,7 +449,7 @@ void SubstituteAndAppend( ABSL_BAD_CALL_IF( substitute_internal::PlaceholderBitmask(format) != 511, "There were 9 substitution arguments given, but " - "this format std::string is either missing its $0-$8, or contains a $9"); + "this format string is either missing its $0-$8, or contains a $9"); void SubstituteAndAppend( std::string* output, const char* format, const substitute_internal::Arg& a0, @@ -460,7 +460,7 @@ void SubstituteAndAppend( const substitute_internal::Arg& a9) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 1023, "There were 10 substitution arguments given, but this " - "format std::string doesn't contain all of $0 through $9"); + "format string doesn't contain all of $0 through $9"); #endif // ABSL_BAD_CALL_IF // Substitute() @@ -586,19 +586,19 @@ ABSL_MUST_USE_RESULT inline std::string Substitute( std::string Substitute(const char* format) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 0, "There were no substitution arguments " - "but this format std::string has a $[0-9] in it"); + "but this format string has a $[0-9] in it"); std::string Substitute(const char* format, const substitute_internal::Arg& a0) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 1, "There was 1 substitution argument given, but " - "this format std::string is either missing its $0, or " + "this format string is either missing its $0, or " "contains one of $1-$9"); std::string Substitute(const char* format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a1) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 3, "There were 2 substitution arguments given, but " - "this format std::string is either missing its $0/$1, or " + "this format string is either missing its $0/$1, or " "contains one of $2-$9"); std::string Substitute(const char* format, const substitute_internal::Arg& a0, @@ -606,7 +606,7 @@ std::string Substitute(const char* format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a2) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 7, "There were 3 substitution arguments given, but " - "this format std::string is either missing its $0/$1/$2, or " + "this format string is either missing its $0/$1/$2, or " "contains one of $3-$9"); std::string Substitute(const char* format, const substitute_internal::Arg& a0, @@ -615,7 +615,7 @@ std::string Substitute(const char* format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a3) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 15, "There were 4 substitution arguments given, but " - "this format std::string is either missing its $0-$3, or " + "this format string is either missing its $0-$3, or " "contains one of $4-$9"); std::string Substitute(const char* format, const substitute_internal::Arg& a0, @@ -625,7 +625,7 @@ std::string Substitute(const char* format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a4) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 31, "There were 5 substitution arguments given, but " - "this format std::string is either missing its $0-$4, or " + "this format string is either missing its $0-$4, or " "contains one of $5-$9"); std::string Substitute(const char* format, const substitute_internal::Arg& a0, @@ -636,7 +636,7 @@ std::string Substitute(const char* format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a5) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 63, "There were 6 substitution arguments given, but " - "this format std::string is either missing its $0-$5, or " + "this format string is either missing its $0-$5, or " "contains one of $6-$9"); std::string Substitute(const char* format, const substitute_internal::Arg& a0, @@ -648,7 +648,7 @@ std::string Substitute(const char* format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a6) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 127, "There were 7 substitution arguments given, but " - "this format std::string is either missing its $0-$6, or " + "this format string is either missing its $0-$6, or " "contains one of $7-$9"); std::string Substitute(const char* format, const substitute_internal::Arg& a0, @@ -661,7 +661,7 @@ std::string Substitute(const char* format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a7) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 255, "There were 8 substitution arguments given, but " - "this format std::string is either missing its $0-$7, or " + "this format string is either missing its $0-$7, or " "contains one of $8-$9"); std::string Substitute( @@ -673,7 +673,7 @@ std::string Substitute( ABSL_BAD_CALL_IF( substitute_internal::PlaceholderBitmask(format) != 511, "There were 9 substitution arguments given, but " - "this format std::string is either missing its $0-$8, or contains a $9"); + "this format string is either missing its $0-$8, or contains a $9"); std::string Substitute( const char* format, const substitute_internal::Arg& a0, @@ -684,7 +684,7 @@ std::string Substitute( const substitute_internal::Arg& a9) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 1023, "There were 10 substitution arguments given, but this " - "format std::string doesn't contain all of $0 through $9"); + "format string doesn't contain all of $0 through $9"); #endif // ABSL_BAD_CALL_IF ABSL_NAMESPACE_END diff --git a/absl/strings/substitute_test.cc b/absl/strings/substitute_test.cc index 450cd2bc..442c9215 100644 --- a/absl/strings/substitute_test.cc +++ b/absl/strings/substitute_test.cc @@ -89,7 +89,7 @@ TEST(SubstituteTest, Substitute) { str = absl::Substitute("$0", char_buf); EXPECT_EQ("print me too", str); - // null char* is "doubly" special. Represented as the empty std::string. + // null char* is "doubly" special. Represented as the empty string. char_p = nullptr; str = absl::Substitute("$0", char_p); EXPECT_EQ("", str); @@ -189,14 +189,14 @@ TEST(SubstituteTest, VectorBoolRef) { TEST(SubstituteDeathTest, SubstituteDeath) { EXPECT_DEBUG_DEATH( static_cast(absl::Substitute(absl::string_view("-$2"), "a", "b")), - "Invalid absl::Substitute\\(\\) format std::string: asked for \"\\$2\", " + "Invalid absl::Substitute\\(\\) format string: asked for \"\\$2\", " "but only 2 args were given."); EXPECT_DEBUG_DEATH( static_cast(absl::Substitute(absl::string_view("-$z-"))), - "Invalid absl::Substitute\\(\\) format std::string: \"-\\$z-\""); + "Invalid absl::Substitute\\(\\) format string: \"-\\$z-\""); EXPECT_DEBUG_DEATH( static_cast(absl::Substitute(absl::string_view("-$"))), - "Invalid absl::Substitute\\(\\) format std::string: \"-\\$\""); + "Invalid absl::Substitute\\(\\) format string: \"-\\$\""); } #endif // GTEST_HAS_DEATH_TEST diff --git a/absl/synchronization/mutex.cc b/absl/synchronization/mutex.cc index e0879b05..426558e6 100644 --- a/absl/synchronization/mutex.cc +++ b/absl/synchronization/mutex.cc @@ -299,7 +299,7 @@ static struct SynchEvent { // this is a trivial hash table for the events bool log; // logging turned on // Constant after initialization - char name[1]; // actually longer---NUL-terminated std::string + char name[1]; // actually longer---NUL-terminated string } * synch_event[kNSynchEvent] ABSL_GUARDED_BY(synch_event_mu); // Ensure that the object at "addr" has a SynchEvent struct associated with it, diff --git a/absl/time/civil_time.cc b/absl/time/civil_time.cc index ada82cbc..c4202c73 100644 --- a/absl/time/civil_time.cc +++ b/absl/time/civil_time.cc @@ -38,7 +38,7 @@ std::string FormatYearAnd(string_view fmt, CivilSecond cs) { const CivilSecond ncs(NormalizeYear(cs.year()), cs.month(), cs.day(), cs.hour(), cs.minute(), cs.second()); const TimeZone utc = UTCTimeZone(); - // TODO(absl-team): Avoid conversion of fmt std::string. + // TODO(absl-team): Avoid conversion of fmt string. return StrCat(cs.year(), FormatTime(std::string(fmt), FromCivil(ncs, utc), utc)); } @@ -47,7 +47,7 @@ template bool ParseYearAnd(string_view fmt, string_view s, CivilT* c) { // Civil times support a larger year range than absl::Time, so we need to // parse the year separately, normalize it, then use absl::ParseTime on the - // normalized std::string. + // normalized string. const std::string ss = std::string(s); // TODO(absl-team): Avoid conversion. const char* const np = ss.c_str(); char* endp; @@ -82,7 +82,7 @@ bool ParseAs(string_view s, CivilT2* c) { template bool ParseLenient(string_view s, CivilT* c) { - // A fastpath for when the given std::string data parses exactly into the given + // A fastpath for when the given string data parses exactly into the given // type T (e.g., s="YYYY-MM-DD" and CivilT=CivilDay). if (ParseCivilTime(s, c)) return true; // Try parsing as each of the 6 types, trying the most common types first diff --git a/absl/time/duration.cc b/absl/time/duration.cc index b1af8406..1353fa0a 100644 --- a/absl/time/duration.cc +++ b/absl/time/duration.cc @@ -874,12 +874,12 @@ bool ParseDuration(const std::string& dur_string, Duration* d) { ++start; } - // Can't parse a duration from an empty std::string. + // Can't parse a duration from an empty string. if (*start == '\0') { return false; } - // Special case for a std::string of "0". + // Special case for a string of "0". if (*start == '0' && *(start + 1) == '\0') { *d = ZeroDuration(); return true; diff --git a/absl/time/format_test.cc b/absl/time/format_test.cc index ab1f3059..a9a1eb8e 100644 --- a/absl/time/format_test.cc +++ b/absl/time/format_test.cc @@ -173,7 +173,7 @@ TEST(ParseTime, WithTimeZone) { absl::Time t; std::string e; - // We can parse a std::string without a UTC offset if we supply a timezone. + // We can parse a string without a UTC offset if we supply a timezone. EXPECT_TRUE( absl::ParseTime("%Y-%m-%d %H:%M:%S", "2013-06-28 19:08:09", tz, &t, &e)) << e; @@ -327,7 +327,7 @@ TEST(ParseTime, InfiniteTime) { EXPECT_TRUE(absl::ParseTime("%H:%M blah", " infinite-past ", &t, &err)); EXPECT_EQ(absl::InfinitePast(), t); - // "infinite-future" as literal std::string + // "infinite-future" as literal string absl::TimeZone tz = absl::UTCTimeZone(); EXPECT_TRUE(absl::ParseTime("infinite-future %H:%M", "infinite-future 03:04", &t, &err)); @@ -335,7 +335,7 @@ TEST(ParseTime, InfiniteTime) { EXPECT_EQ(3, tz.At(t).cs.hour()); EXPECT_EQ(4, tz.At(t).cs.minute()); - // "infinite-past" as literal std::string + // "infinite-past" as literal string EXPECT_TRUE( absl::ParseTime("infinite-past %H:%M", "infinite-past 03:04", &t, &err)); EXPECT_NE(absl::InfinitePast(), t); diff --git a/absl/time/internal/cctz/include/cctz/time_zone.h b/absl/time/internal/cctz/include/cctz/time_zone.h index d05147a1..d4ea90ef 100644 --- a/absl/time/internal/cctz/include/cctz/time_zone.h +++ b/absl/time/internal/cctz/include/cctz/time_zone.h @@ -209,7 +209,7 @@ class time_zone { // version() and description() provide additional information about the // time zone. The content of each of the returned strings is unspecified, // however, when the IANA Time Zone Database is the underlying data source - // the version() std::string will be in the familar form (e.g, "2018e") or + // the version() string will be in the familar form (e.g, "2018e") or // empty when unavailable. // // Note: These functions are for informational or testing purposes only. diff --git a/absl/time/internal/cctz/include/cctz/zone_info_source.h b/absl/time/internal/cctz/include/cctz/zone_info_source.h index 912b44ba..012eb4ec 100644 --- a/absl/time/internal/cctz/include/cctz/zone_info_source.h +++ b/absl/time/internal/cctz/include/cctz/zone_info_source.h @@ -37,7 +37,7 @@ class ZoneInfoSource { // Until the zoneinfo data supports versioning information, we provide // a way for a ZoneInfoSource to indicate it out-of-band. The default - // implementation returns an empty std::string. + // implementation returns an empty string. virtual std::string Version() const; }; diff --git a/absl/time/internal/cctz/src/time_zone_format.cc b/absl/time/internal/cctz/src/time_zone_format.cc index 950b23a1..179975e0 100644 --- a/absl/time/internal/cctz/src/time_zone_format.cc +++ b/absl/time/internal/cctz/src/time_zone_format.cc @@ -189,7 +189,7 @@ void FormatTM(std::string* out, const std::string& fmt, const std::tm& tm) { // strftime(3) returns the number of characters placed in the output // array (which may be 0 characters). It also returns 0 to indicate // an error, like the array wasn't large enough. To accommodate this, - // the following code grows the buffer size from 2x the format std::string + // the following code grows the buffer size from 2x the format string // length up to 32x. for (std::size_t i = 2; i != 32; i *= 2) { std::size_t buf_size = fmt.size() * i; @@ -839,7 +839,7 @@ bool parse(const std::string& format, const std::string& input, // Skip any remaining whitespace. while (std::isspace(*data)) ++data; - // parse() must consume the entire input std::string. + // parse() must consume the entire input string. if (*data != '\0') { if (err != nullptr) *err = "Illegal trailing data in input string"; return false; 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 caebcc4d..87382e15 100644 --- a/absl/time/internal/cctz/src/time_zone_format_test.cc +++ b/absl/time/internal/cctz/src/time_zone_format_test.cc @@ -767,7 +767,7 @@ TEST(Parse, WithTimeZone) { EXPECT_TRUE(load_time_zone("America/Los_Angeles", &tz)); time_point tp; - // We can parse a std::string without a UTC offset if we supply a timezone. + // We can parse a string without a UTC offset if we supply a timezone. EXPECT_TRUE(parse("%Y-%m-%d %H:%M:%S", "2013-06-28 19:08:09", tz, &tp)); ExpectTime(tp, tz, 2013, 6, 28, 19, 8, 9, -7 * 60 * 60, true, "PDT"); diff --git a/absl/time/internal/cctz/src/time_zone_impl.h b/absl/time/internal/cctz/src/time_zone_impl.h index 69806c10..7d747ba9 100644 --- a/absl/time/internal/cctz/src/time_zone_impl.h +++ b/absl/time/internal/cctz/src/time_zone_impl.h @@ -71,7 +71,7 @@ class time_zone::Impl { return zone_->PrevTransition(tp, trans); } - // Returns an implementation-defined version std::string for this time zone. + // Returns an implementation-defined version string for this time zone. std::string Version() const { return zone_->Version(); } // Returns an implementation-defined description of this time zone. diff --git a/absl/time/internal/cctz/src/time_zone_info.cc b/absl/time/internal/cctz/src/time_zone_info.cc index f1697cdf..665fb424 100644 --- a/absl/time/internal/cctz/src/time_zone_info.cc +++ b/absl/time/internal/cctz/src/time_zone_info.cc @@ -506,7 +506,7 @@ bool TimeZoneInfo::Load(const std::string& name, ZoneInfoSource* zip) { // If we did not find version information during the standard loading // process (as of tzh_version '3' that is unsupported), then ask the - // ZoneInfoSource for any out-of-bound version std::string it may be privy to. + // ZoneInfoSource for any out-of-bound version string it may be privy to. if (version_.empty()) { version_ = zip->Version(); } 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 99137a08..35911ce5 100644 --- a/absl/time/internal/cctz/src/time_zone_lookup_test.cc +++ b/absl/time/internal/cctz/src/time_zone_lookup_test.cc @@ -749,7 +749,7 @@ TEST(TimeZone, Failures) { EXPECT_EQ(chrono::system_clock::from_time_t(0), convert(civil_second(1970, 1, 1, 0, 0, 0), tz)); // UTC - // Loading an empty std::string timezone should fail. + // Loading an empty string timezone should fail. tz = LoadZone("America/Los_Angeles"); EXPECT_FALSE(load_time_zone("", &tz)); EXPECT_EQ(chrono::system_clock::from_time_t(0), diff --git a/absl/time/internal/cctz/src/tzfile.h b/absl/time/internal/cctz/src/tzfile.h index 1ed55e0f..269fa36c 100644 --- a/absl/time/internal/cctz/src/tzfile.h +++ b/absl/time/internal/cctz/src/tzfile.h @@ -83,13 +83,13 @@ struct tzhead { ** If tzh_version is '2' or greater, the above is followed by a second instance ** of tzhead and a second instance of the data in which each coded transition ** time uses 8 rather than 4 chars, -** then a POSIX-TZ-environment-variable-style std::string for use in handling +** then a POSIX-TZ-environment-variable-style string for use in handling ** instants after the last transition time stored in the file ** (with nothing between the newlines if there is no POSIX representation for ** such instants). ** ** If tz_version is '3' or greater, the above is extended as follows. -** First, the POSIX TZ std::string's hour offset may range from -167 +** First, the POSIX TZ string's hour offset may range from -167 ** through 167 as compared to the POSIX-required 0 through 24. ** Second, its DST start time may be January 1 at 00:00 and its stop ** time December 31 at 24:00 plus the difference between DST and diff --git a/absl/time/time_zone_test.cc b/absl/time/time_zone_test.cc index 8f1e74ac..229fcfcc 100644 --- a/absl/time/time_zone_test.cc +++ b/absl/time/time_zone_test.cc @@ -88,7 +88,7 @@ TEST(TimeZone, Failures) { EXPECT_FALSE(LoadTimeZone("Invalid/TimeZone", &tz)); EXPECT_EQ(absl::UTCTimeZone(), tz); // guaranteed fallback to UTC - // Loading an empty std::string timezone should fail. + // Loading an empty string timezone should fail. tz = absl::time_internal::LoadTimeZone("America/Los_Angeles"); EXPECT_FALSE(LoadTimeZone("", &tz)); EXPECT_EQ(absl::UTCTimeZone(), tz); // guaranteed fallback to UTC diff --git a/absl/types/variant_test.cc b/absl/types/variant_test.cc index 96393333..4639c42e 100644 --- a/absl/types/variant_test.cc +++ b/absl/types/variant_test.cc @@ -679,7 +679,7 @@ TEST(VariantTest, TestSelfAssignment) { object.operator=(object); EXPECT_EQ(0, counter); - // A std::string long enough that it's likely to defeat any inline representation + // A string long enough that it's likely to defeat any inline representation // optimization. const std::string long_str(128, 'a'); -- cgit v1.2.3 From c6954897f7ece5011f0126db9117361dc1a6ff36 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Thu, 12 Mar 2020 09:41:33 -0700 Subject: Export of internal Abseil changes -- 66a0a46462692378f77518f9db766a218bfac40b by Derek Mauro : Internal change PiperOrigin-RevId: 300565981 -- 2a1ad67662b2e22beec7d3be019e6bba23c41c7f by Derek Mauro : Fix CompressedTuple move constructor on MSVC Imports GitHub #637 PiperOrigin-RevId: 300545840 -- a3ee3ad036bbb6b0031121fd58299e5c59a243e1 by Derek Mauro : Disable LLVM's XRay instrumentation on Android Fixes #626 PiperOrigin-RevId: 300540906 -- 87999244b1f825e585ec12a431086cb60daeb24f by Gennadiy Rozental : Increase max cord depth by 2. After reviewing of the paper we can prove that the algorithm in fact can lead to slightly larger max depth. PiperOrigin-RevId: 300422376 -- 98de175ee8ad33290ef883c167c2de4414a11007 by Abseil Team : Use *opt/opt-> instead of opt.value() in an example, after checking the optional has a value. PiperOrigin-RevId: 300253502 -- 1107aa0acf0fe743ef50173e02e48f0d4eb572ef by Derek Mauro : Stop overriding the default system C++ dialect in CMake CMake on MacOS has some very strange defaults, like Wno-c++11-extensions, and doesn't seem to respect CMAKE_CXX_STANDARD, so use CMAKE_CXX_FLAGS instead. PiperOrigin-RevId: 300180753 GitOrigin-RevId: 66a0a46462692378f77518f9db766a218bfac40b Change-Id: Icd7b84c49306441b012cb87f244cc92a11697db8 --- absl/base/attributes.h | 4 +++- absl/copts/AbseilConfigureCopts.cmake | 9 --------- absl/strings/cord.cc | 21 ++++++++++--------- absl/strings/cord.h | 38 +++++++++-------------------------- absl/strings/cord_test.cc | 10 ++++++--- absl/types/optional.h | 2 +- ci/macos_xcode_cmake.sh | 2 +- 7 files changed, 33 insertions(+), 53 deletions(-) (limited to 'absl/strings/cord.h') diff --git a/absl/base/attributes.h b/absl/base/attributes.h index ff138629..b4bb6cf8 100644 --- a/absl/base/attributes.h +++ b/absl/base/attributes.h @@ -507,8 +507,10 @@ // packages/targets, as this may lead to conflicting definitions of functions at // link-time. // +// XRay isn't currently supported on Android: +// https://github.com/android/ndk/issues/368 #if ABSL_HAVE_CPP_ATTRIBUTE(clang::xray_always_instrument) && \ - !defined(ABSL_NO_XRAY_ATTRIBUTES) + !defined(ABSL_NO_XRAY_ATTRIBUTES) && !defined(__ANDROID__) #define ABSL_XRAY_ALWAYS_INSTRUMENT [[clang::xray_always_instrument]] #define ABSL_XRAY_NEVER_INSTRUMENT [[clang::xray_never_instrument]] #if ABSL_HAVE_CPP_ATTRIBUTE(clang::xray_log_args) diff --git a/absl/copts/AbseilConfigureCopts.cmake b/absl/copts/AbseilConfigureCopts.cmake index 77d4ace8..390a07a0 100644 --- a/absl/copts/AbseilConfigureCopts.cmake +++ b/absl/copts/AbseilConfigureCopts.cmake @@ -63,12 +63,3 @@ else() set(ABSL_DEFAULT_COPTS "") set(ABSL_TEST_COPTS "") endif() - -if("${CMAKE_CXX_STANDARD}" EQUAL 98) - message(FATAL_ERROR "Abseil requires at least C++11") -elseif(NOT "${CMAKE_CXX_STANDARD}") - message(STATUS "No CMAKE_CXX_STANDARD set, assuming 11") - set(ABSL_CXX_STANDARD 11) -else() - set(ABSL_CXX_STANDARD "${CMAKE_CXX_STANDARD}") -endif() diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index 9b32b3cc..415b239b 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc @@ -187,19 +187,20 @@ static constexpr size_t TagToLength(uint8_t tag) { // Enforce that kMaxFlatSize maps to a well-known exact tag value. static_assert(TagToAllocatedSize(224) == kMaxFlatSize, "Bad tag logic"); -constexpr uint64_t Fibonacci(uint8_t n, uint64_t a = 0, uint64_t b = 1) { - return n == 0 ? a : n == 1 ? b : Fibonacci(n - 1, b, a + b); +constexpr size_t Fibonacci(uint8_t n, const size_t a = 0, const size_t b = 1) { + return n == 0 + ? a + : n == 1 ? b + : Fibonacci(n - 1, b, + (a > (size_t(-1) - b)) ? size_t(-1) : a + b); } -static_assert(Fibonacci(63) == 6557470319842, - "Fibonacci values computed incorrectly"); - // Minimum length required for a given depth tree -- a tree is considered // balanced if // length(t) >= kMinLength[depth(t)] // The node depth is allowed to become larger to reduce rebalancing // for larger strings (see ShouldRebalance). -constexpr uint64_t kMinLength[] = { +constexpr size_t kMinLength[] = { Fibonacci(2), Fibonacci(3), Fibonacci(4), Fibonacci(5), Fibonacci(6), Fibonacci(7), Fibonacci(8), Fibonacci(9), Fibonacci(10), Fibonacci(11), Fibonacci(12), Fibonacci(13), Fibonacci(14), Fibonacci(15), Fibonacci(16), @@ -218,9 +219,9 @@ constexpr uint64_t kMinLength[] = { Fibonacci(77), Fibonacci(78), Fibonacci(79), Fibonacci(80), Fibonacci(81), Fibonacci(82), Fibonacci(83), Fibonacci(84), Fibonacci(85), Fibonacci(86), Fibonacci(87), Fibonacci(88), Fibonacci(89), Fibonacci(90), Fibonacci(91), - Fibonacci(92), Fibonacci(93)}; + Fibonacci(92), Fibonacci(93), Fibonacci(94), Fibonacci(95)}; -static_assert(sizeof(kMinLength) / sizeof(uint64_t) == +static_assert(sizeof(kMinLength) / sizeof(size_t) >= (cord_internal::MaxCordDepth() + 1), "Not enough elements in kMinLength array to cover all the " "supported Cord depth(s)"); @@ -1169,9 +1170,9 @@ class CordForest { void AddNode(CordRep* node) { CordRep* sum = nullptr; - // Collect together everything with which we will merge node + // Collect together everything with which we will merge with node int i = 0; - for (; node->length > kMinLength[i + 1]; ++i) { + for (; node->length >= kMinLength[i + 1]; ++i) { auto& tree_at_i = trees_[i]; if (tree_at_i == nullptr) continue; diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 3941f19c..eb236e50 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -72,39 +72,21 @@ namespace cord_internal { // It's expensive to keep a tree perfectly balanced, so instead we keep trees // approximately balanced. A tree node N of depth D(N) that contains a string // of L(N) characters is considered balanced if L >= Fibonacci(D + 2). -// The "+ 2" is used to ensure that every leaf node contains at least one -// character. Here we presume that +// The "+ 2" is used to ensure that every balanced leaf node contains at least +// one character. Here we presume that // Fibonacci(0) = 0 // Fibonacci(1) = 1 // Fibonacci(2) = 1 // Fibonacci(3) = 2 // ... -// -// Fibonacci numbers are convenient because it means when two balanced trees of -// the same depth are made the children of a new node, the resulting tree is -// guaranteed to also be balanced: -// -// -// L(left) >= Fibonacci(D(left) + 2) -// L(right) >= Fibonacci(D(right) + 2) -// -// L(left) + L(right) >= Fibonacci(D(left) + 2) + Fibonacci(D(right) + 2) -// L(left) + L(right) == L(new_tree) -// -// L(new_tree) >= 2 * Fibonacci(D(child) + 2) -// D(child) == D(new_tree) - 1 -// -// L(new_tree) >= 2 * Fibonacci(D(new_tree) + 1) -// 2 * Fibonacci(N) >= Fibonacci(N + 1) -// -// L(new_tree) >= Fibonacci(D(new_tree) + 2) -// -// -// The 93rd Fibonacci number is the largest Fibonacci number that can be -// represented in 64 bits, so the size of a balanced Cord of depth 92 is too big -// for an unsigned 64 bit integer to hold. Therefore we can safely assume that -// the maximum depth of a Cord is 91. -constexpr size_t MaxCordDepth() { return 91; } +// The algorithm is based on paper by Hans Boehm et al: +// https://www.cs.rit.edu/usr/local/pub/jeh/courses/QUARTERS/FP/Labs/CedarRope/rope-paper.pdf +// In this paper authors shows that rebalancing based on cord forest of already +// balanced subtrees can be proven to never produce tree of depth larger than +// largest Fibonacci number representable in the same integral type as cord size +// For 64 bit integers this is the 93rd Fibonacci number. For 32 bit integrals +// this is 47th Fibonacci number. +constexpr size_t MaxCordDepth() { return sizeof(size_t) == 8 ? 93 : 47; } // This class models fixed max size stack of CordRep pointers. // The elements are being pushed back and popped from the back. diff --git a/absl/strings/cord_test.cc b/absl/strings/cord_test.cc index d6e091f8..f2d81d4c 100644 --- a/absl/strings/cord_test.cc +++ b/absl/strings/cord_test.cc @@ -1403,12 +1403,16 @@ TEST(CordChunkIterator, Operations) { } TEST(CordChunkIterator, MaxLengthFullTree) { + // Start with a 1-byte cord, and then double its length in a loop. We should + // be able to do this until the point where we would overflow size_t. + absl::Cord cord; size_t size = 1; AddExternalMemory("x", &cord); EXPECT_EQ(cord.size(), size); - for (int i = 0; i < 63; ++i) { + const int kCordLengthDoublingLimit = std::numeric_limits::digits - 1; + for (int i = 0; i < kCordLengthDoublingLimit; ++i) { cord.Prepend(absl::Cord(cord)); size <<= 1; @@ -1431,7 +1435,7 @@ TEST(CordChunkIterator, MaxDepth) { AddExternalMemory("x", &left_child); absl::Cord root = left_child; - for (int i = 0; i < 91; ++i) { + for (int i = 0; i < absl::cord_internal::MaxCordDepth() - 2; ++i) { size_t new_size = left_child.size() + root.size(); root.Prepend(left_child); EXPECT_EQ(root.size(), new_size); @@ -1442,7 +1446,7 @@ TEST(CordChunkIterator, MaxDepth) { std::swap(left_child, root); } - EXPECT_DEATH_IF_SUPPORTED(root.Prepend(left_child), "Cord depth exceeds max"); + EXPECT_DEATH_IF_SUPPORTED(root.Prepend(left_child), "Cord is too long"); } TEST(CordCharIterator, Traits) { diff --git a/absl/types/optional.h b/absl/types/optional.h index 2025e29f..01d747d7 100644 --- a/absl/types/optional.h +++ b/absl/types/optional.h @@ -444,7 +444,7 @@ class optional : private optional_internal::optional_data, // Returns false if and only if the `optional` is empty. // // if (opt) { - // // do something with opt.value(); + // // do something with *opt or opt->; // } else { // // opt is empty. // } diff --git a/ci/macos_xcode_cmake.sh b/ci/macos_xcode_cmake.sh index a1f4a857..aa9ee15d 100755 --- a/ci/macos_xcode_cmake.sh +++ b/ci/macos_xcode_cmake.sh @@ -36,7 +36,7 @@ for compilation_mode in ${ABSL_CMAKE_BUILD_TYPES}; do time cmake ${ABSEIL_ROOT} \ -GXcode \ -DCMAKE_BUILD_TYPE=${compilation_mode} \ - -DCMAKE_CXX_STANDARD=11 \ + -DCMAKE_CXX_FLAGS=-std=c++14 \ -DABSL_USE_GOOGLETEST_HEAD=ON \ -DABSL_RUN_TESTS=ON time cmake --build . -- cgit v1.2.3 From 7853a7586c492ce8905c9e49f8679dada6354f2c Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Mon, 16 Mar 2020 09:06:23 -0700 Subject: Export of internal Abseil changes -- 91ca367a7548270155721bdda74611aeea2a2153 by Abseil Team : Replace the only usage of btree_node::swap with simpler logic using transfers and delete btree_node::swap. Add a benchmark for constructing small containers. PiperOrigin-RevId: 301169874 -- ff9d73a7125b7f8ab5733cda877204dfbfac138e by Derek Mauro : Ensure ABSL_CXX_STANDARD is set. Fixes #640 PiperOrigin-RevId: 301160106 -- 14ca0beee8c109e532134e7e9da7b072da1bf911 by Abseil Team : Rollback the change to make Cord iterators a fixed size. That change increased the iterator size, which can cause a deep recursion call to hit the stack memory limit, in turn causing a signal 11 failure. PiperOrigin-RevId: 301084915 -- 619e3cd9e56408bdb8b3b5a1e08dda1e95242264 by Matthew Brown : Internal Change PiperOrigin-RevId: 300832828 -- 64f8d62ab4c4c78077dbe85a9595a8eeb6d16608 by Gennadiy Rozental : Fix for empty braces support. We will call proper aggregate construction in case when {} is used as default value. In other words instead of "new T", we'll call "new T{}". PiperOrigin-RevId: 300715686 -- db3f65594d6db8b104b01262f884dff465b696ef by Abseil Team : Emscripten supports thread-local storage nowadays. PiperOrigin-RevId: 300675185 GitOrigin-RevId: 91ca367a7548270155721bdda74611aeea2a2153 Change-Id: I3344f745f9c3fc78775532b1808442fabd98e34a --- absl/base/config.h | 7 - absl/container/btree_benchmark.cc | 22 +++ absl/container/internal/btree.h | 76 +++----- absl/copts/AbseilConfigureCopts.cmake | 2 + absl/flags/flag_test.cc | 67 +++++++ absl/flags/internal/flag.h | 2 +- absl/strings/cord.cc | 249 ++++++++++++++------------- absl/strings/cord.h | 53 +----- absl/strings/cord_test.cc | 47 ----- absl/strings/internal/str_format/extension.h | 16 +- absl/strings/str_format.h | 2 +- ci/macos_xcode_cmake.sh | 2 +- 12 files changed, 258 insertions(+), 287 deletions(-) (limited to 'absl/strings/cord.h') diff --git a/absl/base/config.h b/absl/base/config.h index ee99f946..f54466de 100644 --- a/absl/base/config.h +++ b/absl/base/config.h @@ -262,13 +262,6 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || #endif #endif // defined(__ANDROID__) && defined(__clang__) -// Emscripten doesn't yet support `thread_local` or `__thread`. -// https://github.com/emscripten-core/emscripten/issues/3502 -#if defined(__EMSCRIPTEN__) -#undef ABSL_HAVE_TLS -#undef ABSL_HAVE_THREAD_LOCAL -#endif // defined(__EMSCRIPTEN__) - // ABSL_HAVE_INTRINSIC_INT128 // // Checks whether the __int128 compiler extension for a 128-bit integral type is diff --git a/absl/container/btree_benchmark.cc b/absl/container/btree_benchmark.cc index ca4d575c..46798676 100644 --- a/absl/container/btree_benchmark.cc +++ b/absl/container/btree_benchmark.cc @@ -134,6 +134,27 @@ void BM_InsertEnd(benchmark::State& state) { } } +// Benchmark inserting the first few elements in a container. In b-tree, this is +// when the root node grows. +template +void BM_InsertSmall(benchmark::State& state) { + using V = typename remove_pair_const::type; + + const int kSize = 8; + std::vector values = GenerateValues(kSize); + T container; + + while (state.KeepRunningBatch(kSize)) { + for (int i = 0; i < kSize; ++i) { + benchmark::DoNotOptimize(container.insert(values[i])); + } + state.PauseTiming(); + // Do not measure the time it takes to clear the container. + container.clear(); + state.ResumeTiming(); + } +} + template void BM_LookupImpl(benchmark::State& state, bool sorted) { using V = typename remove_pair_const::type; @@ -493,6 +514,7 @@ BTREE_TYPES(Time); MY_BENCHMARK4(type, Insert); \ MY_BENCHMARK4(type, InsertSorted); \ MY_BENCHMARK4(type, InsertEnd); \ + MY_BENCHMARK4(type, InsertSmall); \ MY_BENCHMARK4(type, Lookup); \ MY_BENCHMARK4(type, FullLookup); \ MY_BENCHMARK4(type, Delete); \ diff --git a/absl/container/internal/btree.h b/absl/container/internal/btree.h index d986f81e..adf49f81 100644 --- a/absl/container/internal/btree.h +++ b/absl/container/internal/btree.h @@ -776,9 +776,6 @@ class btree_node { // delimiting key in the parent node onto itself. void merge(btree_node *src, allocator_type *alloc); - // Swaps the contents of `this` and `other`. - void swap(btree_node *other, allocator_type *alloc); - // Node allocation/deletion routines. void init_leaf(btree_node *parent, int max_count) { set_parent(parent); @@ -820,6 +817,14 @@ class btree_node { absl::container_internal::SanitizerPoisonObject(slot(i)); } + // Transfers value from slot `src_i` in `src` to slot `dest_i` in `this`. + void transfer(const size_type dest_i, const size_type src_i, btree_node *src, + allocator_type *alloc) { + absl::container_internal::SanitizerUnpoisonObject(slot(dest_i)); + params_type::transfer(alloc, slot(dest_i), src->slot(src_i)); + absl::container_internal::SanitizerPoisonObject(src->slot(src_i)); + } + // Move n values starting at value i in this node into the values starting at // value j in dest_node. void uninitialized_move_n(const size_type n, const size_type i, @@ -1752,54 +1757,6 @@ void btree_node

::merge(btree_node *src, allocator_type *alloc) { parent()->remove_value(position(), alloc); } -template -void btree_node

::swap(btree_node *other, allocator_type *alloc) { - using std::swap; - assert(leaf() == other->leaf()); - - // Determine which is the smaller/larger node. - btree_node *smaller = this, *larger = other; - if (smaller->count() > larger->count()) { - swap(smaller, larger); - } - - // Swap the values. - for (slot_type *a = smaller->start_slot(), *b = larger->start_slot(), - *end = smaller->finish_slot(); - a != end; ++a, ++b) { - params_type::swap(alloc, a, b); - } - - // Move values that can't be swapped. - const size_type to_move = larger->count() - smaller->count(); - larger->uninitialized_move_n(to_move, smaller->finish(), smaller->finish(), - smaller, alloc); - larger->value_destroy_n(smaller->finish(), to_move, alloc); - - if (!leaf()) { - // Swap the child pointers. - std::swap_ranges(&smaller->mutable_child(smaller->start()), - &smaller->mutable_child(smaller->finish() + 1), - &larger->mutable_child(larger->start())); - // Update swapped children's parent pointers. - int i = smaller->start(); - int j = larger->start(); - for (; i <= smaller->finish(); ++i, ++j) { - smaller->child(i)->set_parent(smaller); - larger->child(j)->set_parent(larger); - } - // Move the child pointers that couldn't be swapped. - for (; j <= larger->finish(); ++i, ++j) { - smaller->init_child(i, larger->child(j)); - larger->clear_child(j); - } - } - - // Swap the `finish`s. - // TODO(ezb): with floating storage, will also need to swap starts. - swap(mutable_finish(), other->mutable_finish()); -} - //// // btree_iterator methods template @@ -2492,6 +2449,7 @@ inline auto btree

::internal_emplace(iterator iter, Args &&... args) ++iter.position; } const int max_count = iter.node->max_count(); + allocator_type *alloc = mutable_allocator(); if (iter.node->count() == max_count) { // Make room in the leaf for the new item. if (max_count < kNodeValues) { @@ -2500,15 +2458,21 @@ inline auto btree

::internal_emplace(iterator iter, Args &&... args) assert(iter.node == root()); iter.node = new_leaf_root_node((std::min)(kNodeValues, 2 * max_count)); - iter.node->swap(root(), mutable_allocator()); - delete_leaf_node(root()); - mutable_root() = rightmost_ = iter.node; + // Transfer the values from the old root to the new root. + node_type *old_root = root(); + node_type *new_root = iter.node; + for (int i = old_root->start(), f = old_root->finish(); i < f; ++i) { + new_root->transfer(i, i, old_root, alloc); + } + new_root->set_finish(old_root->finish()); + old_root->set_finish(old_root->start()); + delete_leaf_node(old_root); + mutable_root() = rightmost_ = new_root; } else { rebalance_or_split(&iter); } } - iter.node->emplace_value(iter.position, mutable_allocator(), - std::forward(args)...); + iter.node->emplace_value(iter.position, alloc, std::forward(args)...); ++size_; return iter; } diff --git a/absl/copts/AbseilConfigureCopts.cmake b/absl/copts/AbseilConfigureCopts.cmake index 390a07a0..9557e36f 100644 --- a/absl/copts/AbseilConfigureCopts.cmake +++ b/absl/copts/AbseilConfigureCopts.cmake @@ -63,3 +63,5 @@ else() set(ABSL_DEFAULT_COPTS "") set(ABSL_TEST_COPTS "") endif() + +set(ABSL_CXX_STANDARD "${CMAKE_CXX_STANDARD}") diff --git a/absl/flags/flag_test.cc b/absl/flags/flag_test.cc index 1e01b49c..3a025576 100644 --- a/absl/flags/flag_test.cc +++ b/absl/flags/flag_test.cc @@ -293,6 +293,18 @@ TEST_F(FlagTest, TestFlagDefinition) { // -------------------------------------------------------------------- TEST_F(FlagTest, TestDefault) { + EXPECT_EQ(FLAGS_test_flag_01.DefaultValue(), "true"); + EXPECT_EQ(FLAGS_test_flag_02.DefaultValue(), "1234"); + EXPECT_EQ(FLAGS_test_flag_03.DefaultValue(), "-34"); + EXPECT_EQ(FLAGS_test_flag_04.DefaultValue(), "189"); + EXPECT_EQ(FLAGS_test_flag_05.DefaultValue(), "10765"); + EXPECT_EQ(FLAGS_test_flag_06.DefaultValue(), "40000"); + EXPECT_EQ(FLAGS_test_flag_07.DefaultValue(), "-1234567"); + EXPECT_EQ(FLAGS_test_flag_08.DefaultValue(), "9876543"); + EXPECT_EQ(FLAGS_test_flag_09.DefaultValue(), "-9.876e-50"); + EXPECT_EQ(FLAGS_test_flag_10.DefaultValue(), "1.234e+12"); + EXPECT_EQ(FLAGS_test_flag_11.DefaultValue(), ""); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_01), true); EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_02), 1234); EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_03), -34); @@ -308,6 +320,61 @@ TEST_F(FlagTest, TestDefault) { // -------------------------------------------------------------------- +struct NonTriviallyCopyableAggregate { + NonTriviallyCopyableAggregate() = default; + NonTriviallyCopyableAggregate(const NonTriviallyCopyableAggregate& rhs) + : value(rhs.value) {} + NonTriviallyCopyableAggregate& operator=( + const NonTriviallyCopyableAggregate& rhs) { + value = rhs.value; + return *this; + } + + int value; +}; +bool AbslParseFlag(absl::string_view src, NonTriviallyCopyableAggregate* f, + std::string* e) { + return absl::ParseFlag(src, &f->value, e); +} +std::string AbslUnparseFlag(const NonTriviallyCopyableAggregate& ntc) { + return absl::StrCat(ntc.value); +} + +bool operator==(const NonTriviallyCopyableAggregate& ntc1, + const NonTriviallyCopyableAggregate& ntc2) { + return ntc1.value == ntc2.value; +} + +} // namespace + +ABSL_FLAG(bool, test_flag_eb_01, {}, ""); +ABSL_FLAG(int32_t, test_flag_eb_02, {}, ""); +ABSL_FLAG(int64_t, test_flag_eb_03, {}, ""); +ABSL_FLAG(double, test_flag_eb_04, {}, ""); +ABSL_FLAG(std::string, test_flag_eb_05, {}, ""); +ABSL_FLAG(NonTriviallyCopyableAggregate, test_flag_eb_06, {}, ""); + +namespace { + +TEST_F(FlagTest, TestEmptyBracesDefault) { + EXPECT_EQ(FLAGS_test_flag_eb_01.DefaultValue(), "false"); + EXPECT_EQ(FLAGS_test_flag_eb_02.DefaultValue(), "0"); + EXPECT_EQ(FLAGS_test_flag_eb_03.DefaultValue(), "0"); + EXPECT_EQ(FLAGS_test_flag_eb_04.DefaultValue(), "0"); + EXPECT_EQ(FLAGS_test_flag_eb_05.DefaultValue(), ""); + EXPECT_EQ(FLAGS_test_flag_eb_06.DefaultValue(), "0"); + + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_eb_01), false); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_eb_02), 0); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_eb_03), 0); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_eb_04), 0.0); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_eb_05), ""); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_eb_06), + NonTriviallyCopyableAggregate{}); +} + +// -------------------------------------------------------------------- + TEST_F(FlagTest, TestGetSet) { absl::SetFlag(&FLAGS_test_flag_01, false); EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_01), false); diff --git a/absl/flags/internal/flag.h b/absl/flags/internal/flag.h index 344e31f6..0ef0ee74 100644 --- a/absl/flags/internal/flag.h +++ b/absl/flags/internal/flag.h @@ -647,7 +647,7 @@ T* MakeFromDefaultValue(T t) { template T* MakeFromDefaultValue(EmptyBraces) { - return new T; + return new T{}; } } // namespace flags_internal diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index 415b239b..4f64f799 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc @@ -30,6 +30,7 @@ #include "absl/base/internal/raw_logging.h" #include "absl/base/port.h" #include "absl/container/fixed_array.h" +#include "absl/container/inlined_vector.h" #include "absl/strings/escaping.h" #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/resize_uninitialized.h" @@ -131,14 +132,6 @@ inline const CordRepExternal* CordRep::external() const { return static_cast(this); } -using CordTreeConstPath = CordTreePath; - -// This type is used to store the list of pending nodes during re-balancing. -// Its maximum size is 2 * MaxCordDepth() because the tree has a maximum -// possible depth of MaxCordDepth() and every concat node along a tree path -// could theoretically be split during rebalancing. -using RebalancingStack = CordTreePath; - } // namespace cord_internal static const size_t kFlatOverhead = offsetof(CordRep, data); @@ -187,78 +180,98 @@ static constexpr size_t TagToLength(uint8_t tag) { // Enforce that kMaxFlatSize maps to a well-known exact tag value. static_assert(TagToAllocatedSize(224) == kMaxFlatSize, "Bad tag logic"); -constexpr size_t Fibonacci(uint8_t n, const size_t a = 0, const size_t b = 1) { - return n == 0 - ? a - : n == 1 ? b - : Fibonacci(n - 1, b, - (a > (size_t(-1) - b)) ? size_t(-1) : a + b); +constexpr uint64_t Fibonacci(unsigned char n, uint64_t a = 0, uint64_t b = 1) { + return n == 0 ? a : Fibonacci(n - 1, b, a + b); } +static_assert(Fibonacci(63) == 6557470319842, + "Fibonacci values computed incorrectly"); + // Minimum length required for a given depth tree -- a tree is considered // balanced if -// length(t) >= kMinLength[depth(t)] -// The node depth is allowed to become larger to reduce rebalancing -// for larger strings (see ShouldRebalance). -constexpr size_t kMinLength[] = { - Fibonacci(2), Fibonacci(3), Fibonacci(4), Fibonacci(5), Fibonacci(6), - Fibonacci(7), Fibonacci(8), Fibonacci(9), Fibonacci(10), Fibonacci(11), - Fibonacci(12), Fibonacci(13), Fibonacci(14), Fibonacci(15), Fibonacci(16), - Fibonacci(17), Fibonacci(18), Fibonacci(19), Fibonacci(20), Fibonacci(21), - Fibonacci(22), Fibonacci(23), Fibonacci(24), Fibonacci(25), Fibonacci(26), - Fibonacci(27), Fibonacci(28), Fibonacci(29), Fibonacci(30), Fibonacci(31), - Fibonacci(32), Fibonacci(33), Fibonacci(34), Fibonacci(35), Fibonacci(36), - Fibonacci(37), Fibonacci(38), Fibonacci(39), Fibonacci(40), Fibonacci(41), - Fibonacci(42), Fibonacci(43), Fibonacci(44), Fibonacci(45), Fibonacci(46), - Fibonacci(47), Fibonacci(48), Fibonacci(49), Fibonacci(50), Fibonacci(51), - Fibonacci(52), Fibonacci(53), Fibonacci(54), Fibonacci(55), Fibonacci(56), - Fibonacci(57), Fibonacci(58), Fibonacci(59), Fibonacci(60), Fibonacci(61), - Fibonacci(62), Fibonacci(63), Fibonacci(64), Fibonacci(65), Fibonacci(66), - Fibonacci(67), Fibonacci(68), Fibonacci(69), Fibonacci(70), Fibonacci(71), - Fibonacci(72), Fibonacci(73), Fibonacci(74), Fibonacci(75), Fibonacci(76), - Fibonacci(77), Fibonacci(78), Fibonacci(79), Fibonacci(80), Fibonacci(81), - Fibonacci(82), Fibonacci(83), Fibonacci(84), Fibonacci(85), Fibonacci(86), - Fibonacci(87), Fibonacci(88), Fibonacci(89), Fibonacci(90), Fibonacci(91), - Fibonacci(92), Fibonacci(93), Fibonacci(94), Fibonacci(95)}; - -static_assert(sizeof(kMinLength) / sizeof(size_t) >= - (cord_internal::MaxCordDepth() + 1), - "Not enough elements in kMinLength array to cover all the " - "supported Cord depth(s)"); - -inline bool ShouldRebalance(const CordRep* node) { - if (node->tag != CONCAT) return false; - - size_t node_depth = node->concat()->depth(); - - if (node_depth <= 15) return false; - - // Rebalancing Cords is expensive, so we reduce how often rebalancing occurs - // by allowing shallow Cords to have twice the depth that the Fibonacci rule - // would otherwise imply. Deep Cords need to follow the rule more closely, - // however to ensure algorithm correctness. We implement this with linear - // interpolation. Cords of depth 16 are treated as though they have a depth - // of 16 * 1/2, and Cords of depth MaxCordDepth() interpolate to - // MaxCordDepth() * 1. - return node->length < - kMinLength[(node_depth * (cord_internal::MaxCordDepth() - 16)) / - (2 * cord_internal::MaxCordDepth() - 16 - node_depth)]; -} - -// Unlike root balancing condition this one is part of the re-balancing -// algorithm and has to be always matching against right depth for -// algorithm to be correct. -inline bool IsNodeBalanced(const CordRep* node) { - if (node->tag != CONCAT) return true; - - size_t node_depth = node->concat()->depth(); - - return node->length >= kMinLength[node_depth]; +// length(t) >= min_length[depth(t)] +// The root node depth is allowed to become twice as large to reduce rebalancing +// for larger strings (see IsRootBalanced). +static constexpr uint64_t min_length[] = { + Fibonacci(2), + Fibonacci(3), + Fibonacci(4), + Fibonacci(5), + Fibonacci(6), + Fibonacci(7), + Fibonacci(8), + Fibonacci(9), + Fibonacci(10), + Fibonacci(11), + Fibonacci(12), + Fibonacci(13), + Fibonacci(14), + Fibonacci(15), + Fibonacci(16), + Fibonacci(17), + Fibonacci(18), + Fibonacci(19), + Fibonacci(20), + Fibonacci(21), + Fibonacci(22), + Fibonacci(23), + Fibonacci(24), + Fibonacci(25), + Fibonacci(26), + Fibonacci(27), + Fibonacci(28), + Fibonacci(29), + Fibonacci(30), + Fibonacci(31), + Fibonacci(32), + Fibonacci(33), + Fibonacci(34), + Fibonacci(35), + Fibonacci(36), + Fibonacci(37), + Fibonacci(38), + Fibonacci(39), + Fibonacci(40), + Fibonacci(41), + Fibonacci(42), + Fibonacci(43), + Fibonacci(44), + Fibonacci(45), + Fibonacci(46), + Fibonacci(47), + 0xffffffffffffffffull, // Avoid overflow +}; + +static const int kMinLengthSize = ABSL_ARRAYSIZE(min_length); + +// The inlined size to use with absl::InlinedVector. +// +// Note: The InlinedVectors in this file (and in cord.h) do not need to use +// the same value for their inlined size. The fact that they do is historical. +// It may be desirable for each to use a different inlined size optimized for +// that InlinedVector's usage. +// +// TODO(jgm): Benchmark to see if there's a more optimal value than 47 for +// the inlined vector size (47 exists for backward compatibility). +static const int kInlinedVectorSize = 47; + +static inline bool IsRootBalanced(CordRep* node) { + if (node->tag != CONCAT) { + return true; + } else if (node->concat()->depth() <= 15) { + return true; + } else if (node->concat()->depth() > kMinLengthSize) { + return false; + } else { + // Allow depth to become twice as large as implied by fibonacci rule to + // reduce rebalancing for larger strings. + return (node->length >= min_length[node->concat()->depth() / 2]); + } } static CordRep* Rebalance(CordRep* node); -static void DumpNode(const CordRep* rep, bool include_data, std::ostream* os); -static bool VerifyNode(const CordRep* root, const CordRep* start_node, +static void DumpNode(CordRep* rep, bool include_data, std::ostream* os); +static bool VerifyNode(CordRep* root, CordRep* start_node, bool full_validation); static inline CordRep* VerifyTree(CordRep* node) { @@ -305,8 +318,7 @@ __attribute__((preserve_most)) static void UnrefInternal(CordRep* rep) { assert(rep != nullptr); - cord_internal::RebalancingStack pending; - + absl::InlinedVector pending; while (true) { if (rep->tag == CONCAT) { CordRepConcat* rep_concat = rep->concat(); @@ -388,11 +400,6 @@ static void SetConcatChildren(CordRepConcat* concat, CordRep* left, concat->length = left->length + right->length; concat->set_depth(1 + std::max(Depth(left), Depth(right))); - - ABSL_INTERNAL_CHECK(concat->depth() <= cord_internal::MaxCordDepth(), - "Cord depth exceeds max"); - ABSL_INTERNAL_CHECK(concat->length >= left->length, "Cord is too long"); - ABSL_INTERNAL_CHECK(concat->length >= right->length, "Cord is too long"); } // Create a concatenation of the specified nodes. @@ -418,7 +425,7 @@ static CordRep* RawConcat(CordRep* left, CordRep* right) { static CordRep* Concat(CordRep* left, CordRep* right) { CordRep* rep = RawConcat(left, right); - if (rep != nullptr && ShouldRebalance(rep)) { + if (rep != nullptr && !IsRootBalanced(rep)) { rep = Rebalance(rep); } return VerifyTree(rep); @@ -909,7 +916,7 @@ void Cord::Prepend(absl::string_view src) { static CordRep* RemovePrefixFrom(CordRep* node, size_t n) { if (n >= node->length) return nullptr; if (n == 0) return Ref(node); - cord_internal::CordTreeMutablePath rhs_stack; + absl::InlinedVector rhs_stack; while (node->tag == CONCAT) { assert(n <= node->length); @@ -950,7 +957,7 @@ static CordRep* RemovePrefixFrom(CordRep* node, size_t n) { static CordRep* RemoveSuffixFrom(CordRep* node, size_t n) { if (n >= node->length) return nullptr; if (n == 0) return Ref(node); - absl::cord_internal::CordTreeMutablePath lhs_stack; + absl::InlinedVector lhs_stack; bool inplace_ok = node->refcount.IsOne(); while (node->tag == CONCAT) { @@ -1021,7 +1028,6 @@ void Cord::RemoveSuffix(size_t n) { // Work item for NewSubRange(). struct SubRange { - SubRange() = default; SubRange(CordRep* a_node, size_t a_pos, size_t a_n) : node(a_node), pos(a_pos), n(a_n) {} CordRep* node; // nullptr means concat last 2 results. @@ -1030,11 +1036,8 @@ struct SubRange { }; static CordRep* NewSubRange(CordRep* node, size_t pos, size_t n) { - cord_internal::CordTreeMutablePath results; - // The algorithm below in worst case scenario adds up to 3 nodes to the `todo` - // list, but we also pop one out on every cycle. If original tree has depth d - // todo list can grew up to 2*d in size. - cord_internal::CordTreePath todo; + absl::InlinedVector results; + absl::InlinedVector todo; todo.push_back(SubRange(node, pos, n)); do { const SubRange& sr = todo.back(); @@ -1071,7 +1074,7 @@ static CordRep* NewSubRange(CordRep* node, size_t pos, size_t n) { } } while (!todo.empty()); assert(results.size() == 1); - return results.back(); + return results[0]; } Cord Cord::Subcord(size_t pos, size_t new_size) const { @@ -1110,12 +1113,11 @@ Cord Cord::Subcord(size_t pos, size_t new_size) const { class CordForest { public: - explicit CordForest(size_t length) : root_length_(length), trees_({}) {} + explicit CordForest(size_t length) + : root_length_(length), trees_(kMinLengthSize, nullptr) {} void Build(CordRep* cord_root) { - // We are adding up to two nodes to the `pending` list, but we also popping - // one, so the size of `pending` will never exceed `MaxCordDepth()`. - cord_internal::CordTreeMutablePath pending(cord_root); + std::vector pending = {cord_root}; while (!pending.empty()) { CordRep* node = pending.back(); @@ -1127,20 +1129,21 @@ class CordForest { } CordRepConcat* concat_node = node->concat(); - if (IsNodeBalanced(concat_node)) { - AddNode(node); - continue; - } - pending.push_back(concat_node->right); - pending.push_back(concat_node->left); - - if (concat_node->refcount.IsOne()) { - concat_node->left = concat_freelist_; - concat_freelist_ = concat_node; + if (concat_node->depth() >= kMinLengthSize || + concat_node->length < min_length[concat_node->depth()]) { + pending.push_back(concat_node->right); + pending.push_back(concat_node->left); + + if (concat_node->refcount.IsOne()) { + concat_node->left = concat_freelist_; + concat_freelist_ = concat_node; + } else { + Ref(concat_node->right); + Ref(concat_node->left); + Unref(concat_node); + } } else { - Ref(concat_node->right); - Ref(concat_node->left); - Unref(concat_node); + AddNode(node); } } } @@ -1172,7 +1175,7 @@ class CordForest { // Collect together everything with which we will merge with node int i = 0; - for (; node->length >= kMinLength[i + 1]; ++i) { + for (; node->length > min_length[i + 1]; ++i) { auto& tree_at_i = trees_[i]; if (tree_at_i == nullptr) continue; @@ -1183,7 +1186,7 @@ class CordForest { sum = AppendNode(node, sum); // Insert sum into appropriate place in the forest - for (; sum->length >= kMinLength[i]; ++i) { + for (; sum->length >= min_length[i]; ++i) { auto& tree_at_i = trees_[i]; if (tree_at_i == nullptr) continue; @@ -1191,7 +1194,7 @@ class CordForest { tree_at_i = nullptr; } - // kMinLength[0] == 1, which means sum->length >= kMinLength[0] + // min_length[0] == 1, which means sum->length >= min_length[0] assert(i > 0); trees_[i - 1] = sum; } @@ -1224,7 +1227,9 @@ class CordForest { } size_t root_length_; - std::array trees_; + + // use an inlined vector instead of a flat array to get bounds checking + absl::InlinedVector trees_; // List of concat nodes we can re-use for Cord balancing. CordRepConcat* concat_freelist_ = nullptr; @@ -1836,18 +1841,18 @@ absl::string_view Cord::FlattenSlowPath() { } } -static void DumpNode(const CordRep* rep, bool include_data, std::ostream* os) { +static void DumpNode(CordRep* rep, bool include_data, std::ostream* os) { const int kIndentStep = 1; int indent = 0; - cord_internal::CordTreeConstPath stack; - cord_internal::CordTreePath indents; + absl::InlinedVector stack; + absl::InlinedVector indents; for (;;) { *os << std::setw(3) << rep->refcount.Get(); *os << " " << std::setw(7) << rep->length; *os << " ["; - if (include_data) *os << static_cast(rep); + if (include_data) *os << static_cast(rep); *os << "]"; - *os << " " << (IsNodeBalanced(rep) ? 'b' : 'u'); + *os << " " << (IsRootBalanced(rep) ? 'b' : 'u'); *os << " " << std::setw(indent) << ""; if (rep->tag == CONCAT) { *os << "CONCAT depth=" << Depth(rep) << "\n"; @@ -1868,7 +1873,7 @@ static void DumpNode(const CordRep* rep, bool include_data, std::ostream* os) { } else { *os << "FLAT cap=" << TagToLength(rep->tag) << " ["; if (include_data) - *os << absl::CEscape(absl::string_view(rep->data, rep->length)); + *os << absl::CEscape(std::string(rep->data, rep->length)); *os << "]\n"; } if (stack.empty()) break; @@ -1881,19 +1886,19 @@ static void DumpNode(const CordRep* rep, bool include_data, std::ostream* os) { ABSL_INTERNAL_CHECK(indents.empty(), ""); } -static std::string ReportError(const CordRep* root, const CordRep* node) { +static std::string ReportError(CordRep* root, CordRep* node) { std::ostringstream buf; buf << "Error at node " << node << " in:"; DumpNode(root, true, &buf); return buf.str(); } -static bool VerifyNode(const CordRep* root, const CordRep* start_node, +static bool VerifyNode(CordRep* root, CordRep* start_node, bool full_validation) { - cord_internal::CordTreeConstPath worklist; + absl::InlinedVector worklist; worklist.push_back(start_node); do { - const CordRep* node = worklist.back(); + CordRep* node = worklist.back(); worklist.pop_back(); ABSL_INTERNAL_CHECK(node != nullptr, ReportError(root, node)); @@ -1943,7 +1948,7 @@ static bool VerifyNode(const CordRep* root, const CordRep* start_node, // Iterate over the tree. cur_node is never a leaf node and leaf nodes will // never be appended to tree_stack. This reduces overhead from manipulating // tree_stack. - cord_internal::CordTreeConstPath tree_stack; + absl::InlinedVector tree_stack; const CordRep* cur_node = rep; while (true) { const CordRep* next_node = nullptr; diff --git a/absl/strings/cord.h b/absl/strings/cord.h index eb236e50..66645eef 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -48,6 +48,7 @@ #include "absl/base/internal/per_thread_tls.h" #include "absl/base/macros.h" #include "absl/base/port.h" +#include "absl/container/inlined_vector.h" #include "absl/functional/function_ref.h" #include "absl/meta/type_traits.h" #include "absl/strings/internal/cord_internal.h" @@ -67,55 +68,6 @@ template H HashFragmentedCord(H, const Cord&); } -namespace cord_internal { - -// It's expensive to keep a tree perfectly balanced, so instead we keep trees -// approximately balanced. A tree node N of depth D(N) that contains a string -// of L(N) characters is considered balanced if L >= Fibonacci(D + 2). -// The "+ 2" is used to ensure that every balanced leaf node contains at least -// one character. Here we presume that -// Fibonacci(0) = 0 -// Fibonacci(1) = 1 -// Fibonacci(2) = 1 -// Fibonacci(3) = 2 -// ... -// The algorithm is based on paper by Hans Boehm et al: -// https://www.cs.rit.edu/usr/local/pub/jeh/courses/QUARTERS/FP/Labs/CedarRope/rope-paper.pdf -// In this paper authors shows that rebalancing based on cord forest of already -// balanced subtrees can be proven to never produce tree of depth larger than -// largest Fibonacci number representable in the same integral type as cord size -// For 64 bit integers this is the 93rd Fibonacci number. For 32 bit integrals -// this is 47th Fibonacci number. -constexpr size_t MaxCordDepth() { return sizeof(size_t) == 8 ? 93 : 47; } - -// This class models fixed max size stack of CordRep pointers. -// The elements are being pushed back and popped from the back. -template -class CordTreePath { - public: - CordTreePath() {} - explicit CordTreePath(CordRepPtr root) { push_back(root); } - - bool empty() const { return size_ == 0; } - size_t size() const { return size_; } - void clear() { size_ = 0; } - - CordRepPtr back() { return data_[size_ - 1]; } - - void pop_back() { - --size_; - assert(size_ < N); - } - void push_back(CordRepPtr elem) { data_[size_++] = elem; } - - private: - CordRepPtr data_[N]; - size_t size_ = 0; -}; - -using CordTreeMutablePath = CordTreePath; -} // namespace cord_internal - // A Cord is a sequence of characters. class Cord { private: @@ -333,7 +285,8 @@ class Cord { absl::cord_internal::CordRep* current_leaf_ = nullptr; // The number of bytes left in the `Cord` over which we are iterating. size_t bytes_remaining_ = 0; - absl::cord_internal::CordTreeMutablePath stack_of_right_children_; + absl::InlinedVector + stack_of_right_children_; }; // Returns an iterator to the first chunk of the `Cord`. diff --git a/absl/strings/cord_test.cc b/absl/strings/cord_test.cc index f2d81d4c..4afa4a26 100644 --- a/absl/strings/cord_test.cc +++ b/absl/strings/cord_test.cc @@ -1402,53 +1402,6 @@ TEST(CordChunkIterator, Operations) { VerifyChunkIterator(subcords, 128); } -TEST(CordChunkIterator, MaxLengthFullTree) { - // Start with a 1-byte cord, and then double its length in a loop. We should - // be able to do this until the point where we would overflow size_t. - - absl::Cord cord; - size_t size = 1; - AddExternalMemory("x", &cord); - EXPECT_EQ(cord.size(), size); - - const int kCordLengthDoublingLimit = std::numeric_limits::digits - 1; - for (int i = 0; i < kCordLengthDoublingLimit; ++i) { - cord.Prepend(absl::Cord(cord)); - size <<= 1; - - EXPECT_EQ(cord.size(), size); - - auto chunk_it = cord.chunk_begin(); - EXPECT_EQ(*chunk_it, "x"); - } - - EXPECT_DEATH_IF_SUPPORTED( - (cord.Prepend(absl::Cord(cord)), *cord.chunk_begin()), - "Cord is too long"); -} - -TEST(CordChunkIterator, MaxDepth) { - // By reusing nodes, it's possible in pathological cases to build a Cord that - // exceeds both the maximum permissible length and depth. In this case, the - // violation of the maximum depth is reported. - absl::Cord left_child; - AddExternalMemory("x", &left_child); - absl::Cord root = left_child; - - for (int i = 0; i < absl::cord_internal::MaxCordDepth() - 2; ++i) { - size_t new_size = left_child.size() + root.size(); - root.Prepend(left_child); - EXPECT_EQ(root.size(), new_size); - - auto chunk_it = root.chunk_begin(); - EXPECT_EQ(*chunk_it, "x"); - - std::swap(left_child, root); - } - - EXPECT_DEATH_IF_SUPPORTED(root.Prepend(left_child), "Cord is too long"); -} - TEST(CordCharIterator, Traits) { static_assert(std::is_copy_constructible::value, ""); diff --git a/absl/strings/internal/str_format/extension.h b/absl/strings/internal/str_format/extension.h index d1665753..968850eb 100644 --- a/absl/strings/internal/str_format/extension.h +++ b/absl/strings/internal/str_format/extension.h @@ -24,6 +24,7 @@ #include "absl/base/config.h" #include "absl/base/port.h" +#include "absl/meta/type_traits.h" #include "absl/strings/internal/str_format/output.h" #include "absl/strings/string_view.h" @@ -365,11 +366,22 @@ constexpr FormatConversionCharSet operator|(FormatConversionCharSet a, static_cast(b)); } +// Overloaded conversion functions to support absl::ParsedFormat. // Get a conversion with a single character in it. -constexpr FormatConversionCharSet ConversionCharToConv(char c) { - return FormatConversionCharSet(FormatConversionCharToConvValue(c)); +constexpr FormatConversionCharSet ToFormatConversionCharSet(char c) { + return static_cast( + FormatConversionCharToConvValue(c)); } +// Get a conversion with a single character in it. +constexpr FormatConversionCharSet ToFormatConversionCharSet( + FormatConversionCharSet c) { + return c; +} + +template +void ToFormatConversionCharSet(T) = delete; + // Checks whether `c` exists in `set`. constexpr bool Contains(FormatConversionCharSet set, char c) { return (static_cast(set) & FormatConversionCharToConvValue(c)) != 0; diff --git a/absl/strings/str_format.h b/absl/strings/str_format.h index 2f9b4b27..d40fca11 100644 --- a/absl/strings/str_format.h +++ b/absl/strings/str_format.h @@ -285,7 +285,7 @@ using FormatSpec = // } template using ParsedFormat = str_format_internal::ExtendedParsedFormat< - str_format_internal::ConversionCharToConv(Conv)...>; + absl::str_format_internal::ToFormatConversionCharSet(Conv)...>; // StrFormat() // diff --git a/ci/macos_xcode_cmake.sh b/ci/macos_xcode_cmake.sh index aa9ee15d..a1f4a857 100755 --- a/ci/macos_xcode_cmake.sh +++ b/ci/macos_xcode_cmake.sh @@ -36,7 +36,7 @@ for compilation_mode in ${ABSL_CMAKE_BUILD_TYPES}; do time cmake ${ABSEIL_ROOT} \ -GXcode \ -DCMAKE_BUILD_TYPE=${compilation_mode} \ - -DCMAKE_CXX_FLAGS=-std=c++14 \ + -DCMAKE_CXX_STANDARD=11 \ -DABSL_USE_GOOGLETEST_HEAD=ON \ -DABSL_RUN_TESTS=ON time cmake --build . -- cgit v1.2.3 From fba8a316c30690097de5d6127ad307d84a1b74ca Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Tue, 31 Mar 2020 12:32:35 -0700 Subject: Export of internal Abseil changes -- 2dd5008c7b4176859e320c7c337078adb173b662 by Tom Manshreck : Internal change PiperOrigin-RevId: 304022549 -- 6442abd78697b03cfe698b0d0dac7f1eb4b5cb38 by Andy Getzendanner : Internal change PiperOrigin-RevId: 303890410 -- eb8b37b468b0f23da09d3de714272928ef61f942 by Gennadiy Rozental : Roll changes forward with ChunkIterator templatized. This should facilitate usage of "small" chunk iterator for a regular usage and proper "big" iterator internally in Cord implementation. This way Cord users are not exposed to stack size overhead if they have a lot of chunk iterators or recursive implementation which relies on chunk iterators. PiperOrigin-RevId: 303877118 -- 9623c569e7c55b45254e95f2d14c5badf9c901aa by Gennadiy Rozental : Switch Flags implementation of fast type id to use absl/base/internal/fast_type_id.h PiperOrigin-RevId: 303861019 -- e2931e8d53c86d0816da6bbc8ba58cf5a3a443bb by Matthew Brown : Internal Change PiperOrigin-RevId: 303832407 -- b549ed6e441e920b8ad6f02a80b9fd543820ef86 by Tom Manshreck : Update Cord header file comments to Abseil standards PiperOrigin-RevId: 303823232 -- fc633d4f31a2d058f2b6a7029fc7c9820cd71c92 by Evan Brown : Remove top-level const from K/V in map_slot_type::mutable_value and map_slot_type::key. This allows us to move between `map_slot_type::mutable_value`s internally even when the key_type and/or mapped_type specified by the user are const. PiperOrigin-RevId: 303811694 -- 909b3ce7cb3583ee9c374d36ff5f82bba02a1b64 by Derek Mauro : Add hardening assertions to the preconditions of absl::Cord PiperOrigin-RevId: 303419537 -- 9d32f79eabd54e6cb17bcc28b53e9bcfeb3cf6f4 by Greg Falcon : Don't use MSVC-specific bit manipulations when using Clang on Windows. This fixes a compiler warning. Note that we do not have continuous testing for this configuration; this CL is best-effort support. PiperOrigin-RevId: 303322582 -- f6e0a35a2b9081d2a9eef73789b7bc1b5e46e5ad by Gennadiy Rozental : Introduce standlone FastTypeId utility to represent compile time unique type id. PiperOrigin-RevId: 303180545 -- 99120e9fbdb5b2d327139ab8f617533d7bc3345b by Abseil Team : Changed absl's import of std::string_view to using string_view = std::string_view. This should help tools (e.g. include-what-you-use) discover where absl::string_view is defined. PiperOrigin-RevId: 303169095 GitOrigin-RevId: 2dd5008c7b4176859e320c7c337078adb173b662 Change-Id: I1e18ae08e23686ac963e7ea5e5bd499e18d51048 --- CMake/AbseilDll.cmake | 2 +- absl/base/BUILD.bazel | 25 + absl/base/CMakeLists.txt | 25 + absl/base/internal/bits.h | 24 +- absl/base/internal/fast_type_id.h | 48 ++ absl/base/internal/fast_type_id_test.cc | 123 +++++ absl/container/BUILD.bazel | 2 + absl/container/CMakeLists.txt | 2 + absl/container/internal/container_memory.h | 6 +- absl/container/internal/container_memory_test.cc | 30 +- absl/flags/BUILD.bazel | 1 + absl/flags/CMakeLists.txt | 1 + absl/flags/internal/commandlineflag.h | 22 +- absl/flags/internal/flag.cc | 34 +- absl/flags/internal/flag.h | 46 +- absl/flags/internal/registry.cc | 8 +- absl/flags/internal/registry.h | 4 +- absl/strings/BUILD.bazel | 1 + absl/strings/CMakeLists.txt | 1 + absl/strings/cord.cc | 291 ++++++----- absl/strings/cord.h | 638 ++++++++++++++++------- absl/strings/cord_test.cc | 68 +++ absl/strings/internal/str_format/extension.h | 18 +- absl/strings/string_view.h | 2 +- 24 files changed, 1002 insertions(+), 420 deletions(-) create mode 100644 absl/base/internal/fast_type_id.h create mode 100644 absl/base/internal/fast_type_id_test.cc (limited to 'absl/strings/cord.h') diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index ed01a48d..7646c154 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake @@ -19,6 +19,7 @@ set(ABSL_INTERNAL_DLL_FILES "base/internal/errno_saver.h" "base/internal/exponential_biased.cc" "base/internal/exponential_biased.h" + "base/internal/fast_type_id.h" "base/internal/hide_ptr.h" "base/internal/identity.h" "base/internal/invoke.h" @@ -130,7 +131,6 @@ set(ABSL_INTERNAL_DLL_FILES "random/bit_gen_ref.h" "random/discrete_distribution.cc" "random/discrete_distribution.h" - "random/distribution_format_traits.h" "random/distributions.h" "random/exponential_distribution.h" "random/gaussian_distribution.cc" diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel index 24dab791..1af9e45e 100644 --- a/absl/base/BUILD.bazel +++ b/absl/base/BUILD.bazel @@ -750,3 +750,28 @@ cc_binary( "@com_github_google_benchmark//:benchmark_main", ], ) + +cc_library( + name = "fast_type_id", + hdrs = ["internal/fast_type_id.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl:__subpackages__", + ], + deps = [ + ":config", + ], +) + +cc_test( + name = "fast_type_id_test", + size = "small", + srcs = ["internal/fast_type_id_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":fast_type_id", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt index 4230d2e7..54549920 100644 --- a/absl/base/CMakeLists.txt +++ b/absl/base/CMakeLists.txt @@ -674,3 +674,28 @@ absl_cc_test( gmock gtest_main ) + +absl_cc_library( + NAME + fast_type_id + HDRS + "internal/fast_type_id.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config +) + +absl_cc_test( + NAME + fast_type_id_test + SRCS + "internal/fast_type_id_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::fast_type_id + gtest_main +) diff --git a/absl/base/internal/bits.h b/absl/base/internal/bits.h index 8b03453c..14c51d8b 100644 --- a/absl/base/internal/bits.h +++ b/absl/base/internal/bits.h @@ -24,7 +24,7 @@ // Clang on Windows has __builtin_clzll; otherwise we need to use the // windows intrinsic functions. -#if defined(_MSC_VER) +#if defined(_MSC_VER) && !defined(__clang__) #include #if defined(_M_X64) #pragma intrinsic(_BitScanReverse64) @@ -36,7 +36,7 @@ #include "absl/base/attributes.h" -#if defined(_MSC_VER) +#if defined(_MSC_VER) && !defined(__clang__) // 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. @@ -73,14 +73,14 @@ ABSL_BASE_INTERNAL_FORCEINLINE int CountLeadingZeros64Slow(uint64_t n) { } ABSL_BASE_INTERNAL_FORCEINLINE int CountLeadingZeros64(uint64_t n) { -#if defined(_MSC_VER) && defined(_M_X64) +#if defined(_MSC_VER) && !defined(__clang__) && defined(_M_X64) // MSVC does not have __buitin_clzll. Use _BitScanReverse64. unsigned long result = 0; // NOLINT(runtime/int) if (_BitScanReverse64(&result, n)) { return 63 - result; } return 64; -#elif defined(_MSC_VER) +#elif defined(_MSC_VER) && !defined(__clang__) // MSVC does not have __buitin_clzll. Compose two calls to _BitScanReverse unsigned long result = 0; // NOLINT(runtime/int) if ((n >> 32) && _BitScanReverse(&result, n >> 32)) { @@ -90,7 +90,7 @@ ABSL_BASE_INTERNAL_FORCEINLINE int CountLeadingZeros64(uint64_t n) { return 63 - result; } return 64; -#elif defined(__GNUC__) +#elif defined(__GNUC__) || defined(__clang__) // Use __builtin_clzll, which uses the following instructions: // x86: bsr // ARM64: clz @@ -126,13 +126,13 @@ ABSL_BASE_INTERNAL_FORCEINLINE int CountLeadingZeros32Slow(uint64_t n) { } ABSL_BASE_INTERNAL_FORCEINLINE int CountLeadingZeros32(uint32_t n) { -#if defined(_MSC_VER) +#if defined(_MSC_VER) && !defined(__clang__) unsigned long result = 0; // NOLINT(runtime/int) if (_BitScanReverse(&result, n)) { return 31 - result; } return 32; -#elif defined(__GNUC__) +#elif defined(__GNUC__) || defined(__clang__) // Use __builtin_clz, which uses the following instructions: // x86: bsr // ARM64: clz @@ -163,11 +163,11 @@ ABSL_BASE_INTERNAL_FORCEINLINE int CountTrailingZerosNonZero64Slow(uint64_t n) { } ABSL_BASE_INTERNAL_FORCEINLINE int CountTrailingZerosNonZero64(uint64_t n) { -#if defined(_MSC_VER) && defined(_M_X64) +#if defined(_MSC_VER) && !defined(__clang__) && defined(_M_X64) unsigned long result = 0; // NOLINT(runtime/int) _BitScanForward64(&result, n); return result; -#elif defined(_MSC_VER) +#elif defined(_MSC_VER) && !defined(__clang__) unsigned long result = 0; // NOLINT(runtime/int) if (static_cast(n) == 0) { _BitScanForward(&result, n >> 32); @@ -175,7 +175,7 @@ ABSL_BASE_INTERNAL_FORCEINLINE int CountTrailingZerosNonZero64(uint64_t n) { } _BitScanForward(&result, n); return result; -#elif defined(__GNUC__) +#elif defined(__GNUC__) || defined(__clang__) static_assert(sizeof(unsigned long long) == sizeof(n), // NOLINT(runtime/int) "__builtin_ctzll does not take 64-bit arg"); return __builtin_ctzll(n); @@ -196,11 +196,11 @@ ABSL_BASE_INTERNAL_FORCEINLINE int CountTrailingZerosNonZero32Slow(uint32_t n) { } ABSL_BASE_INTERNAL_FORCEINLINE int CountTrailingZerosNonZero32(uint32_t n) { -#if defined(_MSC_VER) +#if defined(_MSC_VER) && !defined(__clang__) unsigned long result = 0; // NOLINT(runtime/int) _BitScanForward(&result, n); return result; -#elif defined(__GNUC__) +#elif defined(__GNUC__) || defined(__clang__) static_assert(sizeof(int) == sizeof(n), "__builtin_ctz does not take 32-bit arg"); return __builtin_ctz(n); diff --git a/absl/base/internal/fast_type_id.h b/absl/base/internal/fast_type_id.h new file mode 100644 index 00000000..3db59e83 --- /dev/null +++ b/absl/base/internal/fast_type_id.h @@ -0,0 +1,48 @@ +// +// Copyright 2020 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef ABSL_BASE_INTERNAL_FAST_TYPE_ID_H_ +#define ABSL_BASE_INTERNAL_FAST_TYPE_ID_H_ + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace base_internal { + +template +struct FastTypeTag { + constexpr static char dummy_var = 0; +}; + +template +constexpr char FastTypeTag::dummy_var; + +// FastTypeId() evaluates at compile/link-time to a unique pointer for the +// passed-in type. These are meant to be good match for keys into maps or +// straight up comparisons. +using FastTypeIdType = const void*; + +template +constexpr inline FastTypeIdType FastTypeId() { + return &FastTypeTag::dummy_var; +} + +} // namespace base_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_BASE_INTERNAL_FAST_TYPE_ID_H_ diff --git a/absl/base/internal/fast_type_id_test.cc b/absl/base/internal/fast_type_id_test.cc new file mode 100644 index 00000000..16f3c145 --- /dev/null +++ b/absl/base/internal/fast_type_id_test.cc @@ -0,0 +1,123 @@ +// Copyright 2020 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/base/internal/fast_type_id.h" + +#include +#include +#include + +#include "gtest/gtest.h" + +namespace { +namespace bi = absl::base_internal; + +// NOLINTNEXTLINE +#define PRIM_TYPES(A) \ + A(bool) \ + A(short) \ + A(unsigned short) \ + A(int) \ + A(unsigned int) \ + A(long) \ + A(unsigned long) \ + A(long long) \ + A(unsigned long long) \ + A(float) \ + A(double) \ + A(long double) + +TEST(FastTypeIdTest, PrimitiveTypes) { + bi::FastTypeIdType type_ids[] = { +#define A(T) bi::FastTypeId(), + PRIM_TYPES(A) +#undef A +#define A(T) bi::FastTypeId(), + PRIM_TYPES(A) +#undef A +#define A(T) bi::FastTypeId(), + PRIM_TYPES(A) +#undef A +#define A(T) bi::FastTypeId(), + PRIM_TYPES(A) +#undef A + }; + size_t total_type_ids = sizeof(type_ids) / sizeof(bi::FastTypeIdType); + + for (int i = 0; i < total_type_ids; ++i) { + EXPECT_EQ(type_ids[i], type_ids[i]); + for (int j = 0; j < i; ++j) { + EXPECT_NE(type_ids[i], type_ids[j]); + } + } +} + +#define FIXED_WIDTH_TYPES(A) \ + A(int8_t) \ + A(uint8_t) \ + A(int16_t) \ + A(uint16_t) \ + A(int32_t) \ + A(uint32_t) \ + A(int64_t) \ + A(uint64_t) + +TEST(FastTypeIdTest, FixedWidthTypes) { + bi::FastTypeIdType type_ids[] = { +#define A(T) bi::FastTypeId(), + FIXED_WIDTH_TYPES(A) +#undef A +#define A(T) bi::FastTypeId(), + FIXED_WIDTH_TYPES(A) +#undef A +#define A(T) bi::FastTypeId(), + FIXED_WIDTH_TYPES(A) +#undef A +#define A(T) bi::FastTypeId(), + FIXED_WIDTH_TYPES(A) +#undef A + }; + size_t total_type_ids = sizeof(type_ids) / sizeof(bi::FastTypeIdType); + + for (int i = 0; i < total_type_ids; ++i) { + EXPECT_EQ(type_ids[i], type_ids[i]); + for (int j = 0; j < i; ++j) { + EXPECT_NE(type_ids[i], type_ids[j]); + } + } +} + +TEST(FastTypeIdTest, AliasTypes) { + using int_alias = int; + EXPECT_EQ(bi::FastTypeId(), bi::FastTypeId()); +} + +TEST(FastTypeIdTest, TemplateSpecializations) { + EXPECT_NE(bi::FastTypeId>(), + bi::FastTypeId>()); + + EXPECT_NE((bi::FastTypeId>()), + (bi::FastTypeId>())); +} + +struct Base {}; +struct Derived : Base {}; +struct PDerived : private Base {}; + +TEST(FastTypeIdTest, Inheritance) { + EXPECT_NE(bi::FastTypeId(), bi::FastTypeId()); + EXPECT_NE(bi::FastTypeId(), bi::FastTypeId()); +} + +} // namespace diff --git a/absl/container/BUILD.bazel b/absl/container/BUILD.bazel index 4bed5735..1b0710b8 100644 --- a/absl/container/BUILD.bazel +++ b/absl/container/BUILD.bazel @@ -366,6 +366,7 @@ cc_library( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ "//absl/memory", + "//absl/meta:type_traits", "//absl/utility", ], ) @@ -378,6 +379,7 @@ cc_test( tags = NOTEST_TAGS_NONMOBILE, deps = [ ":container_memory", + ":test_instance_tracker", "//absl/strings", "@com_google_googletest//:gtest_main", ], diff --git a/absl/container/CMakeLists.txt b/absl/container/CMakeLists.txt index a732fe82..d79fa12e 100644 --- a/absl/container/CMakeLists.txt +++ b/absl/container/CMakeLists.txt @@ -421,6 +421,7 @@ absl_cc_library( ${ABSL_DEFAULT_COPTS} DEPS absl::memory + absl::type_traits absl::utility PUBLIC ) @@ -435,6 +436,7 @@ absl_cc_test( DEPS absl::container_memory absl::strings + absl::test_instance_tracker gmock_main ) diff --git a/absl/container/internal/container_memory.h b/absl/container/internal/container_memory.h index 55b59c7f..3487ac18 100644 --- a/absl/container/internal/container_memory.h +++ b/absl/container/internal/container_memory.h @@ -31,6 +31,7 @@ #include #include "absl/memory/memory.h" +#include "absl/meta/type_traits.h" #include "absl/utility/utility.h" namespace absl { @@ -319,11 +320,12 @@ union map_slot_type { map_slot_type() {} ~map_slot_type() = delete; using value_type = std::pair; - using mutable_value_type = std::pair; + using mutable_value_type = + std::pair, absl::remove_const_t>; value_type value; mutable_value_type mutable_value; - K key; + absl::remove_const_t key; }; template diff --git a/absl/container/internal/container_memory_test.cc b/absl/container/internal/container_memory_test.cc index e3262e3c..6a7fcd29 100644 --- a/absl/container/internal/container_memory_test.cc +++ b/absl/container/internal/container_memory_test.cc @@ -22,6 +22,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/container/internal/test_instance_tracker.h" #include "absl/strings/string_view.h" namespace absl { @@ -29,9 +30,11 @@ ABSL_NAMESPACE_BEGIN namespace container_internal { namespace { -using ::testing::Gt; +using ::absl::test_internal::CopyableMovableInstance; +using ::absl::test_internal::InstanceTracker; using ::testing::_; using ::testing::ElementsAre; +using ::testing::Gt; using ::testing::Pair; TEST(Memory, AlignmentLargerThanBase) { @@ -222,6 +225,31 @@ TEST(DecomposePair, NotDecomposable) { std::make_tuple(0.5))); } +TEST(MapSlotPolicy, ConstKeyAndValue) { + using slot_policy = map_slot_policy; + using slot_type = typename slot_policy::slot_type; + + union Slots { + Slots() {} + ~Slots() {} + slot_type slots[100]; + } slots; + + std::allocator< + std::pair> + alloc; + InstanceTracker tracker; + slot_policy::construct(&alloc, &slots.slots[0], CopyableMovableInstance(1), + CopyableMovableInstance(1)); + for (int i = 0; i < 99; ++i) { + slot_policy::transfer(&alloc, &slots.slots[i + 1], &slots.slots[i]); + } + slot_policy::destroy(&alloc, &slots.slots[99]); + + EXPECT_EQ(tracker.copies(), 0); +} + } // namespace } // namespace container_internal ABSL_NAMESPACE_END diff --git a/absl/flags/BUILD.bazel b/absl/flags/BUILD.bazel index 908e7761..4b51d9d4 100644 --- a/absl/flags/BUILD.bazel +++ b/absl/flags/BUILD.bazel @@ -147,6 +147,7 @@ cc_library( ":marshalling", "//absl/base:config", "//absl/base:core_headers", + "//absl/base:fast_type_id", "//absl/strings", "//absl/types:optional", ], diff --git a/absl/flags/CMakeLists.txt b/absl/flags/CMakeLists.txt index 01cf09b1..2204b0ff 100644 --- a/absl/flags/CMakeLists.txt +++ b/absl/flags/CMakeLists.txt @@ -128,6 +128,7 @@ absl_cc_library( ${ABSL_DEFAULT_LINKOPTS} DEPS absl::config + absl::fast_type_id absl::flags_config absl::flags_marshalling absl::core_headers diff --git a/absl/flags/internal/commandlineflag.h b/absl/flags/internal/commandlineflag.h index 338a1228..ef992f7f 100644 --- a/absl/flags/internal/commandlineflag.h +++ b/absl/flags/internal/commandlineflag.h @@ -24,6 +24,7 @@ #include #include "absl/base/config.h" +#include "absl/base/internal/fast_type_id.h" #include "absl/base/macros.h" #include "absl/flags/config.h" #include "absl/flags/marshalling.h" @@ -34,23 +35,12 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace flags_internal { -// An alias for flag static type id. Values of type identify the flag value type -// simialarly to typeid(T), but without relying on RTTI being available. In most +// An alias for flag fast type id. This value identifies the flag value type +// simialarly to typeid(T), without relying on RTTI being available. In most // cases this id is enough to uniquely identify the flag's value type. In a few // cases we'll have to resort to using actual RTTI implementation if it is // available. -using FlagStaticTypeId = void* (*)(); - -// Address of this function template is used in current implementation as a flag -// static type id. -template -void* FlagStaticTypeIdGen() { -#if defined(ABSL_FLAGS_INTERNAL_HAS_RTTI) - return const_cast(&typeid(T)); -#else - return nullptr; -#endif -} +using FlagFastTypeId = base_internal::FastTypeIdType; // Options that control SetCommandLineOptionWithMode. enum FlagSettingMode { @@ -97,7 +87,7 @@ class CommandLineFlag { // Return true iff flag has type T. template inline bool IsOfType() const { - return TypeId() == &flags_internal::FlagStaticTypeIdGen; + return TypeId() == base_internal::FastTypeId(); } // Attempts to retrieve the flag value. Returns value on success, @@ -150,7 +140,7 @@ class CommandLineFlag { // Returns true iff this is a handle to an Abseil Flag. virtual bool IsAbseilFlag() const; // Returns id of the flag's value type. - virtual FlagStaticTypeId TypeId() const = 0; + virtual FlagFastTypeId TypeId() const = 0; virtual bool IsModified() const = 0; virtual bool IsSpecifiedOnCommandLine() const = 0; virtual std::string DefaultValue() const = 0; diff --git a/absl/flags/internal/flag.cc b/absl/flags/internal/flag.cc index 5b4499ab..f3c424ad 100644 --- a/absl/flags/internal/flag.cc +++ b/absl/flags/internal/flag.cc @@ -48,9 +48,9 @@ const char kStrippedFlagHelp[] = "\001\002\003\004 (unknown) \004\003\002\001"; namespace { // Currently we only validate flag values for user-defined flag types. -bool ShouldValidateFlagValue(FlagStaticTypeId flag_type_id) { +bool ShouldValidateFlagValue(FlagFastTypeId flag_type_id) { #define DONT_VALIDATE(T) \ - if (flag_type_id == &FlagStaticTypeIdGen) return false; + if (flag_type_id == base_internal::FastTypeId()) return false; ABSL_FLAGS_INTERNAL_BUILTIN_TYPES(DONT_VALIDATE) #undef DONT_VALIDATE @@ -161,24 +161,24 @@ absl::Mutex* FlagImpl::DataGuard() const { return reinterpret_cast(&data_guard_); } -void FlagImpl::AssertValidType(FlagStaticTypeId type_id) const { - FlagStaticTypeId this_type_id = flags_internal::StaticTypeId(op_); +void FlagImpl::AssertValidType(FlagFastTypeId rhs_type_id, + const std::type_info* (*gen_rtti)()) const { + FlagFastTypeId lhs_type_id = flags_internal::FastTypeId(op_); - // `type_id` is the type id corresponding to the declaration visibile at the - // call site. `this_type_id` is the type id corresponding to the type stored - // during flag definition. They must match for this operation to be - // well-defined. - if (ABSL_PREDICT_TRUE(type_id == this_type_id)) return; + // `rhs_type_id` is the fast type id corresponding to the declaration + // visibile at the call site. `lhs_type_id` is the fast type id + // corresponding to the type specified in flag definition. They must match + // for this operation to be well-defined. + if (ABSL_PREDICT_TRUE(lhs_type_id == rhs_type_id)) return; - void* lhs_runtime_type_id = type_id(); - void* rhs_runtime_type_id = this_type_id(); + const std::type_info* lhs_runtime_type_id = + flags_internal::RuntimeTypeId(op_); + const std::type_info* rhs_runtime_type_id = (*gen_rtti)(); if (lhs_runtime_type_id == rhs_runtime_type_id) return; #if defined(ABSL_FLAGS_INTERNAL_HAS_RTTI) - if (*reinterpret_cast(lhs_runtime_type_id) == - *reinterpret_cast(rhs_runtime_type_id)) - return; + if (*lhs_runtime_type_id == *rhs_runtime_type_id) return; #endif ABSL_INTERNAL_LOG( @@ -233,8 +233,8 @@ std::string FlagImpl::Help() const { : help_.gen_func(); } -FlagStaticTypeId FlagImpl::TypeId() const { - return flags_internal::StaticTypeId(op_); +FlagFastTypeId FlagImpl::TypeId() const { + return flags_internal::FastTypeId(op_); } bool FlagImpl::IsModified() const { @@ -429,7 +429,7 @@ void FlagImpl::Read(void* dst) const { void FlagImpl::Write(const void* src) { absl::MutexLock l(DataGuard()); - if (ShouldValidateFlagValue(flags_internal::StaticTypeId(op_))) { + if (ShouldValidateFlagValue(flags_internal::FastTypeId(op_))) { std::unique_ptr obj{flags_internal::Clone(op_, src), DynValueDeleter{op_}}; std::string ignored_error; diff --git a/absl/flags/internal/flag.h b/absl/flags/internal/flag.h index 19119bbb..c1bf8652 100644 --- a/absl/flags/internal/flag.h +++ b/absl/flags/internal/flag.h @@ -23,6 +23,7 @@ #include #include #include +#include #include "absl/base/call_once.h" #include "absl/base/config.h" @@ -50,7 +51,8 @@ enum class FlagOp { kCopy, kCopyConstruct, kSizeof, - kStaticTypeId, + kFastTypeId, + kRuntimeTypeId, kParse, kUnparse, kValueOffset, @@ -96,10 +98,15 @@ inline size_t Sizeof(FlagOpFn op) { return static_cast(reinterpret_cast( op(FlagOp::kSizeof, nullptr, nullptr, nullptr))); } -// Returns static type id coresponding to the value type. -inline FlagStaticTypeId StaticTypeId(FlagOpFn op) { - return reinterpret_cast( - op(FlagOp::kStaticTypeId, nullptr, nullptr, nullptr)); +// Returns fast type id coresponding to the value type. +inline FlagFastTypeId FastTypeId(FlagOpFn op) { + return reinterpret_cast( + op(FlagOp::kFastTypeId, nullptr, nullptr, nullptr)); +} +// Returns fast type id coresponding to the value type. +inline const std::type_info* RuntimeTypeId(FlagOpFn op) { + return reinterpret_cast( + op(FlagOp::kRuntimeTypeId, nullptr, nullptr, nullptr)); } // Returns offset of the field value_ from the field impl_ inside of // absl::Flag data. Given FlagImpl pointer p you can get the @@ -112,6 +119,16 @@ inline ptrdiff_t ValueOffset(FlagOpFn op) { op(FlagOp::kValueOffset, nullptr, nullptr, nullptr))); } +// Returns an address of RTTI's typeid(T). +template +inline const std::type_info* GenRuntimeTypeId() { +#if defined(ABSL_FLAGS_INTERNAL_HAS_RTTI) + return &typeid(T); +#else + return nullptr; +#endif +} + /////////////////////////////////////////////////////////////////////////////// // Flag help auxiliary structs. @@ -374,9 +391,10 @@ class FlagImpl final : public flags_internal::CommandLineFlag { // For example if flag is declared as absl::Flag FLAGS_foo, a call to // absl::GetFlag(FLAGS_foo) validates that the type of FLAGS_foo is indeed // int. To do that we pass the "assumed" type id (which is deduced from type - // int) as an argument `op`, which is in turn is validated against the type id - // stored in flag object by flag definition statement. - void AssertValidType(FlagStaticTypeId type_id) const; + // int) as an argument `type_id`, which is in turn is validated against the + // type id stored in flag object by flag definition statement. + void AssertValidType(FlagFastTypeId type_id, + const std::type_info* (*gen_rtti)()) const; private: template @@ -433,7 +451,7 @@ class FlagImpl final : public flags_internal::CommandLineFlag { std::string Filename() const override; absl::string_view Typename() const override; std::string Help() const override; - FlagStaticTypeId TypeId() const override; + FlagFastTypeId TypeId() const override; bool IsModified() const override ABSL_LOCKS_EXCLUDED(*DataGuard()); bool IsSpecifiedOnCommandLine() const override ABSL_LOCKS_EXCLUDED(*DataGuard()); @@ -539,14 +557,14 @@ class Flag { U u; #if !defined(NDEBUG) - impl_.AssertValidType(&flags_internal::FlagStaticTypeIdGen); + impl_.AssertValidType(base_internal::FastTypeId(), &GenRuntimeTypeId); #endif if (!value_.Get(&u.value)) impl_.Read(&u.value); return std::move(u.value); } void Set(const T& v) { - impl_.AssertValidType(&flags_internal::FlagStaticTypeIdGen); + impl_.AssertValidType(base_internal::FastTypeId(), &GenRuntimeTypeId); impl_.Write(&v); } void SetCallback(const FlagCallbackFunc mutation_callback) { @@ -595,8 +613,10 @@ void* FlagOps(FlagOp op, const void* v1, void* v2, void* v3) { return nullptr; case FlagOp::kSizeof: return reinterpret_cast(static_cast(sizeof(T))); - case FlagOp::kStaticTypeId: - return reinterpret_cast(&FlagStaticTypeIdGen); + case FlagOp::kFastTypeId: + return const_cast(base_internal::FastTypeId()); + case FlagOp::kRuntimeTypeId: + return const_cast(GenRuntimeTypeId()); case FlagOp::kParse: { // Initialize the temporary instance of type T based on current value in // destination (which is going to be flag's default value). diff --git a/absl/flags/internal/registry.cc b/absl/flags/internal/registry.cc index 9ed91214..eb619c70 100644 --- a/absl/flags/internal/registry.cc +++ b/absl/flags/internal/registry.cc @@ -284,14 +284,14 @@ namespace { class RetiredFlagObj final : public flags_internal::CommandLineFlag { public: - constexpr RetiredFlagObj(const char* name, FlagStaticTypeId type_id) + constexpr RetiredFlagObj(const char* name, FlagFastTypeId type_id) : name_(name), type_id_(type_id) {} private: absl::string_view Name() const override { return name_; } std::string Filename() const override { return "RETIRED"; } absl::string_view Typename() const override { return ""; } - FlagStaticTypeId TypeId() const override { return type_id_; } + FlagFastTypeId TypeId() const override { return type_id_; } std::string Help() const override { return ""; } bool IsRetired() const override { return true; } bool IsModified() const override { return false; } @@ -317,7 +317,7 @@ class RetiredFlagObj final : public flags_internal::CommandLineFlag { // Data members const char* const name_; - const FlagStaticTypeId type_id_; + const FlagFastTypeId type_id_; }; void DestroyRetiredFlag(flags_internal::CommandLineFlag* flag) { @@ -327,7 +327,7 @@ void DestroyRetiredFlag(flags_internal::CommandLineFlag* flag) { } // namespace -bool Retire(const char* name, FlagStaticTypeId type_id) { +bool Retire(const char* name, FlagFastTypeId type_id) { auto* flag = new flags_internal::RetiredFlagObj(name, type_id); FlagRegistry::GlobalRegistry()->RegisterFlag(flag); return true; diff --git a/absl/flags/internal/registry.h b/absl/flags/internal/registry.h index 69ff889f..af8ed6b9 100644 --- a/absl/flags/internal/registry.h +++ b/absl/flags/internal/registry.h @@ -79,12 +79,12 @@ bool RegisterCommandLineFlag(CommandLineFlag*); // // Retire flag with name "name" and type indicated by ops. -bool Retire(const char* name, FlagStaticTypeId type_id); +bool Retire(const char* name, FlagFastTypeId type_id); // Registered a retired flag with name 'flag_name' and type 'T'. template inline bool RetiredFlag(const char* flag_name) { - return flags_internal::Retire(flag_name, &FlagStaticTypeIdGen); + return flags_internal::Retire(flag_name, base_internal::FastTypeId()); } // If the flag is retired, returns true and indicates in |*type_is_bool| diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index 64f55fb4..38901122 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -313,6 +313,7 @@ cc_test( ":strings", "//absl/base", "//absl/base:config", + "//absl/base:core_headers", "//absl/base:endian", "//absl/base:raw_logging_internal", "//absl/container:fixed_array", diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index c7874ecf..d3a8bd7e 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt @@ -578,6 +578,7 @@ absl_cc_test( absl::strings absl::base absl::config + absl::core_headers absl::endian absl::raw_logging_internal absl::fixed_array diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index 4f64f799..7de7766c 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc @@ -28,9 +28,9 @@ #include "absl/base/casts.h" #include "absl/base/internal/raw_logging.h" +#include "absl/base/macros.h" #include "absl/base/port.h" #include "absl/container/fixed_array.h" -#include "absl/container/inlined_vector.h" #include "absl/strings/escaping.h" #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/resize_uninitialized.h" @@ -132,6 +132,14 @@ inline const CordRepExternal* CordRep::external() const { return static_cast(this); } +using CordTreeConstPath = CordTreePath; + +// This type is used to store the list of pending nodes during re-balancing. +// Its maximum size is 2 * MaxCordDepth() because the tree has a maximum +// possible depth of MaxCordDepth() and every concat node along a tree path +// could theoretically be split during rebalancing. +using RebalancingStack = CordTreePath; + } // namespace cord_internal static const size_t kFlatOverhead = offsetof(CordRep, data); @@ -180,98 +188,78 @@ static constexpr size_t TagToLength(uint8_t tag) { // Enforce that kMaxFlatSize maps to a well-known exact tag value. static_assert(TagToAllocatedSize(224) == kMaxFlatSize, "Bad tag logic"); -constexpr uint64_t Fibonacci(unsigned char n, uint64_t a = 0, uint64_t b = 1) { - return n == 0 ? a : Fibonacci(n - 1, b, a + b); +constexpr size_t Fibonacci(uint8_t n, const size_t a = 0, const size_t b = 1) { + return n == 0 + ? a + : n == 1 ? b + : Fibonacci(n - 1, b, + (a > (size_t(-1) - b)) ? size_t(-1) : a + b); } -static_assert(Fibonacci(63) == 6557470319842, - "Fibonacci values computed incorrectly"); - // Minimum length required for a given depth tree -- a tree is considered // balanced if -// length(t) >= min_length[depth(t)] -// The root node depth is allowed to become twice as large to reduce rebalancing -// for larger strings (see IsRootBalanced). -static constexpr uint64_t min_length[] = { - Fibonacci(2), - Fibonacci(3), - Fibonacci(4), - Fibonacci(5), - Fibonacci(6), - Fibonacci(7), - Fibonacci(8), - Fibonacci(9), - Fibonacci(10), - Fibonacci(11), - Fibonacci(12), - Fibonacci(13), - Fibonacci(14), - Fibonacci(15), - Fibonacci(16), - Fibonacci(17), - Fibonacci(18), - Fibonacci(19), - Fibonacci(20), - Fibonacci(21), - Fibonacci(22), - Fibonacci(23), - Fibonacci(24), - Fibonacci(25), - Fibonacci(26), - Fibonacci(27), - Fibonacci(28), - Fibonacci(29), - Fibonacci(30), - Fibonacci(31), - Fibonacci(32), - Fibonacci(33), - Fibonacci(34), - Fibonacci(35), - Fibonacci(36), - Fibonacci(37), - Fibonacci(38), - Fibonacci(39), - Fibonacci(40), - Fibonacci(41), - Fibonacci(42), - Fibonacci(43), - Fibonacci(44), - Fibonacci(45), - Fibonacci(46), - Fibonacci(47), - 0xffffffffffffffffull, // Avoid overflow -}; - -static const int kMinLengthSize = ABSL_ARRAYSIZE(min_length); - -// The inlined size to use with absl::InlinedVector. -// -// Note: The InlinedVectors in this file (and in cord.h) do not need to use -// the same value for their inlined size. The fact that they do is historical. -// It may be desirable for each to use a different inlined size optimized for -// that InlinedVector's usage. -// -// TODO(jgm): Benchmark to see if there's a more optimal value than 47 for -// the inlined vector size (47 exists for backward compatibility). -static const int kInlinedVectorSize = 47; - -static inline bool IsRootBalanced(CordRep* node) { - if (node->tag != CONCAT) { - return true; - } else if (node->concat()->depth() <= 15) { - return true; - } else if (node->concat()->depth() > kMinLengthSize) { - return false; - } else { - // Allow depth to become twice as large as implied by fibonacci rule to - // reduce rebalancing for larger strings. - return (node->length >= min_length[node->concat()->depth() / 2]); - } +// length(t) >= kMinLength[depth(t)] +// The node depth is allowed to become larger to reduce rebalancing +// for larger strings (see ShouldRebalance). +constexpr size_t kMinLength[] = { + Fibonacci(2), Fibonacci(3), Fibonacci(4), Fibonacci(5), Fibonacci(6), + Fibonacci(7), Fibonacci(8), Fibonacci(9), Fibonacci(10), Fibonacci(11), + Fibonacci(12), Fibonacci(13), Fibonacci(14), Fibonacci(15), Fibonacci(16), + Fibonacci(17), Fibonacci(18), Fibonacci(19), Fibonacci(20), Fibonacci(21), + Fibonacci(22), Fibonacci(23), Fibonacci(24), Fibonacci(25), Fibonacci(26), + Fibonacci(27), Fibonacci(28), Fibonacci(29), Fibonacci(30), Fibonacci(31), + Fibonacci(32), Fibonacci(33), Fibonacci(34), Fibonacci(35), Fibonacci(36), + Fibonacci(37), Fibonacci(38), Fibonacci(39), Fibonacci(40), Fibonacci(41), + Fibonacci(42), Fibonacci(43), Fibonacci(44), Fibonacci(45), Fibonacci(46), + Fibonacci(47), Fibonacci(48), Fibonacci(49), Fibonacci(50), Fibonacci(51), + Fibonacci(52), Fibonacci(53), Fibonacci(54), Fibonacci(55), Fibonacci(56), + Fibonacci(57), Fibonacci(58), Fibonacci(59), Fibonacci(60), Fibonacci(61), + Fibonacci(62), Fibonacci(63), Fibonacci(64), Fibonacci(65), Fibonacci(66), + Fibonacci(67), Fibonacci(68), Fibonacci(69), Fibonacci(70), Fibonacci(71), + Fibonacci(72), Fibonacci(73), Fibonacci(74), Fibonacci(75), Fibonacci(76), + Fibonacci(77), Fibonacci(78), Fibonacci(79), Fibonacci(80), Fibonacci(81), + Fibonacci(82), Fibonacci(83), Fibonacci(84), Fibonacci(85), Fibonacci(86), + Fibonacci(87), Fibonacci(88), Fibonacci(89), Fibonacci(90), Fibonacci(91), + Fibonacci(92), Fibonacci(93), Fibonacci(94), Fibonacci(95)}; + +static_assert(sizeof(kMinLength) / sizeof(size_t) >= + (cord_internal::MaxCordDepth() + 1), + "Not enough elements in kMinLength array to cover all the " + "supported Cord depth(s)"); + +inline bool ShouldRebalance(const CordRep* node) { + if (node->tag != CONCAT) return false; + + size_t node_depth = node->concat()->depth(); + + if (node_depth <= 15) return false; + + // Rebalancing Cords is expensive, so we reduce how often rebalancing occurs + // by allowing shallow Cords to have twice the depth that the Fibonacci rule + // would otherwise imply. Deep Cords need to follow the rule more closely, + // however to ensure algorithm correctness. We implement this with linear + // interpolation. Cords of depth 16 are treated as though they have a depth + // of 16 * 1/2, and Cords of depth MaxCordDepth() interpolate to + // MaxCordDepth() * 1. + return node->length < + kMinLength[(node_depth * (cord_internal::MaxCordDepth() - 16)) / + (2 * cord_internal::MaxCordDepth() - 16 - node_depth)]; +} + +// Unlike root balancing condition this one is part of the re-balancing +// algorithm and has to be always matching against right depth for +// algorithm to be correct. +inline bool IsNodeBalanced(const CordRep* node) { + if (node->tag != CONCAT) return true; + + size_t node_depth = node->concat()->depth(); + + return node->length >= kMinLength[node_depth]; } static CordRep* Rebalance(CordRep* node); -static void DumpNode(CordRep* rep, bool include_data, std::ostream* os); -static bool VerifyNode(CordRep* root, CordRep* start_node, +static void DumpNode(const CordRep* rep, bool include_data, std::ostream* os); +static bool VerifyNode(const CordRep* root, const CordRep* start_node, bool full_validation); static inline CordRep* VerifyTree(CordRep* node) { @@ -318,7 +306,8 @@ __attribute__((preserve_most)) static void UnrefInternal(CordRep* rep) { assert(rep != nullptr); - absl::InlinedVector pending; + cord_internal::RebalancingStack pending; + while (true) { if (rep->tag == CONCAT) { CordRepConcat* rep_concat = rep->concat(); @@ -400,6 +389,11 @@ static void SetConcatChildren(CordRepConcat* concat, CordRep* left, concat->length = left->length + right->length; concat->set_depth(1 + std::max(Depth(left), Depth(right))); + + ABSL_INTERNAL_CHECK(concat->depth() <= cord_internal::MaxCordDepth(), + "Cord depth exceeds max"); + ABSL_INTERNAL_CHECK(concat->length >= left->length, "Cord is too long"); + ABSL_INTERNAL_CHECK(concat->length >= right->length, "Cord is too long"); } // Create a concatenation of the specified nodes. @@ -425,7 +419,7 @@ static CordRep* RawConcat(CordRep* left, CordRep* right) { static CordRep* Concat(CordRep* left, CordRep* right) { CordRep* rep = RawConcat(left, right); - if (rep != nullptr && !IsRootBalanced(rep)) { + if (rep != nullptr && ShouldRebalance(rep)) { rep = Rebalance(rep); } return VerifyTree(rep); @@ -720,6 +714,14 @@ void Cord::InlineRep::ClearSlow() { memset(data_, 0, sizeof(data_)); } +inline Cord::InternalChunkIterator Cord::internal_chunk_begin() const { + return InternalChunkIterator(this); +} + +inline Cord::InternalChunkRange Cord::InternalChunks() const { + return InternalChunkRange(this); +} + // -------------------------------------------------------------------- // Constructors and destructors @@ -916,7 +918,7 @@ void Cord::Prepend(absl::string_view src) { static CordRep* RemovePrefixFrom(CordRep* node, size_t n) { if (n >= node->length) return nullptr; if (n == 0) return Ref(node); - absl::InlinedVector rhs_stack; + cord_internal::CordTreeMutablePath rhs_stack; while (node->tag == CONCAT) { assert(n <= node->length); @@ -957,7 +959,7 @@ static CordRep* RemovePrefixFrom(CordRep* node, size_t n) { static CordRep* RemoveSuffixFrom(CordRep* node, size_t n) { if (n >= node->length) return nullptr; if (n == 0) return Ref(node); - absl::InlinedVector lhs_stack; + absl::cord_internal::CordTreeMutablePath lhs_stack; bool inplace_ok = node->refcount.IsOne(); while (node->tag == CONCAT) { @@ -1028,6 +1030,7 @@ void Cord::RemoveSuffix(size_t n) { // Work item for NewSubRange(). struct SubRange { + SubRange() = default; SubRange(CordRep* a_node, size_t a_pos, size_t a_n) : node(a_node), pos(a_pos), n(a_n) {} CordRep* node; // nullptr means concat last 2 results. @@ -1036,8 +1039,11 @@ struct SubRange { }; static CordRep* NewSubRange(CordRep* node, size_t pos, size_t n) { - absl::InlinedVector results; - absl::InlinedVector todo; + cord_internal::CordTreeMutablePath results; + // The algorithm below in worst case scenario adds up to 3 nodes to the `todo` + // list, but we also pop one out on every cycle. If original tree has depth d + // todo list can grew up to 2*d in size. + cord_internal::CordTreePath todo; todo.push_back(SubRange(node, pos, n)); do { const SubRange& sr = todo.back(); @@ -1074,7 +1080,7 @@ static CordRep* NewSubRange(CordRep* node, size_t pos, size_t n) { } } while (!todo.empty()); assert(results.size() == 1); - return results[0]; + return results.back(); } Cord Cord::Subcord(size_t pos, size_t new_size) const { @@ -1090,7 +1096,7 @@ Cord Cord::Subcord(size_t pos, size_t new_size) const { } else if (new_size == 0) { // We want to return empty subcord, so nothing to do. } else if (new_size <= InlineRep::kMaxInline) { - Cord::ChunkIterator it = chunk_begin(); + Cord::InternalChunkIterator it = internal_chunk_begin(); it.AdvanceBytes(pos); char* dest = sub_cord.contents_.data_; size_t remaining_size = new_size; @@ -1113,11 +1119,12 @@ Cord Cord::Subcord(size_t pos, size_t new_size) const { class CordForest { public: - explicit CordForest(size_t length) - : root_length_(length), trees_(kMinLengthSize, nullptr) {} + explicit CordForest(size_t length) : root_length_(length), trees_({}) {} void Build(CordRep* cord_root) { - std::vector pending = {cord_root}; + // We are adding up to two nodes to the `pending` list, but we also popping + // one, so the size of `pending` will never exceed `MaxCordDepth()`. + cord_internal::CordTreeMutablePath pending(cord_root); while (!pending.empty()) { CordRep* node = pending.back(); @@ -1129,21 +1136,20 @@ class CordForest { } CordRepConcat* concat_node = node->concat(); - if (concat_node->depth() >= kMinLengthSize || - concat_node->length < min_length[concat_node->depth()]) { - pending.push_back(concat_node->right); - pending.push_back(concat_node->left); - - if (concat_node->refcount.IsOne()) { - concat_node->left = concat_freelist_; - concat_freelist_ = concat_node; - } else { - Ref(concat_node->right); - Ref(concat_node->left); - Unref(concat_node); - } - } else { + if (IsNodeBalanced(concat_node)) { AddNode(node); + continue; + } + pending.push_back(concat_node->right); + pending.push_back(concat_node->left); + + if (concat_node->refcount.IsOne()) { + concat_node->left = concat_freelist_; + concat_freelist_ = concat_node; + } else { + Ref(concat_node->right); + Ref(concat_node->left); + Unref(concat_node); } } } @@ -1175,7 +1181,7 @@ class CordForest { // Collect together everything with which we will merge with node int i = 0; - for (; node->length > min_length[i + 1]; ++i) { + for (; node->length >= kMinLength[i + 1]; ++i) { auto& tree_at_i = trees_[i]; if (tree_at_i == nullptr) continue; @@ -1186,7 +1192,7 @@ class CordForest { sum = AppendNode(node, sum); // Insert sum into appropriate place in the forest - for (; sum->length >= min_length[i]; ++i) { + for (; sum->length >= kMinLength[i]; ++i) { auto& tree_at_i = trees_[i]; if (tree_at_i == nullptr) continue; @@ -1194,7 +1200,7 @@ class CordForest { tree_at_i = nullptr; } - // min_length[0] == 1, which means sum->length >= min_length[0] + // kMinLength[0] == 1, which means sum->length >= kMinLength[0] assert(i > 0); trees_[i - 1] = sum; } @@ -1227,9 +1233,7 @@ class CordForest { } size_t root_length_; - - // use an inlined vector instead of a flat array to get bounds checking - absl::InlinedVector trees_; + std::array trees_; // List of concat nodes we can re-use for Cord balancing. CordRepConcat* concat_freelist_ = nullptr; @@ -1330,7 +1334,7 @@ inline absl::string_view Cord::InlineRep::FindFlatStartPiece() const { inline int Cord::CompareSlowPath(absl::string_view rhs, size_t compared_size, size_t size_to_compare) const { - auto advance = [](Cord::ChunkIterator* it, absl::string_view* chunk) { + auto advance = [](Cord::InternalChunkIterator* it, absl::string_view* chunk) { if (!chunk->empty()) return true; ++*it; if (it->bytes_remaining_ == 0) return false; @@ -1338,7 +1342,7 @@ inline int Cord::CompareSlowPath(absl::string_view rhs, size_t compared_size, return true; }; - Cord::ChunkIterator lhs_it = chunk_begin(); + Cord::InternalChunkIterator lhs_it = internal_chunk_begin(); // compared_size is inside first chunk. absl::string_view lhs_chunk = @@ -1360,7 +1364,7 @@ inline int Cord::CompareSlowPath(absl::string_view rhs, size_t compared_size, inline int Cord::CompareSlowPath(const Cord& rhs, size_t compared_size, size_t size_to_compare) const { - auto advance = [](Cord::ChunkIterator* it, absl::string_view* chunk) { + auto advance = [](Cord::InternalChunkIterator* it, absl::string_view* chunk) { if (!chunk->empty()) return true; ++*it; if (it->bytes_remaining_ == 0) return false; @@ -1368,8 +1372,8 @@ inline int Cord::CompareSlowPath(const Cord& rhs, size_t compared_size, return true; }; - Cord::ChunkIterator lhs_it = chunk_begin(); - Cord::ChunkIterator rhs_it = rhs.chunk_begin(); + Cord::InternalChunkIterator lhs_it = internal_chunk_begin(); + Cord::InternalChunkIterator rhs_it = rhs.internal_chunk_begin(); // compared_size is inside both first chunks. absl::string_view lhs_chunk = @@ -1503,8 +1507,11 @@ void Cord::CopyToArraySlowPath(char* dst) const { } } -Cord::ChunkIterator& Cord::ChunkIterator::operator++() { - assert(bytes_remaining_ > 0 && "Attempted to iterate past `end()`"); +template +Cord::GenericChunkIterator& +Cord::GenericChunkIterator::operator++() { + ABSL_HARDENING_ASSERT(bytes_remaining_ > 0 && + "Attempted to iterate past `end()`"); assert(bytes_remaining_ >= current_chunk_.size()); bytes_remaining_ -= current_chunk_.size(); @@ -1542,8 +1549,10 @@ Cord::ChunkIterator& Cord::ChunkIterator::operator++() { return *this; } -Cord Cord::ChunkIterator::AdvanceAndReadBytes(size_t n) { - assert(bytes_remaining_ >= n && "Attempted to iterate past `end()`"); +template +Cord Cord::GenericChunkIterator::AdvanceAndReadBytes(size_t n) { + ABSL_HARDENING_ASSERT(bytes_remaining_ >= n && + "Attempted to iterate past `end()`"); Cord subcord; if (n <= InlineRep::kMaxInline) { @@ -1655,7 +1664,8 @@ Cord Cord::ChunkIterator::AdvanceAndReadBytes(size_t n) { return subcord; } -void Cord::ChunkIterator::AdvanceBytesSlowPath(size_t n) { +template +void Cord::GenericChunkIterator::AdvanceBytesSlowPath(size_t n) { assert(bytes_remaining_ >= n && "Attempted to iterate past `end()`"); assert(n >= current_chunk_.size()); // This should only be called when // iterating to a new node. @@ -1714,7 +1724,7 @@ void Cord::ChunkIterator::AdvanceBytesSlowPath(size_t n) { } char Cord::operator[](size_t i) const { - assert(i < size()); + ABSL_HARDENING_ASSERT(i < size()); size_t offset = i; const CordRep* rep = contents_.tree(); if (rep == nullptr) { @@ -1841,18 +1851,18 @@ absl::string_view Cord::FlattenSlowPath() { } } -static void DumpNode(CordRep* rep, bool include_data, std::ostream* os) { +static void DumpNode(const CordRep* rep, bool include_data, std::ostream* os) { const int kIndentStep = 1; int indent = 0; - absl::InlinedVector stack; - absl::InlinedVector indents; + cord_internal::CordTreeConstPath stack; + cord_internal::CordTreePath indents; for (;;) { *os << std::setw(3) << rep->refcount.Get(); *os << " " << std::setw(7) << rep->length; *os << " ["; - if (include_data) *os << static_cast(rep); + if (include_data) *os << static_cast(rep); *os << "]"; - *os << " " << (IsRootBalanced(rep) ? 'b' : 'u'); + *os << " " << (IsNodeBalanced(rep) ? 'b' : 'u'); *os << " " << std::setw(indent) << ""; if (rep->tag == CONCAT) { *os << "CONCAT depth=" << Depth(rep) << "\n"; @@ -1873,7 +1883,7 @@ static void DumpNode(CordRep* rep, bool include_data, std::ostream* os) { } else { *os << "FLAT cap=" << TagToLength(rep->tag) << " ["; if (include_data) - *os << absl::CEscape(std::string(rep->data, rep->length)); + *os << absl::CEscape(absl::string_view(rep->data, rep->length)); *os << "]\n"; } if (stack.empty()) break; @@ -1886,19 +1896,19 @@ static void DumpNode(CordRep* rep, bool include_data, std::ostream* os) { ABSL_INTERNAL_CHECK(indents.empty(), ""); } -static std::string ReportError(CordRep* root, CordRep* node) { +static std::string ReportError(const CordRep* root, const CordRep* node) { std::ostringstream buf; buf << "Error at node " << node << " in:"; DumpNode(root, true, &buf); return buf.str(); } -static bool VerifyNode(CordRep* root, CordRep* start_node, +static bool VerifyNode(const CordRep* root, const CordRep* start_node, bool full_validation) { - absl::InlinedVector worklist; + cord_internal::CordTreeConstPath worklist; worklist.push_back(start_node); do { - CordRep* node = worklist.back(); + const CordRep* node = worklist.back(); worklist.pop_back(); ABSL_INTERNAL_CHECK(node != nullptr, ReportError(root, node)); @@ -1948,7 +1958,7 @@ static bool VerifyNode(CordRep* root, CordRep* start_node, // Iterate over the tree. cur_node is never a leaf node and leaf nodes will // never be appended to tree_stack. This reduces overhead from manipulating // tree_stack. - absl::InlinedVector tree_stack; + cord_internal::CordTreeConstPath tree_stack; const CordRep* cur_node = rep; while (true) { const CordRep* next_node = nullptr; @@ -1995,6 +2005,9 @@ std::ostream& operator<<(std::ostream& out, const Cord& cord) { return out; } +template class Cord::GenericChunkIterator; +template class Cord::GenericChunkIterator; + namespace strings_internal { size_t CordTestAccess::FlatOverhead() { return kFlatOverhead; } size_t CordTestAccess::MaxFlatLength() { return kMaxFlatLength; } diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 66645eef..3ab3cb87 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -11,25 +11,52 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - -// A Cord is a sequence of characters with some unusual access propreties. -// A Cord supports efficient insertions and deletions at the start and end of -// the byte sequence, but random access reads are slower, and random access -// modifications are not supported by the API. Cord also provides cheap copies -// (using a copy-on-write strategy) and cheap substring operations. // -// Thread safety -// ------------- +// ----------------------------------------------------------------------------- +// File: cord.h +// ----------------------------------------------------------------------------- +// +// This file defines the `absl::Cord` data structure and operations on that data +// structure. A Cord is a string-like sequence of characters optimized for +// specific use cases. Unlike a `std::string`, which stores an array of +// contiguous characters, Cord data is stored in a structure consisting of +// separate, reference-counted "chunks." (Currently, this implementation is a +// tree structure, though that implementation may change.) +// +// Because a Cord consists of these chunks, data can be added to or removed from +// a Cord during its lifetime. Chunks may also be shared between Cords. Unlike a +// `std::string`, a Cord can therefore accomodate data that changes over its +// lifetime, though it's not quite "mutable"; it can change only in the +// attachment, detachment, or rearrangement of chunks of its constituent data. +// +// A Cord provides some benefit over `std::string` under the following (albeit +// narrow) circumstances: +// +// * Cord data is designed to grow and shrink over a Cord's lifetime. Cord +// provides efficient insertions and deletions at the start and end of the +// character sequences, avoiding copies in those cases. Static data should +// generally be stored as strings. +// * External memory consisting of string-like data can be directly added to +// a Cord without requiring copies or allocations. +// * Cord data may be shared and copied cheaply. Cord provides a copy-on-write +// implementation and cheap sub-Cord operations. Copying a Cord is an O(1) +// operation. +// +// As a consequence to the above, Cord data is generally large. Small data +// should generally use strings, as construction of a Cord requires some +// overhead. Small Cords (<= 15 bytes) are represented inline, but most small +// Cords are expected to grow over their lifetimes. +// +// Note that because a Cord is made up of separate chunked data, random access +// to character data within a Cord is slower than within a `std::string`. +// +// Thread Safety +// // Cord has the same thread-safety properties as many other types like // std::string, std::vector<>, int, etc -- it is thread-compatible. In -// particular, if no thread may call a non-const method, then it is safe to -// concurrently call const methods. Copying a Cord produces a new instance that -// can be used concurrently with the original in arbitrary ways. -// -// Implementation is similar to the "Ropes" described in: -// Ropes: An alternative to strings -// Hans J. Boehm, Russ Atkinson, Michael Plass -// Software Practice and Experience, December 1995 +// particular, if threads do not call non-const methods, then it is safe to call +// const methods without synchronization. Copying a Cord produces a new instance +// that can be used concurrently with the original in arbitrary ways. #ifndef ABSL_STRINGS_CORD_H_ #define ABSL_STRINGS_CORD_H_ @@ -68,6 +95,90 @@ template H HashFragmentedCord(H, const Cord&); } +// Cord +// +// A Cord is a sequence of characters, designed to be more efficient than a +// `std::string` in certain circumstances: namely, large string data that needs +// to change over its lifetime or shared, especially when such data is shared +// across API boundaries. +// +// A Cord stores its character data in a structure that allows efficient prepend +// and append operations. This makes a Cord useful for large string data sent +// over in a wire format that may need to be prepended or appended at some point +// during the data exchange (e.g. HTTP, protocol buffers). For example, a +// Cord is useful for storing an HTTP request, and prepending an HTTP header to +// such a request. +// +// Cords should not be used for storing general string data, however. They +// require overhead to construct and are slower than strings for random access. +// +// The Cord API provides the following common API operations: +// +// * Create or assign Cords out of existing string data, memory, or other Cords +// * Append and prepend data to an existing Cord +// * Create new Sub-Cords from existing Cord data +// * Swap Cord data and compare Cord equality +// * Write out Cord data by constructing a `std::string` +// +// Additionally, the API provides iterator utilities to iterate through Cord +// data via chunks or character bytes. +// + +namespace cord_internal { + +// It's expensive to keep a Cord's tree perfectly balanced, so instead we keep +// trees approximately balanced. A tree node N of depth D(N) that contains a +// string of L(N) characters is considered balanced if L >= Fibonacci(D + 2). +// The "+ 2" is used to ensure that every balanced leaf node contains at least +// one character. Here we presume that +// Fibonacci(0) = 0 +// Fibonacci(1) = 1 +// Fibonacci(2) = 1 +// Fibonacci(3) = 2 +// ... +// The algorithm is based on paper by Hans Boehm et al: +// https://www.cs.rit.edu/usr/local/pub/jeh/courses/QUARTERS/FP/Labs/CedarRope/rope-paper.pdf +// In this paper authors shows that rebalancing based on cord forest of already +// balanced subtrees can be proven to never produce tree of depth larger than +// largest Fibonacci number representable in the same integral type as cord size +// For 64 bit integers this is the 93rd Fibonacci number. For 32 bit integrals +// this is 47th Fibonacci number. +constexpr size_t MaxCordDepth() { return sizeof(size_t) == 8 ? 93 : 47; } + +// This class models fixed max size stack of CordRep pointers. +// The elements are being pushed back and popped from the back. +template +class CordTreePath { + public: + CordTreePath() {} + explicit CordTreePath(CordRepPtr root) { push_back(root); } + + bool empty() const { return size_ == 0; } + size_t size() const { return size_; } + void clear() { size_ = 0; } + + CordRepPtr back() { return data_[size_ - 1]; } + + void pop_back() { + --size_; + assert(size_ < N); + } + void push_back(CordRepPtr elem) { data_[size_++] = elem; } + + private: + CordRepPtr data_[N]; + size_t size_ = 0; +}; + +// Fixed length container for mutable "path" in cord tree, which can hold any +// possible valid path in cord tree. +using CordTreeMutablePath = CordTreePath; +// Variable length container for mutable "path" in cord tree. It starts with +// capacity for 15 elements and grow if necessary. +using CordTreeDynamicPath = + absl::InlinedVector; +} // namespace cord_internal + // A Cord is a sequence of characters. class Cord { private: @@ -75,53 +186,124 @@ class Cord { using EnableIfString = absl::enable_if_t::value, int>; + //---------------------------------------------------------------------------- + // Cord::GenericChunkIterator + //---------------------------------------------------------------------------- + // + // A `Cord::GenericChunkIterator` provides an interface for the standard + // `Cord::ChunkIterator` as well as some private implementations. + template + class GenericChunkIterator { + public: + using iterator_category = std::input_iterator_tag; + using value_type = absl::string_view; + using difference_type = ptrdiff_t; + using pointer = const value_type*; + using reference = value_type; + + GenericChunkIterator() = default; + + GenericChunkIterator& operator++(); + GenericChunkIterator operator++(int); + bool operator==(const GenericChunkIterator& other) const; + bool operator!=(const GenericChunkIterator& other) const; + reference operator*() const; + pointer operator->() const; + + friend class Cord; + friend class CharIterator; + + private: + // Constructs a `begin()` iterator from `cord`. + explicit GenericChunkIterator(const Cord* cord); + + // Removes `n` bytes from `current_chunk_`. Expects `n` to be smaller than + // `current_chunk_.size()`. + void RemoveChunkPrefix(size_t n); + Cord AdvanceAndReadBytes(size_t n); + void AdvanceBytes(size_t n); + // Iterates `n` bytes, where `n` is expected to be greater than or equal to + // `current_chunk_.size()`. + void AdvanceBytesSlowPath(size_t n); + + // A view into bytes of the current `CordRep`. It may only be a view to a + // suffix of bytes if this is being used by `CharIterator`. + absl::string_view current_chunk_; + // The current leaf, or `nullptr` if the iterator points to short data. + // If the current chunk is a substring node, current_leaf_ points to the + // underlying flat or external node. + cord_internal::CordRep* current_leaf_ = nullptr; + // The number of bytes left in the `Cord` over which we are iterating. + size_t bytes_remaining_ = 0; + StorageType stack_of_right_children_; + }; + template + class GenericChunkRange { + public: + explicit GenericChunkRange(const Cord* cord) : cord_(cord) {} + + IteratorType begin() const { return IteratorType(cord_); } + IteratorType end() const { return IteratorType(); } + + private: + const Cord* cord_; + }; + public: - // -------------------------------------------------------------------- - // Constructors, destructors and helper factories + // Cord::Cord() Constructors - // Create an empty cord + // Creates an empty Cord constexpr Cord() noexcept; - // Cord is copyable and efficiently movable. - // The moved-from state is valid but unspecified. + // Creates a Cord from an existing Cord. Cord is copyable and efficiently + // movable. The moved-from state is valid but unspecified. Cord(const Cord& src); Cord(Cord&& src) noexcept; Cord& operator=(const Cord& x); Cord& operator=(Cord&& x) noexcept; - // Create a cord out of "src". This constructor is explicit on - // purpose so that people do not get automatic type conversions. + // Creates a Cord from a `src` string. This constructor is marked explicit to + // prevent implicit Cord constructions from arguments convertible to an + // `absl::string_view`. explicit Cord(absl::string_view src); Cord& operator=(absl::string_view src); - // These are templated to avoid ambiguities for types that are convertible to - // both `absl::string_view` and `std::string`, such as `const char*`. + // Creates a Cord from a `std::string&&` rvalue. These constructors are + // templated to avoid ambiguities for types that are convertible to both + // `absl::string_view` and `std::string`, such as `const char*`. // - // Note that these functions reserve the right to reuse the `string&&`'s + // Note that these functions reserve the right to use the `string&&`'s // memory and that they will do so in the future. template = 0> explicit Cord(T&& src) : Cord(absl::string_view(src)) {} template = 0> Cord& operator=(T&& src); - // Destroy the cord + // Cord::~Cord() + // + // Destructs the Cord ~Cord() { if (contents_.is_tree()) DestroyCordSlow(); } - // Creates a Cord that takes ownership of external memory. The contents of - // `data` are not copied. + // Cord::MakeCordFromExternal(data, callable) + // + // Creates a Cord that takes ownership of external string memory. The + // contents of `data` are not copied to the Cord; instead, the external + // memory is added to the Cord and reference-counted. This data may not be + // changed for the life of the Cord, though it may be prepended or appended + // to. + // + // `MakeCordFromExternal()` takes a callable "releaser" that is invoked when + // the reference count for `data` reaches zero. As noted above, this data must + // remain live until the releaser is invoked. The callable releaser also must: // - // This function takes a callable that is invoked when all Cords are - // finished with `data`. The data must remain live and unchanging until the - // releaser is called. The requirements for the releaser are that it: - // * is move constructible, - // * supports `void operator()(absl::string_view) const` or - // `void operator()() const`, - // * does not have alignment requirement greater than what is guaranteed by - // ::operator new. This is dictated by alignof(std::max_align_t) before - // C++17 and __STDCPP_DEFAULT_NEW_ALIGNMENT__ if compiling with C++17 or - // it is supported by the implementation. + // * be move constructible + // * support `void operator()(absl::string_view) const` or `void operator()` + // * not have alignment requirement greater than what is guaranteed by + // `::operator new`. This alignment is dictated by + // `alignof(std::max_align_t)` (pre-C++17 code) or + // `__STDCPP_DEFAULT_NEW_ALIGNMENT__` (C++17 code). // // Example: // @@ -135,8 +317,8 @@ class Cord { // }); // } // - // WARNING: It's likely a bug if your releaser doesn't do anything. - // For example, consider the following: + // WARNING: Because a Cord can be reference-counted, it's likely a bug if your + // releaser doesn't do anything. For example, consider the following: // // void Foo(const char* buffer, int len) { // auto c = absl::MakeCordFromExternal(absl::string_view(buffer, len), @@ -150,67 +332,100 @@ class Cord { template friend Cord MakeCordFromExternal(absl::string_view data, Releaser&& releaser); - // -------------------------------------------------------------------- - // Mutations - + // Cord::Clear() + // + // Releases the Cord data. Any nodes that share data with other Cords, if + // applicable, will have their reference counts reduced by 1. void Clear(); + // Cord::Append() + // + // Appends data to the Cord, which may come from another Cord or other string + // data. void Append(const Cord& src); void Append(Cord&& src); void Append(absl::string_view src); template = 0> void Append(T&& src); + // Cord::Prepend() + // + // Prepends data to the Cord, which may come from another Cord or other string + // data. void Prepend(const Cord& src); void Prepend(absl::string_view src); template = 0> void Prepend(T&& src); + // Cord::RemovePrefix() + // + // Removes the first `n` bytes of a Cord. void RemovePrefix(size_t n); void RemoveSuffix(size_t n); - // Returns a new cord representing the subrange [pos, pos + new_size) of + // Cord::Subcord() + // + // Returns a new Cord representing the subrange [pos, pos + new_size) of // *this. If pos >= size(), the result is empty(). If // (pos + new_size) >= size(), the result is the subrange [pos, size()). Cord Subcord(size_t pos, size_t new_size) const; + // swap() + // + // Swaps the data of Cord `x` with Cord `y`. friend void swap(Cord& x, Cord& y) noexcept; - // -------------------------------------------------------------------- - // Accessors - + // Cord::size() + // + // Returns the size of the Cord. size_t size() const; + + // Cord::empty() + // + // Determines whether the given Cord is empty, returning `true` is so. bool empty() const; - // Returns the approximate number of bytes pinned by this Cord. Note that - // Cords that share memory could each be "charged" independently for the same - // shared memory. + // Cord:EstimatedMemoryUsage() + // + // Returns the *approximate* number of bytes held in full or in part by this + // Cord (which may not remain the same between invocations). Note that Cords + // that share memory could each be "charged" independently for the same shared + // memory. size_t EstimatedMemoryUsage() const; - // -------------------------------------------------------------------- - // Comparators - - // Compares 'this' Cord with rhs. This function and its relatives - // treat Cords as sequences of unsigned bytes. The comparison is a - // straightforward lexicographic comparison. Return value: + // Cord::Compare() + // + // Compares 'this' Cord with rhs. This function and its relatives treat Cords + // as sequences of unsigned bytes. The comparison is a straightforward + // lexicographic comparison. `Cord::Compare()` returns values as follows: + // // -1 'this' Cord is smaller // 0 two Cords are equal // 1 'this' Cord is larger int Compare(absl::string_view rhs) const; int Compare(const Cord& rhs) const; - // Does 'this' cord start/end with rhs + // Cord::StartsWith() + // + // Determines whether the Cord starts with the passed string data `rhs`. bool StartsWith(const Cord& rhs) const; bool StartsWith(absl::string_view rhs) const; + + // Cord::EndsWidth() + // + // Determines whether the Cord ends with the passed string data `rhs`. bool EndsWith(absl::string_view rhs) const; bool EndsWith(const Cord& rhs) const; - // -------------------------------------------------------------------- - // Conversion to other types - + // Cord::operator std::string() + // + // Converts a Cord into a `std::string()`. This operator is marked explicit to + // prevent unintended Cord usage in functions that take a string. explicit operator std::string() const; - // Copies the contents from `src` to `*dst`. + // CopyCordToString() + // + // Copies the contents of a `src` Cord into a `*dst` string. // // This function optimizes the case of reusing the destination string since it // can reuse previously allocated capacity. However, this function does not @@ -219,80 +434,46 @@ class Cord { // object, prefer to simply use the conversion operator to `std::string`. friend void CopyCordToString(const Cord& src, std::string* dst); - // -------------------------------------------------------------------- - // Iteration - class CharIterator; - // Type for iterating over the chunks of a `Cord`. See comments for - // `Cord::chunk_begin()`, `Cord::chunk_end()` and `Cord::Chunks()` below for - // preferred usage. + //---------------------------------------------------------------------------- + // Cord::ChunkIterator + //---------------------------------------------------------------------------- + // + // A `Cord::ChunkIterator` allows iteration over the constituent chunks of its + // Cord. Such iteration allows you to perform non-const operatons on the data + // of a Cord without modifying it. + // + // Generally, you do not instantiate a `Cord::ChunkIterator` directly; + // instead, you create one implicitly through use of the `Cord::Chunks()` + // member function. // - // Additional notes: + // The `Cord::ChunkIterator` has the following properties: + // + // * The iterator is invalidated after any non-const operation on the + // Cord object over which it iterates. // * The `string_view` returned by dereferencing a valid, non-`end()` // iterator is guaranteed to be non-empty. - // * A `ChunkIterator` object is invalidated after any non-const - // operation on the `Cord` object over which it iterates. - // * Two `ChunkIterator` objects can be equality compared if and only if - // they remain valid and iterate over the same `Cord`. - // * This is a proxy iterator. This means the `string_view` returned by the - // iterator does not live inside the Cord, and its lifetime is limited to - // the lifetime of the iterator itself. To help prevent issues, - // `ChunkIterator::reference` is not a true reference type and is - // equivalent to `value_type`. - // * The iterator keeps state that can grow for `Cord`s that contain many + // * Two `ChunkIterator` objects can be compared equal if and only if they + // remain valid and iterate over the same Cord. + // * The iterator in this case is a proxy iterator; the `string_view` + // returned by the iterator does not live inside the Cord, and its + // lifetime is limited to the lifetime of the iterator itself. To help + // prevent lifetime issues, `ChunkIterator::reference` is not a true + // reference type and is equivalent to `value_type`. + // * The iterator keeps state that can grow for Cords that contain many // nodes and are imbalanced due to sharing. Prefer to pass this type by // const reference instead of by value. - class ChunkIterator { - public: - using iterator_category = std::input_iterator_tag; - using value_type = absl::string_view; - using difference_type = ptrdiff_t; - using pointer = const value_type*; - using reference = value_type; - - ChunkIterator() = default; - - ChunkIterator& operator++(); - ChunkIterator operator++(int); - bool operator==(const ChunkIterator& other) const; - bool operator!=(const ChunkIterator& other) const; - reference operator*() const; - pointer operator->() const; - - friend class Cord; - friend class CharIterator; - - private: - // Constructs a `begin()` iterator from `cord`. - explicit ChunkIterator(const Cord* cord); - - // Removes `n` bytes from `current_chunk_`. Expects `n` to be smaller than - // `current_chunk_.size()`. - void RemoveChunkPrefix(size_t n); - Cord AdvanceAndReadBytes(size_t n); - void AdvanceBytes(size_t n); - // Iterates `n` bytes, where `n` is expected to be greater than or equal to - // `current_chunk_.size()`. - void AdvanceBytesSlowPath(size_t n); - - // A view into bytes of the current `CordRep`. It may only be a view to a - // suffix of bytes if this is being used by `CharIterator`. - absl::string_view current_chunk_; - // The current leaf, or `nullptr` if the iterator points to short data. - // If the current chunk is a substring node, current_leaf_ points to the - // underlying flat or external node. - absl::cord_internal::CordRep* current_leaf_ = nullptr; - // The number of bytes left in the `Cord` over which we are iterating. - size_t bytes_remaining_ = 0; - absl::InlinedVector - stack_of_right_children_; - }; + using ChunkIterator = + GenericChunkIterator; + // Cord::ChunkIterator::chunk_begin() + // // Returns an iterator to the first chunk of the `Cord`. // - // This is useful for getting a `ChunkIterator` outside the context of a - // range-based for-loop (in which case see `Cord::Chunks()` below). + // Generally, prefer using `Cord::Chunks()` within a range-based for loop for + // iterating over the chunks of a Cord. This method may be useful for getting + // a `ChunkIterator` where range-based for-loops are not useful. // // Example: // @@ -301,26 +482,35 @@ class Cord { // return std::find(c.chunk_begin(), c.chunk_end(), s); // } ChunkIterator chunk_begin() const; + + // Cord::ChunkItertator::chunk_end() + // // Returns an iterator one increment past the last chunk of the `Cord`. + // + // Generally, prefer using `Cord::Chunks()` within a range-based for loop for + // iterating over the chunks of a Cord. This method may be useful for getting + // a `ChunkIterator` where range-based for-loops may not be available. ChunkIterator chunk_end() const; - // Convenience wrapper over `Cord::chunk_begin()` and `Cord::chunk_end()` to - // enable range-based for-loop iteration over `Cord` chunks. + //---------------------------------------------------------------------------- + // Cord::ChunkIterator::ChunkRange + //---------------------------------------------------------------------------- // - // Prefer to use `Cord::Chunks()` below instead of constructing this directly. - class ChunkRange { - public: - explicit ChunkRange(const Cord* cord) : cord_(cord) {} - - ChunkIterator begin() const; - ChunkIterator end() const; - - private: - const Cord* cord_; - }; + // `ChunkRange` is a helper class for iterating over the chunks of the `Cord`, + // producing an iterator which can be used within a range-based for loop. + // Construction of a `ChunkRange` will return an iterator pointing to the + // first chunk of the Cord. Generally, do not construct a `ChunkRange` + // directly; instead, prefer to use the `Cord::Chunks()` method. + // + // Implementation note: `ChunkRange` is simply a convenience wrapper over + // `Cord::chunk_begin()` and `Cord::chunk_end()`. + using ChunkRange = GenericChunkRange; - // Returns a range for iterating over the chunks of a `Cord` with a - // range-based for-loop. + // Cord::Chunks() + // + // Returns a `Cord::ChunkIterator::ChunkRange` for iterating over the chunks + // of a `Cord` with a range-based for-loop. For most iteration tasks on a + // Cord, use `Cord::Chunks()` to retrieve this iterator. // // Example: // @@ -337,22 +527,30 @@ class Cord { // } ChunkRange Chunks() const; - // Type for iterating over the characters of a `Cord`. See comments for - // `Cord::char_begin()`, `Cord::char_end()` and `Cord::Chars()` below for - // preferred usage. + //---------------------------------------------------------------------------- + // Cord::CharIterator + //---------------------------------------------------------------------------- + // + // A `Cord::CharIterator` allows iteration over the constituent characters of + // a `Cord`. + // + // Generally, you do not instantiate a `Cord::CharIterator` directly; instead, + // you create one implicitly through use of the `Cord::Chars()` member + // function. + // + // A `Cord::CharIterator` has the following properties: // - // Additional notes: - // * A `CharIterator` object is invalidated after any non-const - // operation on the `Cord` object over which it iterates. - // * Two `CharIterator` objects can be equality compared if and only if - // they remain valid and iterate over the same `Cord`. - // * The iterator keeps state that can grow for `Cord`s that contain many + // * The iterator is invalidated after any non-const operation on the + // Cord object over which it iterates. + // * Two `CharIterator` objects can be compared equal if and only if they + // remain valid and iterate over the same Cord. + // * The iterator keeps state that can grow for Cords that contain many // nodes and are imbalanced due to sharing. Prefer to pass this type by // const reference instead of by value. - // * This type cannot be a forward iterator because a `Cord` can reuse - // sections of memory. This violates the requirement that if dereferencing - // two iterators returns the same object, the iterators must compare - // equal. + // * This type cannot act as a forward iterator because a `Cord` can reuse + // sections of memory. This fact violates the requirement for forward + // iterators to compare equal if dereferencing them returns the same + // object. class CharIterator { public: using iterator_category = std::input_iterator_tag; @@ -378,34 +576,56 @@ class Cord { ChunkIterator chunk_iterator_; }; - // Advances `*it` by `n_bytes` and returns the bytes passed as a `Cord`. + // Cord::CharIterator::AdvanceAndRead() // - // `n_bytes` must be less than or equal to the number of bytes remaining for - // iteration. Otherwise the behavior is undefined. It is valid to pass - // `char_end()` and 0. + // Advances the `Cord::CharIterator` by `n_bytes` and returns the bytes + // advanced as a separate `Cord`. `n_bytes` must be less than or equal to the + // number of bytes within the Cord; otherwise, behavior is undefined. It is + // valid to pass `char_end()` and `0`. static Cord AdvanceAndRead(CharIterator* it, size_t n_bytes); - // Advances `*it` by `n_bytes`. + // Cord::CharIterator::Advance() // - // `n_bytes` must be less than or equal to the number of bytes remaining for - // iteration. Otherwise the behavior is undefined. It is valid to pass - // `char_end()` and 0. + // Advances the `Cord::CharIterator` by `n_bytes`. `n_bytes` must be less than + // or equal to the number of bytes remaining within the Cord; otherwise, + // behavior is undefined. It is valid to pass `char_end()` and `0`. static void Advance(CharIterator* it, size_t n_bytes); + // Cord::CharIterator::ChunkRemaining() + // // Returns the longest contiguous view starting at the iterator's position. // // `it` must be dereferenceable. static absl::string_view ChunkRemaining(const CharIterator& it); + // Cord::CharIterator::char_begin() + // // Returns an iterator to the first character of the `Cord`. + // + // Generally, prefer using `Cord::Chars()` within a range-based for loop for + // iterating over the chunks of a Cord. This method may be useful for getting + // a `CharIterator` where range-based for-loops may not be available. CharIterator char_begin() const; + + // Cord::CharIterator::char_end() + // // Returns an iterator to one past the last character of the `Cord`. + // + // Generally, prefer using `Cord::Chars()` within a range-based for loop for + // iterating over the chunks of a Cord. This method may be useful for getting + // a `CharIterator` where range-based for-loops are not useful. CharIterator char_end() const; - // Convenience wrapper over `Cord::char_begin()` and `Cord::char_end()` to - // enable range-based for-loop iterator over the characters of a `Cord`. + // Cord::CharIterator::CharRange // - // Prefer to use `Cord::Chars()` below instead of constructing this directly. + // `CharRange` is a helper class for iterating over the characters of a + // producing an iterator which can be used within a range-based for loop. + // Construction of a `CharRange` will return an iterator pointing to the first + // character of the Cord. Generally, do not construct a `CharRange` directly; + // instead, prefer to use the `Cord::Chars()` method show below. + // + // Implementation note: `CharRange` is simply a convenience wrapper over + // `Cord::char_begin()` and `Cord::char_end()`. class CharRange { public: explicit CharRange(const Cord* cord) : cord_(cord) {} @@ -417,8 +637,11 @@ class Cord { const Cord* cord_; }; - // Returns a range for iterating over the characters of a `Cord` with a - // range-based for-loop. + // Cord::CharIterator::Chars() + // + // Returns a `Cord::CharIterator` for iterating over the characters of a + // `Cord` with a range-based for-loop. For most character-based iteration + // tasks on a Cord, use `Cord::Chars()` to retrieve this iterator. // // Example: // @@ -435,23 +658,26 @@ class Cord { // } CharRange Chars() const; - // -------------------------------------------------------------------- - // Miscellaneous - - // Get the "i"th character of 'this' and return it. - // NOTE: This routine is reasonably efficient. It is roughly - // logarithmic in the number of nodes that make up the cord. Still, - // if you need to iterate over the contents of a cord, you should - // use a CharIterator/CordIterator rather than call operator[] or Get() - // repeatedly in a loop. + // Cord::operator[] + // + // Get the "i"th character of the Cord and returns it, provided that + // 0 <= i < Cord.size(). // - // REQUIRES: 0 <= i < size() + // NOTE: This routine is reasonably efficient. It is roughly + // logarithmic based on the number of chunks that make up the cord. Still, + // if you need to iterate over the contents of a cord, you should + // use a CharIterator/ChunkIterator rather than call operator[] or Get() + // repeatedly in a loop. char operator[](size_t i) const; + // Cord::TryFlat() + // // If this cord's representation is a single flat array, return a // string_view referencing that array. Otherwise return nullopt. absl::optional TryFlat() const; + // Cord::Flatten() + // // Flattens the cord into a single array and returns a view of the data. // // If the cord was already flat, the contents are not modified. @@ -574,6 +800,14 @@ class Cord { static bool GetFlatAux(absl::cord_internal::CordRep* rep, absl::string_view* fragment); + // Iterators for use inside Cord implementation + using InternalChunkIterator = + GenericChunkIterator; + using InternalChunkRange = GenericChunkRange; + + InternalChunkIterator internal_chunk_begin() const; + InternalChunkRange InternalChunks() const; + // Helper for ForEachChunk() static void ForEachChunkAux( absl::cord_internal::CordRep* rep, @@ -608,6 +842,11 @@ class Cord { void AppendImpl(C&& src); }; +extern template class Cord::GenericChunkIterator< + cord_internal::CordTreeMutablePath>; +extern template class Cord::GenericChunkIterator< + cord_internal::CordTreeDynamicPath>; + ABSL_NAMESPACE_END } // namespace absl @@ -947,7 +1186,9 @@ inline bool Cord::StartsWith(absl::string_view rhs) const { return EqualsImpl(rhs, rhs_size); } -inline Cord::ChunkIterator::ChunkIterator(const Cord* cord) +template +inline Cord::GenericChunkIterator::GenericChunkIterator( + const Cord* cord) : bytes_remaining_(cord->size()) { if (cord->empty()) return; if (cord->contents_.is_tree()) { @@ -958,37 +1199,50 @@ inline Cord::ChunkIterator::ChunkIterator(const Cord* cord) } } -inline Cord::ChunkIterator Cord::ChunkIterator::operator++(int) { - ChunkIterator tmp(*this); +template +inline Cord::GenericChunkIterator +Cord::GenericChunkIterator::operator++(int) { + GenericChunkIterator tmp(*this); operator++(); return tmp; } -inline bool Cord::ChunkIterator::operator==(const ChunkIterator& other) const { +template +inline bool Cord::GenericChunkIterator::operator==( + const GenericChunkIterator& other) const { return bytes_remaining_ == other.bytes_remaining_; } -inline bool Cord::ChunkIterator::operator!=(const ChunkIterator& other) const { +template +inline bool Cord::GenericChunkIterator::operator!=( + const GenericChunkIterator& other) const { return !(*this == other); } -inline Cord::ChunkIterator::reference Cord::ChunkIterator::operator*() const { - assert(bytes_remaining_ != 0); +template +inline typename Cord::GenericChunkIterator::reference +Cord::GenericChunkIterator::operator*() const { + ABSL_HARDENING_ASSERT(bytes_remaining_ != 0); return current_chunk_; } -inline Cord::ChunkIterator::pointer Cord::ChunkIterator::operator->() const { - assert(bytes_remaining_ != 0); +template +inline typename Cord::GenericChunkIterator::pointer +Cord::GenericChunkIterator::operator->() const { + ABSL_HARDENING_ASSERT(bytes_remaining_ != 0); return ¤t_chunk_; } -inline void Cord::ChunkIterator::RemoveChunkPrefix(size_t n) { +template +inline void Cord::GenericChunkIterator::RemoveChunkPrefix( + size_t n) { assert(n < current_chunk_.size()); current_chunk_.remove_prefix(n); bytes_remaining_ -= n; } -inline void Cord::ChunkIterator::AdvanceBytes(size_t n) { +template +inline void Cord::GenericChunkIterator::AdvanceBytes(size_t n) { if (ABSL_PREDICT_TRUE(n < current_chunk_.size())) { RemoveChunkPrefix(n); } else if (n != 0) { @@ -1002,14 +1256,6 @@ inline Cord::ChunkIterator Cord::chunk_begin() const { inline Cord::ChunkIterator Cord::chunk_end() const { return ChunkIterator(); } -inline Cord::ChunkIterator Cord::ChunkRange::begin() const { - return cord_->chunk_begin(); -} - -inline Cord::ChunkIterator Cord::ChunkRange::end() const { - return cord_->chunk_end(); -} - inline Cord::ChunkRange Cord::Chunks() const { return ChunkRange(this); } inline Cord::CharIterator& Cord::CharIterator::operator++() { diff --git a/absl/strings/cord_test.cc b/absl/strings/cord_test.cc index 4afa4a26..0ec93b4c 100644 --- a/absl/strings/cord_test.cc +++ b/absl/strings/cord_test.cc @@ -18,6 +18,7 @@ #include "absl/base/config.h" #include "absl/base/internal/endian.h" #include "absl/base/internal/raw_logging.h" +#include "absl/base/macros.h" #include "absl/container/fixed_array.h" #include "absl/strings/cord_test_helpers.h" #include "absl/strings/str_cat.h" @@ -1402,6 +1403,53 @@ TEST(CordChunkIterator, Operations) { VerifyChunkIterator(subcords, 128); } +TEST(CordChunkIterator, MaxLengthFullTree) { + // Start with a 1-byte cord, and then double its length in a loop. We should + // be able to do this until the point where we would overflow size_t. + + absl::Cord cord; + size_t size = 1; + AddExternalMemory("x", &cord); + EXPECT_EQ(cord.size(), size); + + const int kCordLengthDoublingLimit = std::numeric_limits::digits - 1; + for (int i = 0; i < kCordLengthDoublingLimit; ++i) { + cord.Prepend(absl::Cord(cord)); + size <<= 1; + + EXPECT_EQ(cord.size(), size); + + auto chunk_it = cord.chunk_begin(); + EXPECT_EQ(*chunk_it, "x"); + } + + EXPECT_DEATH_IF_SUPPORTED( + (cord.Prepend(absl::Cord(cord)), *cord.chunk_begin()), + "Cord is too long"); +} + +TEST(CordChunkIterator, MaxDepth) { + // By reusing nodes, it's possible in pathological cases to build a Cord that + // exceeds both the maximum permissible length and depth. In this case, the + // violation of the maximum depth is reported. + absl::Cord left_child; + AddExternalMemory("x", &left_child); + absl::Cord root = left_child; + + for (int i = 0; i < absl::cord_internal::MaxCordDepth() - 2; ++i) { + size_t new_size = left_child.size() + root.size(); + root.Prepend(left_child); + EXPECT_EQ(root.size(), new_size); + + auto chunk_it = root.chunk_begin(); + EXPECT_EQ(*chunk_it, "x"); + + std::swap(left_child, root); + } + + EXPECT_DEATH_IF_SUPPORTED(root.Prepend(left_child), "Cord is too long"); +} + TEST(CordCharIterator, Traits) { static_assert(std::is_copy_constructible::value, ""); @@ -1580,3 +1628,23 @@ TEST(Cord, SmallBufferAssignFromOwnData) { } } } + +TEST(CordDeathTest, Hardening) { + absl::Cord cord("hello"); + // These statement should abort the program in all builds modes. + EXPECT_DEATH_IF_SUPPORTED(cord.RemovePrefix(6), ""); + EXPECT_DEATH_IF_SUPPORTED(cord.RemoveSuffix(6), ""); + + bool test_hardening = false; + ABSL_HARDENING_ASSERT([&]() { + // This only runs when ABSL_HARDENING_ASSERT is active. + test_hardening = true; + return true; + }()); + if (!test_hardening) return; + + EXPECT_DEATH_IF_SUPPORTED(cord[5], ""); + EXPECT_DEATH_IF_SUPPORTED(*cord.chunk_end(), ""); + EXPECT_DEATH_IF_SUPPORTED(static_cast(cord.chunk_end()->empty()), ""); + EXPECT_DEATH_IF_SUPPORTED(++cord.chunk_end(), ""); +} diff --git a/absl/strings/internal/str_format/extension.h b/absl/strings/internal/str_format/extension.h index 968850eb..bae2c078 100644 --- a/absl/strings/internal/str_format/extension.h +++ b/absl/strings/internal/str_format/extension.h @@ -155,8 +155,7 @@ enum class FormatConversionChar : uint8_t { d, i, o, u, x, X, // int f, F, e, E, g, G, a, A, // float n, p, // misc - kNone, - none = kNone + kNone }; // clang-format on @@ -288,11 +287,6 @@ class FormatConversionSpec { // negative value. int precision() const { return precision_; } - // Deprecated (use has_x_flag() instead). - Flags flags() const { return flags_; } - // Deprecated - FormatConversionChar conv() const { return conversion_char(); } - private: friend struct str_format_internal::FormatConversionSpecImplFriend; FormatConversionChar conv_ = FormatConversionChar::kNone; @@ -344,15 +338,7 @@ enum class FormatConversionCharSet : uint64_t { kFloating = a | e | f | g | A | E | F | G, kNumeric = kIntegral | kFloating, kString = s, - kPointer = p, - - // The following are deprecated - star = kStar, - integral = kIntegral, - floating = kFloating, - numeric = kNumeric, - string = kString, - pointer = kPointer + kPointer = p }; // Type safe OR operator. diff --git a/absl/strings/string_view.h b/absl/strings/string_view.h index 8e348fcd..8a9db8c3 100644 --- a/absl/strings/string_view.h +++ b/absl/strings/string_view.h @@ -48,7 +48,7 @@ namespace absl { ABSL_NAMESPACE_BEGIN -using std::string_view; +using string_view = std::string_view; ABSL_NAMESPACE_END } // namespace absl -- cgit v1.2.3 From 62f05b1f57ad660e9c09e02ce7d591dcc4d0ca08 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Wed, 1 Apr 2020 07:32:27 -0700 Subject: Export of internal Abseil changes -- 3e6352709da9a529e608eabff862a12bfaecb587 by Gennadiy Rozental : Replace local copy of FastTypeId with one shared in absl/base/internal. PiperOrigin-RevId: 304181357 -- c89ea428f732226f4dceb508cd6ba3955a1e49e1 by Andy Getzendanner : Typo fix: add a missing colon. PiperOrigin-RevId: 304064210 -- de2ee7a96bdc7193ffcceb6a2fd6bf464955cbe7 by Samuel Benzaquen : Reduce the overhead of the registration token by using an empty struct instead of bool. PiperOrigin-RevId: 304054311 -- 222f05d24fb1df7e815946543a7dc78847c83f92 by Derek Mauro : Turn off hashtablez in opensource builds. Hashtablez is an unsupported, internal-only feature for collecting information about hashtable usage and performance. By turning it off in builds where it is unsupported, we get just a little more performance. PiperOrigin-RevId: 304035460 GitOrigin-RevId: 3e6352709da9a529e608eabff862a12bfaecb587 Change-Id: I0bfe9b5df808a7e35c154b39e6c80e68b0da2b70 --- absl/container/BUILD.bazel | 2 ++ absl/container/CMakeLists.txt | 2 ++ absl/container/flat_hash_map_test.cc | 14 ++++++++++++++ absl/container/flat_hash_set_test.cc | 12 ++++++++++++ absl/container/internal/hashtablez_sampler.h | 5 ----- absl/flags/flag.h | 10 ++++++---- absl/flags/internal/flag.h | 8 +++++--- absl/strings/cord.h | 2 +- absl/types/BUILD.bazel | 1 + absl/types/CMakeLists.txt | 1 + absl/types/any.h | 25 +++---------------------- 11 files changed, 47 insertions(+), 35 deletions(-) (limited to 'absl/strings/cord.h') diff --git a/absl/container/BUILD.bazel b/absl/container/BUILD.bazel index 1b0710b8..3606faa7 100644 --- a/absl/container/BUILD.bazel +++ b/absl/container/BUILD.bazel @@ -257,6 +257,7 @@ cc_test( ":unordered_map_lookup_test", ":unordered_map_members_test", ":unordered_map_modifiers_test", + "//absl/base:raw_logging_internal", "//absl/types:any", "@com_google_googletest//:gtest_main", ], @@ -290,6 +291,7 @@ cc_test( ":unordered_set_lookup_test", ":unordered_set_members_test", ":unordered_set_modifiers_test", + "//absl/base:raw_logging_internal", "//absl/memory", "//absl/strings", "@com_google_googletest//:gtest_main", diff --git a/absl/container/CMakeLists.txt b/absl/container/CMakeLists.txt index d79fa12e..392f3284 100644 --- a/absl/container/CMakeLists.txt +++ b/absl/container/CMakeLists.txt @@ -303,6 +303,7 @@ absl_cc_test( absl::unordered_map_members_test absl::unordered_map_modifiers_test absl::any + absl::raw_logging_internal gmock_main ) @@ -339,6 +340,7 @@ absl_cc_test( absl::unordered_set_members_test absl::unordered_set_modifiers_test absl::memory + absl::raw_logging_internal absl::strings gmock_main ) diff --git a/absl/container/flat_hash_map_test.cc b/absl/container/flat_hash_map_test.cc index 728b693a..2823c32b 100644 --- a/absl/container/flat_hash_map_test.cc +++ b/absl/container/flat_hash_map_test.cc @@ -16,6 +16,7 @@ #include +#include "absl/base/internal/raw_logging.h" #include "absl/container/internal/hash_generator_testing.h" #include "absl/container/internal/unordered_map_constructor_test.h" #include "absl/container/internal/unordered_map_lookup_test.h" @@ -34,6 +35,19 @@ using ::testing::IsEmpty; using ::testing::Pair; using ::testing::UnorderedElementsAre; +// Check that absl::flat_hash_map works in a global constructor. +struct BeforeMain { + BeforeMain() { + absl::flat_hash_map x; + x.insert({1, 1}); + ABSL_RAW_CHECK(x.find(0) == x.end(), "x should not contain 0"); + auto it = x.find(1); + ABSL_RAW_CHECK(it != x.end(), "x should contain 1"); + ABSL_RAW_CHECK(it->second, "1 should map to 1"); + } +}; +const BeforeMain before_main; + template using Map = flat_hash_map>>; diff --git a/absl/container/flat_hash_set_test.cc b/absl/container/flat_hash_set_test.cc index 40d7f85c..8f6f9944 100644 --- a/absl/container/flat_hash_set_test.cc +++ b/absl/container/flat_hash_set_test.cc @@ -16,6 +16,7 @@ #include +#include "absl/base/internal/raw_logging.h" #include "absl/container/internal/hash_generator_testing.h" #include "absl/container/internal/unordered_set_constructor_test.h" #include "absl/container/internal/unordered_set_lookup_test.h" @@ -36,6 +37,17 @@ using ::testing::Pointee; using ::testing::UnorderedElementsAre; using ::testing::UnorderedElementsAreArray; +// Check that absl::flat_hash_set works in a global constructor. +struct BeforeMain { + BeforeMain() { + absl::flat_hash_set x; + x.insert(1); + ABSL_RAW_CHECK(!x.contains(0), "x should not contain 0"); + ABSL_RAW_CHECK(x.contains(1), "x should contain 1"); + } +}; +const BeforeMain before_main; + template using Set = absl::flat_hash_set>; diff --git a/absl/container/internal/hashtablez_sampler.h b/absl/container/internal/hashtablez_sampler.h index 8aaffc35..308119cf 100644 --- a/absl/container/internal/hashtablez_sampler.h +++ b/absl/container/internal/hashtablez_sampler.h @@ -184,11 +184,6 @@ class HashtablezInfoHandle { #error ABSL_INTERNAL_HASHTABLEZ_SAMPLE cannot be directly set #endif // defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE) -#if (ABSL_PER_THREAD_TLS == 1) && !defined(ABSL_BUILD_DLL) && \ - !defined(ABSL_CONSUME_DLL) -#define ABSL_INTERNAL_HASHTABLEZ_SAMPLE -#endif - #if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE) extern ABSL_PER_THREAD_TLS_KEYWORD int64_t global_next_sample; #endif // ABSL_PER_THREAD_TLS diff --git a/absl/flags/flag.h b/absl/flags/flag.h index bb917654..4cc8ae37 100644 --- a/absl/flags/flag.h +++ b/absl/flags/flag.h @@ -333,8 +333,9 @@ ABSL_NAMESPACE_END ABSL_FLAG_IMPL_FLAGNAME(#name), ABSL_FLAG_IMPL_FILENAME(), \ absl::flags_internal::HelpArg(0), \ &AbslFlagsInitFlag##name}; \ - extern bool FLAGS_no##name; \ - bool FLAGS_no##name = ABSL_FLAG_IMPL_REGISTRAR(Type, FLAGS_##name) + extern absl::flags_internal::FlagRegistrarEmpty FLAGS_no##name; \ + absl::flags_internal::FlagRegistrarEmpty FLAGS_no##name = \ + ABSL_FLAG_IMPL_REGISTRAR(Type, FLAGS_##name) #else // MSVC version uses aggregate initialization. We also do not try to // optimize away help wrapper. @@ -345,8 +346,9 @@ ABSL_NAMESPACE_END ABSL_CONST_INIT absl::Flag FLAGS_##name{ \ ABSL_FLAG_IMPL_FLAGNAME(#name), ABSL_FLAG_IMPL_FILENAME(), \ &AbslFlagHelpGenFor##name::NonConst, &AbslFlagsInitFlag##name}; \ - extern bool FLAGS_no##name; \ - bool FLAGS_no##name = ABSL_FLAG_IMPL_REGISTRAR(Type, FLAGS_##name) + extern absl::flags_internal::FlagRegistrarEmpty FLAGS_no##name; \ + absl::flags_internal::FlagRegistrarEmpty FLAGS_no##name = \ + ABSL_FLAG_IMPL_REGISTRAR(Type, FLAGS_##name) #endif // ABSL_RETIRED_FLAG diff --git a/absl/flags/internal/flag.h b/absl/flags/internal/flag.h index c1bf8652..ae42dedc 100644 --- a/absl/flags/internal/flag.h +++ b/absl/flags/internal/flag.h @@ -648,6 +648,7 @@ void* FlagOps(FlagOp op, const void* v1, void* v2, void* v3) { // This class facilitates Flag object registration and tail expression-based // flag definition, for example: // ABSL_FLAG(int, foo, 42, "Foo help").OnUpdate(NotifyFooWatcher); +struct FlagRegistrarEmpty {}; template class FlagRegistrar { public: @@ -660,9 +661,10 @@ class FlagRegistrar { return *this; } - // Make the registrar "die" gracefully as a bool on a line where registration - // happens. Registrar objects are intended to live only as temporary. - operator bool() const { return true; } // NOLINT + // Make the registrar "die" gracefully as an empty struct on a line where + // registration happens. Registrar objects are intended to live only as + // temporary. + operator FlagRegistrarEmpty() const { return {}; } // NOLINT private: Flag* flag_; // Flag being registered (not owned). diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 3ab3cb87..d5b9363f 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -385,7 +385,7 @@ class Cord { // Determines whether the given Cord is empty, returning `true` is so. bool empty() const; - // Cord:EstimatedMemoryUsage() + // Cord::EstimatedMemoryUsage() // // Returns the *approximate* number of bytes held in full or in part by this // Cord (which may not remain the same between invocations). Note that Cords diff --git a/absl/types/BUILD.bazel b/absl/types/BUILD.bazel index f2ea9f39..c64417cc 100644 --- a/absl/types/BUILD.bazel +++ b/absl/types/BUILD.bazel @@ -35,6 +35,7 @@ cc_library( ":bad_any_cast", "//absl/base:config", "//absl/base:core_headers", + "//absl/base:fast_type_id", "//absl/meta:type_traits", "//absl/utility", ], diff --git a/absl/types/CMakeLists.txt b/absl/types/CMakeLists.txt index c7c88250..1b4d453b 100644 --- a/absl/types/CMakeLists.txt +++ b/absl/types/CMakeLists.txt @@ -24,6 +24,7 @@ absl_cc_library( absl::bad_any_cast absl::config absl::core_headers + absl::fast_type_id absl::type_traits absl::utility PUBLIC diff --git a/absl/types/any.h b/absl/types/any.h index 16bda79c..7eed5197 100644 --- a/absl/types/any.h +++ b/absl/types/any.h @@ -80,6 +80,7 @@ ABSL_NAMESPACE_END #include #include +#include "absl/base/internal/fast_type_id.h" #include "absl/base/macros.h" #include "absl/meta/type_traits.h" #include "absl/types/bad_any_cast.h" @@ -95,26 +96,6 @@ ABSL_NAMESPACE_END namespace absl { ABSL_NAMESPACE_BEGIN -namespace any_internal { - -template -struct TypeTag { - constexpr static char dummy_var = 0; -}; - -template -constexpr char TypeTag::dummy_var; - -// FastTypeId() evaluates at compile/link-time to a unique pointer for the -// passed in type. These are meant to be good match for keys into maps or -// straight up comparisons. -template -constexpr inline const void* FastTypeId() { - return &TypeTag::dummy_var; -} - -} // namespace any_internal - class any; // swap() @@ -423,11 +404,11 @@ class any { using NormalizedType = typename std::remove_cv::type>::type; - return any_internal::FastTypeId(); + return base_internal::FastTypeId(); } const void* GetObjTypeId() const { - return obj_ ? obj_->ObjTypeId() : any_internal::FastTypeId(); + return obj_ ? obj_->ObjTypeId() : base_internal::FastTypeId(); } // `absl::any` nonmember functions // -- cgit v1.2.3 From 73ea9a95720c4f4faff9b1bcbad48d23f2cb2a46 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Mon, 6 Apr 2020 20:53:47 -0700 Subject: Export of internal Abseil changes -- 87cdfd6aa40941e116cd79ef70f9a7a8271db163 by Abseil Team : Fix a typo in random.h API documentation. PiperOrigin-RevId: 305176308 -- 8a38e1df49a18a954daca3ce617fd69045ff4c19 by Derek Mauro : Import GitHub #647: Allow external add_subdirectory for using GoogleTest PiperOrigin-RevId: 305156797 -- b1a2441536d4964fbe4e2329e74c322e6c41a4e6 by Gennadiy Rozental : temporary roll back. PiperOrigin-RevId: 305149619 -- c78767577264348d2f881893f9407aadfe73ab75 by CJ Johnson : Rollback update to linux_clang-latest container while investigating a compiler bug. PiperOrigin-RevId: 304897689 -- 3c6fd38f53d2e982569fdba4043f75271c7b5de4 by Derek Mauro : Update linux_clang-latest container to one based on Ubuntu 18.04, which has libstdc++-8. PiperOrigin-RevId: 304885120 GitOrigin-RevId: 87cdfd6aa40941e116cd79ef70f9a7a8271db163 Change-Id: Iefa6efee93907ec0eecb8add804c5cc2f052b64d --- CMakeLists.txt | 3 +- absl/random/random.h | 2 +- absl/strings/cord.cc | 248 +++++++++++++++++++--------------------------- absl/strings/cord.h | 232 ++++++++++++++----------------------------- absl/strings/cord_test.cc | 47 --------- 5 files changed, 177 insertions(+), 355 deletions(-) (limited to 'absl/strings/cord.h') diff --git a/CMakeLists.txt b/CMakeLists.txt index 560f5b4b..47670454 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,8 @@ endif() find_package(Threads REQUIRED) option(ABSL_USE_EXTERNAL_GOOGLETEST - "If ON, abseil will assume that the targets for googletest are already provided by the including project folder. This makes sense when abseil is used with add_subproject." OFF) + "If ON, Abseil will assume that the targets for GoogleTest are already provided by the including project. This makes sense when Abseil is used with add_subproject." OFF) + option(ABSL_USE_GOOGLETEST_HEAD "If ON, abseil will download HEAD from googletest at config time." OFF) diff --git a/absl/random/random.h b/absl/random/random.h index c8f326e6..71b63092 100644 --- a/absl/random/random.h +++ b/absl/random/random.h @@ -109,7 +109,7 @@ ABSL_NAMESPACE_BEGIN // absl::BitGen::max() // -// Returns the largest possible value from this bit generator., and +// Returns the largest possible value from this bit generator. // absl::BitGen::discard(num) // diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index 7de7766c..fa490c18 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc @@ -31,6 +31,7 @@ #include "absl/base/macros.h" #include "absl/base/port.h" #include "absl/container/fixed_array.h" +#include "absl/container/inlined_vector.h" #include "absl/strings/escaping.h" #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/resize_uninitialized.h" @@ -132,14 +133,6 @@ inline const CordRepExternal* CordRep::external() const { return static_cast(this); } -using CordTreeConstPath = CordTreePath; - -// This type is used to store the list of pending nodes during re-balancing. -// Its maximum size is 2 * MaxCordDepth() because the tree has a maximum -// possible depth of MaxCordDepth() and every concat node along a tree path -// could theoretically be split during rebalancing. -using RebalancingStack = CordTreePath; - } // namespace cord_internal static const size_t kFlatOverhead = offsetof(CordRep, data); @@ -188,78 +181,64 @@ static constexpr size_t TagToLength(uint8_t tag) { // Enforce that kMaxFlatSize maps to a well-known exact tag value. static_assert(TagToAllocatedSize(224) == kMaxFlatSize, "Bad tag logic"); -constexpr size_t Fibonacci(uint8_t n, const size_t a = 0, const size_t b = 1) { - return n == 0 - ? a - : n == 1 ? b - : Fibonacci(n - 1, b, - (a > (size_t(-1) - b)) ? size_t(-1) : a + b); +constexpr uint64_t Fibonacci(unsigned char n, uint64_t a = 0, uint64_t b = 1) { + return n == 0 ? a : Fibonacci(n - 1, b, a + b); } +static_assert(Fibonacci(63) == 6557470319842, + "Fibonacci values computed incorrectly"); + // Minimum length required for a given depth tree -- a tree is considered // balanced if -// length(t) >= kMinLength[depth(t)] -// The node depth is allowed to become larger to reduce rebalancing -// for larger strings (see ShouldRebalance). -constexpr size_t kMinLength[] = { - Fibonacci(2), Fibonacci(3), Fibonacci(4), Fibonacci(5), Fibonacci(6), - Fibonacci(7), Fibonacci(8), Fibonacci(9), Fibonacci(10), Fibonacci(11), - Fibonacci(12), Fibonacci(13), Fibonacci(14), Fibonacci(15), Fibonacci(16), - Fibonacci(17), Fibonacci(18), Fibonacci(19), Fibonacci(20), Fibonacci(21), - Fibonacci(22), Fibonacci(23), Fibonacci(24), Fibonacci(25), Fibonacci(26), - Fibonacci(27), Fibonacci(28), Fibonacci(29), Fibonacci(30), Fibonacci(31), - Fibonacci(32), Fibonacci(33), Fibonacci(34), Fibonacci(35), Fibonacci(36), - Fibonacci(37), Fibonacci(38), Fibonacci(39), Fibonacci(40), Fibonacci(41), - Fibonacci(42), Fibonacci(43), Fibonacci(44), Fibonacci(45), Fibonacci(46), - Fibonacci(47), Fibonacci(48), Fibonacci(49), Fibonacci(50), Fibonacci(51), - Fibonacci(52), Fibonacci(53), Fibonacci(54), Fibonacci(55), Fibonacci(56), - Fibonacci(57), Fibonacci(58), Fibonacci(59), Fibonacci(60), Fibonacci(61), - Fibonacci(62), Fibonacci(63), Fibonacci(64), Fibonacci(65), Fibonacci(66), - Fibonacci(67), Fibonacci(68), Fibonacci(69), Fibonacci(70), Fibonacci(71), - Fibonacci(72), Fibonacci(73), Fibonacci(74), Fibonacci(75), Fibonacci(76), - Fibonacci(77), Fibonacci(78), Fibonacci(79), Fibonacci(80), Fibonacci(81), - Fibonacci(82), Fibonacci(83), Fibonacci(84), Fibonacci(85), Fibonacci(86), - Fibonacci(87), Fibonacci(88), Fibonacci(89), Fibonacci(90), Fibonacci(91), - Fibonacci(92), Fibonacci(93), Fibonacci(94), Fibonacci(95)}; - -static_assert(sizeof(kMinLength) / sizeof(size_t) >= - (cord_internal::MaxCordDepth() + 1), - "Not enough elements in kMinLength array to cover all the " - "supported Cord depth(s)"); - -inline bool ShouldRebalance(const CordRep* node) { - if (node->tag != CONCAT) return false; - - size_t node_depth = node->concat()->depth(); - - if (node_depth <= 15) return false; - - // Rebalancing Cords is expensive, so we reduce how often rebalancing occurs - // by allowing shallow Cords to have twice the depth that the Fibonacci rule - // would otherwise imply. Deep Cords need to follow the rule more closely, - // however to ensure algorithm correctness. We implement this with linear - // interpolation. Cords of depth 16 are treated as though they have a depth - // of 16 * 1/2, and Cords of depth MaxCordDepth() interpolate to - // MaxCordDepth() * 1. - return node->length < - kMinLength[(node_depth * (cord_internal::MaxCordDepth() - 16)) / - (2 * cord_internal::MaxCordDepth() - 16 - node_depth)]; -} - -// Unlike root balancing condition this one is part of the re-balancing -// algorithm and has to be always matching against right depth for -// algorithm to be correct. -inline bool IsNodeBalanced(const CordRep* node) { - if (node->tag != CONCAT) return true; - - size_t node_depth = node->concat()->depth(); - - return node->length >= kMinLength[node_depth]; +// length(t) >= min_length[depth(t)] +// The root node depth is allowed to become twice as large to reduce rebalancing +// for larger strings (see IsRootBalanced). +static constexpr uint64_t min_length[] = { + Fibonacci(2), Fibonacci(3), Fibonacci(4), Fibonacci(5), + Fibonacci(6), Fibonacci(7), Fibonacci(8), Fibonacci(9), + Fibonacci(10), Fibonacci(11), Fibonacci(12), Fibonacci(13), + Fibonacci(14), Fibonacci(15), Fibonacci(16), Fibonacci(17), + Fibonacci(18), Fibonacci(19), Fibonacci(20), Fibonacci(21), + Fibonacci(22), Fibonacci(23), Fibonacci(24), Fibonacci(25), + Fibonacci(26), Fibonacci(27), Fibonacci(28), Fibonacci(29), + Fibonacci(30), Fibonacci(31), Fibonacci(32), Fibonacci(33), + Fibonacci(34), Fibonacci(35), Fibonacci(36), Fibonacci(37), + Fibonacci(38), Fibonacci(39), Fibonacci(40), Fibonacci(41), + Fibonacci(42), Fibonacci(43), Fibonacci(44), Fibonacci(45), + Fibonacci(46), Fibonacci(47), + 0xffffffffffffffffull, // Avoid overflow +}; + +static const int kMinLengthSize = ABSL_ARRAYSIZE(min_length); + +// The inlined size to use with absl::InlinedVector. +// +// Note: The InlinedVectors in this file (and in cord.h) do not need to use +// the same value for their inlined size. The fact that they do is historical. +// It may be desirable for each to use a different inlined size optimized for +// that InlinedVector's usage. +// +// TODO(jgm): Benchmark to see if there's a more optimal value than 47 for +// the inlined vector size (47 exists for backward compatibility). +static const int kInlinedVectorSize = 47; + +static inline bool IsRootBalanced(CordRep* node) { + if (node->tag != CONCAT) { + return true; + } else if (node->concat()->depth() <= 15) { + return true; + } else if (node->concat()->depth() > kMinLengthSize) { + return false; + } else { + // Allow depth to become twice as large as implied by fibonacci rule to + // reduce rebalancing for larger strings. + return (node->length >= min_length[node->concat()->depth() / 2]); + } } static CordRep* Rebalance(CordRep* node); -static void DumpNode(const CordRep* rep, bool include_data, std::ostream* os); -static bool VerifyNode(const CordRep* root, const CordRep* start_node, +static void DumpNode(CordRep* rep, bool include_data, std::ostream* os); +static bool VerifyNode(CordRep* root, CordRep* start_node, bool full_validation); static inline CordRep* VerifyTree(CordRep* node) { @@ -306,8 +285,7 @@ __attribute__((preserve_most)) static void UnrefInternal(CordRep* rep) { assert(rep != nullptr); - cord_internal::RebalancingStack pending; - + absl::InlinedVector pending; while (true) { if (rep->tag == CONCAT) { CordRepConcat* rep_concat = rep->concat(); @@ -389,11 +367,6 @@ static void SetConcatChildren(CordRepConcat* concat, CordRep* left, concat->length = left->length + right->length; concat->set_depth(1 + std::max(Depth(left), Depth(right))); - - ABSL_INTERNAL_CHECK(concat->depth() <= cord_internal::MaxCordDepth(), - "Cord depth exceeds max"); - ABSL_INTERNAL_CHECK(concat->length >= left->length, "Cord is too long"); - ABSL_INTERNAL_CHECK(concat->length >= right->length, "Cord is too long"); } // Create a concatenation of the specified nodes. @@ -419,7 +392,7 @@ static CordRep* RawConcat(CordRep* left, CordRep* right) { static CordRep* Concat(CordRep* left, CordRep* right) { CordRep* rep = RawConcat(left, right); - if (rep != nullptr && ShouldRebalance(rep)) { + if (rep != nullptr && !IsRootBalanced(rep)) { rep = Rebalance(rep); } return VerifyTree(rep); @@ -714,14 +687,6 @@ void Cord::InlineRep::ClearSlow() { memset(data_, 0, sizeof(data_)); } -inline Cord::InternalChunkIterator Cord::internal_chunk_begin() const { - return InternalChunkIterator(this); -} - -inline Cord::InternalChunkRange Cord::InternalChunks() const { - return InternalChunkRange(this); -} - // -------------------------------------------------------------------- // Constructors and destructors @@ -918,7 +883,7 @@ void Cord::Prepend(absl::string_view src) { static CordRep* RemovePrefixFrom(CordRep* node, size_t n) { if (n >= node->length) return nullptr; if (n == 0) return Ref(node); - cord_internal::CordTreeMutablePath rhs_stack; + absl::InlinedVector rhs_stack; while (node->tag == CONCAT) { assert(n <= node->length); @@ -959,7 +924,7 @@ static CordRep* RemovePrefixFrom(CordRep* node, size_t n) { static CordRep* RemoveSuffixFrom(CordRep* node, size_t n) { if (n >= node->length) return nullptr; if (n == 0) return Ref(node); - absl::cord_internal::CordTreeMutablePath lhs_stack; + absl::InlinedVector lhs_stack; bool inplace_ok = node->refcount.IsOne(); while (node->tag == CONCAT) { @@ -1030,7 +995,6 @@ void Cord::RemoveSuffix(size_t n) { // Work item for NewSubRange(). struct SubRange { - SubRange() = default; SubRange(CordRep* a_node, size_t a_pos, size_t a_n) : node(a_node), pos(a_pos), n(a_n) {} CordRep* node; // nullptr means concat last 2 results. @@ -1039,11 +1003,8 @@ struct SubRange { }; static CordRep* NewSubRange(CordRep* node, size_t pos, size_t n) { - cord_internal::CordTreeMutablePath results; - // The algorithm below in worst case scenario adds up to 3 nodes to the `todo` - // list, but we also pop one out on every cycle. If original tree has depth d - // todo list can grew up to 2*d in size. - cord_internal::CordTreePath todo; + absl::InlinedVector results; + absl::InlinedVector todo; todo.push_back(SubRange(node, pos, n)); do { const SubRange& sr = todo.back(); @@ -1080,7 +1041,7 @@ static CordRep* NewSubRange(CordRep* node, size_t pos, size_t n) { } } while (!todo.empty()); assert(results.size() == 1); - return results.back(); + return results[0]; } Cord Cord::Subcord(size_t pos, size_t new_size) const { @@ -1096,7 +1057,7 @@ Cord Cord::Subcord(size_t pos, size_t new_size) const { } else if (new_size == 0) { // We want to return empty subcord, so nothing to do. } else if (new_size <= InlineRep::kMaxInline) { - Cord::InternalChunkIterator it = internal_chunk_begin(); + Cord::ChunkIterator it = chunk_begin(); it.AdvanceBytes(pos); char* dest = sub_cord.contents_.data_; size_t remaining_size = new_size; @@ -1119,12 +1080,11 @@ Cord Cord::Subcord(size_t pos, size_t new_size) const { class CordForest { public: - explicit CordForest(size_t length) : root_length_(length), trees_({}) {} + explicit CordForest(size_t length) + : root_length_(length), trees_(kMinLengthSize, nullptr) {} void Build(CordRep* cord_root) { - // We are adding up to two nodes to the `pending` list, but we also popping - // one, so the size of `pending` will never exceed `MaxCordDepth()`. - cord_internal::CordTreeMutablePath pending(cord_root); + std::vector pending = {cord_root}; while (!pending.empty()) { CordRep* node = pending.back(); @@ -1136,20 +1096,21 @@ class CordForest { } CordRepConcat* concat_node = node->concat(); - if (IsNodeBalanced(concat_node)) { - AddNode(node); - continue; - } - pending.push_back(concat_node->right); - pending.push_back(concat_node->left); - - if (concat_node->refcount.IsOne()) { - concat_node->left = concat_freelist_; - concat_freelist_ = concat_node; + if (concat_node->depth() >= kMinLengthSize || + concat_node->length < min_length[concat_node->depth()]) { + pending.push_back(concat_node->right); + pending.push_back(concat_node->left); + + if (concat_node->refcount.IsOne()) { + concat_node->left = concat_freelist_; + concat_freelist_ = concat_node; + } else { + Ref(concat_node->right); + Ref(concat_node->left); + Unref(concat_node); + } } else { - Ref(concat_node->right); - Ref(concat_node->left); - Unref(concat_node); + AddNode(node); } } } @@ -1181,7 +1142,7 @@ class CordForest { // Collect together everything with which we will merge with node int i = 0; - for (; node->length >= kMinLength[i + 1]; ++i) { + for (; node->length > min_length[i + 1]; ++i) { auto& tree_at_i = trees_[i]; if (tree_at_i == nullptr) continue; @@ -1192,7 +1153,7 @@ class CordForest { sum = AppendNode(node, sum); // Insert sum into appropriate place in the forest - for (; sum->length >= kMinLength[i]; ++i) { + for (; sum->length >= min_length[i]; ++i) { auto& tree_at_i = trees_[i]; if (tree_at_i == nullptr) continue; @@ -1200,7 +1161,7 @@ class CordForest { tree_at_i = nullptr; } - // kMinLength[0] == 1, which means sum->length >= kMinLength[0] + // min_length[0] == 1, which means sum->length >= min_length[0] assert(i > 0); trees_[i - 1] = sum; } @@ -1233,7 +1194,9 @@ class CordForest { } size_t root_length_; - std::array trees_; + + // use an inlined vector instead of a flat array to get bounds checking + absl::InlinedVector trees_; // List of concat nodes we can re-use for Cord balancing. CordRepConcat* concat_freelist_ = nullptr; @@ -1334,7 +1297,7 @@ inline absl::string_view Cord::InlineRep::FindFlatStartPiece() const { inline int Cord::CompareSlowPath(absl::string_view rhs, size_t compared_size, size_t size_to_compare) const { - auto advance = [](Cord::InternalChunkIterator* it, absl::string_view* chunk) { + auto advance = [](Cord::ChunkIterator* it, absl::string_view* chunk) { if (!chunk->empty()) return true; ++*it; if (it->bytes_remaining_ == 0) return false; @@ -1342,7 +1305,7 @@ inline int Cord::CompareSlowPath(absl::string_view rhs, size_t compared_size, return true; }; - Cord::InternalChunkIterator lhs_it = internal_chunk_begin(); + Cord::ChunkIterator lhs_it = chunk_begin(); // compared_size is inside first chunk. absl::string_view lhs_chunk = @@ -1364,7 +1327,7 @@ inline int Cord::CompareSlowPath(absl::string_view rhs, size_t compared_size, inline int Cord::CompareSlowPath(const Cord& rhs, size_t compared_size, size_t size_to_compare) const { - auto advance = [](Cord::InternalChunkIterator* it, absl::string_view* chunk) { + auto advance = [](Cord::ChunkIterator* it, absl::string_view* chunk) { if (!chunk->empty()) return true; ++*it; if (it->bytes_remaining_ == 0) return false; @@ -1372,8 +1335,8 @@ inline int Cord::CompareSlowPath(const Cord& rhs, size_t compared_size, return true; }; - Cord::InternalChunkIterator lhs_it = internal_chunk_begin(); - Cord::InternalChunkIterator rhs_it = rhs.internal_chunk_begin(); + Cord::ChunkIterator lhs_it = chunk_begin(); + Cord::ChunkIterator rhs_it = rhs.chunk_begin(); // compared_size is inside both first chunks. absl::string_view lhs_chunk = @@ -1507,9 +1470,7 @@ void Cord::CopyToArraySlowPath(char* dst) const { } } -template -Cord::GenericChunkIterator& -Cord::GenericChunkIterator::operator++() { +Cord::ChunkIterator& Cord::ChunkIterator::operator++() { ABSL_HARDENING_ASSERT(bytes_remaining_ > 0 && "Attempted to iterate past `end()`"); assert(bytes_remaining_ >= current_chunk_.size()); @@ -1549,8 +1510,7 @@ Cord::GenericChunkIterator::operator++() { return *this; } -template -Cord Cord::GenericChunkIterator::AdvanceAndReadBytes(size_t n) { +Cord Cord::ChunkIterator::AdvanceAndReadBytes(size_t n) { ABSL_HARDENING_ASSERT(bytes_remaining_ >= n && "Attempted to iterate past `end()`"); Cord subcord; @@ -1664,8 +1624,7 @@ Cord Cord::GenericChunkIterator::AdvanceAndReadBytes(size_t n) { return subcord; } -template -void Cord::GenericChunkIterator::AdvanceBytesSlowPath(size_t n) { +void Cord::ChunkIterator::AdvanceBytesSlowPath(size_t n) { assert(bytes_remaining_ >= n && "Attempted to iterate past `end()`"); assert(n >= current_chunk_.size()); // This should only be called when // iterating to a new node. @@ -1851,18 +1810,18 @@ absl::string_view Cord::FlattenSlowPath() { } } -static void DumpNode(const CordRep* rep, bool include_data, std::ostream* os) { +static void DumpNode(CordRep* rep, bool include_data, std::ostream* os) { const int kIndentStep = 1; int indent = 0; - cord_internal::CordTreeConstPath stack; - cord_internal::CordTreePath indents; + absl::InlinedVector stack; + absl::InlinedVector indents; for (;;) { *os << std::setw(3) << rep->refcount.Get(); *os << " " << std::setw(7) << rep->length; *os << " ["; - if (include_data) *os << static_cast(rep); + if (include_data) *os << static_cast(rep); *os << "]"; - *os << " " << (IsNodeBalanced(rep) ? 'b' : 'u'); + *os << " " << (IsRootBalanced(rep) ? 'b' : 'u'); *os << " " << std::setw(indent) << ""; if (rep->tag == CONCAT) { *os << "CONCAT depth=" << Depth(rep) << "\n"; @@ -1883,7 +1842,7 @@ static void DumpNode(const CordRep* rep, bool include_data, std::ostream* os) { } else { *os << "FLAT cap=" << TagToLength(rep->tag) << " ["; if (include_data) - *os << absl::CEscape(absl::string_view(rep->data, rep->length)); + *os << absl::CEscape(std::string(rep->data, rep->length)); *os << "]\n"; } if (stack.empty()) break; @@ -1896,19 +1855,19 @@ static void DumpNode(const CordRep* rep, bool include_data, std::ostream* os) { ABSL_INTERNAL_CHECK(indents.empty(), ""); } -static std::string ReportError(const CordRep* root, const CordRep* node) { +static std::string ReportError(CordRep* root, CordRep* node) { std::ostringstream buf; buf << "Error at node " << node << " in:"; DumpNode(root, true, &buf); return buf.str(); } -static bool VerifyNode(const CordRep* root, const CordRep* start_node, +static bool VerifyNode(CordRep* root, CordRep* start_node, bool full_validation) { - cord_internal::CordTreeConstPath worklist; + absl::InlinedVector worklist; worklist.push_back(start_node); do { - const CordRep* node = worklist.back(); + CordRep* node = worklist.back(); worklist.pop_back(); ABSL_INTERNAL_CHECK(node != nullptr, ReportError(root, node)); @@ -1958,7 +1917,7 @@ static bool VerifyNode(const CordRep* root, const CordRep* start_node, // Iterate over the tree. cur_node is never a leaf node and leaf nodes will // never be appended to tree_stack. This reduces overhead from manipulating // tree_stack. - cord_internal::CordTreeConstPath tree_stack; + absl::InlinedVector tree_stack; const CordRep* cur_node = rep; while (true) { const CordRep* next_node = nullptr; @@ -2005,9 +1964,6 @@ std::ostream& operator<<(std::ostream& out, const Cord& cord) { return out; } -template class Cord::GenericChunkIterator; -template class Cord::GenericChunkIterator; - namespace strings_internal { size_t CordTestAccess::FlatOverhead() { return kFlatOverhead; } size_t CordTestAccess::MaxFlatLength() { return kMaxFlatLength; } diff --git a/absl/strings/cord.h b/absl/strings/cord.h index d5b9363f..5136f926 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -123,132 +123,12 @@ H HashFragmentedCord(H, const Cord&); // Additionally, the API provides iterator utilities to iterate through Cord // data via chunks or character bytes. // - -namespace cord_internal { - -// It's expensive to keep a Cord's tree perfectly balanced, so instead we keep -// trees approximately balanced. A tree node N of depth D(N) that contains a -// string of L(N) characters is considered balanced if L >= Fibonacci(D + 2). -// The "+ 2" is used to ensure that every balanced leaf node contains at least -// one character. Here we presume that -// Fibonacci(0) = 0 -// Fibonacci(1) = 1 -// Fibonacci(2) = 1 -// Fibonacci(3) = 2 -// ... -// The algorithm is based on paper by Hans Boehm et al: -// https://www.cs.rit.edu/usr/local/pub/jeh/courses/QUARTERS/FP/Labs/CedarRope/rope-paper.pdf -// In this paper authors shows that rebalancing based on cord forest of already -// balanced subtrees can be proven to never produce tree of depth larger than -// largest Fibonacci number representable in the same integral type as cord size -// For 64 bit integers this is the 93rd Fibonacci number. For 32 bit integrals -// this is 47th Fibonacci number. -constexpr size_t MaxCordDepth() { return sizeof(size_t) == 8 ? 93 : 47; } - -// This class models fixed max size stack of CordRep pointers. -// The elements are being pushed back and popped from the back. -template -class CordTreePath { - public: - CordTreePath() {} - explicit CordTreePath(CordRepPtr root) { push_back(root); } - - bool empty() const { return size_ == 0; } - size_t size() const { return size_; } - void clear() { size_ = 0; } - - CordRepPtr back() { return data_[size_ - 1]; } - - void pop_back() { - --size_; - assert(size_ < N); - } - void push_back(CordRepPtr elem) { data_[size_++] = elem; } - - private: - CordRepPtr data_[N]; - size_t size_ = 0; -}; - -// Fixed length container for mutable "path" in cord tree, which can hold any -// possible valid path in cord tree. -using CordTreeMutablePath = CordTreePath; -// Variable length container for mutable "path" in cord tree. It starts with -// capacity for 15 elements and grow if necessary. -using CordTreeDynamicPath = - absl::InlinedVector; -} // namespace cord_internal - -// A Cord is a sequence of characters. class Cord { private: template using EnableIfString = absl::enable_if_t::value, int>; - //---------------------------------------------------------------------------- - // Cord::GenericChunkIterator - //---------------------------------------------------------------------------- - // - // A `Cord::GenericChunkIterator` provides an interface for the standard - // `Cord::ChunkIterator` as well as some private implementations. - template - class GenericChunkIterator { - public: - using iterator_category = std::input_iterator_tag; - using value_type = absl::string_view; - using difference_type = ptrdiff_t; - using pointer = const value_type*; - using reference = value_type; - - GenericChunkIterator() = default; - - GenericChunkIterator& operator++(); - GenericChunkIterator operator++(int); - bool operator==(const GenericChunkIterator& other) const; - bool operator!=(const GenericChunkIterator& other) const; - reference operator*() const; - pointer operator->() const; - - friend class Cord; - friend class CharIterator; - - private: - // Constructs a `begin()` iterator from `cord`. - explicit GenericChunkIterator(const Cord* cord); - - // Removes `n` bytes from `current_chunk_`. Expects `n` to be smaller than - // `current_chunk_.size()`. - void RemoveChunkPrefix(size_t n); - Cord AdvanceAndReadBytes(size_t n); - void AdvanceBytes(size_t n); - // Iterates `n` bytes, where `n` is expected to be greater than or equal to - // `current_chunk_.size()`. - void AdvanceBytesSlowPath(size_t n); - - // A view into bytes of the current `CordRep`. It may only be a view to a - // suffix of bytes if this is being used by `CharIterator`. - absl::string_view current_chunk_; - // The current leaf, or `nullptr` if the iterator points to short data. - // If the current chunk is a substring node, current_leaf_ points to the - // underlying flat or external node. - cord_internal::CordRep* current_leaf_ = nullptr; - // The number of bytes left in the `Cord` over which we are iterating. - size_t bytes_remaining_ = 0; - StorageType stack_of_right_children_; - }; - template - class GenericChunkRange { - public: - explicit GenericChunkRange(const Cord* cord) : cord_(cord) {} - - IteratorType begin() const { return IteratorType(cord_); } - IteratorType end() const { return IteratorType(); } - - private: - const Cord* cord_; - }; - public: // Cord::Cord() Constructors @@ -464,8 +344,51 @@ class Cord { // * The iterator keeps state that can grow for Cords that contain many // nodes and are imbalanced due to sharing. Prefer to pass this type by // const reference instead of by value. - using ChunkIterator = - GenericChunkIterator; + class ChunkIterator { + public: + using iterator_category = std::input_iterator_tag; + using value_type = absl::string_view; + using difference_type = ptrdiff_t; + using pointer = const value_type*; + using reference = value_type; + + ChunkIterator() = default; + + ChunkIterator& operator++(); + ChunkIterator operator++(int); + bool operator==(const ChunkIterator& other) const; + bool operator!=(const ChunkIterator& other) const; + reference operator*() const; + pointer operator->() const; + + friend class Cord; + friend class CharIterator; + + private: + // Constructs a `begin()` iterator from `cord`. + explicit ChunkIterator(const Cord* cord); + + // Removes `n` bytes from `current_chunk_`. Expects `n` to be smaller than + // `current_chunk_.size()`. + void RemoveChunkPrefix(size_t n); + Cord AdvanceAndReadBytes(size_t n); + void AdvanceBytes(size_t n); + // Iterates `n` bytes, where `n` is expected to be greater than or equal to + // `current_chunk_.size()`. + void AdvanceBytesSlowPath(size_t n); + + // A view into bytes of the current `CordRep`. It may only be a view to a + // suffix of bytes if this is being used by `CharIterator`. + absl::string_view current_chunk_; + // The current leaf, or `nullptr` if the iterator points to short data. + // If the current chunk is a substring node, current_leaf_ points to the + // underlying flat or external node. + absl::cord_internal::CordRep* current_leaf_ = nullptr; + // The number of bytes left in the `Cord` over which we are iterating. + size_t bytes_remaining_ = 0; + absl::InlinedVector + stack_of_right_children_; + }; // Cord::ChunkIterator::chunk_begin() // @@ -504,7 +427,16 @@ class Cord { // // Implementation note: `ChunkRange` is simply a convenience wrapper over // `Cord::chunk_begin()` and `Cord::chunk_end()`. - using ChunkRange = GenericChunkRange; + class ChunkRange { + public: + explicit ChunkRange(const Cord* cord) : cord_(cord) {} + + ChunkIterator begin() const; + ChunkIterator end() const; + + private: + const Cord* cord_; + }; // Cord::Chunks() // @@ -800,14 +732,6 @@ class Cord { static bool GetFlatAux(absl::cord_internal::CordRep* rep, absl::string_view* fragment); - // Iterators for use inside Cord implementation - using InternalChunkIterator = - GenericChunkIterator; - using InternalChunkRange = GenericChunkRange; - - InternalChunkIterator internal_chunk_begin() const; - InternalChunkRange InternalChunks() const; - // Helper for ForEachChunk() static void ForEachChunkAux( absl::cord_internal::CordRep* rep, @@ -842,11 +766,6 @@ class Cord { void AppendImpl(C&& src); }; -extern template class Cord::GenericChunkIterator< - cord_internal::CordTreeMutablePath>; -extern template class Cord::GenericChunkIterator< - cord_internal::CordTreeDynamicPath>; - ABSL_NAMESPACE_END } // namespace absl @@ -1186,9 +1105,7 @@ inline bool Cord::StartsWith(absl::string_view rhs) const { return EqualsImpl(rhs, rhs_size); } -template -inline Cord::GenericChunkIterator::GenericChunkIterator( - const Cord* cord) +inline Cord::ChunkIterator::ChunkIterator(const Cord* cord) : bytes_remaining_(cord->size()) { if (cord->empty()) return; if (cord->contents_.is_tree()) { @@ -1199,50 +1116,37 @@ inline Cord::GenericChunkIterator::GenericChunkIterator( } } -template -inline Cord::GenericChunkIterator -Cord::GenericChunkIterator::operator++(int) { - GenericChunkIterator tmp(*this); +inline Cord::ChunkIterator Cord::ChunkIterator::operator++(int) { + ChunkIterator tmp(*this); operator++(); return tmp; } -template -inline bool Cord::GenericChunkIterator::operator==( - const GenericChunkIterator& other) const { +inline bool Cord::ChunkIterator::operator==(const ChunkIterator& other) const { return bytes_remaining_ == other.bytes_remaining_; } -template -inline bool Cord::GenericChunkIterator::operator!=( - const GenericChunkIterator& other) const { +inline bool Cord::ChunkIterator::operator!=(const ChunkIterator& other) const { return !(*this == other); } -template -inline typename Cord::GenericChunkIterator::reference -Cord::GenericChunkIterator::operator*() const { +inline Cord::ChunkIterator::reference Cord::ChunkIterator::operator*() const { ABSL_HARDENING_ASSERT(bytes_remaining_ != 0); return current_chunk_; } -template -inline typename Cord::GenericChunkIterator::pointer -Cord::GenericChunkIterator::operator->() const { +inline Cord::ChunkIterator::pointer Cord::ChunkIterator::operator->() const { ABSL_HARDENING_ASSERT(bytes_remaining_ != 0); return ¤t_chunk_; } -template -inline void Cord::GenericChunkIterator::RemoveChunkPrefix( - size_t n) { +inline void Cord::ChunkIterator::RemoveChunkPrefix(size_t n) { assert(n < current_chunk_.size()); current_chunk_.remove_prefix(n); bytes_remaining_ -= n; } -template -inline void Cord::GenericChunkIterator::AdvanceBytes(size_t n) { +inline void Cord::ChunkIterator::AdvanceBytes(size_t n) { if (ABSL_PREDICT_TRUE(n < current_chunk_.size())) { RemoveChunkPrefix(n); } else if (n != 0) { @@ -1256,6 +1160,14 @@ inline Cord::ChunkIterator Cord::chunk_begin() const { inline Cord::ChunkIterator Cord::chunk_end() const { return ChunkIterator(); } +inline Cord::ChunkIterator Cord::ChunkRange::begin() const { + return cord_->chunk_begin(); +} + +inline Cord::ChunkIterator Cord::ChunkRange::end() const { + return cord_->chunk_end(); +} + inline Cord::ChunkRange Cord::Chunks() const { return ChunkRange(this); } inline Cord::CharIterator& Cord::CharIterator::operator++() { diff --git a/absl/strings/cord_test.cc b/absl/strings/cord_test.cc index 0ec93b4c..49178498 100644 --- a/absl/strings/cord_test.cc +++ b/absl/strings/cord_test.cc @@ -1403,53 +1403,6 @@ TEST(CordChunkIterator, Operations) { VerifyChunkIterator(subcords, 128); } -TEST(CordChunkIterator, MaxLengthFullTree) { - // Start with a 1-byte cord, and then double its length in a loop. We should - // be able to do this until the point where we would overflow size_t. - - absl::Cord cord; - size_t size = 1; - AddExternalMemory("x", &cord); - EXPECT_EQ(cord.size(), size); - - const int kCordLengthDoublingLimit = std::numeric_limits::digits - 1; - for (int i = 0; i < kCordLengthDoublingLimit; ++i) { - cord.Prepend(absl::Cord(cord)); - size <<= 1; - - EXPECT_EQ(cord.size(), size); - - auto chunk_it = cord.chunk_begin(); - EXPECT_EQ(*chunk_it, "x"); - } - - EXPECT_DEATH_IF_SUPPORTED( - (cord.Prepend(absl::Cord(cord)), *cord.chunk_begin()), - "Cord is too long"); -} - -TEST(CordChunkIterator, MaxDepth) { - // By reusing nodes, it's possible in pathological cases to build a Cord that - // exceeds both the maximum permissible length and depth. In this case, the - // violation of the maximum depth is reported. - absl::Cord left_child; - AddExternalMemory("x", &left_child); - absl::Cord root = left_child; - - for (int i = 0; i < absl::cord_internal::MaxCordDepth() - 2; ++i) { - size_t new_size = left_child.size() + root.size(); - root.Prepend(left_child); - EXPECT_EQ(root.size(), new_size); - - auto chunk_it = root.chunk_begin(); - EXPECT_EQ(*chunk_it, "x"); - - std::swap(left_child, root); - } - - EXPECT_DEATH_IF_SUPPORTED(root.Prepend(left_child), "Cord is too long"); -} - TEST(CordCharIterator, Traits) { static_assert(std::is_copy_constructible::value, ""); -- cgit v1.2.3 From bf6166a635ab57fe0559b00dcd3ff09a8c42de2e Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Fri, 10 Apr 2020 13:33:13 -0700 Subject: Export of internal Abseil changes -- 1eb20c4802ccaa316ecebc237877210b77ac84f7 by Abseil Team : Use constraint_values to detect windows. This resolves ambiguous copts when cross compiling with LLVM on Windows. PiperOrigin-RevId: 305935379 -- 47c96948132a577b14642ad4c910052768c41d62 by Abseil Team : Add StrSplit conversion tests for the swisstable containers. PiperOrigin-RevId: 305747160 -- 0daea0f78b50d49520bd6e67d093cd87d057bb86 by Abseil Team : Typo fix: Removes duplicate word. PiperOrigin-RevId: 305502962 GitOrigin-RevId: 1eb20c4802ccaa316ecebc237877210b77ac84f7 Change-Id: I1bfa0beda0260027a22bc671344cc8b74315b77a --- absl/BUILD.bazel | 7 ++++--- absl/strings/BUILD.bazel | 2 ++ absl/strings/CMakeLists.txt | 2 ++ absl/strings/cord.h | 3 +-- absl/strings/str_split_test.cc | 14 ++++++++++++++ 5 files changed, 23 insertions(+), 5 deletions(-) (limited to 'absl/strings/cord.h') diff --git a/absl/BUILD.bazel b/absl/BUILD.bazel index 5a03acf8..f7fc2a7f 100644 --- a/absl/BUILD.bazel +++ b/absl/BUILD.bazel @@ -44,9 +44,10 @@ config_setting( config_setting( name = "windows", - values = { - "cpu": "x64_windows", - }, + constraint_values = [ + "@bazel_tools//platforms:x86_64", + "@bazel_tools//platforms:windows", + ], visibility = [":__subpackages__"], ) diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index 38901122..404c707f 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -368,6 +368,8 @@ cc_test( ":strings", "//absl/base:core_headers", "//absl/base:dynamic_annotations", + "//absl/container:flat_hash_map", + "//absl/container:node_hash_map", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index d3a8bd7e..2106148c 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt @@ -210,6 +210,8 @@ absl_cc_test( absl::base absl::core_headers absl::dynamic_annotations + absl::flat_hash_map + absl::node_hash_map gmock_main ) diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 5136f926..2d92f6d6 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -634,8 +634,7 @@ class Cord { // class so that we can isolate the bulk of cord.cc from changes // to the representation. // - // InlineRep holds either either a tree pointer, or an array of kMaxInline - // bytes. + // InlineRep holds either a tree pointer, or an array of kMaxInline bytes. class InlineRep { public: static const unsigned char kMaxInline = 15; diff --git a/absl/strings/str_split_test.cc b/absl/strings/str_split_test.cc index 67f62a78..fcd58d2e 100644 --- a/absl/strings/str_split_test.cc +++ b/absl/strings/str_split_test.cc @@ -29,6 +29,8 @@ #include "gtest/gtest.h" #include "absl/base/dynamic_annotations.h" // for RunningOnValgrind #include "absl/base/macros.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/node_hash_map.h" #include "absl/strings/numbers.h" namespace { @@ -421,6 +423,18 @@ TEST(Splitter, ConversionOperator) { TestMapConversionOperator>(splitter); TestMapConversionOperator>( splitter); + TestMapConversionOperator< + absl::node_hash_map>(splitter); + TestMapConversionOperator< + absl::node_hash_map>(splitter); + TestMapConversionOperator< + absl::node_hash_map>(splitter); + TestMapConversionOperator< + absl::flat_hash_map>(splitter); + TestMapConversionOperator< + absl::flat_hash_map>(splitter); + TestMapConversionOperator< + absl::flat_hash_map>(splitter); // Tests conversion to std::pair -- cgit v1.2.3 From df60c82df43e33274550e758c7a93fa49f88e0fe Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Mon, 20 Apr 2020 07:20:27 -0700 Subject: Export of internal Abseil changes -- b885a238ec13effcc407e250583e293052bd7984 by Greg Falcon : Remove the dependency of //absl/hash on //absl/strings:cord. The `AbslHashValue` definition should reside in cord.h, but the implementation currently needs internal details from the hash library. This CL changes the way that Cord gains access to those internals. Note that PiecewiseCombiner remains an internal namespace API, and we still reserve the right to make changes to it. The cord_benchmark shows no statistically significant changes in hash performance with this change. PiperOrigin-RevId: 307393448 -- ca449f230ee719d069d9217ba28a07bf5b3bd8b1 by Derek Mauro : Move the extension to use absl::Format() with absl::Cord as a sink to cord.h PiperOrigin-RevId: 307077162 GitOrigin-RevId: b885a238ec13effcc407e250583e293052bd7984 Change-Id: If24a90782c786fa0b4343bc7d72d053b66c153ea --- absl/hash/BUILD.bazel | 1 - absl/hash/CMakeLists.txt | 1 - absl/hash/internal/hash.h | 135 +++++++++------------ absl/strings/BUILD.bazel | 2 +- absl/strings/CMakeLists.txt | 2 +- absl/strings/cord.h | 31 ++++- absl/strings/cord_test.cc | 9 ++ absl/strings/internal/str_format/extension_test.cc | 9 -- absl/strings/internal/str_format/output.h | 9 -- 9 files changed, 92 insertions(+), 107 deletions(-) (limited to 'absl/strings/cord.h') diff --git a/absl/hash/BUILD.bazel b/absl/hash/BUILD.bazel index 59eac784..6c77f1a1 100644 --- a/absl/hash/BUILD.bazel +++ b/absl/hash/BUILD.bazel @@ -43,7 +43,6 @@ cc_library( "//absl/meta:type_traits", "//absl/numeric:int128", "//absl/strings", - "//absl/strings:cord", "//absl/types:optional", "//absl/types:variant", "//absl/utility", diff --git a/absl/hash/CMakeLists.txt b/absl/hash/CMakeLists.txt index 4e555147..61365e9b 100644 --- a/absl/hash/CMakeLists.txt +++ b/absl/hash/CMakeLists.txt @@ -25,7 +25,6 @@ absl_cc_library( COPTS ${ABSL_DEFAULT_COPTS} DEPS - absl::cord absl::core_headers absl::endian absl::fixed_array diff --git a/absl/hash/internal/hash.h b/absl/hash/internal/hash.h index 025d287f..a71bd4a6 100644 --- a/absl/hash/internal/hash.h +++ b/absl/hash/internal/hash.h @@ -43,7 +43,6 @@ #include "absl/container/fixed_array.h" #include "absl/meta/type_traits.h" #include "absl/numeric/int128.h" -#include "absl/strings/cord.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "absl/types/variant.h" @@ -54,12 +53,65 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace hash_internal { -class PiecewiseCombiner; - // Internal detail: Large buffers are hashed in smaller chunks. This function // returns the size of these chunks. constexpr size_t PiecewiseChunkSize() { return 1024; } +// PiecewiseCombiner +// +// PiecewiseCombiner is an internal-only helper class for hashing a piecewise +// buffer of `char` or `unsigned char` as though it were contiguous. This class +// provides two methods: +// +// H add_buffer(state, data, size) +// H finalize(state) +// +// `add_buffer` can be called zero or more times, followed by a single call to +// `finalize`. This will produce the same hash expansion as concatenating each +// buffer piece into a single contiguous buffer, and passing this to +// `H::combine_contiguous`. +// +// Example usage: +// PiecewiseCombiner combiner; +// for (const auto& piece : pieces) { +// state = combiner.add_buffer(std::move(state), piece.data, piece.size); +// } +// return combiner.finalize(std::move(state)); +class PiecewiseCombiner { + public: + PiecewiseCombiner() : position_(0) {} + PiecewiseCombiner(const PiecewiseCombiner&) = delete; + PiecewiseCombiner& operator=(const PiecewiseCombiner&) = delete; + + // PiecewiseCombiner::add_buffer() + // + // Appends the given range of bytes to the sequence to be hashed, which may + // modify the provided hash state. + template + H add_buffer(H state, const unsigned char* data, size_t size); + template + H add_buffer(H state, const char* data, size_t size) { + return add_buffer(std::move(state), + reinterpret_cast(data), size); + } + + // PiecewiseCombiner::finalize() + // + // Finishes combining the hash sequence, which may may modify the provided + // hash state. + // + // Once finalize() is called, add_buffer() may no longer be called. The + // resulting hash state will be the same as if the pieces passed to + // add_buffer() were concatenated into a single flat buffer, and then provided + // to H::combine_contiguous(). + template + H finalize(H state); + + private: + unsigned char buf_[PiecewiseChunkSize()]; + size_t position_; +}; + // HashStateBase // // A hash state object represents an intermediate state in the computation @@ -126,8 +178,7 @@ class HashStateBase { template static H combine_contiguous(H state, const T* data, size_t size); - private: - friend class PiecewiseCombiner; + using AbslInternalPiecewiseCombiner = PiecewiseCombiner; }; // is_uniquely_represented @@ -198,61 +249,6 @@ H hash_bytes(H hash_state, const T& value) { return H::combine_contiguous(std::move(hash_state), start, sizeof(value)); } -// PiecewiseCombiner -// -// PiecewiseCombiner is an internal-only helper class for hashing a piecewise -// buffer of `char` or `unsigned char` as though it were contiguous. This class -// provides two methods: -// -// H add_buffer(state, data, size) -// H finalize(state) -// -// `add_buffer` can be called zero or more times, followed by a single call to -// `finalize`. This will produce the same hash expansion as concatenating each -// buffer piece into a single contiguous buffer, and passing this to -// `H::combine_contiguous`. -// -// Example usage: -// PiecewiseCombiner combiner; -// for (const auto& piece : pieces) { -// state = combiner.add_buffer(std::move(state), piece.data, piece.size); -// } -// return combiner.finalize(std::move(state)); -class PiecewiseCombiner { - public: - PiecewiseCombiner() : position_(0) {} - PiecewiseCombiner(const PiecewiseCombiner&) = delete; - PiecewiseCombiner& operator=(const PiecewiseCombiner&) = delete; - - // PiecewiseCombiner::add_buffer() - // - // Appends the given range of bytes to the sequence to be hashed, which may - // modify the provided hash state. - template - H add_buffer(H state, const unsigned char* data, size_t size); - template - H add_buffer(H state, const char* data, size_t size) { - return add_buffer(std::move(state), - reinterpret_cast(data), size); - } - - // PiecewiseCombiner::finalize() - // - // Finishes combining the hash sequence, which may may modify the provided - // hash state. - // - // Once finalize() is called, add_buffer() may no longer be called. The - // resulting hash state will be the same as if the pieces passed to - // add_buffer() were concatenated into a single flat buffer, and then provided - // to H::combine_contiguous(). - template - H finalize(H state); - - private: - unsigned char buf_[PiecewiseChunkSize()]; - size_t position_; -}; - // ----------------------------------------------------------------------------- // AbslHashValue for Basic Types // ----------------------------------------------------------------------------- @@ -443,25 +439,6 @@ H AbslHashValue( str.size()); } -template -H HashFragmentedCord(H hash_state, const absl::Cord& c) { - PiecewiseCombiner combiner; - c.ForEachChunk([&combiner, &hash_state](absl::string_view chunk) { - hash_state = - combiner.add_buffer(std::move(hash_state), chunk.data(), chunk.size()); - }); - return H::combine(combiner.finalize(std::move(hash_state)), c.size()); -} - -template -H AbslHashValue(H hash_state, const absl::Cord& c) { - absl::optional maybe_flat = c.TryFlat(); - if (maybe_flat.has_value()) { - return H::combine(std::move(hash_state), *maybe_flat); - } - return hash_internal::HashFragmentedCord(std::move(hash_state), c); -} - // ----------------------------------------------------------------------------- // AbslHashValue for Sequence Containers // ----------------------------------------------------------------------------- diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index 4ee5a2ca..8aecbe59 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -310,6 +310,7 @@ cc_test( deps = [ ":cord", ":cord_test_helpers", + ":str_format", ":strings", "//absl/base", "//absl/base:config", @@ -667,7 +668,6 @@ cc_test( copts = ABSL_TEST_COPTS, visibility = ["//visibility:private"], deps = [ - ":cord", ":str_format", ":str_format_internal", ":strings", diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index 10213022..b6705ed0 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt @@ -425,7 +425,6 @@ absl_cc_test( DEPS absl::str_format absl::str_format_internal - absl::cord absl::strings gmock_main ) @@ -581,6 +580,7 @@ absl_cc_test( ${ABSL_TEST_COPTS} DEPS absl::cord + absl::str_format absl::strings absl::base absl::config diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 2d92f6d6..9d99b2af 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -90,10 +90,6 @@ class CordTestPeer; template Cord MakeCordFromExternal(absl::string_view, Releaser&&); void CopyCordToString(const Cord& src, std::string* dst); -namespace hash_internal { -template -H HashFragmentedCord(H, const Cord&); -} // Cord // @@ -615,10 +611,22 @@ class Cord { // If the cord was already flat, the contents are not modified. absl::string_view Flatten(); + // Support absl::Cord as a sink object for absl::Format(). + friend void AbslFormatFlush(absl::Cord* cord, absl::string_view part) { + cord->Append(part); + } + + template + friend H AbslHashValue(H hash_state, const absl::Cord& c) { + absl::optional maybe_flat = c.TryFlat(); + if (maybe_flat.has_value()) { + return H::combine(std::move(hash_state), *maybe_flat); + } + return c.HashFragmented(std::move(hash_state)); + } + private: friend class CordTestPeer; - template - friend H absl::hash_internal::HashFragmentedCord(H, const Cord&); friend bool operator==(const Cord& lhs, const Cord& rhs); friend bool operator==(const Cord& lhs, absl::string_view rhs); @@ -763,6 +771,17 @@ class Cord { // Helper for Append() template void AppendImpl(C&& src); + + // Helper for AbslHashValue() + template + H HashFragmented(H hash_state) const { + typename H::AbslInternalPiecewiseCombiner combiner; + ForEachChunk([&combiner, &hash_state](absl::string_view chunk) { + hash_state = combiner.add_buffer(std::move(hash_state), chunk.data(), + chunk.size()); + }); + return H::combine(combiner.finalize(std::move(hash_state)), size()); + } }; ABSL_NAMESPACE_END diff --git a/absl/strings/cord_test.cc b/absl/strings/cord_test.cc index 49178498..336cedde 100644 --- a/absl/strings/cord_test.cc +++ b/absl/strings/cord_test.cc @@ -22,6 +22,7 @@ #include "absl/container/fixed_array.h" #include "absl/strings/cord_test_helpers.h" #include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" #include "absl/strings/string_view.h" typedef std::mt19937_64 RandomEngine; @@ -1582,6 +1583,14 @@ TEST(Cord, SmallBufferAssignFromOwnData) { } } +TEST(Cord, Format) { + absl::Cord c; + absl::Format(&c, "There were %04d little %s.", 3, "pigs"); + EXPECT_EQ(c, "There were 0003 little pigs."); + absl::Format(&c, "And %-3llx bad wolf!", 1); + EXPECT_EQ(c, "There were 0003 little pigs.And 1 bad wolf!"); +} + TEST(CordDeathTest, Hardening) { absl::Cord cord("hello"); // These statement should abort the program in all builds modes. diff --git a/absl/strings/internal/str_format/extension_test.cc b/absl/strings/internal/str_format/extension_test.cc index dc5576b6..561eaa36 100644 --- a/absl/strings/internal/str_format/extension_test.cc +++ b/absl/strings/internal/str_format/extension_test.cc @@ -19,7 +19,6 @@ #include #include -#include "absl/strings/cord.h" #include "gtest/gtest.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" @@ -82,14 +81,6 @@ TEST(FormatExtensionTest, SinkAppendChars) { } } -TEST(FormatExtensionTest, CordSink) { - absl::Cord c; - absl::Format(&c, "There were %04d little %s.", 3, "pigs"); - EXPECT_EQ(c, "There were 0003 little pigs."); - absl::Format(&c, "And %-3llx bad wolf!", 1); - EXPECT_EQ(c, "There were 0003 little pigs.And 1 bad wolf!"); -} - TEST(FormatExtensionTest, CustomSink) { my_namespace::UserDefinedType sink; absl::Format(&sink, "There were %04d little %s.", 3, "pigs"); diff --git a/absl/strings/internal/str_format/output.h b/absl/strings/internal/str_format/output.h index c3168d20..8030dae0 100644 --- a/absl/strings/internal/str_format/output.h +++ b/absl/strings/internal/str_format/output.h @@ -30,9 +30,6 @@ namespace absl { ABSL_NAMESPACE_BEGIN - -class Cord; - namespace str_format_internal { // RawSink implementation that writes into a char* buffer. @@ -77,12 +74,6 @@ inline void AbslFormatFlush(std::ostream* out, string_view s) { out->write(s.data(), s.size()); } -template ::value>::type> -inline void AbslFormatFlush(AbslCord* out, string_view s) { - out->Append(s); -} - inline void AbslFormatFlush(FILERawSink* sink, string_view v) { sink->Write(v); } -- cgit v1.2.3 From cb52b05ea20e8d196f17b45a420c2d323e401324 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Mon, 20 Apr 2020 18:19:20 -0700 Subject: Export of internal Abseil changes -- adcea274552c5671bdafae7f85797c49c9639b67 by Abseil Team : change some const declarations to constexpr PiperOrigin-RevId: 307516528 -- f828d23bdd437f38ae52ba232fd44c477643ddaf by Derek Mauro : Use "-lrt" instead of the resolved find_library result when linking librt. find_library defaults to shared objects. Imports #665. PiperOrigin-RevId: 307465639 GitOrigin-RevId: adcea274552c5671bdafae7f85797c49c9639b67 Change-Id: I8f562ef8f57f5ed55192936c764fa6532e3b8adb --- absl/strings/cord.h | 6 +++--- absl/synchronization/internal/waiter.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'absl/strings/cord.h') diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 9d99b2af..ae3d2e71 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -645,12 +645,12 @@ class Cord { // InlineRep holds either a tree pointer, or an array of kMaxInline bytes. class InlineRep { public: - static const unsigned char kMaxInline = 15; + static constexpr unsigned char kMaxInline = 15; static_assert(kMaxInline >= sizeof(absl::cord_internal::CordRep*), ""); // Tag byte & kMaxInline means we are storing a pointer. - static const unsigned char kTreeFlag = 1 << 4; + static constexpr unsigned char kTreeFlag = 1 << 4; // Tag byte & kProfiledFlag means we are profiling the Cord. - static const unsigned char kProfiledFlag = 1 << 5; + static constexpr unsigned char kProfiledFlag = 1 << 5; constexpr InlineRep() : data_{} {} InlineRep(const InlineRep& src); diff --git a/absl/synchronization/internal/waiter.h b/absl/synchronization/internal/waiter.h index a6e6d4c7..ae83907b 100644 --- a/absl/synchronization/internal/waiter.h +++ b/absl/synchronization/internal/waiter.h @@ -101,7 +101,7 @@ class Waiter { // How many periods to remain idle before releasing resources #ifndef THREAD_SANITIZER - static const int kIdlePeriods = 60; + static constexpr int kIdlePeriods = 60; #else // Memory consumption under ThreadSanitizer is a serious concern, // so we release resources sooner. The value of 1 leads to 1 to 2 second -- cgit v1.2.3 From d85783fd0b1bb32b3d3e04d18367cec8d96c9e9a Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Tue, 5 May 2020 07:54:14 -0700 Subject: Export of internal Abseil changes -- f34cd235a12ad0ee1fea3a1ee5a427272dc2b285 by Abseil Team : Migrates uses of deprecated map types to recommended types. PiperOrigin-RevId: 309945156 -- e3410a47ad32c0775b6911610bc47b22938decad by Matthew Brown : Internal Change PiperOrigin-RevId: 309856021 -- a58cfa25e0bb59e7fa9647ac1aae65eaccff0086 by Greg Falcon : Internal change. PiperOrigin-RevId: 309804612 -- cdc5ec310035fbe25f496bda283fe655d94d7769 by Mark Barolak : Standardize the header comments for friend functions in cord.h PiperOrigin-RevId: 309779073 -- fe61602701be795e54477b0fdbf5ffc1df12a6b7 by Samuel Benzaquen : Implement %f natively for any input. It evaluates the input at runtime and allocates stack space accordingly. This removes a potential fallback into snprintf, improves performance, and removes all memory allocations in this formatting path. PiperOrigin-RevId: 309752501 -- 79e2a24f3f959e8b06ddf1d440bbabbd5f89b5b7 by Greg Falcon : Add a Cord::swap() method. Many other Abseil types already provide this, but it was missing here. We already provided a two-argument free function form of `swap()`, but that API is better suited for generic code. The swap member function is a better API when the types are known. PiperOrigin-RevId: 309751740 -- 85cdf60024f153fb4fcb7fe68ed2b14b9faf119d by Derek Mauro : Cleanup uses of "linker initialized" SpinLocks PiperOrigin-RevId: 309581867 -- 9e5443bfcec4b94056b13c75326576e987ab88fb by Matt Kulukundis : Clarify intended mixing properties of `absl::Hash` PiperOrigin-RevId: 309520174 -- a0630f0827b67f217aaeae68a448fe4c1101e17d by Greg Falcon : Comment out a test in Emscripten to sidestep `long double` issues. PiperOrigin-RevId: 309482953 GitOrigin-RevId: f34cd235a12ad0ee1fea3a1ee5a427272dc2b285 Change-Id: Icce0c9d547117374d596b9d684e4054ddd118669 --- absl/base/BUILD.bazel | 5 +- absl/base/CMakeLists.txt | 1 + absl/base/internal/low_level_alloc_test.cc | 4 +- absl/debugging/symbolize_elf.inc | 6 +- absl/hash/hash.h | 7 +- absl/random/gaussian_distribution_test.cc | 5 +- absl/random/internal/wide_multiply_test.cc | 2 +- absl/strings/BUILD.bazel | 5 +- absl/strings/CMakeLists.txt | 1 + absl/strings/cord.h | 21 +- absl/strings/cord_test.cc | 3 + absl/strings/internal/str_format/arg.cc | 112 ++-- absl/strings/internal/str_format/arg.h | 78 ++- absl/strings/internal/str_format/arg_test.cc | 5 +- absl/strings/internal/str_format/bind.h | 7 +- absl/strings/internal/str_format/checker_test.cc | 2 +- absl/strings/internal/str_format/convert_test.cc | 245 +++++++- absl/strings/internal/str_format/extension.h | 5 - .../internal/str_format/float_conversion.cc | 694 ++++++++++++++++++++- .../strings/internal/str_format/float_conversion.h | 6 +- absl/strings/internal/str_format/parser.h | 8 +- absl/strings/internal/str_format/parser_test.cc | 6 +- absl/strings/str_format_test.cc | 69 +- absl/strings/substitute.h | 2 +- 24 files changed, 1107 insertions(+), 192 deletions(-) (limited to 'absl/strings/cord.h') diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel index 1af9e45e..1664a351 100644 --- a/absl/base/BUILD.bazel +++ b/absl/base/BUILD.bazel @@ -541,7 +541,10 @@ cc_test( copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, tags = ["no_test_ios_x86_64"], - deps = [":malloc_internal"], + deps = [ + ":malloc_internal", + "//absl/container:node_hash_map", + ], ) cc_test( diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt index a63b591c..2df2e971 100644 --- a/absl/base/CMakeLists.txt +++ b/absl/base/CMakeLists.txt @@ -497,6 +497,7 @@ absl_cc_test( ${ABSL_TEST_COPTS} DEPS absl::malloc_internal + absl::node_hash_map Threads::Threads ) diff --git a/absl/base/internal/low_level_alloc_test.cc b/absl/base/internal/low_level_alloc_test.cc index 7abbbf9c..2f2eaffa 100644 --- a/absl/base/internal/low_level_alloc_test.cc +++ b/absl/base/internal/low_level_alloc_test.cc @@ -21,6 +21,8 @@ #include #include +#include "absl/container/node_hash_map.h" + namespace absl { ABSL_NAMESPACE_BEGIN namespace base_internal { @@ -75,7 +77,7 @@ static bool using_low_level_alloc = false; // allocations and deallocations are reported via the MallocHook // interface. static void Test(bool use_new_arena, bool call_malloc_hook, int n) { - typedef std::unordered_map AllocMap; + typedef absl::node_hash_map AllocMap; AllocMap allocated; AllocMap::iterator it; BlockDesc block_desc; diff --git a/absl/debugging/symbolize_elf.inc b/absl/debugging/symbolize_elf.inc index fe1d36ee..ec86f9a9 100644 --- a/absl/debugging/symbolize_elf.inc +++ b/absl/debugging/symbolize_elf.inc @@ -149,13 +149,15 @@ struct FileMappingHint { // Moreover, we are using only TryLock(), if the decorator list // is being modified (is busy), we skip all decorators, and possibly // loose some info. Sorry, that's the best we could do. -base_internal::SpinLock g_decorators_mu(base_internal::kLinkerInitialized); +ABSL_CONST_INIT absl::base_internal::SpinLock g_decorators_mu( + absl::kConstInit, absl::base_internal::SCHEDULE_KERNEL_ONLY); const int kMaxFileMappingHints = 8; int g_num_file_mapping_hints; FileMappingHint g_file_mapping_hints[kMaxFileMappingHints]; // Protects g_file_mapping_hints. -base_internal::SpinLock g_file_mapping_mu(base_internal::kLinkerInitialized); +ABSL_CONST_INIT absl::base_internal::SpinLock g_file_mapping_mu( + absl::kConstInit, absl::base_internal::SCHEDULE_KERNEL_ONLY); // Async-signal-safe function to zero a buffer. // memset() is not guaranteed to be async-signal-safe. diff --git a/absl/hash/hash.h b/absl/hash/hash.h index 3dbeab69..d7386f6c 100644 --- a/absl/hash/hash.h +++ b/absl/hash/hash.h @@ -37,8 +37,11 @@ // types. Hashing of that combined state is separately done by `absl::Hash`. // // One should assume that a hash algorithm is chosen randomly at the start of -// each process. E.g., absl::Hash()(9) in one process and -// absl::Hash()(9) in another process are likely to differ. +// each process. E.g., `absl::Hash{}(9)` in one process and +// `absl::Hash{}(9)` in another process are likely to differ. +// +// `absl::Hash` is intended to strongly mix input bits with a target of passing +// an [Avalanche Test](https://en.wikipedia.org/wiki/Avalanche_effect). // // Example: // diff --git a/absl/random/gaussian_distribution_test.cc b/absl/random/gaussian_distribution_test.cc index 49c07513..398f0131 100644 --- a/absl/random/gaussian_distribution_test.cc +++ b/absl/random/gaussian_distribution_test.cc @@ -130,12 +130,15 @@ TYPED_TEST(GaussianDistributionInterfaceTest, SerializeTest) { ss >> after; #if defined(__powerpc64__) || defined(__PPC64__) || defined(__powerpc__) || \ - defined(__ppc__) || defined(__PPC__) + defined(__ppc__) || defined(__PPC__) || defined(__EMSCRIPTEN__) if (std::is_same::value) { // Roundtripping floating point values requires sufficient precision // to reconstruct the exact value. It turns out that long double // has some errors doing this on ppc, particularly for values // near {1.0 +/- epsilon}. + // + // Emscripten is even worse, implementing long double as a 128-bit + // type, but shipping with a strtold() that doesn't support that. if (mean <= std::numeric_limits::max() && mean >= std::numeric_limits::lowest()) { EXPECT_EQ(static_cast(before.mean()), diff --git a/absl/random/internal/wide_multiply_test.cc b/absl/random/internal/wide_multiply_test.cc index 922603f2..ca8ce923 100644 --- a/absl/random/internal/wide_multiply_test.cc +++ b/absl/random/internal/wide_multiply_test.cc @@ -28,7 +28,7 @@ TEST(WideMultiplyTest, MultiplyU64ToU128Test) { EXPECT_EQ(absl::uint128(0), MultiplyU64ToU128(0, 0)); - // Max uint64 + // Max uint64_t EXPECT_EQ(MultiplyU64ToU128(kMax, kMax), absl::MakeUint128(0xfffffffffffffffe, 0x0000000000000001)); EXPECT_EQ(absl::MakeUint128(0, kMax), MultiplyU64ToU128(kMax, 1)); diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index 8aecbe59..8220896d 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -638,10 +638,13 @@ cc_library( visibility = ["//visibility:private"], deps = [ ":strings", + "//absl/base:bits", "//absl/base:config", "//absl/base:core_headers", + "//absl/functional:function_ref", "//absl/meta:type_traits", "//absl/numeric:int128", + "//absl/types:optional", "//absl/types:span", ], ) @@ -718,7 +721,7 @@ cc_test( deps = [ ":str_format_internal", "//absl/base:raw_logging_internal", - "//absl/numeric:int128", + "//absl/types:optional", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index 003794f9..c0ea0c8e 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt @@ -392,6 +392,7 @@ absl_cc_library( COPTS ${ABSL_DEFAULT_COPTS} DEPS + absl::bits absl::strings absl::config absl::core_headers diff --git a/absl/strings/cord.h b/absl/strings/cord.h index ae3d2e71..86ae76fd 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -162,7 +162,7 @@ class Cord { if (contents_.is_tree()) DestroyCordSlow(); } - // Cord::MakeCordFromExternal(data, callable) + // MakeCordFromExternal() // // Creates a Cord that takes ownership of external string memory. The // contents of `data` are not copied to the Cord; instead, the external @@ -246,10 +246,17 @@ class Cord { // (pos + new_size) >= size(), the result is the subrange [pos, size()). Cord Subcord(size_t pos, size_t new_size) const; + // Cord::swap() + // + // Swaps the contents of the Cord with `other`. + void swap(Cord& other) noexcept; + // swap() // - // Swaps the data of Cord `x` with Cord `y`. - friend void swap(Cord& x, Cord& y) noexcept; + // Swaps the contents of two Cords. + friend void swap(Cord& x, Cord& y) noexcept { + x.swap(y); + } // Cord::size() // @@ -1032,6 +1039,10 @@ inline Cord& Cord::operator=(const Cord& x) { inline Cord::Cord(Cord&& src) noexcept : contents_(std::move(src.contents_)) {} +inline void Cord::swap(Cord& other) noexcept { + contents_.Swap(&other.contents_); +} + inline Cord& Cord::operator=(Cord&& x) noexcept { contents_ = std::move(x.contents_); return *this; @@ -1308,10 +1319,6 @@ inline bool operator<=(absl::string_view x, const Cord& y) { return !(y < x); } inline bool operator>=(const Cord& x, absl::string_view y) { return !(x < y); } inline bool operator>=(absl::string_view x, const Cord& y) { return !(x < y); } -// Overload of swap for Cord. The use of non-const references is -// required. :( -inline void swap(Cord& x, Cord& y) noexcept { y.contents_.Swap(&x.contents_); } - // Some internals exposed to test code. namespace strings_internal { class CordTestAccess { diff --git a/absl/strings/cord_test.cc b/absl/strings/cord_test.cc index 336cedde..4443c828 100644 --- a/absl/strings/cord_test.cc +++ b/absl/strings/cord_test.cc @@ -396,6 +396,9 @@ TEST(Cord, Swap) { swap(x, y); ASSERT_EQ(x, absl::Cord(b)); ASSERT_EQ(y, absl::Cord(a)); + x.swap(y); + ASSERT_EQ(x, absl::Cord(a)); + ASSERT_EQ(y, absl::Cord(b)); } static void VerifyCopyToString(const absl::Cord& cord) { diff --git a/absl/strings/internal/str_format/arg.cc b/absl/strings/internal/str_format/arg.cc index a112071c..964f25f7 100644 --- a/absl/strings/internal/str_format/arg.cc +++ b/absl/strings/internal/str_format/arg.cc @@ -167,24 +167,26 @@ class IntDigits { // Note: 'o' conversions do not have a base indicator, it's just that // the '#' flag is specified to modify the precision for 'o' conversions. string_view BaseIndicator(const IntDigits &as_digits, - const ConversionSpec conv) { + const FormatConversionSpecImpl conv) { // always show 0x for %p. - bool alt = conv.has_alt_flag() || conv.conversion_char() == ConversionChar::p; - bool hex = (conv.conversion_char() == FormatConversionChar::x || - conv.conversion_char() == FormatConversionChar::X || - conv.conversion_char() == FormatConversionChar::p); + bool alt = conv.has_alt_flag() || + conv.conversion_char() == FormatConversionCharInternal::p; + bool hex = (conv.conversion_char() == FormatConversionCharInternal::x || + conv.conversion_char() == FormatConversionCharInternal::X || + conv.conversion_char() == FormatConversionCharInternal::p); // From the POSIX description of '#' flag: // "For x or X conversion specifiers, a non-zero result shall have // 0x (or 0X) prefixed to it." if (alt && hex && !as_digits.without_neg_or_zero().empty()) { - return conv.conversion_char() == FormatConversionChar::X ? "0X" : "0x"; + return conv.conversion_char() == FormatConversionCharInternal::X ? "0X" + : "0x"; } return {}; } -string_view SignColumn(bool neg, const ConversionSpec conv) { - if (conv.conversion_char() == FormatConversionChar::d || - conv.conversion_char() == FormatConversionChar::i) { +string_view SignColumn(bool neg, const FormatConversionSpecImpl conv) { + if (conv.conversion_char() == FormatConversionCharInternal::d || + conv.conversion_char() == FormatConversionCharInternal::i) { if (neg) return "-"; if (conv.has_show_pos_flag()) return "+"; if (conv.has_sign_col_flag()) return " "; @@ -192,7 +194,7 @@ string_view SignColumn(bool neg, const ConversionSpec conv) { return {}; } -bool ConvertCharImpl(unsigned char v, const ConversionSpec conv, +bool ConvertCharImpl(unsigned char v, const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { size_t fill = 0; if (conv.width() >= 0) fill = conv.width(); @@ -204,7 +206,8 @@ bool ConvertCharImpl(unsigned char v, const ConversionSpec conv, } bool ConvertIntImplInnerSlow(const IntDigits &as_digits, - const ConversionSpec conv, FormatSinkImpl *sink) { + const FormatConversionSpecImpl conv, + FormatSinkImpl *sink) { // Print as a sequence of Substrings: // [left_spaces][sign][base_indicator][zeroes][formatted][right_spaces] size_t fill = 0; @@ -224,7 +227,8 @@ bool ConvertIntImplInnerSlow(const IntDigits &as_digits, if (!precision_specified) precision = 1; - if (conv.has_alt_flag() && conv.conversion_char() == ConversionChar::o) { + if (conv.has_alt_flag() && + conv.conversion_char() == FormatConversionCharInternal::o) { // From POSIX description of the '#' (alt) flag: // "For o conversion, it increases the precision (if necessary) to // force the first digit of the result to be zero." @@ -258,42 +262,43 @@ bool ConvertIntImplInnerSlow(const IntDigits &as_digits, } template -bool ConvertIntArg(T v, const ConversionSpec conv, FormatSinkImpl *sink) { +bool ConvertIntArg(T v, const FormatConversionSpecImpl conv, + FormatSinkImpl *sink) { using U = typename MakeUnsigned::type; IntDigits as_digits; switch (conv.conversion_char()) { - case FormatConversionChar::c: + case FormatConversionCharInternal::c: return ConvertCharImpl(static_cast(v), conv, sink); - case FormatConversionChar::o: + case FormatConversionCharInternal::o: as_digits.PrintAsOct(static_cast(v)); break; - case FormatConversionChar::x: + case FormatConversionCharInternal::x: as_digits.PrintAsHexLower(static_cast(v)); break; - case FormatConversionChar::X: + case FormatConversionCharInternal::X: as_digits.PrintAsHexUpper(static_cast(v)); break; - case FormatConversionChar::u: + case FormatConversionCharInternal::u: as_digits.PrintAsDec(static_cast(v)); break; - case FormatConversionChar::d: - case FormatConversionChar::i: + case FormatConversionCharInternal::d: + case FormatConversionCharInternal::i: as_digits.PrintAsDec(v); break; - case FormatConversionChar::a: - case FormatConversionChar::e: - case FormatConversionChar::f: - case FormatConversionChar::g: - case FormatConversionChar::A: - case FormatConversionChar::E: - case FormatConversionChar::F: - case FormatConversionChar::G: + case FormatConversionCharInternal::a: + case FormatConversionCharInternal::e: + case FormatConversionCharInternal::f: + case FormatConversionCharInternal::g: + case FormatConversionCharInternal::A: + case FormatConversionCharInternal::E: + case FormatConversionCharInternal::F: + case FormatConversionCharInternal::G: return ConvertFloatImpl(static_cast(v), conv, sink); default: @@ -308,12 +313,13 @@ bool ConvertIntArg(T v, const ConversionSpec conv, FormatSinkImpl *sink) { } template -bool ConvertFloatArg(T v, const ConversionSpec conv, FormatSinkImpl *sink) { +bool ConvertFloatArg(T v, const FormatConversionSpecImpl conv, + FormatSinkImpl *sink) { return FormatConversionCharIsFloat(conv.conversion_char()) && ConvertFloatImpl(v, conv, sink); } -inline bool ConvertStringArg(string_view v, const ConversionSpec conv, +inline bool ConvertStringArg(string_view v, const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { if (conv.conversion_char() != FormatConversionCharInternal::s) return false; if (conv.is_basic()) { @@ -328,19 +334,20 @@ inline bool ConvertStringArg(string_view v, const ConversionSpec conv, // ==================== Strings ==================== StringConvertResult FormatConvertImpl(const std::string &v, - const ConversionSpec conv, + const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return {ConvertStringArg(v, conv, sink)}; } -StringConvertResult FormatConvertImpl(string_view v, const ConversionSpec conv, +StringConvertResult FormatConvertImpl(string_view v, + const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return {ConvertStringArg(v, conv, sink)}; } ArgConvertResult -FormatConvertImpl(const char *v, const ConversionSpec conv, +FormatConvertImpl(const char *v, const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { if (conv.conversion_char() == FormatConversionCharInternal::p) return {FormatConvertImpl(VoidPtr(v), conv, sink).value}; @@ -358,7 +365,7 @@ FormatConvertImpl(const char *v, const ConversionSpec conv, // ==================== Raw pointers ==================== ArgConvertResult FormatConvertImpl( - VoidPtr v, const ConversionSpec conv, FormatSinkImpl *sink) { + VoidPtr v, const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { if (conv.conversion_char() != FormatConversionCharInternal::p) return {false}; if (!v.value) { sink->Append("(nil)"); @@ -370,82 +377,87 @@ ArgConvertResult FormatConvertImpl( } // ==================== Floats ==================== -FloatingConvertResult FormatConvertImpl(float v, const ConversionSpec conv, +FloatingConvertResult FormatConvertImpl(float v, + const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return {ConvertFloatArg(v, conv, sink)}; } -FloatingConvertResult FormatConvertImpl(double v, const ConversionSpec conv, +FloatingConvertResult FormatConvertImpl(double v, + const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return {ConvertFloatArg(v, conv, sink)}; } FloatingConvertResult FormatConvertImpl(long double v, - const ConversionSpec conv, + const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return {ConvertFloatArg(v, conv, sink)}; } // ==================== Chars ==================== -IntegralConvertResult FormatConvertImpl(char v, const ConversionSpec conv, +IntegralConvertResult FormatConvertImpl(char v, + const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return {ConvertIntArg(v, conv, sink)}; } IntegralConvertResult FormatConvertImpl(signed char v, - const ConversionSpec conv, + const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return {ConvertIntArg(v, conv, sink)}; } IntegralConvertResult FormatConvertImpl(unsigned char v, - const ConversionSpec conv, + const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return {ConvertIntArg(v, conv, sink)}; } // ==================== Ints ==================== IntegralConvertResult FormatConvertImpl(short v, // NOLINT - const ConversionSpec conv, + const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return {ConvertIntArg(v, conv, sink)}; } IntegralConvertResult FormatConvertImpl(unsigned short v, // NOLINT - const ConversionSpec conv, + const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return {ConvertIntArg(v, conv, sink)}; } -IntegralConvertResult FormatConvertImpl(int v, const ConversionSpec conv, +IntegralConvertResult FormatConvertImpl(int v, + const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return {ConvertIntArg(v, conv, sink)}; } -IntegralConvertResult FormatConvertImpl(unsigned v, const ConversionSpec conv, +IntegralConvertResult FormatConvertImpl(unsigned v, + const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return {ConvertIntArg(v, conv, sink)}; } IntegralConvertResult FormatConvertImpl(long v, // NOLINT - const ConversionSpec conv, + const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return {ConvertIntArg(v, conv, sink)}; } IntegralConvertResult FormatConvertImpl(unsigned long v, // NOLINT - const ConversionSpec conv, + const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return {ConvertIntArg(v, conv, sink)}; } IntegralConvertResult FormatConvertImpl(long long v, // NOLINT - const ConversionSpec conv, + const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return {ConvertIntArg(v, conv, sink)}; } IntegralConvertResult FormatConvertImpl(unsigned long long v, // NOLINT - const ConversionSpec conv, + const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return {ConvertIntArg(v, conv, sink)}; } IntegralConvertResult FormatConvertImpl(absl::int128 v, - const ConversionSpec conv, + const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return {ConvertIntArg(v, conv, sink)}; } IntegralConvertResult FormatConvertImpl(absl::uint128 v, - const ConversionSpec conv, + const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return {ConvertIntArg(v, conv, sink)}; } diff --git a/absl/strings/internal/str_format/arg.h b/absl/strings/internal/str_format/arg.h index f4ac940a..9a1e5ef2 100644 --- a/absl/strings/internal/str_format/arg.h +++ b/absl/strings/internal/str_format/arg.h @@ -67,20 +67,24 @@ constexpr FormatConversionCharSet ExtractCharSet(ArgConvertResult) { using StringConvertResult = ArgConvertResult; ArgConvertResult FormatConvertImpl( - VoidPtr v, ConversionSpec conv, FormatSinkImpl* sink); + VoidPtr v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); // Strings. -StringConvertResult FormatConvertImpl(const std::string& v, ConversionSpec conv, +StringConvertResult FormatConvertImpl(const std::string& v, + FormatConversionSpecImpl conv, FormatSinkImpl* sink); -StringConvertResult FormatConvertImpl(string_view v, ConversionSpec conv, +StringConvertResult FormatConvertImpl(string_view v, + FormatConversionSpecImpl conv, FormatSinkImpl* sink); ArgConvertResult -FormatConvertImpl(const char* v, ConversionSpec conv, FormatSinkImpl* sink); +FormatConvertImpl(const char* v, const FormatConversionSpecImpl conv, + FormatSinkImpl* sink); + template ::value>::type* = nullptr> StringConvertResult FormatConvertImpl(const AbslCord& value, - ConversionSpec conv, + FormatConversionSpecImpl conv, FormatSinkImpl* sink) { if (conv.conversion_char() != FormatConversionCharInternal::s) { return {false}; @@ -127,50 +131,55 @@ using FloatingConvertResult = ArgConvertResult; // Floats. -FloatingConvertResult FormatConvertImpl(float v, ConversionSpec conv, +FloatingConvertResult FormatConvertImpl(float v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); -FloatingConvertResult FormatConvertImpl(double v, ConversionSpec conv, +FloatingConvertResult FormatConvertImpl(double v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); -FloatingConvertResult FormatConvertImpl(long double v, ConversionSpec conv, +FloatingConvertResult FormatConvertImpl(long double v, + FormatConversionSpecImpl conv, FormatSinkImpl* sink); // Chars. -IntegralConvertResult FormatConvertImpl(char v, ConversionSpec conv, +IntegralConvertResult FormatConvertImpl(char v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); -IntegralConvertResult FormatConvertImpl(signed char v, ConversionSpec conv, +IntegralConvertResult FormatConvertImpl(signed char v, + FormatConversionSpecImpl conv, FormatSinkImpl* sink); -IntegralConvertResult FormatConvertImpl(unsigned char v, ConversionSpec conv, +IntegralConvertResult FormatConvertImpl(unsigned char v, + FormatConversionSpecImpl conv, FormatSinkImpl* sink); // Ints. IntegralConvertResult FormatConvertImpl(short v, // NOLINT - ConversionSpec conv, + FormatConversionSpecImpl conv, FormatSinkImpl* sink); IntegralConvertResult FormatConvertImpl(unsigned short v, // NOLINT - ConversionSpec conv, + FormatConversionSpecImpl conv, FormatSinkImpl* sink); -IntegralConvertResult FormatConvertImpl(int v, ConversionSpec conv, +IntegralConvertResult FormatConvertImpl(int v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); -IntegralConvertResult FormatConvertImpl(unsigned v, ConversionSpec conv, +IntegralConvertResult FormatConvertImpl(unsigned v, + FormatConversionSpecImpl conv, FormatSinkImpl* sink); IntegralConvertResult FormatConvertImpl(long v, // NOLINT - ConversionSpec conv, + FormatConversionSpecImpl conv, FormatSinkImpl* sink); IntegralConvertResult FormatConvertImpl(unsigned long v, // NOLINT - ConversionSpec conv, + FormatConversionSpecImpl conv, FormatSinkImpl* sink); IntegralConvertResult FormatConvertImpl(long long v, // NOLINT - ConversionSpec conv, + FormatConversionSpecImpl conv, FormatSinkImpl* sink); IntegralConvertResult FormatConvertImpl(unsigned long long v, // NOLINT - ConversionSpec conv, + FormatConversionSpecImpl conv, FormatSinkImpl* sink); -IntegralConvertResult FormatConvertImpl(int128 v, ConversionSpec conv, +IntegralConvertResult FormatConvertImpl(int128 v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); -IntegralConvertResult FormatConvertImpl(uint128 v, ConversionSpec conv, +IntegralConvertResult FormatConvertImpl(uint128 v, + FormatConversionSpecImpl conv, FormatSinkImpl* sink); template ::value, int> = 0> -IntegralConvertResult FormatConvertImpl(T v, ConversionSpec conv, +IntegralConvertResult FormatConvertImpl(T v, FormatConversionSpecImpl conv, FormatSinkImpl* sink) { return FormatConvertImpl(static_cast(v), conv, sink); } @@ -181,11 +190,11 @@ template typename std::enable_if::value && !HasUserDefinedConvert::value, IntegralConvertResult>::type -FormatConvertImpl(T v, ConversionSpec conv, FormatSinkImpl* sink); +FormatConvertImpl(T v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); template StringConvertResult FormatConvertImpl(const StreamedWrapper& v, - ConversionSpec conv, + FormatConversionSpecImpl conv, FormatSinkImpl* out) { std::ostringstream oss; oss << v.v_; @@ -198,7 +207,8 @@ StringConvertResult FormatConvertImpl(const StreamedWrapper& v, struct FormatCountCaptureHelper { template static ArgConvertResult ConvertHelper( - const FormatCountCapture& v, ConversionSpec conv, FormatSinkImpl* sink) { + const FormatCountCapture& v, FormatConversionSpecImpl conv, + FormatSinkImpl* sink) { const absl::enable_if_t& v2 = v; if (conv.conversion_char() != @@ -212,7 +222,8 @@ struct FormatCountCaptureHelper { template ArgConvertResult FormatConvertImpl( - const FormatCountCapture& v, ConversionSpec conv, FormatSinkImpl* sink) { + const FormatCountCapture& v, FormatConversionSpecImpl conv, + FormatSinkImpl* sink) { return FormatCountCaptureHelper::ConvertHelper(v, conv, sink); } @@ -221,13 +232,13 @@ ArgConvertResult FormatConvertImpl( struct FormatArgImplFriend { template static bool ToInt(Arg arg, int* out) { - // A value initialized ConversionSpec has a `none` conv, which tells the - // dispatcher to run the `int` conversion. + // A value initialized FormatConversionSpecImpl has a `none` conv, which + // tells the dispatcher to run the `int` conversion. return arg.dispatcher_(arg.data_, {}, out); } template - static bool Convert(Arg arg, str_format_internal::ConversionSpec conv, + static bool Convert(Arg arg, FormatConversionSpecImpl conv, FormatSinkImpl* out) { return arg.dispatcher_(arg.data_, conv, out); } @@ -251,7 +262,7 @@ class FormatArgImpl { char buf[kInlinedSpace]; }; - using Dispatcher = bool (*)(Data, ConversionSpec, void* out); + using Dispatcher = bool (*)(Data, FormatConversionSpecImpl, void* out); template struct store_by_value @@ -393,7 +404,7 @@ class FormatArgImpl { } template - static bool Dispatch(Data arg, ConversionSpec spec, void* out) { + static bool Dispatch(Data arg, FormatConversionSpecImpl spec, void* out) { // A `none` conv indicates that we want the `int` conversion. if (ABSL_PREDICT_FALSE(spec.conversion_char() == FormatConversionCharInternal::kNone)) { @@ -410,8 +421,9 @@ class FormatArgImpl { Dispatcher dispatcher_; }; -#define ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(T, E) \ - E template bool FormatArgImpl::Dispatch(Data, ConversionSpec, void*) +#define ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(T, E) \ + E template bool FormatArgImpl::Dispatch(Data, FormatConversionSpecImpl, \ + void*) #define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(...) \ ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(str_format_internal::VoidPtr, \ diff --git a/absl/strings/internal/str_format/arg_test.cc b/absl/strings/internal/str_format/arg_test.cc index 8d30d8b8..37e5b754 100644 --- a/absl/strings/internal/str_format/arg_test.cc +++ b/absl/strings/internal/str_format/arg_test.cc @@ -95,8 +95,9 @@ TEST_F(FormatArgImplTest, OtherPtrDecayToVoidPtr) { TEST_F(FormatArgImplTest, WorksWithCharArraysOfUnknownSize) { std::string s; FormatSinkImpl sink(&s); - ConversionSpec conv; - FormatConversionSpecImplFriend::SetConversionChar(ConversionChar::s, &conv); + FormatConversionSpecImpl conv; + FormatConversionSpecImplFriend::SetConversionChar(FormatConversionChar::s, + &conv); FormatConversionSpecImplFriend::SetFlags(Flags(), &conv); FormatConversionSpecImplFriend::SetWidth(-1, &conv); FormatConversionSpecImplFriend::SetPrecision(-1, &conv); diff --git a/absl/strings/internal/str_format/bind.h b/absl/strings/internal/str_format/bind.h index 05105d8d..585246e7 100644 --- a/absl/strings/internal/str_format/bind.h +++ b/absl/strings/internal/str_format/bind.h @@ -19,7 +19,7 @@ class UntypedFormatSpec; namespace str_format_internal { -class BoundConversion : public ConversionSpec { +class BoundConversion : public FormatConversionSpecImpl { public: const FormatArgImpl* arg() const { return arg_; } void set_arg(const FormatArgImpl* a) { arg_ = a; } @@ -119,7 +119,7 @@ class FormatSpecTemplate #endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER - template ::type> @@ -190,7 +190,8 @@ class StreamedWrapper { private: template friend ArgConvertResult FormatConvertImpl( - const StreamedWrapper& v, ConversionSpec conv, FormatSinkImpl* out); + const StreamedWrapper& v, FormatConversionSpecImpl conv, + FormatSinkImpl* out); const T& v_; }; diff --git a/absl/strings/internal/str_format/checker_test.cc b/absl/strings/internal/str_format/checker_test.cc index 49a24b40..23348174 100644 --- a/absl/strings/internal/str_format/checker_test.cc +++ b/absl/strings/internal/str_format/checker_test.cc @@ -24,7 +24,7 @@ std::string ConvToString(FormatConversionCharSet conv) { } TEST(StrFormatChecker, ArgumentToConv) { - Conv conv = ArgumentToConv(); + FormatConversionCharSet conv = ArgumentToConv(); EXPECT_EQ(ConvToString(conv), "s"); conv = ArgumentToConv(); diff --git a/absl/strings/internal/str_format/convert_test.cc b/absl/strings/internal/str_format/convert_test.cc index cbcd7caf..dd167f76 100644 --- a/absl/strings/internal/str_format/convert_test.cc +++ b/absl/strings/internal/str_format/convert_test.cc @@ -1,14 +1,18 @@ #include #include #include + #include #include +#include #include +#include // NOLINT #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/internal/raw_logging.h" #include "absl/strings/internal/str_format/bind.h" +#include "absl/types/optional.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -57,7 +61,7 @@ std::string Esc(const T &v) { return oss.str(); } -void StrAppend(std::string *dst, const char *format, va_list ap) { +void StrAppendV(std::string *dst, const char *format, va_list ap) { // First try with a small fixed size buffer static const int kSpaceLength = 1024; char space[kSpaceLength]; @@ -98,11 +102,18 @@ void StrAppend(std::string *dst, const char *format, va_list ap) { delete[] buf; } +void StrAppend(std::string *out, const char *format, ...) { + va_list ap; + va_start(ap, format); + StrAppendV(out, format, ap); + va_end(ap); +} + std::string StrPrint(const char *format, ...) { va_list ap; va_start(ap, format); std::string result; - StrAppend(&result, format, ap); + StrAppendV(&result, format, ap); va_end(ap); return result; } @@ -471,8 +482,8 @@ TEST_F(FormatConvertTest, Float) { #endif // _MSC_VER const char *const kFormats[] = { - "%", "%.3", "%8.5", "%9", "%.60", "%.30", "%03", "%+", - "% ", "%-10", "%#15.3", "%#.0", "%.0", "%1$*2$", "%1$.*2$"}; + "%", "%.3", "%8.5", "%500", "%.5000", "%.60", "%.30", "%03", + "%+", "% ", "%-10", "%#15.3", "%#.0", "%.0", "%1$*2$", "%1$.*2$"}; std::vector doubles = {0.0, -0.0, @@ -489,11 +500,6 @@ TEST_F(FormatConvertTest, Float) { std::numeric_limits::infinity(), -std::numeric_limits::infinity()}; -#ifndef __APPLE__ - // Apple formats NaN differently (+nan) vs. (nan) - doubles.push_back(std::nan("")); -#endif - // Some regression tests. doubles.push_back(0.99999999999999989); @@ -512,43 +518,204 @@ TEST_F(FormatConvertTest, Float) { } } + // Workaround libc bug. + // https://sourceware.org/bugzilla/show_bug.cgi?id=22142 + const bool gcc_bug_22142 = + StrPrint("%f", std::numeric_limits::max()) != + "1797693134862315708145274237317043567980705675258449965989174768031" + "5726078002853876058955863276687817154045895351438246423432132688946" + "4182768467546703537516986049910576551282076245490090389328944075868" + "5084551339423045832369032229481658085593321233482747978262041447231" + "68738177180919299881250404026184124858368.000000"; + + if (!gcc_bug_22142) { + for (int exp = -300; exp <= 300; ++exp) { + const double all_ones_mantissa = 0x1fffffffffffff; + doubles.push_back(std::ldexp(all_ones_mantissa, exp)); + } + } + + if (gcc_bug_22142) { + for (auto &d : doubles) { + using L = std::numeric_limits; + double d2 = std::abs(d); + if (d2 == L::max() || d2 == L::min() || d2 == L::denorm_min()) { + d = 0; + } + } + } + + // Remove duplicates to speed up the logic below. + std::sort(doubles.begin(), doubles.end()); + doubles.erase(std::unique(doubles.begin(), doubles.end()), doubles.end()); + +#ifndef __APPLE__ + // Apple formats NaN differently (+nan) vs. (nan) + doubles.push_back(std::nan("")); +#endif + + // Reserve the space to ensure we don't allocate memory in the output itself. + std::string str_format_result; + str_format_result.reserve(1 << 20); + std::string string_printf_result; + string_printf_result.reserve(1 << 20); + for (const char *fmt : kFormats) { for (char f : {'f', 'F', // 'g', 'G', // 'a', 'A', // 'e', 'E'}) { std::string fmt_str = std::string(fmt) + f; + + if (fmt == absl::string_view("%.5000") && f != 'f' && f != 'F') { + // This particular test takes way too long with snprintf. + // Disable for the case we are not implementing natively. + continue; + } + for (double d : doubles) { int i = -10; FormatArgImpl args[2] = {FormatArgImpl(d), FormatArgImpl(i)}; UntypedFormatSpecImpl format(fmt_str); - // We use ASSERT_EQ here because failures are usually correlated and a - // bug would print way too many failed expectations causing the test to - // time out. - ASSERT_EQ(StrPrint(fmt_str.c_str(), d, i), - FormatPack(format, absl::MakeSpan(args))) - << fmt_str << " " << StrPrint("%.18g", d) << " " - << StrPrint("%.999f", d); + + string_printf_result.clear(); + StrAppend(&string_printf_result, fmt_str.c_str(), d, i); + str_format_result.clear(); + + { + AppendPack(&str_format_result, format, absl::MakeSpan(args)); + } + + if (string_printf_result != str_format_result) { + // We use ASSERT_EQ here because failures are usually correlated and a + // bug would print way too many failed expectations causing the test + // to time out. + ASSERT_EQ(string_printf_result, str_format_result) + << fmt_str << " " << StrPrint("%.18g", d) << " " + << StrPrint("%a", d) << " " << StrPrint("%.1080f", d); + } } } } } +TEST_F(FormatConvertTest, FloatRound) { + std::string s; + const auto format = [&](const char *fmt, double d) -> std::string & { + s.clear(); + FormatArgImpl args[1] = {FormatArgImpl(d)}; + AppendPack(&s, UntypedFormatSpecImpl(fmt), absl::MakeSpan(args)); +#if !defined(_MSC_VER) + // MSVC has a different rounding policy than us so we can't test our + // implementation against the native one there. + EXPECT_EQ(StrPrint(fmt, d), s); +#endif // _MSC_VER + + return s; + }; + // All of these values have to be exactly represented. + // Otherwise we might not be testing what we think we are testing. + + // These values can fit in a 64bit "fast" representation. + const double exact_value = 0.00000000000005684341886080801486968994140625; + assert(exact_value == std::pow(2, -44)); + // Round up at a 5xx. + EXPECT_EQ(format("%.13f", exact_value), "0.0000000000001"); + // Round up at a >5 + EXPECT_EQ(format("%.14f", exact_value), "0.00000000000006"); + // Round down at a <5 + EXPECT_EQ(format("%.16f", exact_value), "0.0000000000000568"); + // Nine handling + EXPECT_EQ(format("%.35f", exact_value), + "0.00000000000005684341886080801486969"); + EXPECT_EQ(format("%.36f", exact_value), + "0.000000000000056843418860808014869690"); + // Round down the last nine. + EXPECT_EQ(format("%.37f", exact_value), + "0.0000000000000568434188608080148696899"); + EXPECT_EQ(format("%.10f", 0.000003814697265625), "0.0000038147"); + // Round up the last nine + EXPECT_EQ(format("%.11f", 0.000003814697265625), "0.00000381470"); + EXPECT_EQ(format("%.12f", 0.000003814697265625), "0.000003814697"); + + // Round to even (down) + EXPECT_EQ(format("%.43f", exact_value), + "0.0000000000000568434188608080148696899414062"); + // Exact + EXPECT_EQ(format("%.44f", exact_value), + "0.00000000000005684341886080801486968994140625"); + // Round to even (up), let make the last digits 75 instead of 25 + EXPECT_EQ(format("%.43f", exact_value + std::pow(2, -43)), + "0.0000000000001705302565824240446090698242188"); + // Exact, just to check. + EXPECT_EQ(format("%.44f", exact_value + std::pow(2, -43)), + "0.00000000000017053025658242404460906982421875"); + + // This value has to be small enough that it won't fit in the uint128 + // representation for printing. + const double small_exact_value = + 0.000000000000000000000000000000000000752316384526264005099991383822237233803945956334136013765601092018187046051025390625; // NOLINT + assert(small_exact_value == std::pow(2, -120)); + // Round up at a 5xx. + EXPECT_EQ(format("%.37f", small_exact_value), + "0.0000000000000000000000000000000000008"); + // Round down at a <5 + EXPECT_EQ(format("%.38f", small_exact_value), + "0.00000000000000000000000000000000000075"); + // Round up at a >5 + EXPECT_EQ(format("%.41f", small_exact_value), + "0.00000000000000000000000000000000000075232"); + // Nine handling + EXPECT_EQ(format("%.55f", small_exact_value), + "0.0000000000000000000000000000000000007523163845262640051"); + EXPECT_EQ(format("%.56f", small_exact_value), + "0.00000000000000000000000000000000000075231638452626400510"); + EXPECT_EQ(format("%.57f", small_exact_value), + "0.000000000000000000000000000000000000752316384526264005100"); + EXPECT_EQ(format("%.58f", small_exact_value), + "0.0000000000000000000000000000000000007523163845262640051000"); + // Round down the last nine + EXPECT_EQ(format("%.59f", small_exact_value), + "0.00000000000000000000000000000000000075231638452626400509999"); + // Round up the last nine + EXPECT_EQ(format("%.79f", small_exact_value), + "0.000000000000000000000000000000000000" + "7523163845262640050999913838222372338039460"); + + // Round to even (down) + EXPECT_EQ(format("%.119f", small_exact_value), + "0.000000000000000000000000000000000000" + "75231638452626400509999138382223723380" + "394595633413601376560109201818704605102539062"); + // Exact + EXPECT_EQ(format("%.120f", small_exact_value), + "0.000000000000000000000000000000000000" + "75231638452626400509999138382223723380" + "3945956334136013765601092018187046051025390625"); + // Round to even (up), let make the last digits 75 instead of 25 + EXPECT_EQ(format("%.119f", small_exact_value + std::pow(2, -119)), + "0.000000000000000000000000000000000002" + "25694915357879201529997415146671170141" + "183786900240804129680327605456113815307617188"); + // Exact, just to check. + EXPECT_EQ(format("%.120f", small_exact_value + std::pow(2, -119)), + "0.000000000000000000000000000000000002" + "25694915357879201529997415146671170141" + "1837869002408041296803276054561138153076171875"); +} + TEST_F(FormatConvertTest, LongDouble) { - const char *const kFormats[] = {"%", "%.3", "%8.5", "%9", +#ifdef _MSC_VER + // MSVC has a different rounding policy than us so we can't test our + // implementation against the native one there. + return; +#endif // _MSC_VER + const char *const kFormats[] = {"%", "%.3", "%8.5", "%9", "%.5000", "%.60", "%+", "% ", "%-10"}; - // This value is not representable in double, but it is in long double that - // uses the extended format. - // This is to verify that we are not truncating the value mistakenly through a - // double. - long double very_precise = 10000000000000000.25L; - std::vector doubles = { 0.0, -0.0, - very_precise, - 1 / very_precise, std::numeric_limits::max(), -std::numeric_limits::max(), std::numeric_limits::min(), @@ -556,22 +723,44 @@ TEST_F(FormatConvertTest, LongDouble) { std::numeric_limits::infinity(), -std::numeric_limits::infinity()}; + for (long double base : {1.L, 12.L, 123.L, 1234.L, 12345.L, 123456.L, + 1234567.L, 12345678.L, 123456789.L, 1234567890.L, + 12345678901.L, 123456789012.L, 1234567890123.L, + // This value is not representable in double, but it + // is in long double that uses the extended format. + // This is to verify that we are not truncating the + // value mistakenly through a double. + 10000000000000000.25L}) { + for (int exp : {-1000, -500, 0, 500, 1000}) { + for (int sign : {1, -1}) { + doubles.push_back(sign * std::ldexp(base, exp)); + doubles.push_back(sign / std::ldexp(base, exp)); + } + } + } + for (const char *fmt : kFormats) { for (char f : {'f', 'F', // 'g', 'G', // 'a', 'A', // 'e', 'E'}) { std::string fmt_str = std::string(fmt) + 'L' + f; + + if (fmt == absl::string_view("%.5000") && f != 'f' && f != 'F') { + // This particular test takes way too long with snprintf. + // Disable for the case we are not implementing natively. + continue; + } + for (auto d : doubles) { FormatArgImpl arg(d); UntypedFormatSpecImpl format(fmt_str); // We use ASSERT_EQ here because failures are usually correlated and a // bug would print way too many failed expectations causing the test to // time out. - ASSERT_EQ(StrPrint(fmt_str.c_str(), d), - FormatPack(format, {&arg, 1})) + ASSERT_EQ(StrPrint(fmt_str.c_str(), d), FormatPack(format, {&arg, 1})) << fmt_str << " " << StrPrint("%.18Lg", d) << " " - << StrPrint("%.999Lf", d); + << StrPrint("%La", d) << " " << StrPrint("%.1080Lf", d); } } } diff --git a/absl/strings/internal/str_format/extension.h b/absl/strings/internal/str_format/extension.h index f0cffe1e..33903df0 100644 --- a/absl/strings/internal/str_format/extension.h +++ b/absl/strings/internal/str_format/extension.h @@ -411,11 +411,6 @@ inline size_t Excess(size_t used, size_t capacity) { return used < capacity ? capacity - used : 0; } -// Type alias for use during migration. -using ConversionChar = FormatConversionChar; -using ConversionSpec = FormatConversionSpecImpl; -using Conv = FormatConversionCharSet; - class FormatConversionSpec { public: // Width and precison are not specified, no flags are set. diff --git a/absl/strings/internal/str_format/float_conversion.cc b/absl/strings/internal/str_format/float_conversion.cc index d6858cff..cdccc86f 100644 --- a/absl/strings/internal/str_format/float_conversion.cc +++ b/absl/strings/internal/str_format/float_conversion.cc @@ -1,12 +1,22 @@ #include "absl/strings/internal/str_format/float_conversion.h" #include + #include #include #include +#include #include +#include "absl/base/attributes.h" #include "absl/base/config.h" +#include "absl/base/internal/bits.h" +#include "absl/base/optimization.h" +#include "absl/functional/function_ref.h" +#include "absl/meta/type_traits.h" +#include "absl/numeric/int128.h" +#include "absl/types/optional.h" +#include "absl/types/span.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -14,13 +24,640 @@ namespace str_format_internal { namespace { -char *CopyStringTo(string_view v, char *out) { +// The code below wants to avoid heap allocations. +// To do so it needs to allocate memory on the stack. +// `StackArray` will allocate memory on the stack in the form of a uint32_t +// array and call the provided callback with said memory. +// It will allocate memory in increments of 512 bytes. We could allocate the +// largest needed unconditionally, but that is more than we need in most of +// cases. This way we use less stack in the common cases. +class StackArray { + using Func = absl::FunctionRef)>; + static constexpr size_t kStep = 512 / sizeof(uint32_t); + // 5 steps is 2560 bytes, which is enough to hold a long double with the + // largest/smallest exponents. + // The operations below will static_assert their particular maximum. + static constexpr size_t kNumSteps = 5; + + // We do not want this function to be inlined. + // Otherwise the caller will allocate the stack space unnecessarily for all + // the variants even though it only calls one. + template + ABSL_ATTRIBUTE_NOINLINE static void RunWithCapacityImpl(Func f) { + uint32_t values[steps * kStep]{}; + f(absl::MakeSpan(values)); + } + + public: + static constexpr size_t kMaxCapacity = kStep * kNumSteps; + + static void RunWithCapacity(size_t capacity, Func f) { + assert(capacity <= kMaxCapacity); + const size_t step = (capacity + kStep - 1) / kStep; + assert(step <= kNumSteps); + switch (step) { + case 1: + return RunWithCapacityImpl<1>(f); + case 2: + return RunWithCapacityImpl<2>(f); + case 3: + return RunWithCapacityImpl<3>(f); + case 4: + return RunWithCapacityImpl<4>(f); + case 5: + return RunWithCapacityImpl<5>(f); + } + + assert(false && "Invalid capacity"); + } +}; + +// Calculates `10 * (*v) + carry` and stores the result in `*v` and returns +// the carry. +template +inline Int MultiplyBy10WithCarry(Int *v, Int carry) { + using BiggerInt = absl::conditional_t; + BiggerInt tmp = 10 * static_cast(*v) + carry; + *v = static_cast(tmp); + return static_cast(tmp >> (sizeof(Int) * 8)); +} + +// Calculates `(2^64 * carry + *v) / 10`. +// Stores the quotient in `*v` and returns the remainder. +// Requires: `0 <= carry <= 9` +inline uint64_t DivideBy10WithCarry(uint64_t *v, uint64_t carry) { + constexpr uint64_t divisor = 10; + // 2^64 / divisor = chunk_quotient + chunk_remainder / divisor + constexpr uint64_t chunk_quotient = (uint64_t{1} << 63) / (divisor / 2); + constexpr uint64_t chunk_remainder = uint64_t{} - chunk_quotient * divisor; + + const uint64_t mod = *v % divisor; + const uint64_t next_carry = chunk_remainder * carry + mod; + *v = *v / divisor + carry * chunk_quotient + next_carry / divisor; + return next_carry % divisor; +} + +// Generates the decimal representation for an integer of the form `v * 2^exp`, +// where `v` and `exp` are both positive integers. +// It generates the digits from the left (ie the most significant digit first) +// to allow for direct printing into the sink. +// +// Requires `0 <= exp` and `exp <= numeric_limits::max_exponent`. +class BinaryToDecimal { + static constexpr int ChunksNeeded(int exp) { + // We will left shift a uint128 by `exp` bits, so we need `128+exp` total + // bits. Round up to 32. + // See constructor for details about adding `10%` to the value. + return (128 + exp + 31) / 32 * 11 / 10; + } + + public: + // Run the conversion for `v * 2^exp` and call `f(binary_to_decimal)`. + // This function will allocate enough stack space to perform the conversion. + static void RunConversion(uint128 v, int exp, + absl::FunctionRef f) { + assert(exp > 0); + assert(exp <= std::numeric_limits::max_exponent); + static_assert( + StackArray::kMaxCapacity >= + ChunksNeeded(std::numeric_limits::max_exponent), + ""); + + StackArray::RunWithCapacity( + ChunksNeeded(exp), + [=](absl::Span input) { f(BinaryToDecimal(input, v, exp)); }); + } + + int TotalDigits() const { + return static_cast((decimal_end_ - decimal_start_) * kDigitsPerChunk + + CurrentDigits().size()); + } + + // See the current block of digits. + absl::string_view CurrentDigits() const { + return absl::string_view(digits_ + kDigitsPerChunk - size_, size_); + } + + // Advance the current view of digits. + // Returns `false` when no more digits are available. + bool AdvanceDigits() { + if (decimal_start_ >= decimal_end_) return false; + + uint32_t w = data_[decimal_start_++]; + for (size_ = 0; size_ < kDigitsPerChunk; w /= 10) { + digits_[kDigitsPerChunk - ++size_] = w % 10 + '0'; + } + return true; + } + + private: + BinaryToDecimal(absl::Span data, uint128 v, int exp) : data_(data) { + // We need to print the digits directly into the sink object without + // buffering them all first. To do this we need two things: + // - to know the total number of digits to do padding when necessary + // - to generate the decimal digits from the left. + // + // In order to do this, we do a two pass conversion. + // On the first pass we convert the binary representation of the value into + // a decimal representation in which each uint32_t chunk holds up to 9 + // decimal digits. In the second pass we take each decimal-holding-uint32_t + // value and generate the ascii decimal digits into `digits_`. + // + // The binary and decimal representations actually share the same memory + // region. As we go converting the chunks from binary to decimal we free + // them up and reuse them for the decimal representation. One caveat is that + // the decimal representation is around 7% less efficient in space than the + // binary one. We allocate an extra 10% memory to account for this. See + // ChunksNeeded for this calculation. + int chunk_index = exp / 32; + decimal_start_ = decimal_end_ = ChunksNeeded(exp); + const int offset = exp % 32; + // Left shift v by exp bits. + data_[chunk_index] = static_cast(v << offset); + for (v >>= (32 - offset); v; v >>= 32) + data_[++chunk_index] = static_cast(v); + + while (chunk_index >= 0) { + // While we have more than one chunk available, go in steps of 1e9. + // `data_[chunk_index]` holds the highest non-zero binary chunk, so keep + // the variable updated. + uint32_t carry = 0; + for (int i = chunk_index; i >= 0; --i) { + uint64_t tmp = uint64_t{data_[i]} + (uint64_t{carry} << 32); + data_[i] = static_cast(tmp / uint64_t{1000000000}); + carry = static_cast(tmp % uint64_t{1000000000}); + } + + // If the highest chunk is now empty, remove it from view. + if (data_[chunk_index] == 0) --chunk_index; + + --decimal_start_; + assert(decimal_start_ != chunk_index); + data_[decimal_start_] = carry; + } + + // Fill the first set of digits. The first chunk might not be complete, so + // handle differently. + for (uint32_t first = data_[decimal_start_++]; first != 0; first /= 10) { + digits_[kDigitsPerChunk - ++size_] = first % 10 + '0'; + } + } + + private: + static constexpr size_t kDigitsPerChunk = 9; + + int decimal_start_; + int decimal_end_; + + char digits_[kDigitsPerChunk]; + int size_ = 0; + + absl::Span data_; +}; + +// Converts a value of the form `x * 2^-exp` into a sequence of decimal digits. +// Requires `-exp < 0` and +// `-exp >= limits::min_exponent - limits::digits`. +class FractionalDigitGenerator { + public: + // Run the conversion for `v * 2^exp` and call `f(generator)`. + // This function will allocate enough stack space to perform the conversion. + static void RunConversion( + uint128 v, int exp, absl::FunctionRef f) { + assert(-exp < 0); + assert(-exp >= std::numeric_limits::min_exponent - 128); + static_assert( + StackArray::kMaxCapacity >= + (128 - std::numeric_limits::min_exponent + 31) / 32, + ""); + StackArray::RunWithCapacity((exp + 31) / 32, + [=](absl::Span input) { + f(FractionalDigitGenerator(input, v, exp)); + }); + } + + // Returns true if there are any more non-zero digits left. + bool HasMoreDigits() const { return next_digit_ != 0 || chunk_index_ >= 0; } + + // Returns true if the remainder digits are greater than 5000... + bool IsGreaterThanHalf() const { + return next_digit_ > 5 || (next_digit_ == 5 && chunk_index_ >= 0); + } + // Returns true if the remainder digits are exactly 5000... + bool IsExactlyHalf() const { return next_digit_ == 5 && chunk_index_ < 0; } + + struct Digits { + int digit_before_nine; + int num_nines; + }; + + // Get the next set of digits. + // They are composed by a non-9 digit followed by a runs of zero or more 9s. + Digits GetDigits() { + Digits digits{next_digit_, 0}; + + next_digit_ = GetOneDigit(); + while (next_digit_ == 9) { + ++digits.num_nines; + next_digit_ = GetOneDigit(); + } + + return digits; + } + + private: + // Return the next digit. + int GetOneDigit() { + if (chunk_index_ < 0) return 0; + + uint32_t carry = 0; + for (int i = chunk_index_; i >= 0; --i) { + carry = MultiplyBy10WithCarry(&data_[i], carry); + } + // If the lowest chunk is now empty, remove it from view. + if (data_[chunk_index_] == 0) --chunk_index_; + return carry; + } + + FractionalDigitGenerator(absl::Span data, uint128 v, int exp) + : chunk_index_(exp / 32), data_(data) { + const int offset = exp % 32; + // Right shift `v` by `exp` bits. + data_[chunk_index_] = static_cast(v << (32 - offset)); + v >>= offset; + // Make sure we don't overflow the data. We already calculated that + // non-zero bits fit, so we might not have space for leading zero bits. + for (int pos = chunk_index_; v; v >>= 32) + data_[--pos] = static_cast(v); + + // Fill next_digit_, as GetDigits expects it to be populated always. + next_digit_ = GetOneDigit(); + } + + int next_digit_; + int chunk_index_; + absl::Span data_; +}; + +// Count the number of leading zero bits. +int LeadingZeros(uint64_t v) { return base_internal::CountLeadingZeros64(v); } +int LeadingZeros(uint128 v) { + auto high = static_cast(v >> 64); + auto low = static_cast(v); + return high != 0 ? base_internal::CountLeadingZeros64(high) + : 64 + base_internal::CountLeadingZeros64(low); +} + +// Round up the text digits starting at `p`. +// The buffer must have an extra digit that is known to not need rounding. +// This is done below by having an extra '0' digit on the left. +void RoundUp(char *p) { + while (*p == '9' || *p == '.') { + if (*p == '9') *p = '0'; + --p; + } + ++*p; +} + +// Check the previous digit and round up or down to follow the round-to-even +// policy. +void RoundToEven(char *p) { + if (*p == '.') --p; + if (*p % 2 == 1) RoundUp(p); +} + +// Simple integral decimal digit printing for values that fit in 64-bits. +// Returns the pointer to the last written digit. +char *PrintIntegralDigitsFromRightFast(uint64_t v, char *p) { + do { + *--p = DivideBy10WithCarry(&v, 0) + '0'; + } while (v != 0); + return p; +} + +// Simple integral decimal digit printing for values that fit in 128-bits. +// Returns the pointer to the last written digit. +char *PrintIntegralDigitsFromRightFast(uint128 v, char *p) { + auto high = static_cast(v >> 64); + auto low = static_cast(v); + + while (high != 0) { + uint64_t carry = DivideBy10WithCarry(&high, 0); + carry = DivideBy10WithCarry(&low, carry); + *--p = carry + '0'; + } + return PrintIntegralDigitsFromRightFast(low, p); +} + +// Simple fractional decimal digit printing for values that fir in 64-bits after +// shifting. +// Performs rounding if necessary to fit within `precision`. +// Returns the pointer to one after the last character written. +char *PrintFractionalDigitsFast(uint64_t v, char *start, int exp, + int precision) { + char *p = start; + v <<= (64 - exp); + while (precision > 0) { + if (!v) return p; + *p++ = MultiplyBy10WithCarry(&v, uint64_t{0}) + '0'; + --precision; + } + + // We need to round. + if (v < 0x8000000000000000) { + // We round down, so nothing to do. + } else if (v > 0x8000000000000000) { + // We round up. + RoundUp(p - 1); + } else { + RoundToEven(p - 1); + } + + assert(precision == 0); + // Precision can only be zero here. + return p; +} + +// Simple fractional decimal digit printing for values that fir in 128-bits +// after shifting. +// Performs rounding if necessary to fit within `precision`. +// Returns the pointer to one after the last character written. +char *PrintFractionalDigitsFast(uint128 v, char *start, int exp, + int precision) { + char *p = start; + v <<= (128 - exp); + auto high = static_cast(v >> 64); + auto low = static_cast(v); + + // While we have digits to print and `low` is not empty, do the long + // multiplication. + while (precision > 0 && low != 0) { + uint64_t carry = MultiplyBy10WithCarry(&low, uint64_t{0}); + carry = MultiplyBy10WithCarry(&high, carry); + + *p++ = carry + '0'; + --precision; + } + + // Now `low` is empty, so use a faster approach for the rest of the digits. + // This block is pretty much the same as the main loop for the 64-bit case + // above. + while (precision > 0) { + if (!high) return p; + *p++ = MultiplyBy10WithCarry(&high, uint64_t{0}) + '0'; + --precision; + } + + // We need to round. + if (high < 0x8000000000000000) { + // We round down, so nothing to do. + } else if (high > 0x8000000000000000 || low != 0) { + // We round up. + RoundUp(p - 1); + } else { + RoundToEven(p - 1); + } + + assert(precision == 0); + // Precision can only be zero here. + return p; +} + +struct FormatState { + char sign_char; + int precision; + const FormatConversionSpecImpl &conv; + FormatSinkImpl *sink; + + // In `alt` mode (flag #) we keep the `.` even if there are no fractional + // digits. In non-alt mode, we strip it. + bool ShouldPrintDot() const { return precision != 0 || conv.has_alt_flag(); } +}; + +struct Padding { + int left_spaces; + int zeros; + int right_spaces; +}; + +Padding ExtraWidthToPadding(int total_size, const FormatState &state) { + int missing_chars = std::max(state.conv.width() - total_size, 0); + if (state.conv.has_left_flag()) { + return {0, 0, missing_chars}; + } else if (state.conv.has_zero_flag()) { + return {0, missing_chars, 0}; + } else { + return {missing_chars, 0, 0}; + } +} + +void FinalPrint(absl::string_view data, int trailing_zeros, + const FormatState &state) { + if (state.conv.width() < 0) { + // No width specified. Fast-path. + if (state.sign_char != '\0') state.sink->Append(1, state.sign_char); + state.sink->Append(data); + state.sink->Append(trailing_zeros, '0'); + return; + } + + auto padding = + ExtraWidthToPadding((state.sign_char != '\0' ? 1 : 0) + + static_cast(data.size()) + trailing_zeros, + state); + + state.sink->Append(padding.left_spaces, ' '); + if (state.sign_char != '\0') state.sink->Append(1, state.sign_char); + state.sink->Append(padding.zeros, '0'); + state.sink->Append(data); + state.sink->Append(trailing_zeros, '0'); + state.sink->Append(padding.right_spaces, ' '); +} + +// Fastpath %f formatter for when the shifted value fits in a simple integral +// type. +// Prints `v*2^exp` with the options from `state`. +template +void FormatFFast(Int v, int exp, const FormatState &state) { + constexpr int input_bits = sizeof(Int) * 8; + + static constexpr size_t integral_size = + /* in case we need to round up an extra digit */ 1 + + /* decimal digits for uint128 */ 40 + 1; + char buffer[integral_size + /* . */ 1 + /* max digits uint128 */ 128]; + buffer[integral_size] = '.'; + char *const integral_digits_end = buffer + integral_size; + char *integral_digits_start; + char *const fractional_digits_start = buffer + integral_size + 1; + char *fractional_digits_end = fractional_digits_start; + + if (exp >= 0) { + const int total_bits = input_bits - LeadingZeros(v) + exp; + integral_digits_start = + total_bits <= 64 + ? PrintIntegralDigitsFromRightFast(static_cast(v) << exp, + integral_digits_end) + : PrintIntegralDigitsFromRightFast(static_cast(v) << exp, + integral_digits_end); + } else { + exp = -exp; + + integral_digits_start = PrintIntegralDigitsFromRightFast( + exp < input_bits ? v >> exp : 0, integral_digits_end); + // PrintFractionalDigits may pull a carried 1 all the way up through the + // integral portion. + integral_digits_start[-1] = '0'; + + fractional_digits_end = + exp <= 64 ? PrintFractionalDigitsFast(v, fractional_digits_start, exp, + state.precision) + : PrintFractionalDigitsFast(static_cast(v), + fractional_digits_start, exp, + state.precision); + // There was a carry, so include the first digit too. + if (integral_digits_start[-1] != '0') --integral_digits_start; + } + + size_t size = fractional_digits_end - integral_digits_start; + + // In `alt` mode (flag #) we keep the `.` even if there are no fractional + // digits. In non-alt mode, we strip it. + if (!state.ShouldPrintDot()) --size; + FinalPrint(absl::string_view(integral_digits_start, size), + static_cast(state.precision - (fractional_digits_end - + fractional_digits_start)), + state); +} + +// Slow %f formatter for when the shifted value does not fit in a uint128, and +// `exp > 0`. +// Prints `v*2^exp` with the options from `state`. +// This one is guaranteed to not have fractional digits, so we don't have to +// worry about anything after the `.`. +void FormatFPositiveExpSlow(uint128 v, int exp, const FormatState &state) { + BinaryToDecimal::RunConversion(v, exp, [&](BinaryToDecimal btd) { + const int total_digits = + btd.TotalDigits() + (state.ShouldPrintDot() ? state.precision + 1 : 0); + + const auto padding = ExtraWidthToPadding( + total_digits + (state.sign_char != '\0' ? 1 : 0), state); + + state.sink->Append(padding.left_spaces, ' '); + if (state.sign_char != '\0') state.sink->Append(1, state.sign_char); + state.sink->Append(padding.zeros, '0'); + + do { + state.sink->Append(btd.CurrentDigits()); + } while (btd.AdvanceDigits()); + + if (state.ShouldPrintDot()) state.sink->Append(1, '.'); + state.sink->Append(state.precision, '0'); + state.sink->Append(padding.right_spaces, ' '); + }); +} + +// Slow %f formatter for when the shifted value does not fit in a uint128, and +// `exp < 0`. +// Prints `v*2^exp` with the options from `state`. +// This one is guaranteed to be < 1.0, so we don't have to worry about integral +// digits. +void FormatFNegativeExpSlow(uint128 v, int exp, const FormatState &state) { + const int total_digits = + /* 0 */ 1 + (state.ShouldPrintDot() ? state.precision + 1 : 0); + auto padding = + ExtraWidthToPadding(total_digits + (state.sign_char ? 1 : 0), state); + padding.zeros += 1; + state.sink->Append(padding.left_spaces, ' '); + if (state.sign_char != '\0') state.sink->Append(1, state.sign_char); + state.sink->Append(padding.zeros, '0'); + + if (state.ShouldPrintDot()) state.sink->Append(1, '.'); + + // Print digits + int digits_to_go = state.precision; + + FractionalDigitGenerator::RunConversion( + v, exp, [&](FractionalDigitGenerator digit_gen) { + // There are no digits to print here. + if (state.precision == 0) return; + + // We go one digit at a time, while keeping track of runs of nines. + // The runs of nines are used to perform rounding when necessary. + + while (digits_to_go > 0 && digit_gen.HasMoreDigits()) { + auto digits = digit_gen.GetDigits(); + + // Now we have a digit and a run of nines. + // See if we can print them all. + if (digits.num_nines + 1 < digits_to_go) { + // We don't have to round yet, so print them. + state.sink->Append(1, digits.digit_before_nine + '0'); + state.sink->Append(digits.num_nines, '9'); + digits_to_go -= digits.num_nines + 1; + + } else { + // We can't print all the nines, see where we have to truncate. + + bool round_up = false; + if (digits.num_nines + 1 > digits_to_go) { + // We round up at a nine. No need to print them. + round_up = true; + } else { + // We can fit all the nines, but truncate just after it. + if (digit_gen.IsGreaterThanHalf()) { + round_up = true; + } else if (digit_gen.IsExactlyHalf()) { + // Round to even + round_up = + digits.num_nines != 0 || digits.digit_before_nine % 2 == 1; + } + } + + if (round_up) { + state.sink->Append(1, digits.digit_before_nine + '1'); + --digits_to_go; + // The rest will be zeros. + } else { + state.sink->Append(1, digits.digit_before_nine + '0'); + state.sink->Append(digits_to_go - 1, '9'); + digits_to_go = 0; + } + return; + } + } + }); + + state.sink->Append(digits_to_go, '0'); + state.sink->Append(padding.right_spaces, ' '); +} + +template +void FormatF(Int mantissa, int exp, const FormatState &state) { + if (exp >= 0) { + const int total_bits = sizeof(Int) * 8 - LeadingZeros(mantissa) + exp; + + // Fallback to the slow stack-based approach if we can't do it in a 64 or + // 128 bit state. + if (ABSL_PREDICT_FALSE(total_bits > 128)) { + return FormatFPositiveExpSlow(mantissa, exp, state); + } + } else { + // Fallback to the slow stack-based approach if we can't do it in a 64 or + // 128 bit state. + if (ABSL_PREDICT_FALSE(exp < -128)) { + return FormatFNegativeExpSlow(mantissa, -exp, state); + } + } + return FormatFFast(mantissa, exp, state); +} + +char *CopyStringTo(absl::string_view v, char *out) { std::memcpy(out, v.data(), v.size()); return out + v.size(); } template -bool FallbackToSnprintf(const Float v, const ConversionSpec &conv, +bool FallbackToSnprintf(const Float v, const FormatConversionSpecImpl &conv, FormatSinkImpl *sink) { int w = conv.width() >= 0 ? conv.width() : 0; int p = conv.precision() >= 0 ? conv.precision() : -1; @@ -38,12 +675,12 @@ bool FallbackToSnprintf(const Float v, const ConversionSpec &conv, assert(fp < fmt + sizeof(fmt)); } std::string space(512, '\0'); - string_view result; + absl::string_view result; while (true) { int n = snprintf(&space[0], space.size(), fmt, w, p, v); if (n < 0) return false; if (static_cast(n) < space.size()) { - result = string_view(space.data(), n); + result = absl::string_view(space.data(), n); break; } space.resize(n + 1); @@ -96,9 +733,10 @@ enum class FormatStyle { Fixed, Precision }; // Otherwise, return false. template bool ConvertNonNumericFloats(char sign_char, Float v, - const ConversionSpec &conv, FormatSinkImpl *sink) { + const FormatConversionSpecImpl &conv, + FormatSinkImpl *sink) { char text[4], *ptr = text; - if (sign_char) *ptr++ = sign_char; + if (sign_char != '\0') *ptr++ = sign_char; if (std::isnan(v)) { ptr = std::copy_n( FormatConversionCharIsUpper(conv.conversion_char()) ? "NAN" : "nan", 3, @@ -172,7 +810,12 @@ constexpr bool CanFitMantissa() { template struct Decomposed { - Float mantissa; + using MantissaType = + absl::conditional_t::value, uint128, + uint64_t>; + static_assert(std::numeric_limits::digits <= sizeof(MantissaType) * 8, + ""); + MantissaType mantissa; int exponent; }; @@ -183,7 +826,8 @@ Decomposed Decompose(Float v) { Float m = std::frexp(v, &exp); m = std::ldexp(m, std::numeric_limits::digits); exp -= std::numeric_limits::digits; - return {m, exp}; + + return {static_cast::MantissaType>(m), exp}; } // Print 'digits' as decimal. @@ -352,8 +996,9 @@ bool FloatToBuffer(Decomposed decomposed, int precision, Buffer *out, return false; } -void WriteBufferToSink(char sign_char, string_view str, - const ConversionSpec &conv, FormatSinkImpl *sink) { +void WriteBufferToSink(char sign_char, absl::string_view str, + const FormatConversionSpecImpl &conv, + FormatSinkImpl *sink) { int left_spaces = 0, zeros = 0, right_spaces = 0; int missing_chars = conv.width() >= 0 ? std::max(conv.width() - static_cast(str.size()) - @@ -369,14 +1014,14 @@ void WriteBufferToSink(char sign_char, string_view str, } sink->Append(left_spaces, ' '); - if (sign_char) sink->Append(1, sign_char); + if (sign_char != '\0') sink->Append(1, sign_char); sink->Append(zeros, '0'); sink->Append(str); sink->Append(right_spaces, ' '); } template -bool FloatToSink(const Float v, const ConversionSpec &conv, +bool FloatToSink(const Float v, const FormatConversionSpecImpl &conv, FormatSinkImpl *sink) { // Print the sign or the sign column. Float abs_v = v; @@ -407,11 +1052,9 @@ bool FloatToSink(const Float v, const ConversionSpec &conv, if (c == FormatConversionCharInternal::f || c == FormatConversionCharInternal::F) { - if (!FloatToBuffer(decomposed, precision, &buffer, - nullptr)) { - return FallbackToSnprintf(v, conv, sink); - } - if (!conv.has_alt_flag() && buffer.back() == '.') buffer.pop_back(); + FormatF(decomposed.mantissa, decomposed.exponent, + {sign_char, precision, conv, sink}); + return true; } else if (c == FormatConversionCharInternal::e || c == FormatConversionCharInternal::E) { if (!FloatToBuffer(decomposed, precision, &buffer, @@ -462,25 +1105,32 @@ bool FloatToSink(const Float v, const ConversionSpec &conv, } WriteBufferToSink(sign_char, - string_view(buffer.begin, buffer.end - buffer.begin), conv, - sink); + absl::string_view(buffer.begin, buffer.end - buffer.begin), + conv, sink); return true; } } // namespace -bool ConvertFloatImpl(long double v, const ConversionSpec &conv, +bool ConvertFloatImpl(long double v, const FormatConversionSpecImpl &conv, FormatSinkImpl *sink) { + if (std::numeric_limits::digits == + 2 * std::numeric_limits::digits) { + // This is the `double-double` representation of `long double`. + // We do not handle it natively. Fallback to snprintf. + return FallbackToSnprintf(v, conv, sink); + } + return FloatToSink(v, conv, sink); } -bool ConvertFloatImpl(float v, const ConversionSpec &conv, +bool ConvertFloatImpl(float v, const FormatConversionSpecImpl &conv, FormatSinkImpl *sink) { return FloatToSink(v, conv, sink); } -bool ConvertFloatImpl(double v, const ConversionSpec &conv, +bool ConvertFloatImpl(double v, const FormatConversionSpecImpl &conv, FormatSinkImpl *sink) { return FloatToSink(v, conv, sink); } diff --git a/absl/strings/internal/str_format/float_conversion.h b/absl/strings/internal/str_format/float_conversion.h index 49a6a636..e78bc191 100644 --- a/absl/strings/internal/str_format/float_conversion.h +++ b/absl/strings/internal/str_format/float_conversion.h @@ -7,13 +7,13 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace str_format_internal { -bool ConvertFloatImpl(float v, const ConversionSpec &conv, +bool ConvertFloatImpl(float v, const FormatConversionSpecImpl &conv, FormatSinkImpl *sink); -bool ConvertFloatImpl(double v, const ConversionSpec &conv, +bool ConvertFloatImpl(double v, const FormatConversionSpecImpl &conv, FormatSinkImpl *sink); -bool ConvertFloatImpl(long double v, const ConversionSpec &conv, +bool ConvertFloatImpl(long double v, const FormatConversionSpecImpl &conv, FormatSinkImpl *sink); } // namespace str_format_internal diff --git a/absl/strings/internal/str_format/parser.h b/absl/strings/internal/str_format/parser.h index fd2dc970..fffed04f 100644 --- a/absl/strings/internal/str_format/parser.h +++ b/absl/strings/internal/str_format/parser.h @@ -83,7 +83,7 @@ const char* ConsumeUnboundConversion(const char* p, const char* end, // conversions. class ConvTag { public: - constexpr ConvTag(ConversionChar conversion_char) // NOLINT + constexpr ConvTag(FormatConversionChar conversion_char) // NOLINT : tag_(static_cast(conversion_char)) {} // We invert the length modifiers to make them negative so that we can easily // test for them. @@ -94,9 +94,9 @@ class ConvTag { bool is_conv() const { return tag_ >= 0; } bool is_length() const { return tag_ < 0 && tag_ != -128; } - ConversionChar as_conv() const { + FormatConversionChar as_conv() const { assert(is_conv()); - return static_cast(tag_); + return static_cast(tag_); } LengthMod as_length() const { assert(is_length()); @@ -282,7 +282,7 @@ class ParsedFormatBase { // This is the only API that allows the user to pass a runtime specified format // string. These factory functions will return NULL if the format does not match // the conversions requested by the user. -template +template class ExtendedParsedFormat : public str_format_internal::ParsedFormatBase { public: explicit ExtendedParsedFormat(string_view format) diff --git a/absl/strings/internal/str_format/parser_test.cc b/absl/strings/internal/str_format/parser_test.cc index 26f5bec6..dae2d20f 100644 --- a/absl/strings/internal/str_format/parser_test.cc +++ b/absl/strings/internal/str_format/parser_test.cc @@ -41,7 +41,7 @@ TEST(LengthModTest, Names) { TEST(ConversionCharTest, Names) { struct Expectation { - ConversionChar id; + FormatConversionChar id; char name; }; // clang-format off @@ -57,7 +57,7 @@ TEST(ConversionCharTest, Names) { // clang-format on for (auto e : kExpect) { SCOPED_TRACE(e.name); - ConversionChar v = e.id; + FormatConversionChar v = e.id; EXPECT_EQ(e.name, FormatConversionCharToChar(v)); } } @@ -368,7 +368,7 @@ TEST_F(ParsedFormatTest, ValueSemantics) { struct ExpectParse { const char* in; - std::initializer_list conv_set; + std::initializer_list conv_set; const char* out; }; diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc index 160f4c61..3f14dba3 100644 --- a/absl/strings/str_format_test.cc +++ b/absl/strings/str_format_test.cc @@ -532,76 +532,103 @@ TEST_F(ParsedFormatTest, SimpleUncheckedIncorrect) { EXPECT_FALSE((ParsedFormat<'s', 'd', 'g'>::New(format))); } -using str_format_internal::Conv; +using absl::str_format_internal::FormatConversionCharSet; TEST_F(ParsedFormatTest, UncheckedCorrect) { - auto f = ExtendedParsedFormat::New("ABC%dDEF"); + auto f = ExtendedParsedFormat::New("ABC%dDEF"); ASSERT_TRUE(f); EXPECT_EQ("[ABC]{d:1$d}[DEF]", SummarizeParsedFormat(*f)); std::string format = "%sFFF%dZZZ%f"; - auto f2 = ExtendedParsedFormat::New( - format); + auto f2 = + ExtendedParsedFormat::New(format); ASSERT_TRUE(f2); EXPECT_EQ("{s:1$s}[FFF]{d:2$d}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2)); - f2 = ExtendedParsedFormat::New( - "%s %d %f"); + f2 = + ExtendedParsedFormat::New("%s %d %f"); ASSERT_TRUE(f2); EXPECT_EQ("{s:1$s}[ ]{d:2$d}[ ]{f:3$f}", SummarizeParsedFormat(*f2)); - auto star = ExtendedParsedFormat::New("%*d"); + auto star = ExtendedParsedFormat::New("%*d"); ASSERT_TRUE(star); EXPECT_EQ("{*d:2$1$*d}", SummarizeParsedFormat(*star)); - auto dollar = ExtendedParsedFormat::New("%2$s %1$d"); + auto dollar = + ExtendedParsedFormat::New("%2$s %1$d"); ASSERT_TRUE(dollar); EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}", SummarizeParsedFormat(*dollar)); // with reuse - dollar = ExtendedParsedFormat::New("%2$s %1$d %1$d"); + dollar = + ExtendedParsedFormat::New("%2$s %1$d %1$d"); ASSERT_TRUE(dollar); EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}[ ]{1$d:1$d}", SummarizeParsedFormat(*dollar)); } TEST_F(ParsedFormatTest, UncheckedIgnoredArgs) { - EXPECT_FALSE((ExtendedParsedFormat::New("ABC"))); - EXPECT_FALSE((ExtendedParsedFormat::New("%dABC"))); - EXPECT_FALSE((ExtendedParsedFormat::New("ABC%2$s"))); - auto f = ExtendedParsedFormat::NewAllowIgnored("ABC"); + EXPECT_FALSE((ExtendedParsedFormat::New("ABC"))); + EXPECT_FALSE( + (ExtendedParsedFormat::New("%dABC"))); + EXPECT_FALSE( + (ExtendedParsedFormat::New("ABC%2$s"))); + auto f = + ExtendedParsedFormat::NewAllowIgnored("ABC"); ASSERT_TRUE(f); EXPECT_EQ("[ABC]", SummarizeParsedFormat(*f)); - f = ExtendedParsedFormat::NewAllowIgnored("%dABC"); + f = ExtendedParsedFormat< + FormatConversionCharSet::d, + FormatConversionCharSet::s>::NewAllowIgnored("%dABC"); ASSERT_TRUE(f); EXPECT_EQ("{d:1$d}[ABC]", SummarizeParsedFormat(*f)); - f = ExtendedParsedFormat::NewAllowIgnored("ABC%2$s"); + f = ExtendedParsedFormat< + FormatConversionCharSet::d, + FormatConversionCharSet::s>::NewAllowIgnored("ABC%2$s"); ASSERT_TRUE(f); EXPECT_EQ("[ABC]{2$s:2$s}", SummarizeParsedFormat(*f)); } TEST_F(ParsedFormatTest, UncheckedMultipleTypes) { - auto dx = ExtendedParsedFormat::New("%1$d %1$x"); + auto dx = ExtendedParsedFormat::New("%1$d %1$x"); EXPECT_TRUE(dx); EXPECT_EQ("{1$d:1$d}[ ]{1$x:1$x}", SummarizeParsedFormat(*dx)); - dx = ExtendedParsedFormat::New("%1$d"); + dx = ExtendedParsedFormat::New("%1$d"); EXPECT_TRUE(dx); EXPECT_EQ("{1$d:1$d}", SummarizeParsedFormat(*dx)); } TEST_F(ParsedFormatTest, UncheckedIncorrect) { - EXPECT_FALSE(ExtendedParsedFormat::New("")); + EXPECT_FALSE(ExtendedParsedFormat::New("")); - EXPECT_FALSE(ExtendedParsedFormat::New("ABC%dDEF%d")); + EXPECT_FALSE( + ExtendedParsedFormat::New("ABC%dDEF%d")); std::string format = "%sFFF%dZZZ%f"; - EXPECT_FALSE((ExtendedParsedFormat::New(format))); + EXPECT_FALSE((ExtendedParsedFormat::New(format))); } TEST_F(ParsedFormatTest, RegressionMixPositional) { - EXPECT_FALSE((ExtendedParsedFormat::New("%1$d %o"))); + EXPECT_FALSE( + (ExtendedParsedFormat::New("%1$d %o"))); } using FormatWrapperTest = ::testing::Test; diff --git a/absl/strings/substitute.h b/absl/strings/substitute.h index e7b4c1e6..92c47236 100644 --- a/absl/strings/substitute.h +++ b/absl/strings/substitute.h @@ -50,7 +50,7 @@ // // Supported types: // * absl::string_view, std::string, const char* (null is equivalent to "") -// * int32_t, int64_t, uint32_t, uint64 +// * int32_t, int64_t, uint32_t, uint64_t // * float, double // * bool (Printed as "true" or "false") // * pointer types other than char* (Printed as "0x", -- cgit v1.2.3 From cbfd0f0fe53d9d3495a3d607311d984fec37e692 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Tue, 12 May 2020 15:05:26 -0700 Subject: Export of internal Abseil changes -- 6a60bc6c79a3069f49986c2567dd51d2792f8ec1 by Abseil Team : Internal cleanup PiperOrigin-RevId: 311210039 -- a1049de1dd9071efa3a3dda1c3f25ab578b23e27 by Laramie Leavitt : Internal change PiperOrigin-RevId: 311188627 -- c2ccddd1cd89ef9d79c35bbe9e1813164db27031 by Matt Kulukundis : Migrate time parsing/formatting to string_view. - make a copy before handing to cctz but handle local cases without PiperOrigin-RevId: 311009254 -- d91d0cd68f3672a727ff76ee43f2da5226673d60 by Gennadiy Rozental : Eliminate public method absl::Flag::IsSpecfiedOnCommandLine. This interface was never intended to be supported. Prefer to react to the current value of flag. PiperOrigin-RevId: 310991916 -- 8ad41e7ec24f43598ed232545314117802e7895c by Gennadiy Rozental : Internal change PiperOrigin-RevId: 310757743 -- f091f77a13ce9481218cb356f8b4ceb49c1530f9 by Jorg Brown : Change #include of to from absl/strings/cord.h PiperOrigin-RevId: 310657413 -- 39419418af6be4ac9b9204ebe2c7a92a6c3a0bc9 by Derek Mauro : Internal change PiperOrigin-RevId: 310615554 GitOrigin-RevId: 6a60bc6c79a3069f49986c2567dd51d2792f8ec1 Change-Id: I57dd35424269d67740272c4f88b2de54d8022cb2 --- absl/base/BUILD.bazel | 14 +++ absl/base/internal/unique_small_name_test.cc | 77 ++++++++++++++++ absl/base/optimization.h | 26 ++++++ absl/flags/internal/commandlineflag.h | 5 +- absl/flags/internal/commandlineflag_test.cc | 20 ++-- absl/flags/internal/flag.h | 1 + absl/flags/internal/private_handle_accessor.cc | 5 + absl/flags/internal/private_handle_accessor.h | 3 + absl/flags/internal/type_erased.cc | 8 -- absl/flags/internal/type_erased.h | 11 --- absl/random/internal/BUILD.bazel | 13 +-- absl/strings/cord.cc | 1 + absl/strings/cord.h | 2 +- absl/strings/substitute.h | 13 ++- absl/synchronization/mutex.h | 7 +- absl/time/duration.cc | 121 +++++++++++++++---------- absl/time/format.cc | 68 ++++++++------ absl/time/time.h | 12 +-- 18 files changed, 280 insertions(+), 127 deletions(-) create mode 100644 absl/base/internal/unique_small_name_test.cc (limited to 'absl/strings/cord.h') diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel index 816e592b..76122dab 100644 --- a/absl/base/BUILD.bazel +++ b/absl/base/BUILD.bazel @@ -777,3 +777,17 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "unique_small_name_test", + size = "small", + srcs = ["internal/unique_small_name_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + linkstatic = 1, + deps = [ + ":core_headers", + "//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/absl/base/internal/unique_small_name_test.cc b/absl/base/internal/unique_small_name_test.cc new file mode 100644 index 00000000..ff8c2b3f --- /dev/null +++ b/absl/base/internal/unique_small_name_test.cc @@ -0,0 +1,77 @@ +// Copyright 2020 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "gtest/gtest.h" +#include "absl/base/optimization.h" +#include "absl/strings/string_view.h" + +// This test by itself does not do anything fancy, but it serves as binary I can +// query in shell test. + +namespace { + +template +void DoNotOptimize(const T& var) { +#ifdef __GNUC__ + asm volatile("" : "+m"(const_cast(var))); +#else + std::cout << (void*)&var; +#endif +} + +int very_long_int_variable_name ABSL_INTERNAL_UNIQUE_SMALL_NAME() = 0; +char very_long_str_variable_name[] ABSL_INTERNAL_UNIQUE_SMALL_NAME() = "abc"; + +TEST(UniqueSmallName, NonAutomaticVar) { + EXPECT_EQ(very_long_int_variable_name, 0); + EXPECT_EQ(absl::string_view(very_long_str_variable_name), "abc"); +} + +int VeryLongFreeFunctionName() ABSL_INTERNAL_UNIQUE_SMALL_NAME(); + +TEST(UniqueSmallName, FreeFunction) { + DoNotOptimize(&VeryLongFreeFunctionName); + + EXPECT_EQ(VeryLongFreeFunctionName(), 456); +} + +int VeryLongFreeFunctionName() { return 456; } + +struct VeryLongStructName { + explicit VeryLongStructName(int i); + + int VeryLongMethodName() ABSL_INTERNAL_UNIQUE_SMALL_NAME(); + + static int VeryLongStaticMethodName() ABSL_INTERNAL_UNIQUE_SMALL_NAME(); + + private: + int fld; +}; + +TEST(UniqueSmallName, Struct) { + VeryLongStructName var(10); + + DoNotOptimize(var); + DoNotOptimize(&VeryLongStructName::VeryLongMethodName); + DoNotOptimize(&VeryLongStructName::VeryLongStaticMethodName); + + EXPECT_EQ(var.VeryLongMethodName(), 10); + EXPECT_EQ(VeryLongStructName::VeryLongStaticMethodName(), 123); +} + +VeryLongStructName::VeryLongStructName(int i) : fld(i) {} +int VeryLongStructName::VeryLongMethodName() { return fld; } +int VeryLongStructName::VeryLongStaticMethodName() { return 123; } + +} // namespace diff --git a/absl/base/optimization.h b/absl/base/optimization.h index 1541d7a8..92bf9cd3 100644 --- a/absl/base/optimization.h +++ b/absl/base/optimization.h @@ -212,4 +212,30 @@ } while (0) #endif +// ABSL_INTERNAL_UNIQUE_SMALL_NAME(cond) +// This macro forces small unique name on a static file level symbols like +// static local variables or static functions. This is intended to be used in +// macro definitions to optimize the cost of generated code. Do NOT use it on +// symbols exported from translation unit since it may casue a link time +// conflict. +// +// Example: +// +// #define MY_MACRO(txt) +// namespace { +// char VeryVeryLongVarName[] ABSL_INTERNAL_UNIQUE_SMALL_NAME() = txt; +// const char* VeryVeryLongFuncName() ABSL_INTERNAL_UNIQUE_SMALL_NAME(); +// const char* VeryVeryLongFuncName() { return txt; } +// } +// + +#if defined(__GNUC__) +#define ABSL_INTERNAL_UNIQUE_SMALL_NAME2(x) #x +#define ABSL_INTERNAL_UNIQUE_SMALL_NAME1(x) ABSL_INTERNAL_UNIQUE_SMALL_NAME2(x) +#define ABSL_INTERNAL_UNIQUE_SMALL_NAME() \ + asm(ABSL_INTERNAL_UNIQUE_SMALL_NAME1(.absl.__COUNTER__)) +#else +#define ABSL_INTERNAL_UNIQUE_SMALL_NAME() +#endif + #endif // ABSL_BASE_OPTIMIZATION_H_ diff --git a/absl/flags/internal/commandlineflag.h b/absl/flags/internal/commandlineflag.h index 2d3b794d..0a7197b7 100644 --- a/absl/flags/internal/commandlineflag.h +++ b/absl/flags/internal/commandlineflag.h @@ -128,7 +128,6 @@ class CommandLineFlag { virtual std::string Help() const = 0; // Returns true iff this object corresponds to retired flag. virtual bool IsRetired() const; - virtual bool IsSpecifiedOnCommandLine() const = 0; virtual std::string DefaultValue() const = 0; virtual std::string CurrentValue() const = 0; @@ -167,6 +166,10 @@ class CommandLineFlag { // the dst based on the current flag's value. virtual void Read(void* dst) const = 0; + // To be deleted. Used to return true if flag's current value originated from + // command line. + virtual bool IsSpecifiedOnCommandLine() const = 0; + // Validates supplied value usign validator or parseflag routine virtual bool ValidateInputValue(absl::string_view value) const = 0; diff --git a/absl/flags/internal/commandlineflag_test.cc b/absl/flags/internal/commandlineflag_test.cc index 54d50fff..0b5aea37 100644 --- a/absl/flags/internal/commandlineflag_test.cc +++ b/absl/flags/internal/commandlineflag_test.cc @@ -121,42 +121,48 @@ TEST_F(CommandLineFlagTest, TestParseFromCurrentValue) { std::string err; auto* flag_01 = flags::FindCommandLineFlag("int_flag"); - EXPECT_FALSE(flag_01->IsSpecifiedOnCommandLine()); + EXPECT_FALSE( + flags::PrivateHandleAccessor::IsSpecifiedOnCommandLine(*flag_01)); EXPECT_TRUE(flags::PrivateHandleAccessor::ParseFrom( flag_01, "11", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, &err)); EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 11); - EXPECT_FALSE(flag_01->IsSpecifiedOnCommandLine()); + EXPECT_FALSE( + flags::PrivateHandleAccessor::IsSpecifiedOnCommandLine(*flag_01)); EXPECT_TRUE(flags::PrivateHandleAccessor::ParseFrom( flag_01, "-123", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, &err)); EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), -123); - EXPECT_FALSE(flag_01->IsSpecifiedOnCommandLine()); + EXPECT_FALSE( + flags::PrivateHandleAccessor::IsSpecifiedOnCommandLine(*flag_01)); EXPECT_TRUE(!flags::PrivateHandleAccessor::ParseFrom( flag_01, "xyz", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, &err)); EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), -123); EXPECT_EQ(err, "Illegal value 'xyz' specified for flag 'int_flag'"); - EXPECT_FALSE(flag_01->IsSpecifiedOnCommandLine()); + EXPECT_FALSE( + flags::PrivateHandleAccessor::IsSpecifiedOnCommandLine(*flag_01)); EXPECT_TRUE(!flags::PrivateHandleAccessor::ParseFrom( flag_01, "A1", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, &err)); EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), -123); EXPECT_EQ(err, "Illegal value 'A1' specified for flag 'int_flag'"); - EXPECT_FALSE(flag_01->IsSpecifiedOnCommandLine()); + EXPECT_FALSE( + flags::PrivateHandleAccessor::IsSpecifiedOnCommandLine(*flag_01)); EXPECT_TRUE(flags::PrivateHandleAccessor::ParseFrom( flag_01, "0x10", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, &err)); EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 16); - EXPECT_FALSE(flag_01->IsSpecifiedOnCommandLine()); + EXPECT_FALSE( + flags::PrivateHandleAccessor::IsSpecifiedOnCommandLine(*flag_01)); EXPECT_TRUE(flags::PrivateHandleAccessor::ParseFrom( flag_01, "011", flags::SET_FLAGS_VALUE, flags::kCommandLine, &err)); EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 11); - EXPECT_TRUE(flag_01->IsSpecifiedOnCommandLine()); + EXPECT_TRUE(flags::PrivateHandleAccessor::IsSpecifiedOnCommandLine(*flag_01)); EXPECT_TRUE(!flags::PrivateHandleAccessor::ParseFrom( flag_01, "", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, &err)); diff --git a/absl/flags/internal/flag.h b/absl/flags/internal/flag.h index b060199e..146c3efc 100644 --- a/absl/flags/internal/flag.h +++ b/absl/flags/internal/flag.h @@ -624,6 +624,7 @@ class Flag { absl::string_view Name() const { return impl_.Name(); } std::string Filename() const { return impl_.Filename(); } std::string Help() const { return impl_.Help(); } + // Do not use. To be removed. bool IsSpecifiedOnCommandLine() const { return impl_.IsSpecifiedOnCommandLine(); } diff --git a/absl/flags/internal/private_handle_accessor.cc b/absl/flags/internal/private_handle_accessor.cc index ec5776bb..64fe3166 100644 --- a/absl/flags/internal/private_handle_accessor.cc +++ b/absl/flags/internal/private_handle_accessor.cc @@ -28,6 +28,11 @@ std::unique_ptr PrivateHandleAccessor::SaveState( return flag->SaveState(); } +bool PrivateHandleAccessor::IsSpecifiedOnCommandLine( + const CommandLineFlag& flag) { + return flag.IsSpecifiedOnCommandLine(); +} + bool PrivateHandleAccessor::ValidateInputValue(const CommandLineFlag& flag, absl::string_view value) { return flag.ValidateInputValue(value); diff --git a/absl/flags/internal/private_handle_accessor.h b/absl/flags/internal/private_handle_accessor.h index fbb4409c..40591de4 100644 --- a/absl/flags/internal/private_handle_accessor.h +++ b/absl/flags/internal/private_handle_accessor.h @@ -33,6 +33,9 @@ class PrivateHandleAccessor { // Access to CommandLineFlag::SaveState. static std::unique_ptr SaveState(CommandLineFlag* flag); + // Access to CommandLineFlag::IsSpecifiedOnCommandLine. + static bool IsSpecifiedOnCommandLine(const CommandLineFlag& flag); + // Access to CommandLineFlag::ValidateInputValue. static bool ValidateInputValue(const CommandLineFlag& flag, absl::string_view value); diff --git a/absl/flags/internal/type_erased.cc b/absl/flags/internal/type_erased.cc index 3cfc9b2d..c13fb9b0 100644 --- a/absl/flags/internal/type_erased.cc +++ b/absl/flags/internal/type_erased.cc @@ -81,14 +81,6 @@ bool IsValidFlagValue(absl::string_view name, absl::string_view value) { // -------------------------------------------------------------------- -bool SpecifiedOnCommandLine(absl::string_view name) { - CommandLineFlag* flag = flags_internal::FindCommandLineFlag(name); - if (flag != nullptr && !flag->IsRetired()) { - return flag->IsSpecifiedOnCommandLine(); - } - return false; -} - } // namespace flags_internal ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/flags/internal/type_erased.h b/absl/flags/internal/type_erased.h index ffe319ba..b43a0ff7 100644 --- a/absl/flags/internal/type_erased.h +++ b/absl/flags/internal/type_erased.h @@ -55,17 +55,6 @@ bool IsValidFlagValue(absl::string_view name, absl::string_view value); //----------------------------------------------------------------------------- -// Returns true iff a flag named "name" was specified on the command line -// (either directly, or via one of --flagfile or --fromenv or --tryfromenv). -// -// Any non-command-line modification of the flag does not affect the -// result of this function. So for example, if a flag was passed on -// the command line but then reset via SET_FLAGS_DEFAULT, this -// function will still return true. -bool SpecifiedOnCommandLine(absl::string_view name); - -//----------------------------------------------------------------------------- - // If a flag with specified "name" exists and has type T, store // its current value in *dst and return true. Else return false // without touching *dst. T must obey all of the requirements for diff --git a/absl/random/internal/BUILD.bazel b/absl/random/internal/BUILD.bazel index 1c9dabb5..dc452816 100644 --- a/absl/random/internal/BUILD.bazel +++ b/absl/random/internal/BUILD.bazel @@ -37,9 +37,6 @@ cc_library( hdrs = ["traits.h"], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, - visibility = [ - "//absl/random:__pkg__", - ], deps = ["//absl/base:config"], ) @@ -48,9 +45,6 @@ cc_library( hdrs = ["distribution_caller.h"], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, - visibility = [ - "//absl/random:__pkg__", - ], deps = ["//absl/base:config"], ) @@ -76,9 +70,6 @@ cc_library( ], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, - visibility = [ - "//absl/random:__pkg__", - ], deps = ["//absl/base:config"], ) @@ -517,9 +508,6 @@ cc_library( name = "mock_overload_set", testonly = 1, hdrs = ["mock_overload_set.h"], - visibility = [ - "//absl/random:__pkg__", - ], deps = [ "//absl/random:mocking_bit_gen", "@com_google_googletest//:gtest", @@ -669,6 +657,7 @@ cc_library( deps = [ ":platform", ":randen_engine", + "//absl/base:config", "//absl/base:core_headers", "//absl/base:raw_logging_internal", ], diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index 0403cc6e..1ddd6aec 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 86ae76fd..3be8d7d8 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -65,7 +65,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/absl/strings/substitute.h b/absl/strings/substitute.h index 92c47236..c6da4dc6 100644 --- a/absl/strings/substitute.h +++ b/absl/strings/substitute.h @@ -120,7 +120,9 @@ class Arg { // representation. However, we can't really know, so we make the caller decide // what to do. Arg(char value) // NOLINT(runtime/explicit) - : piece_(scratch_, 1) { scratch_[0] = value; } + : piece_(scratch_, 1) { + scratch_[0] = value; + } Arg(short value) // NOLINT(*) : piece_(scratch_, numbers_internal::FastIntToBuffer(value, scratch_) - scratch_) {} @@ -203,10 +205,11 @@ constexpr const char* SkipNumber(const char* format) { } constexpr int PlaceholderBitmask(const char* format) { - return !*format ? 0 : *format != '$' - ? PlaceholderBitmask(format + 1) - : (CalculateOneBit(format + 1) | - PlaceholderBitmask(SkipNumber(format + 1))); + return !*format + ? 0 + : *format != '$' ? PlaceholderBitmask(format + 1) + : (CalculateOneBit(format + 1) | + PlaceholderBitmask(SkipNumber(format + 1))); } #endif // ABSL_BAD_CALL_IF diff --git a/absl/synchronization/mutex.h b/absl/synchronization/mutex.h index 961ff52b..876698ca 100644 --- a/absl/synchronization/mutex.h +++ b/absl/synchronization/mutex.h @@ -769,6 +769,8 @@ class Condition { // class CondVar { public: + // A `CondVar` allocated on the heap or on the stack can use the this + // constructor. CondVar(); ~CondVar(); @@ -900,9 +902,11 @@ class ABSL_SCOPED_LOCKABLE ReleasableMutexLock { }; #ifdef ABSL_INTERNAL_USE_NONPROD_MUTEX + inline constexpr Mutex::Mutex(absl::ConstInitType) : impl_(absl::kConstInit) {} #else + inline Mutex::Mutex() : mu_(0) { ABSL_TSAN_MUTEX_CREATE(this, __tsan_mutex_not_static); } @@ -910,7 +914,8 @@ inline Mutex::Mutex() : mu_(0) { inline constexpr Mutex::Mutex(absl::ConstInitType) : mu_(0) {} inline CondVar::CondVar() : cv_(0) {} -#endif + +#endif // ABSL_INTERNAL_USE_NONPROD_MUTEX // static template diff --git a/absl/time/duration.cc b/absl/time/duration.cc index f0182559..d0f1aadb 100644 --- a/absl/time/duration.cc +++ b/absl/time/duration.cc @@ -67,7 +67,9 @@ #include #include "absl/base/casts.h" +#include "absl/base/macros.h" #include "absl/numeric/int128.h" +#include "absl/strings/strip.h" #include "absl/time/time.h" namespace absl { @@ -800,23 +802,27 @@ namespace { // A helper for ParseDuration() that parses a leading number from the given // string and stores the result in *int_part/*frac_part/*frac_scale. The // given string pointer is modified to point to the first unconsumed char. -bool ConsumeDurationNumber(const char** dpp, int64_t* int_part, +bool ConsumeDurationNumber(const char** dpp, const char* ep, int64_t* int_part, int64_t* frac_part, int64_t* frac_scale) { *int_part = 0; *frac_part = 0; *frac_scale = 1; // invariant: *frac_part < *frac_scale const char* start = *dpp; - for (; std::isdigit(**dpp); *dpp += 1) { + for (; *dpp != ep; *dpp += 1) { const int d = **dpp - '0'; // contiguous digits + if (d < 0 || 10 <= d) break; + if (*int_part > kint64max / 10) return false; *int_part *= 10; if (*int_part > kint64max - d) return false; *int_part += d; } const bool int_part_empty = (*dpp == start); - if (**dpp != '.') return !int_part_empty; - for (*dpp += 1; std::isdigit(**dpp); *dpp += 1) { + if (*dpp == ep || **dpp != '.') return !int_part_empty; + + for (*dpp += 1; *dpp != ep; *dpp += 1) { const int d = **dpp - '0'; // contiguous digits + if (d < 0 || 10 <= d) break; if (*frac_scale <= kint64max / 10) { *frac_part *= 10; *frac_part += d; @@ -830,32 +836,56 @@ bool ConsumeDurationNumber(const char** dpp, int64_t* int_part, // ns, us, ms, s, m, h) from the given string and stores the resulting unit // in "*unit". The given string pointer is modified to point to the first // unconsumed char. -bool ConsumeDurationUnit(const char** start, Duration* unit) { - const char *s = *start; - bool ok = true; - if (strncmp(s, "ns", 2) == 0) { - s += 2; - *unit = Nanoseconds(1); - } else if (strncmp(s, "us", 2) == 0) { - s += 2; - *unit = Microseconds(1); - } else if (strncmp(s, "ms", 2) == 0) { - s += 2; - *unit = Milliseconds(1); - } else if (strncmp(s, "s", 1) == 0) { - s += 1; - *unit = Seconds(1); - } else if (strncmp(s, "m", 1) == 0) { - s += 1; - *unit = Minutes(1); - } else if (strncmp(s, "h", 1) == 0) { - s += 1; - *unit = Hours(1); - } else { - ok = false; +bool ConsumeDurationUnit(const char** start, const char* end, Duration* unit) { + size_t size = end - *start; + switch (size) { + case 0: + return false; + default: + switch (**start) { + case 'n': + if (*(*start + 1) == 's') { + *start += 2; + *unit = Nanoseconds(1); + return true; + } + break; + case 'u': + if (*(*start + 1) == 's') { + *start += 2; + *unit = Microseconds(1); + return true; + } + break; + case 'm': + if (*(*start + 1) == 's') { + *start += 2; + *unit = Milliseconds(1); + return true; + } + break; + default: + break; + } + ABSL_FALLTHROUGH_INTENDED; + case 1: + switch (**start) { + case 's': + *unit = Seconds(1); + *start += 1; + return true; + case 'm': + *unit = Minutes(1); + *start += 1; + return true; + case 'h': + *unit = Hours(1); + *start += 1; + return true; + default: + return false; + } } - *start = s; - return ok; } } // namespace @@ -865,39 +895,38 @@ bool ConsumeDurationUnit(const char** start, Duration* unit) { // a possibly signed sequence of decimal numbers, each with optional // fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". // Valid time units are "ns", "us" "ms", "s", "m", "h". -bool ParseDuration(const std::string& dur_string, Duration* d) { - const char* start = dur_string.c_str(); +bool ParseDuration(absl::string_view dur_sv, Duration* d) { int sign = 1; - - if (*start == '-' || *start == '+') { - sign = *start == '-' ? -1 : 1; - ++start; - } - - // Can't parse a duration from an empty string. - if (*start == '\0') { - return false; + if (absl::ConsumePrefix(&dur_sv, "-")) { + sign = -1; + } else { + absl::ConsumePrefix(&dur_sv, "+"); } + if (dur_sv.empty()) return false; // Special case for a string of "0". - if (*start == '0' && *(start + 1) == '\0') { + if (dur_sv == "0") { *d = ZeroDuration(); return true; } - if (strcmp(start, "inf") == 0) { + if (dur_sv == "inf") { *d = sign * InfiniteDuration(); return true; } + const char* start = dur_sv.data(); + const char* end = start + dur_sv.size(); + Duration dur; - while (*start != '\0') { + while (start != end) { int64_t int_part; int64_t frac_part; int64_t frac_scale; Duration unit; - if (!ConsumeDurationNumber(&start, &int_part, &frac_part, &frac_scale) || - !ConsumeDurationUnit(&start, &unit)) { + if (!ConsumeDurationNumber(&start, end, &int_part, &frac_part, + &frac_scale) || + !ConsumeDurationUnit(&start, end, &unit)) { return false; } if (int_part != 0) dur += sign * int_part * unit; @@ -908,7 +937,7 @@ bool ParseDuration(const std::string& dur_string, Duration* d) { } bool AbslParseFlag(absl::string_view text, Duration* dst, std::string*) { - return ParseDuration(std::string(text), dst); + return ParseDuration(text, dst); } std::string AbslUnparseFlag(Duration d) { return FormatDuration(d); } diff --git a/absl/time/format.cc b/absl/time/format.cc index ee088f33..228940ed 100644 --- a/absl/time/format.cc +++ b/absl/time/format.cc @@ -13,9 +13,12 @@ // limitations under the License. #include + #include #include +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" #include "absl/time/internal/cctz/include/cctz/time_zone.h" #include "absl/time/time.h" @@ -71,12 +74,12 @@ absl::Time Join(const cctz_parts& parts) { } // namespace -std::string FormatTime(const std::string& format, absl::Time t, +std::string FormatTime(absl::string_view format, absl::Time t, absl::TimeZone tz) { - if (t == absl::InfiniteFuture()) return kInfiniteFutureStr; - if (t == absl::InfinitePast()) return kInfinitePastStr; + if (t == absl::InfiniteFuture()) return std::string(kInfiniteFutureStr); + if (t == absl::InfinitePast()) return std::string(kInfinitePastStr); const auto parts = Split(t); - return cctz::detail::format(format, parts.sec, parts.fem, + return cctz::detail::format(std::string(format), parts.sec, parts.fem, cctz::time_zone(tz)); } @@ -88,42 +91,50 @@ std::string FormatTime(absl::Time t) { return absl::FormatTime(RFC3339_full, t, absl::LocalTimeZone()); } -bool ParseTime(const std::string& format, const std::string& input, +bool ParseTime(absl::string_view format, absl::string_view input, absl::Time* time, std::string* err) { return absl::ParseTime(format, input, absl::UTCTimeZone(), time, err); } // If the input string does not contain an explicit UTC offset, interpret // the fields with respect to the given TimeZone. -bool ParseTime(const std::string& format, const std::string& input, +bool ParseTime(absl::string_view format, absl::string_view input, absl::TimeZone tz, absl::Time* time, std::string* err) { - const char* data = input.c_str(); - while (std::isspace(*data)) ++data; - - size_t inf_size = strlen(kInfiniteFutureStr); - if (strncmp(data, kInfiniteFutureStr, inf_size) == 0) { - const char* new_data = data + inf_size; - while (std::isspace(*new_data)) ++new_data; - if (*new_data == '\0') { - *time = InfiniteFuture(); - return true; + auto strip_leading_space = [](absl::string_view* sv) { + while (!sv->empty()) { + if (!std::isspace(sv->front())) return; + sv->remove_prefix(1); } - } - - inf_size = strlen(kInfinitePastStr); - if (strncmp(data, kInfinitePastStr, inf_size) == 0) { - const char* new_data = data + inf_size; - while (std::isspace(*new_data)) ++new_data; - if (*new_data == '\0') { - *time = InfinitePast(); - return true; + }; + + // Portable toolchains means we don't get nice constexpr here. + struct Literal { + const char* name; + size_t size; + absl::Time value; + }; + static Literal literals[] = { + {kInfiniteFutureStr, strlen(kInfiniteFutureStr), InfiniteFuture()}, + {kInfinitePastStr, strlen(kInfinitePastStr), InfinitePast()}, + }; + strip_leading_space(&input); + for (const auto& lit : literals) { + if (absl::StartsWith(input, absl::string_view(lit.name, lit.size))) { + absl::string_view tail = input; + tail.remove_prefix(lit.size); + strip_leading_space(&tail); + if (tail.empty()) { + *time = lit.value; + return true; + } } } std::string error; cctz_parts parts; - const bool b = cctz::detail::parse(format, input, cctz::time_zone(tz), - &parts.sec, &parts.fem, &error); + const bool b = + cctz::detail::parse(std::string(format), std::string(input), + cctz::time_zone(tz), &parts.sec, &parts.fem, &error); if (b) { *time = Join(parts); } else if (err != nullptr) { @@ -134,8 +145,7 @@ bool ParseTime(const std::string& format, const std::string& input, // Functions required to support absl::Time flags. bool AbslParseFlag(absl::string_view text, absl::Time* t, std::string* error) { - return absl::ParseTime(RFC3339_full, std::string(text), absl::UTCTimeZone(), - t, error); + return absl::ParseTime(RFC3339_full, text, absl::UTCTimeZone(), t, error); } std::string AbslUnparseFlag(absl::Time t) { diff --git a/absl/time/time.h b/absl/time/time.h index 152f3ff8..b456a13e 100644 --- a/absl/time/time.h +++ b/absl/time/time.h @@ -545,7 +545,7 @@ inline std::ostream& operator<<(std::ostream& os, Duration d) { // suffix. The valid suffixes are "ns", "us" "ms", "s", "m", and "h". // Simple examples include "300ms", "-1.5h", and "2h45m". Parses "0" as // `ZeroDuration()`. Parses "inf" and "-inf" as +/- `InfiniteDuration()`. -bool ParseDuration(const std::string& dur_string, Duration* d); +bool ParseDuration(absl::string_view dur_string, Duration* d); // Support for flag values of type Duration. Duration flags must be specified // in a format that is valid input for absl::ParseDuration(). @@ -1021,13 +1021,13 @@ class TimeZone { // Loads the named zone. May perform I/O on the initial load of the named // zone. If the name is invalid, or some other kind of error occurs, returns // `false` and `*tz` is set to the UTC time zone. -inline bool LoadTimeZone(const std::string& name, TimeZone* tz) { +inline bool LoadTimeZone(absl::string_view name, TimeZone* tz) { if (name == "localtime") { *tz = TimeZone(time_internal::cctz::local_time_zone()); return true; } time_internal::cctz::time_zone cz; - const bool b = time_internal::cctz::load_time_zone(name, &cz); + const bool b = time_internal::cctz::load_time_zone(std::string(name), &cz); *tz = TimeZone(cz); return b; } @@ -1252,7 +1252,7 @@ ABSL_DLL extern const char // `absl::InfinitePast()`, the returned string will be exactly "infinite-past". // In both cases the given format string and `absl::TimeZone` are ignored. // -std::string FormatTime(const std::string& format, Time t, TimeZone tz); +std::string FormatTime(absl::string_view format, Time t, TimeZone tz); // Convenience functions that format the given time using the RFC3339_full // format. The first overload uses the provided TimeZone, while the second @@ -1313,7 +1313,7 @@ inline std::ostream& operator<<(std::ostream& os, Time t) { // If the input string is "infinite-past", the returned `absl::Time` will be // `absl::InfinitePast()` and `true` will be returned. // -bool ParseTime(const std::string& format, const std::string& input, Time* time, +bool ParseTime(absl::string_view format, absl::string_view input, Time* time, std::string* err); // Like ParseTime() above, but if the format string does not contain a UTC @@ -1323,7 +1323,7 @@ bool ParseTime(const std::string& format, const std::string& input, Time* time, // of ambiguity or non-existence, in which case the "pre" time (as defined // by TimeZone::TimeInfo) is returned. For these reasons we recommend that // all date/time strings include a UTC offset so they're context independent. -bool ParseTime(const std::string& format, const std::string& input, TimeZone tz, +bool ParseTime(absl::string_view format, absl::string_view input, TimeZone tz, Time* time, std::string* err); // ============================================================================ -- cgit v1.2.3 From 33caf1097ecce4fe892567462fa8821d477854b4 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Tue, 26 May 2020 10:57:33 -0700 Subject: Export of internal Abseil changes -- 7d0468a6610ed85586d5c87fd65de8dac5118923 by Derek Mauro : Import of CCTZ from GitHub. PiperOrigin-RevId: 313226473 -- 1131ef6d116f5ce7d46537a82f300ea06dcaaa53 by Gennadiy Rozental : Migrate internal interface to use mutable references. PiperOrigin-RevId: 312931131 -- 96225212a9f5fbd0b38c71fe65539164992c7c3b by Laramie Leavitt : Remove random/internal/distributions.h This file was something of an historical artifact. All of the related code has either been removed or migraged, and so the only remaining type belongs with uniform_helper.h, as it is used to infer the return type of the absl::Uniform method in a few cases. PiperOrigin-RevId: 312878173 -- 6dcbd5be58ad425e08740ff64088373ee7fe4a72 by Mark Barolak : Release the StrFormat test case for Cords to open source. PiperOrigin-RevId: 312707974 -- 34484d18dfb63a0a7ad6e2aaeb570e33592968be by Abseil Team : Let Cord::Cord(string&&), Cord::operator=(string&&), Cord::Append(string&&), and Cord::Prepend(string&&) steal string data and embed it into the Cord as a single external chunk, instead of copying it into flat chunks (at most 4083-byte each). Stealing string data is faster, but it creates a long chunk, which leads to a higher more memory usage if its subcords are created and outlive the whole Cord. These functions revert to copying the data if any of the following conditions holds: - string size is at most kMaxBytesToCopy (511), to avoid the overhead of an external chunk for short strings; - less than half of string capacity is used, to avoid pinning to much unused memory. PiperOrigin-RevId: 312683785 GitOrigin-RevId: 7d0468a6610ed85586d5c87fd65de8dac5118923 Change-Id: If79b5a1dfe6d53a8ddddbc7da84338f11fc4cfa3 --- CMake/AbseilDll.cmake | 1 - absl/flags/commandlineflag.cc | 2 +- absl/flags/commandlineflag.h | 2 +- absl/flags/commandlineflag_test.cc | 50 ++++++------ absl/flags/flag.h | 32 ++++---- absl/flags/flag_test.cc | 10 +-- absl/flags/internal/flag.cc | 38 ++++----- absl/flags/internal/flag.h | 24 +++--- absl/flags/internal/private_handle_accessor.cc | 10 +-- absl/flags/internal/private_handle_accessor.h | 6 +- absl/flags/internal/registry.cc | 95 +++++++++++----------- absl/flags/internal/registry.h | 6 +- absl/flags/internal/type_erased.cc | 2 +- absl/flags/internal/usage.cc | 25 +++--- absl/flags/marshalling.cc | 21 ++--- absl/flags/parse.cc | 38 ++++----- absl/flags/parse_test.cc | 26 +++--- absl/random/BUILD.bazel | 3 +- absl/random/CMakeLists.txt | 29 +------ absl/random/distributions.h | 2 +- absl/random/internal/BUILD.bazel | 18 +--- absl/random/internal/distributions.h | 52 ------------ absl/random/internal/uniform_helper.h | 30 +++++++ absl/strings/cord.cc | 65 +++++++++++++++ absl/strings/cord.h | 27 ++---- absl/strings/str_format_test.cc | 2 + absl/time/internal/cctz/include/cctz/time_zone.h | 3 +- absl/time/internal/cctz/src/cctz_benchmark.cc | 16 ++-- absl/time/internal/cctz/src/time_zone_format.cc | 23 +++++- .../internal/cctz/src/time_zone_format_test.cc | 18 +++- .../internal/cctz/src/time_zone_lookup_test.cc | 2 +- 31 files changed, 345 insertions(+), 333 deletions(-) delete mode 100644 absl/random/internal/distributions.h (limited to 'absl/strings/cord.h') diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index a5c9bc0d..ccd409f3 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake @@ -135,7 +135,6 @@ set(ABSL_INTERNAL_DLL_FILES "random/exponential_distribution.h" "random/gaussian_distribution.cc" "random/gaussian_distribution.h" - "random/internal/distributions.h" "random/internal/distribution_caller.h" "random/internal/fast_uniform_bits.h" "random/internal/fastmath.h" diff --git a/absl/flags/commandlineflag.cc b/absl/flags/commandlineflag.cc index 83d14c1b..cea57234 100644 --- a/absl/flags/commandlineflag.cc +++ b/absl/flags/commandlineflag.cc @@ -21,7 +21,7 @@ ABSL_NAMESPACE_BEGIN bool CommandLineFlag::IsRetired() const { return false; } bool CommandLineFlag::ParseFrom(absl::string_view value, std::string* error) { return ParseFrom(value, flags_internal::SET_FLAGS_VALUE, - flags_internal::kProgrammaticChange, error); + flags_internal::kProgrammaticChange, *error); } namespace flags_internal { diff --git a/absl/flags/commandlineflag.h b/absl/flags/commandlineflag.h index f09f1f70..43055d04 100644 --- a/absl/flags/commandlineflag.h +++ b/absl/flags/commandlineflag.h @@ -159,7 +159,7 @@ class CommandLineFlag { virtual bool ParseFrom(absl::string_view value, flags_internal::FlagSettingMode set_mode, flags_internal::ValueSource source, - std::string* error) = 0; + std::string& error) = 0; // Returns id of the flag's value type. virtual flags_internal::FlagFastTypeId TypeId() const = 0; diff --git a/absl/flags/commandlineflag_test.cc b/absl/flags/commandlineflag_test.cc index 352edccf..4b9718e9 100644 --- a/absl/flags/commandlineflag_test.cc +++ b/absl/flags/commandlineflag_test.cc @@ -129,57 +129,57 @@ TEST_F(CommandLineFlagTest, TestParseFromCurrentValue) { flags::PrivateHandleAccessor::IsSpecifiedOnCommandLine(*flag_01)); EXPECT_TRUE(flags::PrivateHandleAccessor::ParseFrom( - flag_01, "11", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, &err)); + *flag_01, "11", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, err)); EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 11); EXPECT_FALSE( flags::PrivateHandleAccessor::IsSpecifiedOnCommandLine(*flag_01)); EXPECT_TRUE(flags::PrivateHandleAccessor::ParseFrom( - flag_01, "-123", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, - &err)); + *flag_01, "-123", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, + err)); EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), -123); EXPECT_FALSE( flags::PrivateHandleAccessor::IsSpecifiedOnCommandLine(*flag_01)); EXPECT_TRUE(!flags::PrivateHandleAccessor::ParseFrom( - flag_01, "xyz", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, - &err)); + *flag_01, "xyz", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, + err)); EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), -123); EXPECT_EQ(err, "Illegal value 'xyz' specified for flag 'int_flag'"); EXPECT_FALSE( flags::PrivateHandleAccessor::IsSpecifiedOnCommandLine(*flag_01)); EXPECT_TRUE(!flags::PrivateHandleAccessor::ParseFrom( - flag_01, "A1", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, &err)); + *flag_01, "A1", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, err)); EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), -123); EXPECT_EQ(err, "Illegal value 'A1' specified for flag 'int_flag'"); EXPECT_FALSE( flags::PrivateHandleAccessor::IsSpecifiedOnCommandLine(*flag_01)); EXPECT_TRUE(flags::PrivateHandleAccessor::ParseFrom( - flag_01, "0x10", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, - &err)); + *flag_01, "0x10", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, + err)); EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 16); EXPECT_FALSE( flags::PrivateHandleAccessor::IsSpecifiedOnCommandLine(*flag_01)); EXPECT_TRUE(flags::PrivateHandleAccessor::ParseFrom( - flag_01, "011", flags::SET_FLAGS_VALUE, flags::kCommandLine, &err)); + *flag_01, "011", flags::SET_FLAGS_VALUE, flags::kCommandLine, err)); EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 11); EXPECT_TRUE(flags::PrivateHandleAccessor::IsSpecifiedOnCommandLine(*flag_01)); EXPECT_TRUE(!flags::PrivateHandleAccessor::ParseFrom( - flag_01, "", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, &err)); + *flag_01, "", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, err)); EXPECT_EQ(err, "Illegal value '' specified for flag 'int_flag'"); auto* flag_02 = flags::FindCommandLineFlag("string_flag"); EXPECT_TRUE(flags::PrivateHandleAccessor::ParseFrom( - flag_02, "xyz", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, - &err)); + *flag_02, "xyz", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, + err)); EXPECT_EQ(absl::GetFlag(FLAGS_string_flag), "xyz"); EXPECT_TRUE(flags::PrivateHandleAccessor::ParseFrom( - flag_02, "", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, &err)); + *flag_02, "", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, err)); EXPECT_EQ(absl::GetFlag(FLAGS_string_flag), ""); } @@ -191,15 +191,15 @@ TEST_F(CommandLineFlagTest, TestParseFromDefaultValue) { auto* flag_01 = flags::FindCommandLineFlag("int_flag"); EXPECT_TRUE(flags::PrivateHandleAccessor::ParseFrom( - flag_01, "111", flags::SET_FLAGS_DEFAULT, flags::kProgrammaticChange, - &err)); + *flag_01, "111", flags::SET_FLAGS_DEFAULT, flags::kProgrammaticChange, + err)); EXPECT_EQ(flag_01->DefaultValue(), "111"); auto* flag_02 = flags::FindCommandLineFlag("string_flag"); EXPECT_TRUE(flags::PrivateHandleAccessor::ParseFrom( - flag_02, "abc", flags::SET_FLAGS_DEFAULT, flags::kProgrammaticChange, - &err)); + *flag_02, "abc", flags::SET_FLAGS_DEFAULT, flags::kProgrammaticChange, + err)); EXPECT_EQ(flag_02->DefaultValue(), "abc"); } @@ -211,25 +211,25 @@ TEST_F(CommandLineFlagTest, TestParseFromIfDefault) { auto* flag_01 = flags::FindCommandLineFlag("int_flag"); EXPECT_TRUE(flags::PrivateHandleAccessor::ParseFrom( - flag_01, "22", flags::SET_FLAG_IF_DEFAULT, flags::kProgrammaticChange, - &err)) + *flag_01, "22", flags::SET_FLAG_IF_DEFAULT, flags::kProgrammaticChange, + err)) << err; EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 22); EXPECT_TRUE(flags::PrivateHandleAccessor::ParseFrom( - flag_01, "33", flags::SET_FLAG_IF_DEFAULT, flags::kProgrammaticChange, - &err)); + *flag_01, "33", flags::SET_FLAG_IF_DEFAULT, flags::kProgrammaticChange, + err)); EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 22); // EXPECT_EQ(err, "ERROR: int_flag is already set to 22"); // Reset back to default value EXPECT_TRUE(flags::PrivateHandleAccessor::ParseFrom( - flag_01, "201", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, - &err)); + *flag_01, "201", flags::SET_FLAGS_VALUE, flags::kProgrammaticChange, + err)); EXPECT_TRUE(flags::PrivateHandleAccessor::ParseFrom( - flag_01, "33", flags::SET_FLAG_IF_DEFAULT, flags::kProgrammaticChange, - &err)); + *flag_01, "33", flags::SET_FLAG_IF_DEFAULT, flags::kProgrammaticChange, + err)); EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 201); // EXPECT_EQ(err, "ERROR: int_flag is already set to 201"); } diff --git a/absl/flags/flag.h b/absl/flags/flag.h index f84853ea..dd36e6c7 100644 --- a/absl/flags/flag.h +++ b/absl/flags/flag.h @@ -110,12 +110,12 @@ class Flag { impl_(nullptr) {} #endif - flags_internal::Flag* GetImpl() const { + flags_internal::Flag& GetImpl() const { if (!inited_.load(std::memory_order_acquire)) { absl::MutexLock l(flags_internal::GetGlobalConstructionGuard()); if (inited_.load(std::memory_order_acquire)) { - return impl_; + return *impl_; } impl_ = new flags_internal::Flag( @@ -127,28 +127,28 @@ class Flag { inited_.store(true, std::memory_order_release); } - return impl_; + return *impl_; } // Public methods of `absl::Flag` are NOT part of the Abseil Flags API. // See https://abseil.io/docs/cpp/guides/flags - bool IsRetired() const { return GetImpl()->IsRetired(); } - absl::string_view Name() const { return GetImpl()->Name(); } - std::string Help() const { return GetImpl()->Help(); } - bool IsModified() const { return GetImpl()->IsModified(); } + bool IsRetired() const { return GetImpl().IsRetired(); } + absl::string_view Name() const { return GetImpl().Name(); } + std::string Help() const { return GetImpl().Help(); } + bool IsModified() const { return GetImpl().IsModified(); } bool IsSpecifiedOnCommandLine() const { - return GetImpl()->IsSpecifiedOnCommandLine(); + return GetImpl().IsSpecifiedOnCommandLine(); } - std::string Filename() const { return GetImpl()->Filename(); } - std::string DefaultValue() const { return GetImpl()->DefaultValue(); } - std::string CurrentValue() const { return GetImpl()->CurrentValue(); } + std::string Filename() const { return GetImpl().Filename(); } + std::string DefaultValue() const { return GetImpl().DefaultValue(); } + std::string CurrentValue() const { return GetImpl().CurrentValue(); } template inline bool IsOfType() const { - return GetImpl()->template IsOfType(); + return GetImpl().template IsOfType(); } - T Get() const { return GetImpl()->Get(); } - void Set(const T& v) { GetImpl()->Set(v); } - void InvokeCallback() { GetImpl()->InvokeCallback(); } + T Get() const { return GetImpl().Get(); } + void Set(const T& v) { GetImpl().Set(v); } + void InvokeCallback() { GetImpl().InvokeCallback(); } // The data members are logically private, but they need to be public for // this to be an aggregate type. @@ -265,7 +265,7 @@ ABSL_NAMESPACE_END // ABSL_FLAG_IMPL macro definition conditional on ABSL_FLAGS_STRIP_NAMES #if !defined(_MSC_VER) || defined(__clang__) -#define ABSL_FLAG_IMPL_FLAG_PTR(flag) &flag +#define ABSL_FLAG_IMPL_FLAG_PTR(flag) flag #define ABSL_FLAG_IMPL_HELP_ARG(name) \ absl::flags_internal::HelpArg( \ FLAGS_help_storage_##name) diff --git a/absl/flags/flag_test.cc b/absl/flags/flag_test.cc index 8d53ecd6..58a07999 100644 --- a/absl/flags/flag_test.cc +++ b/absl/flags/flag_test.cc @@ -150,21 +150,21 @@ DEFINE_CONSTRUCTED_FLAG(String, &TestMakeDflt, kGenFunc); DEFINE_CONSTRUCTED_FLAG(UDT, &TestMakeDflt, kGenFunc); template -bool TestConstructionFor(const flags::Flag& f1, flags::Flag* f2) { +bool TestConstructionFor(const flags::Flag& f1, flags::Flag& f2) { EXPECT_EQ(f1.Name(), "f1"); EXPECT_EQ(f1.Help(), "literal help"); EXPECT_EQ(f1.Filename(), "file"); flags::FlagRegistrar(f2).OnUpdate(TestCallback); - EXPECT_EQ(f2->Name(), "f2"); - EXPECT_EQ(f2->Help(), "dynamic help"); - EXPECT_EQ(f2->Filename(), "file"); + EXPECT_EQ(f2.Name(), "f2"); + EXPECT_EQ(f2.Help(), "dynamic help"); + EXPECT_EQ(f2.Filename(), "file"); return true; } -#define TEST_CONSTRUCTED_FLAG(T) TestConstructionFor(f1##T, &f2##T); +#define TEST_CONSTRUCTED_FLAG(T) TestConstructionFor(f1##T, f2##T); TEST_F(FlagTest, TestConstruction) { TEST_CONSTRUCTED_FLAG(bool); diff --git a/absl/flags/internal/flag.cc b/absl/flags/internal/flag.cc index 35ae55fb..ee974244 100644 --- a/absl/flags/internal/flag.cc +++ b/absl/flags/internal/flag.cc @@ -62,14 +62,14 @@ bool ShouldValidateFlagValue(FlagFastTypeId flag_type_id) { // need to acquire these locks themselves. class MutexRelock { public: - explicit MutexRelock(absl::Mutex* mu) : mu_(mu) { mu_->Unlock(); } - ~MutexRelock() { mu_->Lock(); } + explicit MutexRelock(absl::Mutex& mu) : mu_(mu) { mu_.Unlock(); } + ~MutexRelock() { mu_.Lock(); } MutexRelock(const MutexRelock&) = delete; MutexRelock& operator=(const MutexRelock&) = delete; private: - absl::Mutex* mu_; + absl::Mutex& mu_; }; } // namespace @@ -82,7 +82,7 @@ class FlagImpl; class FlagState : public flags_internal::FlagStateInterface { public: template - FlagState(FlagImpl* flag_impl, const V& v, bool modified, + FlagState(FlagImpl& flag_impl, const V& v, bool modified, bool on_command_line, int64_t counter) : flag_impl_(flag_impl), value_(v), @@ -91,9 +91,9 @@ class FlagState : public flags_internal::FlagStateInterface { counter_(counter) {} ~FlagState() override { - if (flag_impl_->ValueStorageKind() != FlagValueStorageKind::kAlignedBuffer) + if (flag_impl_.ValueStorageKind() != FlagValueStorageKind::kAlignedBuffer) return; - flags_internal::Delete(flag_impl_->op_, value_.heap_allocated); + flags_internal::Delete(flag_impl_.op_, value_.heap_allocated); } private: @@ -101,15 +101,15 @@ class FlagState : public flags_internal::FlagStateInterface { // Restores the flag to the saved state. void Restore() const override { - if (!flag_impl_->RestoreState(*this)) return; + if (!flag_impl_.RestoreState(*this)) return; - ABSL_INTERNAL_LOG( - INFO, absl::StrCat("Restore saved value of ", flag_impl_->Name(), - " to: ", flag_impl_->CurrentValue())); + ABSL_INTERNAL_LOG(INFO, + absl::StrCat("Restore saved value of ", flag_impl_.Name(), + " to: ", flag_impl_.CurrentValue())); } // Flag and saved flag data. - FlagImpl* flag_impl_; + FlagImpl& flag_impl_; union SavedValue { explicit SavedValue(void* v) : heap_allocated(v) {} explicit SavedValue(int64_t v) : one_word(v) {} @@ -326,7 +326,7 @@ void FlagImpl::InvokeCallback() const { // and it also can be different by the time the callback invocation is // completed. Requires that *primary_lock be held in exclusive mode; it may be // released and reacquired by the implementation. - MutexRelock relock(DataGuard()); + MutexRelock relock(*DataGuard()); absl::MutexLock lock(&callback_->guard); cb(); } @@ -339,17 +339,17 @@ std::unique_ptr FlagImpl::SaveState() { switch (ValueStorageKind()) { case FlagValueStorageKind::kAlignedBuffer: { return absl::make_unique( - this, flags_internal::Clone(op_, AlignedBufferValue()), modified, + *this, flags_internal::Clone(op_, AlignedBufferValue()), modified, on_command_line, counter_); } case FlagValueStorageKind::kOneWordAtomic: { return absl::make_unique( - this, OneWordValue().load(std::memory_order_acquire), modified, + *this, OneWordValue().load(std::memory_order_acquire), modified, on_command_line, counter_); } case FlagValueStorageKind::kTwoWordsAtomic: { return absl::make_unique( - this, TwoWordsValue().load(std::memory_order_acquire), modified, + *this, TwoWordsValue().load(std::memory_order_acquire), modified, on_command_line, counter_); } } @@ -410,14 +410,14 @@ std::atomic& FlagImpl::TwoWordsValue() const { // parsed value. In case if any error is encountered in either step, the error // message is stored in 'err' std::unique_ptr FlagImpl::TryParse( - absl::string_view value, std::string* err) const { + absl::string_view value, std::string& err) const { std::unique_ptr tentative_value = MakeInitValue(); std::string parse_err; if (!flags_internal::Parse(op_, value, tentative_value.get(), &parse_err)) { absl::string_view err_sep = parse_err.empty() ? "" : "; "; - *err = absl::StrCat("Illegal value '", value, "' specified for flag '", - Name(), "'", err_sep, parse_err); + err = absl::StrCat("Illegal value '", value, "' specified for flag '", + Name(), "'", err_sep, parse_err); return nullptr; } @@ -473,7 +473,7 @@ void FlagImpl::Write(const void* src) { // * Update the current flag value if it was never set before // The mode is selected based on 'set_mode' parameter. bool FlagImpl::ParseFrom(absl::string_view value, FlagSettingMode set_mode, - ValueSource source, std::string* err) { + ValueSource source, std::string& err) { absl::MutexLock l(DataGuard()); switch (set_mode) { diff --git a/absl/flags/internal/flag.h b/absl/flags/internal/flag.h index 97ddb5f9..e1885809 100644 --- a/absl/flags/internal/flag.h +++ b/absl/flags/internal/flag.h @@ -374,31 +374,31 @@ struct FlagValue; template struct FlagValue { - bool Get(T*) const { return false; } + bool Get(T&) const { return false; } alignas(T) char value[sizeof(T)]; }; template struct FlagValue : FlagOneWordValue { - bool Get(T* dst) const { + bool Get(T& dst) const { int64_t one_word_val = value.load(std::memory_order_acquire); if (ABSL_PREDICT_FALSE(one_word_val == UninitializedFlagValue())) { return false; } - std::memcpy(dst, static_cast(&one_word_val), sizeof(T)); + std::memcpy(&dst, static_cast(&one_word_val), sizeof(T)); return true; } }; template struct FlagValue : FlagTwoWordsValue { - bool Get(T* dst) const { + bool Get(T& dst) const { AlignedTwoWords two_words_val = value.load(std::memory_order_acquire); if (ABSL_PREDICT_FALSE(!two_words_val.IsInitialized())) { return false; } - std::memcpy(dst, static_cast(&two_words_val), sizeof(T)); + std::memcpy(&dst, static_cast(&two_words_val), sizeof(T)); return true; } }; @@ -502,7 +502,7 @@ class FlagImpl final : public CommandLineFlag { // Attempts to parse supplied `value` string. If parsing is successful, // returns new value. Otherwise returns nullptr. std::unique_ptr TryParse(absl::string_view value, - std::string* err) const + std::string& err) const ABSL_EXCLUSIVE_LOCKS_REQUIRED(*DataGuard()); // Stores the flag value based on the pointer to the source. void StoreValue(const void* src) ABSL_EXCLUSIVE_LOCKS_REQUIRED(*DataGuard()); @@ -544,7 +544,7 @@ class FlagImpl final : public CommandLineFlag { ABSL_LOCKS_EXCLUDED(*DataGuard()); bool ParseFrom(absl::string_view value, FlagSettingMode set_mode, - ValueSource source, std::string* error) override + ValueSource source, std::string& error) override ABSL_LOCKS_EXCLUDED(*DataGuard()); // Immutable flag's state. @@ -651,7 +651,7 @@ class Flag { impl_.AssertValidType(base_internal::FastTypeId(), &GenRuntimeTypeId); #endif - if (!value_.Get(&u.value)) impl_.Read(&u.value); + if (!value_.Get(u.value)) impl_.Read(&u.value); return std::move(u.value); } void Set(const T& v) { @@ -730,12 +730,12 @@ struct FlagRegistrarEmpty {}; template class FlagRegistrar { public: - explicit FlagRegistrar(Flag* flag) : flag_(flag) { - if (do_register) flags_internal::RegisterCommandLineFlag(&flag_->impl_); + explicit FlagRegistrar(Flag& flag) : flag_(flag) { + if (do_register) flags_internal::RegisterCommandLineFlag(flag_.impl_); } FlagRegistrar OnUpdate(FlagCallbackFunc cb) && { - flag_->impl_.SetCallback(cb); + flag_.impl_.SetCallback(cb); return *this; } @@ -745,7 +745,7 @@ class FlagRegistrar { operator FlagRegistrarEmpty() const { return {}; } // NOLINT private: - Flag* flag_; // Flag being registered (not owned). + Flag& flag_; // Flag being registered (not owned). }; } // namespace flags_internal diff --git a/absl/flags/internal/private_handle_accessor.cc b/absl/flags/internal/private_handle_accessor.cc index 64fe3166..24b49136 100644 --- a/absl/flags/internal/private_handle_accessor.cc +++ b/absl/flags/internal/private_handle_accessor.cc @@ -24,8 +24,8 @@ FlagFastTypeId PrivateHandleAccessor::TypeId(const CommandLineFlag& flag) { } std::unique_ptr PrivateHandleAccessor::SaveState( - CommandLineFlag* flag) { - return flag->SaveState(); + CommandLineFlag& flag) { + return flag.SaveState(); } bool PrivateHandleAccessor::IsSpecifiedOnCommandLine( @@ -43,12 +43,12 @@ void PrivateHandleAccessor::CheckDefaultValueParsingRoundtrip( flag.CheckDefaultValueParsingRoundtrip(); } -bool PrivateHandleAccessor::ParseFrom(CommandLineFlag* flag, +bool PrivateHandleAccessor::ParseFrom(CommandLineFlag& flag, absl::string_view value, flags_internal::FlagSettingMode set_mode, flags_internal::ValueSource source, - std::string* error) { - return flag->ParseFrom(value, set_mode, source, error); + std::string& error) { + return flag.ParseFrom(value, set_mode, source, error); } } // namespace flags_internal diff --git a/absl/flags/internal/private_handle_accessor.h b/absl/flags/internal/private_handle_accessor.h index 07838600..9a327a07 100644 --- a/absl/flags/internal/private_handle_accessor.h +++ b/absl/flags/internal/private_handle_accessor.h @@ -31,7 +31,7 @@ class PrivateHandleAccessor { static FlagFastTypeId TypeId(const CommandLineFlag& flag); // Access to CommandLineFlag::SaveState. - static std::unique_ptr SaveState(CommandLineFlag* flag); + static std::unique_ptr SaveState(CommandLineFlag& flag); // Access to CommandLineFlag::IsSpecifiedOnCommandLine. static bool IsSpecifiedOnCommandLine(const CommandLineFlag& flag); @@ -43,9 +43,9 @@ class PrivateHandleAccessor { // Access to CommandLineFlag::CheckDefaultValueParsingRoundtrip. static void CheckDefaultValueParsingRoundtrip(const CommandLineFlag& flag); - static bool ParseFrom(CommandLineFlag* flag, absl::string_view value, + static bool ParseFrom(CommandLineFlag& flag, absl::string_view value, flags_internal::FlagSettingMode set_mode, - flags_internal::ValueSource source, std::string* error); + flags_internal::ValueSource source, std::string& error); }; } // namespace flags_internal diff --git a/absl/flags/internal/registry.cc b/absl/flags/internal/registry.cc index 70d76557..4bcebfa9 100644 --- a/absl/flags/internal/registry.cc +++ b/absl/flags/internal/registry.cc @@ -60,8 +60,8 @@ class FlagRegistry { FlagRegistry() = default; ~FlagRegistry() = default; - // Store a flag in this registry. Takes ownership of *flag. - void RegisterFlag(CommandLineFlag* flag); + // Store a flag in this registry. Takes ownership of *flag. + void RegisterFlag(CommandLineFlag& flag); void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION(lock_) { lock_.Lock(); } void Unlock() ABSL_UNLOCK_FUNCTION(lock_) { lock_.Unlock(); } @@ -74,12 +74,12 @@ class FlagRegistry { // found or not retired. Does not emit a warning. CommandLineFlag* FindRetiredFlagLocked(absl::string_view name); - static FlagRegistry* GlobalRegistry(); // returns a singleton registry + static FlagRegistry& GlobalRegistry(); // returns a singleton registry private: friend class FlagSaverImpl; // reads all the flags in order to copy them friend void ForEachFlagUnlocked( - std::function visitor); + std::function visitor); // The map from name to flag, for FindFlagLocked(). using FlagMap = std::map; @@ -94,65 +94,62 @@ class FlagRegistry { FlagRegistry& operator=(const FlagRegistry&); }; -FlagRegistry* FlagRegistry::GlobalRegistry() { +FlagRegistry& FlagRegistry::GlobalRegistry() { static FlagRegistry* global_registry = new FlagRegistry; - return global_registry; + return *global_registry; } namespace { class FlagRegistryLock { public: - explicit FlagRegistryLock(FlagRegistry* fr) : fr_(fr) { fr_->Lock(); } - ~FlagRegistryLock() { fr_->Unlock(); } + explicit FlagRegistryLock(FlagRegistry& fr) : fr_(fr) { fr_.Lock(); } + ~FlagRegistryLock() { fr_.Unlock(); } private: - FlagRegistry* const fr_; + FlagRegistry& fr_; }; -void DestroyRetiredFlag(CommandLineFlag* flag); +void DestroyRetiredFlag(CommandLineFlag& flag); } // namespace -void FlagRegistry::RegisterFlag(CommandLineFlag* flag) { - FlagRegistryLock registry_lock(this); +void FlagRegistry::RegisterFlag(CommandLineFlag& flag) { + FlagRegistryLock registry_lock(*this); std::pair ins = - flags_.insert(FlagMap::value_type(flag->Name(), flag)); + flags_.insert(FlagMap::value_type(flag.Name(), &flag)); if (ins.second == false) { // means the name was already in the map - CommandLineFlag* old_flag = ins.first->second; - if (flag->IsRetired() != old_flag->IsRetired()) { + CommandLineFlag& old_flag = *ins.first->second; + if (flag.IsRetired() != old_flag.IsRetired()) { // All registrations must agree on the 'retired' flag. flags_internal::ReportUsageError( absl::StrCat( - "Retired flag '", flag->Name(), - "' was defined normally in file '", - (flag->IsRetired() ? old_flag->Filename() : flag->Filename()), - "'."), + "Retired flag '", flag.Name(), "' was defined normally in file '", + (flag.IsRetired() ? old_flag.Filename() : flag.Filename()), "'."), true); - } else if (flags_internal::PrivateHandleAccessor::TypeId(*flag) != - flags_internal::PrivateHandleAccessor::TypeId(*old_flag)) { + } else if (flags_internal::PrivateHandleAccessor::TypeId(flag) != + flags_internal::PrivateHandleAccessor::TypeId(old_flag)) { flags_internal::ReportUsageError( - absl::StrCat("Flag '", flag->Name(), + absl::StrCat("Flag '", flag.Name(), "' was defined more than once but with " "differing types. Defined in files '", - old_flag->Filename(), "' and '", flag->Filename(), "'."), + old_flag.Filename(), "' and '", flag.Filename(), "'."), true); - } else if (old_flag->IsRetired()) { + } else if (old_flag.IsRetired()) { // Retired flag can just be deleted. DestroyRetiredFlag(flag); return; - } else if (old_flag->Filename() != flag->Filename()) { + } else if (old_flag.Filename() != flag.Filename()) { flags_internal::ReportUsageError( - absl::StrCat("Flag '", flag->Name(), + absl::StrCat("Flag '", flag.Name(), "' was defined more than once (in files '", - old_flag->Filename(), "' and '", flag->Filename(), - "')."), + old_flag.Filename(), "' and '", flag.Filename(), "')."), true); } else { flags_internal::ReportUsageError( absl::StrCat( - "Something wrong with flag '", flag->Name(), "' in file '", - flag->Filename(), "'. One possibility: file '", flag->Filename(), + "Something wrong with flag '", flag.Name(), "' in file '", + flag.Filename(), "'. One possibility: file '", flag.Filename(), "' is being linked both statically and dynamically into this " "executable. e.g. some files listed as srcs to a test and also " "listed as srcs of some shared lib deps of the same test."), @@ -206,7 +203,7 @@ class FlagSaverImpl { // It's an error to call this more than once. void SaveFromRegistry() { assert(backup_registry_.empty()); // call only once! - flags_internal::ForEachFlag([&](CommandLineFlag* flag) { + flags_internal::ForEachFlag([&](CommandLineFlag& flag) { if (auto flag_state = flags_internal::PrivateHandleAccessor::SaveState(flag)) { backup_registry_.emplace_back(std::move(flag_state)); @@ -244,39 +241,39 @@ FlagSaver::~FlagSaver() { CommandLineFlag* FindCommandLineFlag(absl::string_view name) { if (name.empty()) return nullptr; - FlagRegistry* const registry = FlagRegistry::GlobalRegistry(); + FlagRegistry& registry = FlagRegistry::GlobalRegistry(); FlagRegistryLock frl(registry); - return registry->FindFlagLocked(name); + return registry.FindFlagLocked(name); } CommandLineFlag* FindRetiredFlag(absl::string_view name) { - FlagRegistry* const registry = FlagRegistry::GlobalRegistry(); + FlagRegistry& registry = FlagRegistry::GlobalRegistry(); FlagRegistryLock frl(registry); - return registry->FindRetiredFlagLocked(name); + return registry.FindRetiredFlagLocked(name); } // -------------------------------------------------------------------- -void ForEachFlagUnlocked(std::function visitor) { - FlagRegistry* const registry = FlagRegistry::GlobalRegistry(); - for (FlagRegistry::FlagConstIterator i = registry->flags_.begin(); - i != registry->flags_.end(); ++i) { - visitor(i->second); +void ForEachFlagUnlocked(std::function visitor) { + FlagRegistry& registry = FlagRegistry::GlobalRegistry(); + for (FlagRegistry::FlagConstIterator i = registry.flags_.begin(); + i != registry.flags_.end(); ++i) { + visitor(*i->second); } } -void ForEachFlag(std::function visitor) { - FlagRegistry* const registry = FlagRegistry::GlobalRegistry(); +void ForEachFlag(std::function visitor) { + FlagRegistry& registry = FlagRegistry::GlobalRegistry(); FlagRegistryLock frl(registry); ForEachFlagUnlocked(visitor); } // -------------------------------------------------------------------- -bool RegisterCommandLineFlag(CommandLineFlag* flag) { - FlagRegistry::GlobalRegistry()->RegisterFlag(flag); +bool RegisterCommandLineFlag(CommandLineFlag& flag) { + FlagRegistry::GlobalRegistry().RegisterFlag(flag); return true; } @@ -307,7 +304,7 @@ class RetiredFlagObj final : public CommandLineFlag { } bool ParseFrom(absl::string_view, flags_internal::FlagSettingMode, - flags_internal::ValueSource, std::string*) override { + flags_internal::ValueSource, std::string&) override { return false; } @@ -320,16 +317,16 @@ class RetiredFlagObj final : public CommandLineFlag { const FlagFastTypeId type_id_; }; -void DestroyRetiredFlag(CommandLineFlag* flag) { - assert(flag->IsRetired()); - delete static_cast(flag); +void DestroyRetiredFlag(CommandLineFlag& flag) { + assert(flag.IsRetired()); + delete static_cast(&flag); } } // namespace bool Retire(const char* name, FlagFastTypeId type_id) { auto* flag = new flags_internal::RetiredFlagObj(name, type_id); - FlagRegistry::GlobalRegistry()->RegisterFlag(flag); + FlagRegistry::GlobalRegistry().RegisterFlag(*flag); return true; } diff --git a/absl/flags/internal/registry.h b/absl/flags/internal/registry.h index f722fd44..a118865a 100644 --- a/absl/flags/internal/registry.h +++ b/absl/flags/internal/registry.h @@ -39,14 +39,14 @@ CommandLineFlag* FindRetiredFlag(absl::string_view name); // Executes specified visitor for each non-retired flag in the registry. // Requires the caller hold the registry lock. -void ForEachFlagUnlocked(std::function visitor); +void ForEachFlagUnlocked(std::function visitor); // Executes specified visitor for each non-retired flag in the registry. While // callback are executed, the registry is locked and can't be changed. -void ForEachFlag(std::function visitor); +void ForEachFlag(std::function visitor); //----------------------------------------------------------------------------- -bool RegisterCommandLineFlag(CommandLineFlag*); +bool RegisterCommandLineFlag(CommandLineFlag&); //----------------------------------------------------------------------------- // Retired registrations: diff --git a/absl/flags/internal/type_erased.cc b/absl/flags/internal/type_erased.cc index 35b0d125..b2523b24 100644 --- a/absl/flags/internal/type_erased.cc +++ b/absl/flags/internal/type_erased.cc @@ -58,7 +58,7 @@ bool SetCommandLineOptionWithMode(absl::string_view name, std::string error; if (!flags_internal::PrivateHandleAccessor::ParseFrom( - flag, value, set_mode, kProgrammaticChange, &error)) { + *flag, value, set_mode, kProgrammaticChange, error)) { // Errors here are all of the form: the provided name was a recognized // flag, but the value was invalid (bad type, or validation failed). flags_internal::ReportUsageError(error, false); diff --git a/absl/flags/internal/usage.cc b/absl/flags/internal/usage.cc index 11664e10..2a2231a7 100644 --- a/absl/flags/internal/usage.cc +++ b/absl/flags/internal/usage.cc @@ -107,8 +107,8 @@ class FlagHelpPrettyPrinter { public: // Pretty printer holds on to the std::ostream& reference to direct an output // to that stream. - FlagHelpPrettyPrinter(int max_line_len, std::ostream* out) - : out_(*out), + FlagHelpPrettyPrinter(int max_line_len, std::ostream& out) + : out_(out), max_line_len_(max_line_len), line_len_(0), first_line_(true) {} @@ -182,7 +182,7 @@ class FlagHelpPrettyPrinter { bool first_line_; }; -void FlagHelpHumanReadable(const CommandLineFlag& flag, std::ostream* out) { +void FlagHelpHumanReadable(const CommandLineFlag& flag, std::ostream& out) { FlagHelpPrettyPrinter printer(80, out); // Max line length is 80. // Flag name. @@ -244,29 +244,28 @@ void FlagsHelpImpl(std::ostream& out, flags_internal::FlagKindFilter filter_cb, // This map is used to output matching flags grouped by package and file // name. std::map>> + std::map>> matching_flags; - flags_internal::ForEachFlag([&](CommandLineFlag* flag) { - std::string flag_filename = flag->Filename(); + flags_internal::ForEachFlag([&](absl::CommandLineFlag& flag) { + std::string flag_filename = flag.Filename(); // Ignore retired flags. - if (flag->IsRetired()) return; + if (flag.IsRetired()) return; // If the flag has been stripped, pretend that it doesn't exist. - if (flag->Help() == flags_internal::kStrippedFlagHelp) return; + if (flag.Help() == flags_internal::kStrippedFlagHelp) return; // Make sure flag satisfies the filter if (!filter_cb || !filter_cb(flag_filename)) return; matching_flags[std::string(flags_internal::Package(flag_filename))] [flag_filename] - .push_back(flag); + .push_back(&flag); }); - absl::string_view - package_separator; // controls blank lines between packages. - absl::string_view file_separator; // controls blank lines between files. + absl::string_view package_separator; // controls blank lines between packages + absl::string_view file_separator; // controls blank lines between files for (const auto& package : matching_flags) { if (format == HelpFormat::kHumanReadable) { out << package_separator; @@ -304,7 +303,7 @@ void FlagsHelpImpl(std::ostream& out, flags_internal::FlagKindFilter filter_cb, void FlagHelp(std::ostream& out, const CommandLineFlag& flag, HelpFormat format) { if (format == HelpFormat::kHumanReadable) - flags_internal::FlagHelpHumanReadable(flag, &out); + flags_internal::FlagHelpHumanReadable(flag, out); } // -------------------------------------------------------------------- diff --git a/absl/flags/marshalling.cc b/absl/flags/marshalling.cc index 09baae88..81f9cebd 100644 --- a/absl/flags/marshalling.cc +++ b/absl/flags/marshalling.cc @@ -74,15 +74,16 @@ static int NumericBase(absl::string_view text) { } template -inline bool ParseFlagImpl(absl::string_view text, IntType* dst) { +inline bool ParseFlagImpl(absl::string_view text, IntType& dst) { text = absl::StripAsciiWhitespace(text); - return absl::numbers_internal::safe_strtoi_base(text, dst, NumericBase(text)); + return absl::numbers_internal::safe_strtoi_base(text, &dst, + NumericBase(text)); } bool AbslParseFlag(absl::string_view text, short* dst, std::string*) { int val; - if (!ParseFlagImpl(text, &val)) return false; + if (!ParseFlagImpl(text, val)) return false; if (static_cast(val) != val) // worked, but number out of range return false; *dst = static_cast(val); @@ -91,7 +92,7 @@ bool AbslParseFlag(absl::string_view text, short* dst, std::string*) { bool AbslParseFlag(absl::string_view text, unsigned short* dst, std::string*) { unsigned int val; - if (!ParseFlagImpl(text, &val)) return false; + if (!ParseFlagImpl(text, val)) return false; if (static_cast(val) != val) // worked, but number out of range return false; @@ -100,28 +101,28 @@ bool AbslParseFlag(absl::string_view text, unsigned short* dst, std::string*) { } bool AbslParseFlag(absl::string_view text, int* dst, std::string*) { - return ParseFlagImpl(text, dst); + return ParseFlagImpl(text, *dst); } bool AbslParseFlag(absl::string_view text, unsigned int* dst, std::string*) { - return ParseFlagImpl(text, dst); + return ParseFlagImpl(text, *dst); } bool AbslParseFlag(absl::string_view text, long* dst, std::string*) { - return ParseFlagImpl(text, dst); + return ParseFlagImpl(text, *dst); } bool AbslParseFlag(absl::string_view text, unsigned long* dst, std::string*) { - return ParseFlagImpl(text, dst); + return ParseFlagImpl(text, *dst); } bool AbslParseFlag(absl::string_view text, long long* dst, std::string*) { - return ParseFlagImpl(text, dst); + return ParseFlagImpl(text, *dst); } bool AbslParseFlag(absl::string_view text, unsigned long long* dst, std::string*) { - return ParseFlagImpl(text, dst); + return ParseFlagImpl(text, *dst); } // -------------------------------------------------------------------- diff --git a/absl/flags/parse.cc b/absl/flags/parse.cc index 3f0a7a75..15300786 100644 --- a/absl/flags/parse.cc +++ b/absl/flags/parse.cc @@ -222,7 +222,7 @@ bool ArgsList::ReadFromFlagfile(const std::string& flag_file_name) { // Reads the environment variable with name `name` and stores results in // `value`. If variable is not present in environment returns false, otherwise // returns true. -bool GetEnvVar(const char* var_name, std::string* var_value) { +bool GetEnvVar(const char* var_name, std::string& var_value) { #ifdef _WIN32 char buf[1024]; auto get_res = GetEnvironmentVariableA(var_name, buf, sizeof(buf)); @@ -234,14 +234,14 @@ bool GetEnvVar(const char* var_name, std::string* var_value) { return false; } - *var_value = std::string(buf, get_res); + var_value = std::string(buf, get_res); #else const char* val = ::getenv(var_name); if (val == nullptr) { return false; } - *var_value = val; + var_value = val; #endif return true; @@ -306,17 +306,17 @@ std::tuple LocateFlag(absl::string_view flag_name) { // back. void CheckDefaultValuesParsingRoundtrip() { #ifndef NDEBUG - flags_internal::ForEachFlag([&](CommandLineFlag* flag) { - if (flag->IsRetired()) return; + flags_internal::ForEachFlag([&](CommandLineFlag& flag) { + if (flag.IsRetired()) return; #define ABSL_FLAGS_INTERNAL_IGNORE_TYPE(T, _) \ - if (flag->IsOfType()) return; + if (flag.IsOfType()) return; ABSL_FLAGS_INTERNAL_SUPPORTED_TYPES(ABSL_FLAGS_INTERNAL_IGNORE_TYPE) #undef ABSL_FLAGS_INTERNAL_IGNORE_TYPE flags_internal::PrivateHandleAccessor::CheckDefaultValueParsingRoundtrip( - *flag); + flag); }); #endif } @@ -329,13 +329,13 @@ void CheckDefaultValuesParsingRoundtrip() { // the first flagfile in the input list are processed before the second flagfile // etc. bool ReadFlagfiles(const std::vector& flagfiles, - std::vector* input_args) { + std::vector& input_args) { bool success = true; for (auto it = flagfiles.rbegin(); it != flagfiles.rend(); ++it) { ArgsList al; if (al.ReadFromFlagfile(*it)) { - input_args->push_back(al); + input_args.push_back(al); } else { success = false; } @@ -350,7 +350,7 @@ bool ReadFlagfiles(const std::vector& flagfiles, // `flag_name` is a string from the input flag_names list. If successful we // append a single ArgList at the end of the input_args. bool ReadFlagsFromEnv(const std::vector& flag_names, - std::vector* input_args, + std::vector& input_args, bool fail_on_absent_in_env) { bool success = true; std::vector args; @@ -371,7 +371,7 @@ bool ReadFlagsFromEnv(const std::vector& flag_names, const std::string envname = absl::StrCat("FLAGS_", flag_name); std::string envval; - if (!GetEnvVar(envname.c_str(), &envval)) { + if (!GetEnvVar(envname.c_str(), envval)) { if (fail_on_absent_in_env) { flags_internal::ReportUsageError( absl::StrCat(envname, " not found in environment"), true); @@ -386,7 +386,7 @@ bool ReadFlagsFromEnv(const std::vector& flag_names, } if (success) { - input_args->emplace_back(args); + input_args.emplace_back(args); } return success; @@ -396,8 +396,8 @@ bool ReadFlagsFromEnv(const std::vector& flag_names, // Returns success status, which is true if were able to handle all generator // flags (flagfile, fromenv, tryfromemv) successfully. -bool HandleGeneratorFlags(std::vector* input_args, - std::vector* flagfile_value) { +bool HandleGeneratorFlags(std::vector& input_args, + std::vector& flagfile_value) { bool success = true; absl::MutexLock l(&flags_internal::processing_checks_guard); @@ -422,9 +422,9 @@ bool HandleGeneratorFlags(std::vector* input_args, if (flags_internal::flagfile_needs_processing) { auto flagfiles = absl::GetFlag(FLAGS_flagfile); - if (input_args->size() == 1) { - flagfile_value->insert(flagfile_value->end(), flagfiles.begin(), - flagfiles.end()); + if (input_args.size() == 1) { + flagfile_value.insert(flagfile_value.end(), flagfiles.begin(), + flagfiles.end()); } success &= ReadFlagfiles(flagfiles, input_args); @@ -647,7 +647,7 @@ std::vector ParseCommandLineImpl(int argc, char* argv[], bool success = true; while (!input_args.empty()) { // 10. First we process the built-in generator flags. - success &= HandleGeneratorFlags(&input_args, &flagfile_value); + success &= HandleGeneratorFlags(input_args, flagfile_value); // 30. Select top-most (most recent) arguments list. If it is empty drop it // and re-try. @@ -733,7 +733,7 @@ std::vector ParseCommandLineImpl(int argc, char* argv[], std::string error; if (!flags_internal::PrivateHandleAccessor::ParseFrom( - flag, value, SET_FLAGS_VALUE, kCommandLine, &error)) { + *flag, value, SET_FLAGS_VALUE, kCommandLine, error)) { flags_internal::ReportUsageError(error, true); success = false; } else { diff --git a/absl/flags/parse_test.cc b/absl/flags/parse_test.cc index e6a53ae6..aea068ee 100644 --- a/absl/flags/parse_test.cc +++ b/absl/flags/parse_test.cc @@ -171,8 +171,8 @@ constexpr const char* const ff2_data[] = { // temporary directory location. This way we can test inclusion of one flagfile // from another flagfile. const char* GetFlagfileFlag(const std::vector& ffd, - std::string* flagfile_flag) { - *flagfile_flag = "--flagfile="; + std::string& flagfile_flag) { + flagfile_flag = "--flagfile="; absl::string_view separator; for (const auto& flagfile_data : ffd) { std::string flagfile_name = @@ -183,11 +183,11 @@ const char* GetFlagfileFlag(const std::vector& ffd, flagfile_out << absl::Substitute(line, GetTestTempDir()) << "\n"; } - absl::StrAppend(flagfile_flag, separator, flagfile_name); + absl::StrAppend(&flagfile_flag, separator, flagfile_name); separator = ","; } - return flagfile_flag->c_str(); + return flagfile_flag.c_str(); } } // namespace @@ -588,14 +588,14 @@ TEST_F(ParseTest, TestSimpleValidFlagfile) { const char* in_args1[] = { "testbin", GetFlagfileFlag({{"parse_test.ff1", absl::MakeConstSpan(ff1_data)}}, - &flagfile_flag), + flagfile_flag), }; TestParse(in_args1, -1, 0.1, "q2w2 ", true); const char* in_args2[] = { "testbin", GetFlagfileFlag({{"parse_test.ff2", absl::MakeConstSpan(ff2_data)}}, - &flagfile_flag), + flagfile_flag), }; TestParse(in_args2, 100, 0.1, "q2w2 ", false); } @@ -609,7 +609,7 @@ TEST_F(ParseTest, TestValidMultiFlagfile) { "testbin", GetFlagfileFlag({{"parse_test.ff2", absl::MakeConstSpan(ff2_data)}, {"parse_test.ff1", absl::MakeConstSpan(ff1_data)}}, - &flagfile_flag), + flagfile_flag), }; TestParse(in_args1, -1, 0.1, "q2w2 ", true); } @@ -622,7 +622,7 @@ TEST_F(ParseTest, TestFlagfileMixedWithRegularFlags) { const char* in_args1[] = { "testbin", "--int_flag=3", GetFlagfileFlag({{"parse_test.ff1", absl::MakeConstSpan(ff1_data)}}, - &flagfile_flag), + flagfile_flag), "-double_flag=0.2"}; TestParse(in_args1, -1, 0.2, "q2w2 ", true); } @@ -640,7 +640,7 @@ TEST_F(ParseTest, TestFlagfileInFlagfile) { const char* in_args1[] = { "testbin", GetFlagfileFlag({{"parse_test.ff3", absl::MakeConstSpan(ff3_data)}}, - &flagfile_flag), + flagfile_flag), }; TestParse(in_args1, 100, 0.1, "q2w2 ", false); } @@ -657,7 +657,7 @@ TEST_F(ParseDeathTest, TestInvalidFlagfiles) { const char* in_args1[] = { "testbin", GetFlagfileFlag({{"parse_test.ff4", - absl::MakeConstSpan(ff4_data)}}, &flagfile_flag), + absl::MakeConstSpan(ff4_data)}}, flagfile_flag), }; EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args1), "Unknown command line flag 'unknown_flag'"); @@ -669,7 +669,7 @@ TEST_F(ParseDeathTest, TestInvalidFlagfiles) { const char* in_args2[] = { "testbin", GetFlagfileFlag({{"parse_test.ff5", - absl::MakeConstSpan(ff5_data)}}, &flagfile_flag), + absl::MakeConstSpan(ff5_data)}}, flagfile_flag), }; EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args2), "Unknown command line flag 'int_flag 10'"); @@ -681,7 +681,7 @@ TEST_F(ParseDeathTest, TestInvalidFlagfiles) { const char* in_args3[] = { "testbin", GetFlagfileFlag({{"parse_test.ff6", absl::MakeConstSpan(ff6_data)}}, - &flagfile_flag), + flagfile_flag), }; EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args3), "Flagfile can't contain position arguments or --"); @@ -702,7 +702,7 @@ TEST_F(ParseDeathTest, TestInvalidFlagfiles) { const char* in_args5[] = { "testbin", GetFlagfileFlag({{"parse_test.ff7", absl::MakeConstSpan(ff7_data)}}, - &flagfile_flag), + flagfile_flag), }; EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args5), "Unexpected line in the flagfile .*: \\*bin\\*"); diff --git a/absl/random/BUILD.bazel b/absl/random/BUILD.bazel index 9ba75b52..4b804c86 100644 --- a/absl/random/BUILD.bazel +++ b/absl/random/BUILD.bazel @@ -69,7 +69,7 @@ cc_library( "//absl/base:config", "//absl/base:core_headers", "//absl/meta:type_traits", - "//absl/random/internal:distributions", + "//absl/random/internal:distribution_caller", "//absl/random/internal:fast_uniform_bits", "//absl/random/internal:fastmath", "//absl/random/internal:generate_real", @@ -78,7 +78,6 @@ cc_library( "//absl/random/internal:uniform_helper", "//absl/random/internal:wide_multiply", "//absl/strings", - "//absl/types:span", ], ) diff --git a/absl/random/CMakeLists.txt b/absl/random/CMakeLists.txt index ec616dd9..85a2ea37 100644 --- a/absl/random/CMakeLists.txt +++ b/absl/random/CMakeLists.txt @@ -183,7 +183,7 @@ absl_cc_library( absl::config absl::core_headers absl::random_internal_generate_real - absl::random_internal_distributions + absl::random_internal_distribution_caller absl::random_internal_fast_uniform_bits absl::random_internal_fastmath absl::random_internal_iostream_state_saver @@ -191,7 +191,6 @@ absl_cc_library( absl::random_internal_uniform_helper absl::random_internal_wide_multiply absl::strings - absl::span absl::type_traits ) @@ -536,27 +535,6 @@ absl_cc_library( absl::config ) -# Internal-only target, do not depend on directly. -absl_cc_library( - NAME - random_internal_distributions - HDRS - "internal/distributions.h" - COPTS - ${ABSL_DEFAULT_COPTS} - LINKOPTS - ${ABSL_DEFAULT_LINKOPTS} - DEPS - absl::random_internal_distribution_caller - absl::random_internal_fast_uniform_bits - absl::random_internal_fastmath - absl::random_internal_traits - absl::random_internal_uniform_helper - absl::span - absl::strings - absl::type_traits -) - # Internal-only target, do not depend on directly. absl_cc_library( NAME @@ -745,7 +723,6 @@ absl_cc_library( absl::random_internal_salted_seed_seq absl::random_internal_seed_material absl::span - absl::strings absl::type_traits ) @@ -1174,9 +1151,7 @@ absl_cc_library( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS - absl::core_headers - absl::random_internal_fast_uniform_bits - absl::random_internal_iostream_state_saver + absl::config absl::random_internal_traits absl::type_traits ) diff --git a/absl/random/distributions.h b/absl/random/distributions.h index 8680f6a6..6775c5d6 100644 --- a/absl/random/distributions.h +++ b/absl/random/distributions.h @@ -57,7 +57,7 @@ #include "absl/random/beta_distribution.h" #include "absl/random/exponential_distribution.h" #include "absl/random/gaussian_distribution.h" -#include "absl/random/internal/distributions.h" // IWYU pragma: export +#include "absl/random/internal/distribution_caller.h" // IWYU pragma: export #include "absl/random/internal/uniform_helper.h" // IWYU pragma: export #include "absl/random/log_uniform_int_distribution.h" #include "absl/random/poisson_distribution.h" diff --git a/absl/random/internal/BUILD.bazel b/absl/random/internal/BUILD.bazel index 85d1fb81..813d926e 100644 --- a/absl/random/internal/BUILD.bazel +++ b/absl/random/internal/BUILD.bazel @@ -48,21 +48,6 @@ cc_library( deps = ["//absl/base:config"], ) -cc_library( - name = "distributions", - hdrs = ["distributions.h"], - copts = ABSL_DEFAULT_COPTS, - linkopts = ABSL_DEFAULT_LINKOPTS, - deps = [ - ":distribution_caller", - ":traits", - ":uniform_helper", - "//absl/base", - "//absl/meta:type_traits", - "//absl/strings", - ], -) - cc_library( name = "fast_uniform_bits", hdrs = [ @@ -221,7 +206,6 @@ cc_library( ":seed_material", "//absl/base:core_headers", "//absl/meta:type_traits", - "//absl/strings", "//absl/types:optional", "//absl/types:span", ], @@ -672,6 +656,8 @@ cc_library( copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ + ":traits", + "//absl/base:config", "//absl/meta:type_traits", ], ) diff --git a/absl/random/internal/distributions.h b/absl/random/internal/distributions.h deleted file mode 100644 index d7e3c016..00000000 --- a/absl/random/internal/distributions.h +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2019 The Abseil Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef ABSL_RANDOM_INTERNAL_DISTRIBUTIONS_H_ -#define ABSL_RANDOM_INTERNAL_DISTRIBUTIONS_H_ - -#include - -#include "absl/meta/type_traits.h" -#include "absl/random/internal/distribution_caller.h" -#include "absl/random/internal/traits.h" -#include "absl/random/internal/uniform_helper.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace random_internal { - -// In the absence of an explicitly provided return-type, the template -// "uniform_inferred_return_t" is used to derive a suitable type, based on -// the data-types of the endpoint-arguments {A lo, B hi}. -// -// Given endpoints {A lo, B hi}, one of {A, B} will be chosen as the -// return-type, if one type can be implicitly converted into the other, in a -// lossless way. The template "is_widening_convertible" implements the -// compile-time logic for deciding if such a conversion is possible. -// -// If no such conversion between {A, B} exists, then the overload for -// absl::Uniform() will be discarded, and the call will be ill-formed. -// Return-type for absl::Uniform() when the return-type is inferred. -template -using uniform_inferred_return_t = - absl::enable_if_t, - is_widening_convertible>::value, - typename std::conditional< - is_widening_convertible::value, B, A>::type>; - -} // namespace random_internal -ABSL_NAMESPACE_END -} // namespace absl - -#endif // ABSL_RANDOM_INTERNAL_DISTRIBUTIONS_H_ diff --git a/absl/random/internal/uniform_helper.h b/absl/random/internal/uniform_helper.h index 663107cb..5b2afecb 100644 --- a/absl/random/internal/uniform_helper.h +++ b/absl/random/internal/uniform_helper.h @@ -19,10 +19,13 @@ #include #include +#include "absl/base/config.h" #include "absl/meta/type_traits.h" +#include "absl/random/internal/traits.h" namespace absl { ABSL_NAMESPACE_BEGIN + template class uniform_int_distribution; @@ -58,6 +61,26 @@ struct IntervalOpenOpenTag : public random_internal::TagTypeCompare {}; namespace random_internal { + +// In the absence of an explicitly provided return-type, the template +// "uniform_inferred_return_t" is used to derive a suitable type, based on +// the data-types of the endpoint-arguments {A lo, B hi}. +// +// Given endpoints {A lo, B hi}, one of {A, B} will be chosen as the +// return-type, if one type can be implicitly converted into the other, in a +// lossless way. The template "is_widening_convertible" implements the +// compile-time logic for deciding if such a conversion is possible. +// +// If no such conversion between {A, B} exists, then the overload for +// absl::Uniform() will be discarded, and the call will be ill-formed. +// Return-type for absl::Uniform() when the return-type is inferred. +template +using uniform_inferred_return_t = + absl::enable_if_t, + is_widening_convertible>::value, + typename std::conditional< + is_widening_convertible::value, B, A>::type>; + // The functions // uniform_lower_bound(tag, a, b) // and @@ -149,12 +172,19 @@ uniform_upper_bound(Tag, FloatType, FloatType b) { return std::nextafter(b, (std::numeric_limits::max)()); } +// UniformDistribution selects either absl::uniform_int_distribution +// or absl::uniform_real_distribution depending on the NumType parameter. template using UniformDistribution = typename std::conditional::value, absl::uniform_int_distribution, absl::uniform_real_distribution>::type; +// UniformDistributionWrapper is used as the underlying distribution type +// by the absl::Uniform template function. It selects the proper Abseil +// uniform distribution and provides constructor overloads that match the +// expected parameter order as well as adjusting distribtuion bounds based +// on the tag. template struct UniformDistributionWrapper : public UniformDistribution { template diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index 1ddd6aec..68f53987 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc @@ -705,6 +705,37 @@ Cord::Cord(absl::string_view src) { } } +template > +Cord::Cord(T&& src) { + if ( + // String is short: copy data to avoid external block overhead. + src.size() <= kMaxBytesToCopy || + // String is wasteful: copy data to avoid pinning too much unused memory. + src.size() < src.capacity() / 2 + ) { + if (src.size() <= InlineRep::kMaxInline) { + contents_.set_data(src.data(), src.size(), false); + } else { + contents_.set_tree(NewTree(src.data(), src.size(), 0)); + } + } else { + struct StringReleaser { + void operator()(absl::string_view /* data */) {} + std::string data; + }; + const absl::string_view original_data = src; + CordRepExternal* rep = + static_cast(absl::cord_internal::NewExternalRep( + original_data, StringReleaser{std::move(src)})); + // Moving src may have invalidated its data pointer, so adjust it. + rep->base = + static_cast(GetExternalReleaser(rep))->data.data(); + contents_.set_tree(rep); + } +} + +template Cord::Cord(std::string&& src); + // The destruction code is separate so that the compiler can determine // that it does not need to call the destructor on a moved-from Cord. void Cord::DestroyCordSlow() { @@ -742,6 +773,18 @@ Cord& Cord::operator=(absl::string_view src) { return *this; } +template > +Cord& Cord::operator=(T&& src) { + if (src.size() <= kMaxBytesToCopy) { + *this = absl::string_view(src); + } else { + *this = Cord(std::move(src)); + } + return *this; +} + +template Cord& Cord::operator=(std::string&& src); + // TODO(sanjay): Move to Cord::InlineRep section of file. For now, // we keep it here to make diffs easier. void Cord::InlineRep::AppendArray(const char* src_data, size_t src_size) { @@ -853,6 +896,17 @@ void Cord::Append(const Cord& src) { AppendImpl(src); } void Cord::Append(Cord&& src) { AppendImpl(std::move(src)); } +template > +void Cord::Append(T&& src) { + if (src.size() <= kMaxBytesToCopy) { + Append(absl::string_view(src)); + } else { + Append(Cord(std::move(src))); + } +} + +template void Cord::Append(std::string&& src); + void Cord::Prepend(const Cord& src) { CordRep* src_tree = src.contents_.tree(); if (src_tree != nullptr) { @@ -882,6 +936,17 @@ void Cord::Prepend(absl::string_view src) { } } +template > +inline void Cord::Prepend(T&& src) { + if (src.size() <= kMaxBytesToCopy) { + Prepend(absl::string_view(src)); + } else { + Prepend(Cord(std::move(src))); + } +} + +template void Cord::Prepend(std::string&& src); + static CordRep* RemovePrefixFrom(CordRep* node, size_t n) { if (n >= node->length) return nullptr; if (n == 0) return Ref(node); diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 3be8d7d8..dc987454 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -147,11 +147,8 @@ class Cord { // Creates a Cord from a `std::string&&` rvalue. These constructors are // templated to avoid ambiguities for types that are convertible to both // `absl::string_view` and `std::string`, such as `const char*`. - // - // Note that these functions reserve the right to use the `string&&`'s - // memory and that they will do so in the future. template = 0> - explicit Cord(T&& src) : Cord(absl::string_view(src)) {} + explicit Cord(T&& src); template = 0> Cord& operator=(T&& src); @@ -1048,11 +1045,8 @@ inline Cord& Cord::operator=(Cord&& x) noexcept { return *this; } -template > -inline Cord& Cord::operator=(T&& src) { - *this = absl::string_view(src); - return *this; -} +extern template Cord::Cord(std::string&& src); +extern template Cord& Cord::operator=(std::string&& src); inline size_t Cord::size() const { // Length is 1st field in str.rep_ @@ -1098,19 +1092,8 @@ inline void Cord::Append(absl::string_view src) { contents_.AppendArray(src.data(), src.size()); } -template > -inline void Cord::Append(T&& src) { - // Note that this function reserves the right to reuse the `string&&`'s - // memory and that it will do so in the future. - Append(absl::string_view(src)); -} - -template > -inline void Cord::Prepend(T&& src) { - // Note that this function reserves the right to reuse the `string&&`'s - // memory and that it will do so in the future. - Prepend(absl::string_view(src)); -} +extern template void Cord::Append(std::string&& src); +extern template void Cord::Prepend(std::string&& src); inline int Cord::Compare(const Cord& rhs) const { if (!contents_.is_tree() && !rhs.contents_.is_tree()) { diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc index 49a68849..22cfef66 100644 --- a/absl/strings/str_format_test.cc +++ b/absl/strings/str_format_test.cc @@ -8,6 +8,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/strings/cord.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" @@ -353,6 +354,7 @@ TEST(StrFormat, BehavesAsDocumented) { EXPECT_EQ(StrFormat("%s", "C"), "C"); EXPECT_EQ(StrFormat("%s", std::string("C++")), "C++"); EXPECT_EQ(StrFormat("%s", string_view("view")), "view"); + EXPECT_EQ(StrFormat("%s", absl::Cord("cord")), "cord"); // Integral Conversion // These format integral types: char, int, long, uint64_t, etc. EXPECT_EQ(StrFormat("%d", char{10}), "10"); diff --git a/absl/time/internal/cctz/include/cctz/time_zone.h b/absl/time/internal/cctz/include/cctz/time_zone.h index d4ea90ef..b33e0c0a 100644 --- a/absl/time/internal/cctz/include/cctz/time_zone.h +++ b/absl/time/internal/cctz/include/cctz/time_zone.h @@ -292,6 +292,7 @@ bool parse(const std::string&, const std::string&, const time_zone&, // - %E#f - Fractional seconds with # digits of precision // - %E*f - Fractional seconds with full precision (a literal '*') // - %E4Y - Four-character years (-999 ... -001, 0000, 0001 ... 9999) +// - %ET - The RFC3339 "date-time" separator "T" // // Note that %E0S behaves like %S, and %E0f produces no characters. In // contrast %E*f always produces at least one digit, which may be '0'. @@ -321,7 +322,7 @@ inline std::string format(const std::string& fmt, const time_point& tp, // returns the corresponding time_point. Uses strftime()-like formatting // options, with the same extensions as cctz::format(), but with the // exceptions that %E#S is interpreted as %E*S, and %E#f as %E*f. %Ez -// and %E*z also accept the same inputs. +// and %E*z also accept the same inputs. %ET accepts either 'T' or 't'. // // %Y consumes as many numeric characters as it can, so the matching data // should always be terminated with a non-numeric. %E4Y always consumes diff --git a/absl/time/internal/cctz/src/cctz_benchmark.cc b/absl/time/internal/cctz/src/cctz_benchmark.cc index a402760d..4e39188f 100644 --- a/absl/time/internal/cctz/src/cctz_benchmark.cc +++ b/absl/time/internal/cctz/src/cctz_benchmark.cc @@ -97,8 +97,8 @@ void BM_PrevWeekday(benchmark::State& state) { } BENCHMARK(BM_PrevWeekday); -const char RFC3339_full[] = "%Y-%m-%dT%H:%M:%E*S%Ez"; -const char RFC3339_sec[] = "%Y-%m-%dT%H:%M:%S%Ez"; +const char RFC3339_full[] = "%Y-%m-%d%ET%H:%M:%E*S%Ez"; +const char RFC3339_sec[] = "%Y-%m-%d%ET%H:%M:%S%Ez"; const char RFC1123_full[] = "%a, %d %b %Y %H:%M:%S %z"; const char RFC1123_no_wday[] = "%d %b %Y %H:%M:%S %z"; @@ -991,12 +991,12 @@ void BM_Time_FromCivilDay0_Libc(benchmark::State& state) { BENCHMARK(BM_Time_FromCivilDay0_Libc); const char* const kFormats[] = { - RFC1123_full, // 0 - RFC1123_no_wday, // 1 - RFC3339_full, // 2 - RFC3339_sec, // 3 - "%Y-%m-%dT%H:%M:%S", // 4 - "%Y-%m-%d", // 5 + RFC1123_full, // 0 + RFC1123_no_wday, // 1 + RFC3339_full, // 2 + RFC3339_sec, // 3 + "%Y-%m-%d%ET%H:%M:%S", // 4 + "%Y-%m-%d", // 5 }; const int kNumFormats = sizeof(kFormats) / sizeof(kFormats[0]); diff --git a/absl/time/internal/cctz/src/time_zone_format.cc b/absl/time/internal/cctz/src/time_zone_format.cc index 179975e0..a4428632 100644 --- a/absl/time/internal/cctz/src/time_zone_format.cc +++ b/absl/time/internal/cctz/src/time_zone_format.cc @@ -290,6 +290,7 @@ const std::int_fast64_t kExp10[kDigits10_64 + 1] = { // - %E#S - Seconds with # digits of fractional precision // - %E*S - Seconds with full fractional precision (a literal '*') // - %E4Y - Four-character years (-999 ... -001, 0000, 0001 ... 9999) +// - %ET - The RFC3339 "date-time" separator "T" // // The standard specifiers from RFC3339_* (%Y, %m, %d, %H, %M, and %S) are // handled internally for performance reasons. strftime(3) is slow due to @@ -448,7 +449,14 @@ std::string format(const std::string& format, const time_point& tp, if (*cur != 'E' || ++cur == end) continue; // Format our extensions. - if (*cur == 'z') { + if (*cur == 'T') { + // Formats %ET. + if (cur - 2 != pending) { + FormatTM(&result, std::string(pending, cur - 2), tm); + } + result.append("T"); + pending = ++cur; + } else if (*cur == 'z') { // Formats %Ez. if (cur - 2 != pending) { FormatTM(&result, std::string(pending, cur - 2), tm); @@ -551,7 +559,7 @@ const char* ParseOffset(const char* dp, const char* mode, int* offset) { } else { dp = nullptr; } - } else if (first == 'Z') { // Zulu + } else if (first == 'Z' || first == 'z') { // Zulu *offset = 0; } else { dp = nullptr; @@ -607,7 +615,7 @@ const char* ParseTM(const char* dp, const char* fmt, std::tm* tm) { // Uses strptime(3) to parse the given input. Supports the same extended // format specifiers as format(), although %E#S and %E*S are treated // identically (and similarly for %E#f and %E*f). %Ez and %E*z also accept -// the same inputs. +// the same inputs. %ET accepts either 'T' or 't'. // // The standard specifiers from RFC3339_* (%Y, %m, %d, %H, %M, and %S) are // handled internally so that we can normally avoid strptime() altogether @@ -742,6 +750,15 @@ bool parse(const std::string& format, const std::string& input, data = (*data == '%' ? data + 1 : nullptr); continue; case 'E': + if (fmt[0] == 'T') { + if (*data == 'T' || *data == 't') { + ++data; + ++fmt; + } else { + data = nullptr; + } + continue; + } if (fmt[0] == 'z' || (fmt[0] == '*' && fmt[1] == 'z')) { data = ParseOffset(data, ":", &offset); if (data != nullptr) saw_offset = true; 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 87382e15..13a4227e 100644 --- a/absl/time/internal/cctz/src/time_zone_format_test.cc +++ b/absl/time/internal/cctz/src/time_zone_format_test.cc @@ -48,8 +48,8 @@ namespace { EXPECT_STREQ(zone, al.abbr); \ } while (0) -const char RFC3339_full[] = "%Y-%m-%dT%H:%M:%E*S%Ez"; -const char RFC3339_sec[] = "%Y-%m-%dT%H:%M:%S%Ez"; +const char RFC3339_full[] = "%Y-%m-%d%ET%H:%M:%E*S%Ez"; +const char RFC3339_sec[] = "%Y-%m-%d%ET%H:%M:%S%Ez"; const char RFC1123_full[] = "%a, %d %b %Y %H:%M:%S %z"; const char RFC1123_no_wday[] = "%d %b %Y %H:%M:%S %z"; @@ -1379,10 +1379,20 @@ TEST(Parse, RFC3339Format) { EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12T20:21:00+00:00", tz, &tp)); ExpectTime(tp, tz, 2014, 2, 12, 20, 21, 0, 0, false, "UTC"); - // Check that %Ez also accepts "Z" as a synonym for "+00:00". + // Check that %ET also accepts "t". time_point tp2; - EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12T20:21:00Z", tz, &tp2)); + EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12t20:21:00+00:00", tz, &tp2)); EXPECT_EQ(tp, tp2); + + // Check that %Ez also accepts "Z" as a synonym for "+00:00". + time_point tp3; + EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12T20:21:00Z", tz, &tp3)); + EXPECT_EQ(tp, tp3); + + // Check that %Ez also accepts "z" as a synonym for "+00:00". + time_point tp4; + EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12T20:21:00z", tz, &tp4)); + EXPECT_EQ(tp, tp4); } TEST(Parse, MaxRange) { 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 0b0c1a3b..8f7ab154 100644 --- a/absl/time/internal/cctz/src/time_zone_lookup_test.cc +++ b/absl/time/internal/cctz/src/time_zone_lookup_test.cc @@ -933,7 +933,7 @@ TEST(MakeTime, Normalization) { // NOTE: Run this with -ftrapv to detect overflow problems. TEST(MakeTime, SysSecondsLimits) { - const char RFC3339[] = "%Y-%m-%dT%H:%M:%S%Ez"; + const char RFC3339[] = "%Y-%m-%d%ET%H:%M:%S%Ez"; const time_zone utc = utc_time_zone(); const time_zone east = fixed_time_zone(chrono::hours(14)); const time_zone west = fixed_time_zone(-chrono::hours(14)); -- cgit v1.2.3 From 10cb35e459f5ecca5b2ff107635da0bfa41011b4 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Mon, 22 Jun 2020 10:03:06 -0700 Subject: Export of internal Abseil changes -- b548087c24ae7c2c709e8040a118b5e312d18e2e by Derek Mauro : Remove the static initialization of global variables used by absl::Mutex as requested by Chromium PiperOrigin-RevId: 317676541 -- f198f5da1e966772efa978ba019bd23576899794 by Greg Miller : fix: work around gcc-4.8 bug in disjunction See https://godbolt.org/z/i7-AmM for a repro of the bug. I realize that Abseil no longer supports gcc 4.8 officially (https://abseil.io/docs/cpp/platforms/platforms), but Cloud C++ still supports gcc 4.8 officially, and so it would be nice to get this simple fix in. fixes https://github.com/abseil/abseil-cpp/issues/718 PiperOrigin-RevId: 317484459 -- ed233f646530c6c0948213b643cc6919db1bee90 by Chris Kennelly : Avoid determining the size of the duration unit at runtime. PiperOrigin-RevId: 317376300 -- 73d4011c17fcf747a990176924a7adc69d443533 by Greg Falcon : Change spelling of internal detail from `Invoke`/`InvokeT` to `invoke`/`invoke_result_t`. This matches the spelling of the C++17 standard library names that perform the same operations. PiperOrigin-RevId: 317311527 GitOrigin-RevId: b548087c24ae7c2c709e8040a118b5e312d18e2e Change-Id: I131809ff0b92cfdb0d96dc94e94d9c6f751cb0ac --- absl/base/call_once.h | 2 +- absl/base/internal/invoke.h | 8 +- absl/base/invoke_test.cc | 136 +++++++++++++++++--------------- absl/functional/function_ref.h | 2 +- absl/functional/internal/front_binder.h | 16 ++-- absl/functional/internal/function_ref.h | 4 +- absl/meta/type_traits.h | 10 +-- absl/strings/cord.h | 8 +- absl/synchronization/mutex.cc | 57 +++++++------ absl/time/duration.cc | 20 ++--- absl/types/internal/variant.h | 8 +- absl/utility/utility.h | 4 +- 12 files changed, 137 insertions(+), 138 deletions(-) (limited to 'absl/strings/cord.h') diff --git a/absl/base/call_once.h b/absl/base/call_once.h index bc5ec937..5b468af8 100644 --- a/absl/base/call_once.h +++ b/absl/base/call_once.h @@ -175,7 +175,7 @@ void CallOnceImpl(std::atomic* control, std::memory_order_relaxed) || base_internal::SpinLockWait(control, ABSL_ARRAYSIZE(trans), trans, scheduling_mode) == kOnceInit) { - base_internal::Invoke(std::forward(fn), + base_internal::invoke(std::forward(fn), std::forward(args)...); // The call to SpinLockWake below is an optimization, because the waiter // in SpinLockWait is waiting with a short timeout. The atomic load/store diff --git a/absl/base/internal/invoke.h b/absl/base/internal/invoke.h index c4eceebd..5c71f328 100644 --- a/absl/base/internal/invoke.h +++ b/absl/base/internal/invoke.h @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -// absl::base_internal::Invoke(f, args...) is an implementation of +// absl::base_internal::invoke(f, args...) is an implementation of // INVOKE(f, args...) from section [func.require] of the C++ standard. // // [func.require] @@ -29,7 +29,7 @@ // is not one of the types described in the previous item; // 5. f(t1, t2, ..., tN) in all other cases. // -// The implementation is SFINAE-friendly: substitution failure within Invoke() +// The implementation is SFINAE-friendly: substitution failure within invoke() // isn't an error. #ifndef ABSL_BASE_INTERNAL_INVOKE_H_ @@ -170,13 +170,13 @@ struct Invoker { // The result type of Invoke. template -using InvokeT = decltype(Invoker::type::Invoke( +using invoke_result_t = decltype(Invoker::type::Invoke( std::declval(), std::declval()...)); // Invoke(f, args...) is an implementation of INVOKE(f, args...) from section // [func.require] of the C++ standard. template -InvokeT Invoke(F&& f, Args&&... args) { +invoke_result_t invoke(F&& f, Args&&... args) { return Invoker::type::Invoke(std::forward(f), std::forward(args)...); } diff --git a/absl/base/invoke_test.cc b/absl/base/invoke_test.cc index 6aa613c9..bcdef36c 100644 --- a/absl/base/invoke_test.cc +++ b/absl/base/invoke_test.cc @@ -86,71 +86,73 @@ struct FlipFlop { int member; }; -// CallMaybeWithArg(f) resolves either to Invoke(f) or Invoke(f, 42), depending +// CallMaybeWithArg(f) resolves either to invoke(f) or invoke(f, 42), depending // on which one is valid. template -decltype(Invoke(std::declval())) CallMaybeWithArg(const F& f) { - return Invoke(f); +decltype(base_internal::invoke(std::declval())) CallMaybeWithArg( + const F& f) { + return base_internal::invoke(f); } template -decltype(Invoke(std::declval(), 42)) CallMaybeWithArg(const F& f) { - return Invoke(f, 42); +decltype(base_internal::invoke(std::declval(), 42)) CallMaybeWithArg( + const F& f) { + return base_internal::invoke(f, 42); } TEST(InvokeTest, Function) { - EXPECT_EQ(1, Invoke(Function, 3, 2)); - EXPECT_EQ(1, Invoke(&Function, 3, 2)); + EXPECT_EQ(1, base_internal::invoke(Function, 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Function, 3, 2)); } TEST(InvokeTest, NonCopyableArgument) { - EXPECT_EQ(42, Invoke(Sink, make_unique(42))); + EXPECT_EQ(42, base_internal::invoke(Sink, make_unique(42))); } TEST(InvokeTest, NonCopyableResult) { - EXPECT_THAT(Invoke(Factory, 42), ::testing::Pointee(42)); + EXPECT_THAT(base_internal::invoke(Factory, 42), ::testing::Pointee(42)); } -TEST(InvokeTest, VoidResult) { - Invoke(NoOp); -} +TEST(InvokeTest, VoidResult) { base_internal::invoke(NoOp); } TEST(InvokeTest, ConstFunctor) { - EXPECT_EQ(1, Invoke(ConstFunctor(), 3, 2)); + EXPECT_EQ(1, base_internal::invoke(ConstFunctor(), 3, 2)); } TEST(InvokeTest, MutableFunctor) { MutableFunctor f; - EXPECT_EQ(1, Invoke(f, 3, 2)); - EXPECT_EQ(1, Invoke(MutableFunctor(), 3, 2)); + EXPECT_EQ(1, base_internal::invoke(f, 3, 2)); + EXPECT_EQ(1, base_internal::invoke(MutableFunctor(), 3, 2)); } TEST(InvokeTest, EphemeralFunctor) { EphemeralFunctor f; - EXPECT_EQ(1, Invoke(std::move(f), 3, 2)); - EXPECT_EQ(1, Invoke(EphemeralFunctor(), 3, 2)); + EXPECT_EQ(1, base_internal::invoke(std::move(f), 3, 2)); + EXPECT_EQ(1, base_internal::invoke(EphemeralFunctor(), 3, 2)); } TEST(InvokeTest, OverloadedFunctor) { OverloadedFunctor f; const OverloadedFunctor& cf = f; - EXPECT_EQ("&", Invoke(f)); - EXPECT_EQ("& 42", Invoke(f, " 42")); + EXPECT_EQ("&", base_internal::invoke(f)); + EXPECT_EQ("& 42", base_internal::invoke(f, " 42")); + + EXPECT_EQ("const&", base_internal::invoke(cf)); + EXPECT_EQ("const& 42", base_internal::invoke(cf, " 42")); - EXPECT_EQ("const&", Invoke(cf)); - EXPECT_EQ("const& 42", Invoke(cf, " 42")); + EXPECT_EQ("&&", base_internal::invoke(std::move(f))); - EXPECT_EQ("&&", Invoke(std::move(f))); - EXPECT_EQ("&& 42", Invoke(std::move(f), " 42")); + OverloadedFunctor f2; + EXPECT_EQ("&& 42", base_internal::invoke(std::move(f2), " 42")); } TEST(InvokeTest, ReferenceWrapper) { ConstFunctor cf; MutableFunctor mf; - EXPECT_EQ(1, Invoke(std::cref(cf), 3, 2)); - EXPECT_EQ(1, Invoke(std::ref(cf), 3, 2)); - EXPECT_EQ(1, Invoke(std::ref(mf), 3, 2)); + EXPECT_EQ(1, base_internal::invoke(std::cref(cf), 3, 2)); + EXPECT_EQ(1, base_internal::invoke(std::ref(cf), 3, 2)); + EXPECT_EQ(1, base_internal::invoke(std::ref(mf), 3, 2)); } TEST(InvokeTest, MemberFunction) { @@ -158,58 +160,62 @@ TEST(InvokeTest, MemberFunction) { std::unique_ptr cp(new Class); std::unique_ptr vp(new Class); - EXPECT_EQ(1, Invoke(&Class::Method, p, 3, 2)); - EXPECT_EQ(1, Invoke(&Class::Method, p.get(), 3, 2)); - EXPECT_EQ(1, Invoke(&Class::Method, *p, 3, 2)); - EXPECT_EQ(1, Invoke(&Class::RefMethod, p, 3, 2)); - EXPECT_EQ(1, Invoke(&Class::RefMethod, p.get(), 3, 2)); - EXPECT_EQ(1, Invoke(&Class::RefMethod, *p, 3, 2)); - EXPECT_EQ(1, Invoke(&Class::RefRefMethod, std::move(*p), 3, 2)); // NOLINT - EXPECT_EQ(1, Invoke(&Class::NoExceptMethod, p, 3, 2)); - EXPECT_EQ(1, Invoke(&Class::NoExceptMethod, p.get(), 3, 2)); - EXPECT_EQ(1, Invoke(&Class::NoExceptMethod, *p, 3, 2)); - - EXPECT_EQ(1, Invoke(&Class::ConstMethod, p, 3, 2)); - EXPECT_EQ(1, Invoke(&Class::ConstMethod, p.get(), 3, 2)); - EXPECT_EQ(1, Invoke(&Class::ConstMethod, *p, 3, 2)); - - EXPECT_EQ(1, Invoke(&Class::ConstMethod, cp, 3, 2)); - EXPECT_EQ(1, Invoke(&Class::ConstMethod, cp.get(), 3, 2)); - EXPECT_EQ(1, Invoke(&Class::ConstMethod, *cp, 3, 2)); - - EXPECT_EQ(1, Invoke(&Class::VolatileMethod, p, 3, 2)); - EXPECT_EQ(1, Invoke(&Class::VolatileMethod, p.get(), 3, 2)); - EXPECT_EQ(1, Invoke(&Class::VolatileMethod, *p, 3, 2)); - EXPECT_EQ(1, Invoke(&Class::VolatileMethod, vp, 3, 2)); - EXPECT_EQ(1, Invoke(&Class::VolatileMethod, vp.get(), 3, 2)); - EXPECT_EQ(1, Invoke(&Class::VolatileMethod, *vp, 3, 2)); - - EXPECT_EQ(1, Invoke(&Class::Method, make_unique(), 3, 2)); - EXPECT_EQ(1, Invoke(&Class::ConstMethod, make_unique(), 3, 2)); - EXPECT_EQ(1, Invoke(&Class::ConstMethod, make_unique(), 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Class::Method, p, 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Class::Method, p.get(), 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Class::Method, *p, 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Class::RefMethod, p, 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Class::RefMethod, p.get(), 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Class::RefMethod, *p, 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Class::RefRefMethod, std::move(*p), 3, + 2)); // NOLINT + EXPECT_EQ(1, base_internal::invoke(&Class::NoExceptMethod, p, 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Class::NoExceptMethod, p.get(), 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Class::NoExceptMethod, *p, 3, 2)); + + EXPECT_EQ(1, base_internal::invoke(&Class::ConstMethod, p, 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Class::ConstMethod, p.get(), 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Class::ConstMethod, *p, 3, 2)); + + EXPECT_EQ(1, base_internal::invoke(&Class::ConstMethod, cp, 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Class::ConstMethod, cp.get(), 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Class::ConstMethod, *cp, 3, 2)); + + EXPECT_EQ(1, base_internal::invoke(&Class::VolatileMethod, p, 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Class::VolatileMethod, p.get(), 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Class::VolatileMethod, *p, 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Class::VolatileMethod, vp, 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Class::VolatileMethod, vp.get(), 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Class::VolatileMethod, *vp, 3, 2)); + + EXPECT_EQ(1, + base_internal::invoke(&Class::Method, make_unique(), 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Class::ConstMethod, make_unique(), + 3, 2)); + EXPECT_EQ(1, base_internal::invoke(&Class::ConstMethod, + make_unique(), 3, 2)); } TEST(InvokeTest, DataMember) { std::unique_ptr p(new Class{42}); std::unique_ptr cp(new Class{42}); - EXPECT_EQ(42, Invoke(&Class::member, p)); - EXPECT_EQ(42, Invoke(&Class::member, *p)); - EXPECT_EQ(42, Invoke(&Class::member, p.get())); + EXPECT_EQ(42, base_internal::invoke(&Class::member, p)); + EXPECT_EQ(42, base_internal::invoke(&Class::member, *p)); + EXPECT_EQ(42, base_internal::invoke(&Class::member, p.get())); - Invoke(&Class::member, p) = 42; - Invoke(&Class::member, p.get()) = 42; + base_internal::invoke(&Class::member, p) = 42; + base_internal::invoke(&Class::member, p.get()) = 42; - EXPECT_EQ(42, Invoke(&Class::member, cp)); - EXPECT_EQ(42, Invoke(&Class::member, *cp)); - EXPECT_EQ(42, Invoke(&Class::member, cp.get())); + EXPECT_EQ(42, base_internal::invoke(&Class::member, cp)); + EXPECT_EQ(42, base_internal::invoke(&Class::member, *cp)); + EXPECT_EQ(42, base_internal::invoke(&Class::member, cp.get())); } TEST(InvokeTest, FlipFlop) { FlipFlop obj = {42}; // This call could resolve to (obj.*&FlipFlop::ConstMethod)() or // ((*obj).*&FlipFlop::ConstMethod)(). We verify that it's the former. - EXPECT_EQ(42, Invoke(&FlipFlop::ConstMethod, obj)); - EXPECT_EQ(42, Invoke(&FlipFlop::member, obj)); + EXPECT_EQ(42, base_internal::invoke(&FlipFlop::ConstMethod, obj)); + EXPECT_EQ(42, base_internal::invoke(&FlipFlop::member, obj)); } TEST(InvokeTest, SfinaeFriendly) { diff --git a/absl/functional/function_ref.h b/absl/functional/function_ref.h index 370acc55..6e03ac2e 100644 --- a/absl/functional/function_ref.h +++ b/absl/functional/function_ref.h @@ -90,7 +90,7 @@ class FunctionRef { // Used to disable constructors for objects that are not compatible with the // signature of this FunctionRef. template > + typename FR = absl::base_internal::invoke_result_t> using EnableIfCompatible = typename std::enable_if::value || std::is_convertible::value>::type; diff --git a/absl/functional/internal/front_binder.h b/absl/functional/internal/front_binder.h index a4d95da4..45f52de7 100644 --- a/absl/functional/internal/front_binder.h +++ b/absl/functional/internal/front_binder.h @@ -33,7 +33,7 @@ namespace functional_internal { // Invoke the method, expanding the tuple of bound arguments. template R Apply(Tuple&& bound, absl::index_sequence, Args&&... free) { - return base_internal::Invoke( + return base_internal::invoke( absl::forward(bound).template get()..., absl::forward(free)...); } @@ -50,22 +50,22 @@ class FrontBinder { constexpr explicit FrontBinder(absl::in_place_t, Ts&&... ts) : bound_args_(absl::forward(ts)...) {} - template > + template > R operator()(FreeArgs&&... free_args) & { return functional_internal::Apply(bound_args_, Idx(), absl::forward(free_args)...); } template > + class R = base_internal::invoke_result_t< + const F&, const BoundArgs&..., FreeArgs&&...>> R operator()(FreeArgs&&... free_args) const& { return functional_internal::Apply(bound_args_, Idx(), absl::forward(free_args)...); } - template > R operator()(FreeArgs&&... free_args) && { // This overload is called when *this is an rvalue. If some of the bound @@ -75,8 +75,8 @@ class FrontBinder { } template > + class R = base_internal::invoke_result_t< + const F&&, const BoundArgs&&..., FreeArgs&&...>> R operator()(FreeArgs&&... free_args) const&& { // This overload is called when *this is an rvalue. If some of the bound // arguments are stored by value or rvalue reference, we move them. diff --git a/absl/functional/internal/function_ref.h b/absl/functional/internal/function_ref.h index d1575054..b5bb8b43 100644 --- a/absl/functional/internal/function_ref.h +++ b/absl/functional/internal/function_ref.h @@ -71,14 +71,14 @@ template R InvokeObject(VoidPtr ptr, typename ForwardT::type... args) { auto o = static_cast(ptr.obj); return static_cast( - absl::base_internal::Invoke(*o, std::forward(args)...)); + absl::base_internal::invoke(*o, std::forward(args)...)); } template R InvokeFunction(VoidPtr ptr, typename ForwardT::type... args) { auto f = reinterpret_cast(ptr.fun); return static_cast( - absl::base_internal::Invoke(f, std::forward(args)...)); + absl::base_internal::invoke(f, std::forward(args)...)); } template diff --git a/absl/meta/type_traits.h b/absl/meta/type_traits.h index ba87d2f0..75689bb6 100644 --- a/absl/meta/type_traits.h +++ b/absl/meta/type_traits.h @@ -219,7 +219,7 @@ using void_t = typename type_traits_internal::VoidTImpl::type; // This metafunction is designed to be a drop-in replacement for the C++17 // `std::conjunction` metafunction. template -struct conjunction; +struct conjunction : std::true_type {}; template struct conjunction @@ -228,9 +228,6 @@ struct conjunction template struct conjunction : T {}; -template <> -struct conjunction<> : std::true_type {}; - // disjunction // // Performs a compile-time logical OR operation on the passed types (which @@ -241,7 +238,7 @@ struct conjunction<> : std::true_type {}; // This metafunction is designed to be a drop-in replacement for the C++17 // `std::disjunction` metafunction. template -struct disjunction; +struct disjunction : std::false_type {}; template struct disjunction : @@ -250,9 +247,6 @@ struct disjunction : template struct disjunction : T {}; -template <> -struct disjunction<> : std::false_type {}; - // negation // // Performs a compile-time logical NOT operation on the passed type (which diff --git a/absl/strings/cord.h b/absl/strings/cord.h index dc987454..8580d803 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -857,16 +857,16 @@ ExternalRepReleaserPair NewExternalWithUninitializedReleaser( struct Rank1 {}; struct Rank0 : Rank1 {}; -template > void InvokeReleaser(Rank0, Releaser&& releaser, absl::string_view data) { - ::absl::base_internal::Invoke(std::forward(releaser), data); + ::absl::base_internal::invoke(std::forward(releaser), data); } template > + typename = ::absl::base_internal::invoke_result_t> void InvokeReleaser(Rank1, Releaser&& releaser, absl::string_view) { - ::absl::base_internal::Invoke(std::forward(releaser)); + ::absl::base_internal::invoke(std::forward(releaser)); } // Creates a new `CordRep` that owns `data` and `releaser` and returns a pointer diff --git a/absl/synchronization/mutex.cc b/absl/synchronization/mutex.cc index 1f8a696e..62fa8e9c 100644 --- a/absl/synchronization/mutex.cc +++ b/absl/synchronization/mutex.cc @@ -39,6 +39,7 @@ #include // NOLINT(build/c++11) #include "absl/base/attributes.h" +#include "absl/base/call_once.h" #include "absl/base/config.h" #include "absl/base/dynamic_annotations.h" #include "absl/base/internal/atomic_hook.h" @@ -85,28 +86,6 @@ ABSL_CONST_INIT std::atomic synch_deadlock_detection( kDeadlockDetectionDefault); ABSL_CONST_INIT std::atomic synch_check_invariants(false); -// ------------------------------------------ spinlock support - -// Make sure read-only globals used in the Mutex code are contained on the -// same cacheline and cacheline aligned to eliminate any false sharing with -// other globals from this and other modules. -static struct MutexGlobals { - MutexGlobals() { - // Find machine-specific data needed for Delay() and - // TryAcquireWithSpinning(). This runs in the global constructor - // sequence, and before that zeros are safe values. - num_cpus = absl::base_internal::NumCPUs(); - spinloop_iterations = num_cpus > 1 ? 1500 : 0; - } - int num_cpus; - int spinloop_iterations; - // Pad this struct to a full cacheline to prevent false sharing. - char padding[ABSL_CACHELINE_SIZE - 2 * sizeof(int)]; -} ABSL_CACHELINE_ALIGNED mutex_globals; -static_assert( - sizeof(MutexGlobals) == ABSL_CACHELINE_SIZE, - "MutexGlobals must occupy an entire cacheline to prevent false sharing"); - ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES absl::base_internal::AtomicHook submit_profile_data; @@ -143,7 +122,22 @@ void RegisterSymbolizer(bool (*fn)(const void *pc, char *out, int out_size)) { symbolizer.Store(fn); } -// spinlock delay on iteration c. Returns new c. +struct ABSL_CACHELINE_ALIGNED MutexGlobals { + absl::once_flag once; + int num_cpus = 0; + int spinloop_iterations = 0; +}; + +static const MutexGlobals& GetMutexGlobals() { + ABSL_CONST_INIT static MutexGlobals data; + absl::base_internal::LowLevelCallOnce(&data.once, [&]() { + data.num_cpus = absl::base_internal::NumCPUs(); + data.spinloop_iterations = data.num_cpus > 1 ? 1500 : 0; + }); + return data; +} + +// Spinlock delay on iteration c. Returns new c. namespace { enum DelayMode { AGGRESSIVE, GENTLE }; }; @@ -153,22 +147,25 @@ static int Delay(int32_t c, DelayMode mode) { // gentle then spin only a few times before yielding. Aggressive spinning is // used to ensure that an Unlock() call, which must get the spin lock for // any thread to make progress gets it without undue delay. - int32_t limit = (mutex_globals.num_cpus > 1) ? - ((mode == AGGRESSIVE) ? 5000 : 250) : 0; + const int32_t limit = + GetMutexGlobals().num_cpus > 1 ? (mode == AGGRESSIVE ? 5000 : 250) : 0; if (c < limit) { - c++; // spin + // Spin. + c++; } else { ABSL_TSAN_MUTEX_PRE_DIVERT(nullptr, 0); - if (c == limit) { // yield once + if (c == limit) { + // Yield once. AbslInternalMutexYield(); c++; - } else { // then wait + } else { + // Then wait. absl::SleepFor(absl::Microseconds(10)); c = 0; } ABSL_TSAN_MUTEX_POST_DIVERT(nullptr, 0); } - return (c); + return c; } // --------------------------Generic atomic ops @@ -1437,7 +1434,7 @@ void Mutex::AssertNotHeld() const { // Attempt to acquire *mu, and return whether successful. The implementation // may spin for a short while if the lock cannot be acquired immediately. static bool TryAcquireWithSpinning(std::atomic* mu) { - int c = mutex_globals.spinloop_iterations; + int c = GetMutexGlobals().spinloop_iterations; do { // do/while somewhat faster on AMD intptr_t v = mu->load(std::memory_order_relaxed); if ((v & (kMuReader|kMuEvent)) != 0) { diff --git a/absl/time/duration.cc b/absl/time/duration.cc index d0f1aadb..952cc093 100644 --- a/absl/time/duration.cc +++ b/absl/time/duration.cc @@ -69,6 +69,7 @@ #include "absl/base/casts.h" #include "absl/base/macros.h" #include "absl/numeric/int128.h" +#include "absl/strings/string_view.h" #include "absl/strings/strip.h" #include "absl/time/time.h" @@ -710,16 +711,17 @@ char* Format64(char* ep, int width, int64_t v) { // fractional digits, because it is in the noise of what a Duration can // represent. struct DisplayUnit { - const char* abbr; + absl::string_view abbr; int prec; double pow10; }; -const DisplayUnit kDisplayNano = {"ns", 2, 1e2}; -const DisplayUnit kDisplayMicro = {"us", 5, 1e5}; -const DisplayUnit kDisplayMilli = {"ms", 8, 1e8}; -const DisplayUnit kDisplaySec = {"s", 11, 1e11}; -const DisplayUnit kDisplayMin = {"m", -1, 0.0}; // prec ignored -const DisplayUnit kDisplayHour = {"h", -1, 0.0}; // prec ignored +ABSL_CONST_INIT const DisplayUnit kDisplayNano = {"ns", 2, 1e2}; +ABSL_CONST_INIT const DisplayUnit kDisplayMicro = {"us", 5, 1e5}; +ABSL_CONST_INIT const DisplayUnit kDisplayMilli = {"ms", 8, 1e8}; +ABSL_CONST_INIT const DisplayUnit kDisplaySec = {"s", 11, 1e11}; +ABSL_CONST_INIT const DisplayUnit kDisplayMin = {"m", -1, 0.0}; // prec ignored +ABSL_CONST_INIT const DisplayUnit kDisplayHour = {"h", -1, + 0.0}; // prec ignored void AppendNumberUnit(std::string* out, int64_t n, DisplayUnit unit) { char buf[sizeof("2562047788015216")]; // hours in max duration @@ -727,7 +729,7 @@ void AppendNumberUnit(std::string* out, int64_t n, DisplayUnit unit) { char* bp = Format64(ep, 0, n); if (*bp != '0' || bp + 1 != ep) { out->append(bp, ep - bp); - out->append(unit.abbr); + out->append(unit.abbr.data(), unit.abbr.size()); } } @@ -750,7 +752,7 @@ void AppendNumberUnit(std::string* out, double n, DisplayUnit unit) { while (ep[-1] == '0') --ep; out->append(bp, ep - bp); } - out->append(unit.abbr); + out->append(unit.abbr.data(), unit.abbr.size()); } } diff --git a/absl/types/internal/variant.h b/absl/types/internal/variant.h index 71bd3adf..d404e80c 100644 --- a/absl/types/internal/variant.h +++ b/absl/types/internal/variant.h @@ -292,7 +292,7 @@ struct UnreachableSwitchCase { template struct ReachableSwitchCase { static VisitIndicesResultT Run(Op&& op) { - return absl::base_internal::Invoke(absl::forward(op), SizeT()); + return absl::base_internal::invoke(absl::forward(op), SizeT()); } }; @@ -424,7 +424,7 @@ struct VisitIndicesSwitch { return PickCase::Run(absl::forward(op)); default: ABSL_ASSERT(i == variant_npos); - return absl::base_internal::Invoke(absl::forward(op), NPos()); + return absl::base_internal::invoke(absl::forward(op), NPos()); } } }; @@ -488,7 +488,7 @@ struct VisitIndicesVariadicImpl, EndIndices...> { template VisitIndicesResultT operator()( SizeT /*index*/) && { - return base_internal::Invoke( + return base_internal::invoke( absl::forward(op), SizeT::value - std::size_t{1}>()...); @@ -930,7 +930,7 @@ struct PerformVisitation { absl::result_of_t...)>>::value, "All visitation overloads must have the same return type."); - return absl::base_internal::Invoke( + return absl::base_internal::invoke( absl::forward(op), VariantCoreAccess::Access( absl::forward(std::get(variant_tup)))...); diff --git a/absl/utility/utility.h b/absl/utility/utility.h index e6647c7b..bf923220 100644 --- a/absl/utility/utility.h +++ b/absl/utility/utility.h @@ -236,10 +236,10 @@ namespace utility_internal { // Helper method for expanding tuple into a called method. template auto apply_helper(Functor&& functor, Tuple&& t, index_sequence) - -> decltype(absl::base_internal::Invoke( + -> decltype(absl::base_internal::invoke( absl::forward(functor), std::get(absl::forward(t))...)) { - return absl::base_internal::Invoke( + return absl::base_internal::invoke( absl::forward(functor), std::get(absl::forward(t))...); } -- cgit v1.2.3 From 184cf2524101310a0ba315c743e82cf45fccccf8 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Thu, 30 Jul 2020 13:58:50 -0700 Subject: Export of internal Abseil changes -- 587e6db882749fa7faa12815e614afab04d218b9 by Derek Mauro : Use attribute detection for other sanitizer related attributes PiperOrigin-RevId: 324077073 -- 3ee55e4935b4235516b1fcac3c55945e510f7afc by Evan Brown : Simplify CordRepExternal allocation/deallocation. I think this can save some memory when `Releaser` is empty and when on platforms where alignof(CordRepExternal) < (default `::operator new` alignment). We no longer need the API requirement that alignof(Releaser) <= (default `::operator new` alignment). Also remove another static_assert from a TODO in cord_internal.h and fix some warnings about calling std::move on a forwarding reference. PiperOrigin-RevId: 324053720 -- 9fc78436565eb3b204d4aa425ee3773354392f45 by Derek Mauro : Use auto-detected sanitizer attributes for ASAN, MSAN, and TSAN builds PiperOrigin-RevId: 323831461 GitOrigin-RevId: 587e6db882749fa7faa12815e614afab04d218b9 Change-Id: Ie0e4a2846d7f66988a2d81a5e50721b62fdb3d6d --- absl/base/BUILD.bazel | 2 + absl/base/CMakeLists.txt | 2 + absl/base/attributes.h | 54 +++++----------- absl/base/internal/dynamic_annotations.h | 11 ++-- absl/base/internal/tsan_mutex_interface.h | 4 +- absl/base/internal/unaligned_access.h | 4 +- absl/base/spinlock_test_common.cc | 3 +- absl/container/BUILD.bazel | 5 ++ absl/container/CMakeLists.txt | 5 ++ absl/container/fixed_array.h | 13 ++-- absl/container/fixed_array_test.cc | 5 +- absl/container/internal/container_memory.h | 25 ++++---- absl/container/internal/layout.h | 12 ++-- absl/container/internal/layout_test.cc | 4 +- absl/container/internal/raw_hash_set_test.cc | 5 +- absl/debugging/BUILD.bazel | 2 + absl/debugging/CMakeLists.txt | 2 + absl/debugging/failure_signal_handler.cc | 4 +- absl/debugging/internal/demangle_test.cc | 8 ++- absl/debugging/symbolize_test.cc | 5 +- absl/strings/BUILD.bazel | 3 +- absl/strings/CMakeLists.txt | 1 + absl/strings/cord.cc | 74 ++++------------------ absl/strings/cord.h | 57 ++--------------- absl/strings/internal/cord_internal.h | 65 +++++++++++++------ absl/strings/string_view_test.cc | 4 +- absl/synchronization/BUILD.bazel | 3 + absl/synchronization/CMakeLists.txt | 2 + absl/synchronization/internal/mutex_nonprod.cc | 3 +- .../internal/per_thread_sem_test.cc | 1 + absl/synchronization/internal/waiter.h | 2 +- absl/synchronization/mutex.cc | 8 +-- absl/synchronization/mutex_benchmark.cc | 3 +- absl/synchronization/mutex_test.cc | 7 +- absl/time/BUILD.bazel | 1 + absl/time/CMakeLists.txt | 1 + absl/time/internal/test_util.cc | 1 + ci/linux_clang-latest_libcxx_asan_bazel.sh | 2 - ci/linux_clang-latest_libcxx_tsan_bazel.sh | 2 - 39 files changed, 180 insertions(+), 235 deletions(-) (limited to 'absl/strings/cord.h') diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel index ad29bb0d..2c4887b3 100644 --- a/absl/base/BUILD.bazel +++ b/absl/base/BUILD.bazel @@ -415,6 +415,7 @@ cc_library( deps = [ ":base", ":base_internal", + ":config", ":core_headers", "//absl/synchronization", "@com_google_googletest//:gtest", @@ -431,6 +432,7 @@ cc_test( deps = [ ":base", ":base_internal", + ":config", ":core_headers", "//absl/synchronization", "@com_google_googletest//:gtest_main", diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt index f5cfc792..9ff5aa24 100644 --- a/absl/base/CMakeLists.txt +++ b/absl/base/CMakeLists.txt @@ -384,6 +384,7 @@ absl_cc_library( ${ABSL_TEST_COPTS} DEPS absl::base + absl::config absl::base_internal absl::core_headers absl::synchronization @@ -402,6 +403,7 @@ absl_cc_test( DEPS absl::base absl::base_internal + absl::config absl::core_headers absl::synchronization gtest_main diff --git a/absl/base/attributes.h b/absl/base/attributes.h index 2cf4b1e8..046fbea3 100644 --- a/absl/base/attributes.h +++ b/absl/base/attributes.h @@ -32,34 +32,12 @@ // of them are not supported in older version of Clang. Thus, we check // `__has_attribute()` first. If the check fails, we check if we are on GCC and // assume the attribute exists on GCC (which is verified on GCC 4.7). -// -// ----------------------------------------------------------------------------- -// Sanitizer Attributes -// ----------------------------------------------------------------------------- -// -// Sanitizer-related attributes are not "defined" in this file (and indeed -// are not defined as such in any file). To utilize the following -// sanitizer-related attributes within your builds, define the following macros -// within your build using a `-D` flag, along with the given value for -// `-fsanitize`: -// -// * `ADDRESS_SANITIZER` + `-fsanitize=address` (Clang, GCC 4.8) -// * `MEMORY_SANITIZER` + `-fsanitize=memory` (Clang-only) -// * `THREAD_SANITIZER` + `-fsanitize=thread` (Clang, GCC 4.8+) -// * `UNDEFINED_BEHAVIOR_SANITIZER` + `-fsanitize=undefined` (Clang, GCC 4.9+) -// * `CONTROL_FLOW_INTEGRITY` + `-fsanitize=cfi` (Clang-only) -// -// Example: -// -// // Enable branches in the Abseil code that are tagged for ASan: -// $ bazel build --copt=-DADDRESS_SANITIZER --copt=-fsanitize=address -// --linkopt=-fsanitize=address *target* -// -// Since these macro names are only supported by GCC and Clang, we only check -// for `__GNUC__` (GCC or Clang) and the above macros. + #ifndef ABSL_BASE_ATTRIBUTES_H_ #define ABSL_BASE_ATTRIBUTES_H_ +#include "absl/base/config.h" + // ABSL_HAVE_ATTRIBUTE // // A function-like feature checking macro that is a wrapper around @@ -234,7 +212,7 @@ // out of bounds or does other scary things with memory. // NOTE: GCC supports AddressSanitizer(asan) since 4.8. // https://gcc.gnu.org/gcc-4.8/changes.html -#if defined(__GNUC__) +#if ABSL_HAVE_ATTRIBUTE(no_sanitize_address) #define ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address)) #else #define ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS @@ -242,13 +220,13 @@ // ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY // -// Tells the MemorySanitizer to relax the handling of a given function. All -// "Use of uninitialized value" warnings from such functions will be suppressed, -// and all values loaded from memory will be considered fully initialized. -// This attribute is similar to the ADDRESS_SANITIZER attribute above, but deals -// with initialized-ness rather than addressability issues. +// Tells the MemorySanitizer to relax the handling of a given function. All "Use +// of uninitialized value" warnings from such functions will be suppressed, and +// all values loaded from memory will be considered fully initialized. This +// attribute is similar to the ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS attribute +// above, but deals with initialized-ness rather than addressability issues. // NOTE: MemorySanitizer(msan) is supported by Clang but not GCC. -#if defined(__clang__) +#if ABSL_HAVE_ATTRIBUTE(no_sanitize_memory) #define ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY __attribute__((no_sanitize_memory)) #else #define ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY @@ -259,7 +237,7 @@ // Tells the ThreadSanitizer to not instrument a given function. // NOTE: GCC supports ThreadSanitizer(tsan) since 4.8. // https://gcc.gnu.org/gcc-4.8/changes.html -#if defined(__GNUC__) +#if ABSL_HAVE_ATTRIBUTE(no_sanitize_thread) #define ABSL_ATTRIBUTE_NO_SANITIZE_THREAD __attribute__((no_sanitize_thread)) #else #define ABSL_ATTRIBUTE_NO_SANITIZE_THREAD @@ -271,8 +249,10 @@ // where certain behavior (eg. division by zero) is being used intentionally. // NOTE: GCC supports UndefinedBehaviorSanitizer(ubsan) since 4.9. // https://gcc.gnu.org/gcc-4.9/changes.html -#if defined(__GNUC__) && \ - (defined(UNDEFINED_BEHAVIOR_SANITIZER) || defined(ADDRESS_SANITIZER)) +#if ABSL_HAVE_ATTRIBUTE(no_sanitize_undefined) +#define ABSL_ATTRIBUTE_NO_SANITIZE_UNDEFINED \ + __attribute__((no_sanitize_undefined)) +#elif ABSL_HAVE_ATTRIBUTE(no_sanitize) #define ABSL_ATTRIBUTE_NO_SANITIZE_UNDEFINED \ __attribute__((no_sanitize("undefined"))) #else @@ -283,7 +263,7 @@ // // Tells the ControlFlowIntegrity sanitizer to not instrument a given function. // See https://clang.llvm.org/docs/ControlFlowIntegrity.html for details. -#if defined(__GNUC__) && defined(CONTROL_FLOW_INTEGRITY) +#if ABSL_HAVE_ATTRIBUTE(no_sanitize) #define ABSL_ATTRIBUTE_NO_SANITIZE_CFI __attribute__((no_sanitize("cfi"))) #else #define ABSL_ATTRIBUTE_NO_SANITIZE_CFI @@ -293,7 +273,7 @@ // // Tells the SafeStack to not instrument a given function. // See https://clang.llvm.org/docs/SafeStack.html for details. -#if defined(__GNUC__) && defined(SAFESTACK_SANITIZER) +#if ABSL_HAVE_ATTRIBUTE(no_sanitize) #define ABSL_ATTRIBUTE_NO_SANITIZE_SAFESTACK \ __attribute__((no_sanitize("safe-stack"))) #else diff --git a/absl/base/internal/dynamic_annotations.h b/absl/base/internal/dynamic_annotations.h index 7d80f41c..b69c1301 100644 --- a/absl/base/internal/dynamic_annotations.h +++ b/absl/base/internal/dynamic_annotations.h @@ -91,12 +91,9 @@ #endif // Memory annotations are also made available to LLVM's Memory Sanitizer -#if defined(MEMORY_SANITIZER) && defined(__has_feature) && \ - !defined(__native_client__) -#if __has_feature(memory_sanitizer) +#if defined(ABSL_HAVE_MEMORY_SANITIZER) && !defined(__native_client__) #define ABSL_INTERNAL_MEMORY_ANNOTATIONS_ENABLED 1 #endif -#endif #ifndef ABSL_INTERNAL_MEMORY_ANNOTATIONS_ENABLED #define ABSL_INTERNAL_MEMORY_ANNOTATIONS_ENABLED 0 @@ -162,7 +159,7 @@ ABSL_INTERNAL_GLOBAL_SCOPED(AnnotateRWLockCreate)(__FILE__, __LINE__, lock) // Report that a linker initialized lock has been created at address `lock`. -#ifdef THREAD_SANITIZER +#ifdef ABSL_HAVE_THREAD_SANITIZER #define ANNOTATE_RWLOCK_CREATE_STATIC(lock) \ ABSL_INTERNAL_GLOBAL_SCOPED(AnnotateRWLockCreateStatic) \ (__FILE__, __LINE__, lock) @@ -367,7 +364,7 @@ // ------------------------------------------------------------------------- // Address sanitizer annotations -#ifdef ADDRESS_SANITIZER +#ifdef ABSL_HAVE_ADDRESS_SANITIZER // Describe the current state of a contiguous container such as e.g. // std::vector or std::string. For more details see // sanitizer/common_interface_defs.h, which is provided by the compiler. @@ -385,7 +382,7 @@ #define ANNOTATE_CONTIGUOUS_CONTAINER(beg, end, old_mid, new_mid) #define ADDRESS_SANITIZER_REDZONE(name) static_assert(true, "") -#endif // ADDRESS_SANITIZER +#endif // ABSL_HAVE_ADDRESS_SANITIZER // ------------------------------------------------------------------------- // Undefine the macros intended only for this file. diff --git a/absl/base/internal/tsan_mutex_interface.h b/absl/base/internal/tsan_mutex_interface.h index 2a510603..39207d8a 100644 --- a/absl/base/internal/tsan_mutex_interface.h +++ b/absl/base/internal/tsan_mutex_interface.h @@ -19,6 +19,8 @@ #ifndef ABSL_BASE_INTERNAL_TSAN_MUTEX_INTERFACE_H_ #define ABSL_BASE_INTERNAL_TSAN_MUTEX_INTERFACE_H_ +#include "absl/base/config.h" + // ABSL_INTERNAL_HAVE_TSAN_INTERFACE // Macro intended only for internal use. // @@ -28,7 +30,7 @@ #error "ABSL_INTERNAL_HAVE_TSAN_INTERFACE cannot be directly set." #endif -#if defined(THREAD_SANITIZER) && defined(__has_include) +#if defined(ABSL_HAVE_THREAD_SANITIZER) && defined(__has_include) #if __has_include() #define ABSL_INTERNAL_HAVE_TSAN_INTERFACE 1 #endif diff --git a/absl/base/internal/unaligned_access.h b/absl/base/internal/unaligned_access.h index 6be56c86..dd5250de 100644 --- a/absl/base/internal/unaligned_access.h +++ b/absl/base/internal/unaligned_access.h @@ -32,8 +32,8 @@ // (namespaces, inline) which are absent or incompatible in C. #if defined(__cplusplus) -#if defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) ||\ - defined(MEMORY_SANITIZER) +#if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ + defined(ABSL_HAVE_THREAD_SANITIZER) || defined(ABSL_HAVE_MEMORY_SANITIZER) // Consider we have an unaligned load/store of 4 bytes from address 0x...05. // AddressSanitizer will treat it as a 3-byte access to the range 05:07 and // will miss a bug if 08 is the first unaddressable byte. diff --git a/absl/base/spinlock_test_common.cc b/absl/base/spinlock_test_common.cc index b33c54ba..dee266e4 100644 --- a/absl/base/spinlock_test_common.cc +++ b/absl/base/spinlock_test_common.cc @@ -25,6 +25,7 @@ #include "gtest/gtest.h" #include "absl/base/attributes.h" +#include "absl/base/config.h" #include "absl/base/internal/low_level_scheduling.h" #include "absl/base/internal/scheduling_mode.h" #include "absl/base/internal/spinlock.h" @@ -104,7 +105,7 @@ static void ThreadedTest(SpinLock* spinlock) { } } -#ifndef THREAD_SANITIZER +#ifndef ABSL_HAVE_THREAD_SANITIZER static_assert(std::is_trivially_destructible(), ""); #endif diff --git a/absl/container/BUILD.bazel b/absl/container/BUILD.bazel index 06921235..cca5d441 100644 --- a/absl/container/BUILD.bazel +++ b/absl/container/BUILD.bazel @@ -60,6 +60,7 @@ cc_library( deps = [ ":compressed_tuple", "//absl/algorithm", + "//absl/base:config", "//absl/base:core_headers", "//absl/base:dynamic_annotations", "//absl/base:throw_delegate", @@ -368,6 +369,7 @@ cc_library( copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ + "//absl/base:config", "//absl/memory", "//absl/meta:type_traits", "//absl/utility", @@ -620,6 +622,7 @@ cc_test( ":hashtable_debug", ":raw_hash_set", "//absl/base", + "//absl/base:config", "//absl/base:core_headers", "//absl/base:raw_logging_internal", "//absl/strings", @@ -647,6 +650,7 @@ cc_library( copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ + "//absl/base:config", "//absl/base:core_headers", "//absl/meta:type_traits", "//absl/strings", @@ -665,6 +669,7 @@ cc_test( visibility = ["//visibility:private"], deps = [ ":layout", + "//absl/base:config", "//absl/base:core_headers", "//absl/base:raw_logging_internal", "//absl/types:span", diff --git a/absl/container/CMakeLists.txt b/absl/container/CMakeLists.txt index 13d96957..eb202c45 100644 --- a/absl/container/CMakeLists.txt +++ b/absl/container/CMakeLists.txt @@ -131,6 +131,7 @@ absl_cc_library( DEPS absl::compressed_tuple absl::algorithm + absl::config absl::core_headers absl::dynamic_annotations absl::throw_delegate @@ -423,6 +424,7 @@ absl_cc_library( COPTS ${ABSL_DEFAULT_COPTS} DEPS + absl::config absl::memory absl::type_traits absl::utility @@ -696,6 +698,7 @@ absl_cc_test( absl::hashtable_debug absl::raw_hash_set absl::base + absl::config absl::core_headers absl::raw_logging_internal absl::strings @@ -724,6 +727,7 @@ absl_cc_library( COPTS ${ABSL_DEFAULT_COPTS} DEPS + absl::config absl::core_headers absl::meta absl::strings @@ -741,6 +745,7 @@ absl_cc_test( ${ABSL_TEST_COPTS} DEPS absl::layout + absl::config absl::core_headers absl::raw_logging_internal absl::span diff --git a/absl/container/fixed_array.h b/absl/container/fixed_array.h index e74802a4..c8fe8d96 100644 --- a/absl/container/fixed_array.h +++ b/absl/container/fixed_array.h @@ -41,6 +41,7 @@ #include #include "absl/algorithm/algorithm.h" +#include "absl/base/config.h" #include "absl/base/dynamic_annotations.h" #include "absl/base/internal/throw_delegate.h" #include "absl/base/macros.h" @@ -422,10 +423,10 @@ class FixedArray { void AnnotateConstruct(size_type n); void AnnotateDestruct(size_type n); -#ifdef ADDRESS_SANITIZER +#ifdef ABSL_HAVE_ADDRESS_SANITIZER void* RedzoneBegin() { return &redzone_begin_; } void* RedzoneEnd() { return &redzone_end_ + 1; } -#endif // ADDRESS_SANITIZER +#endif // ABSL_HAVE_ADDRESS_SANITIZER private: ABSL_ADDRESS_SANITIZER_REDZONE(redzone_begin_); @@ -503,26 +504,26 @@ constexpr typename FixedArray::size_type template void FixedArray::NonEmptyInlinedStorage::AnnotateConstruct( typename FixedArray::size_type n) { -#ifdef ADDRESS_SANITIZER +#ifdef ABSL_HAVE_ADDRESS_SANITIZER if (!n) return; ABSL_ANNOTATE_CONTIGUOUS_CONTAINER(data(), RedzoneEnd(), RedzoneEnd(), data() + n); ABSL_ANNOTATE_CONTIGUOUS_CONTAINER(RedzoneBegin(), data(), data(), RedzoneBegin()); -#endif // ADDRESS_SANITIZER +#endif // ABSL_HAVE_ADDRESS_SANITIZER static_cast(n); // Mark used when not in asan mode } template void FixedArray::NonEmptyInlinedStorage::AnnotateDestruct( typename FixedArray::size_type n) { -#ifdef ADDRESS_SANITIZER +#ifdef ABSL_HAVE_ADDRESS_SANITIZER if (!n) return; ABSL_ANNOTATE_CONTIGUOUS_CONTAINER(data(), RedzoneEnd(), data() + n, RedzoneEnd()); ABSL_ANNOTATE_CONTIGUOUS_CONTAINER(RedzoneBegin(), data(), RedzoneBegin(), data()); -#endif // ADDRESS_SANITIZER +#endif // ABSL_HAVE_ADDRESS_SANITIZER static_cast(n); // Mark used when not in asan mode } ABSL_NAMESPACE_END diff --git a/absl/container/fixed_array_test.cc b/absl/container/fixed_array_test.cc index 064a88a2..49598e7a 100644 --- a/absl/container/fixed_array_test.cc +++ b/absl/container/fixed_array_test.cc @@ -27,6 +27,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/config.h" #include "absl/base/internal/exception_testing.h" #include "absl/base/options.h" #include "absl/container/internal/counting_allocator.h" @@ -767,7 +768,7 @@ TEST(AllocatorSupportTest, SizeValAllocConstructor) { } } -#ifdef ADDRESS_SANITIZER +#ifdef ABSL_HAVE_ADDRESS_SANITIZER TEST(FixedArrayTest, AddressSanitizerAnnotations1) { absl::FixedArray a(10); int* raw = a.data(); @@ -814,7 +815,7 @@ TEST(FixedArrayTest, AddressSanitizerAnnotations4) { // so reading raw[21] should still trigger the correct warning. EXPECT_DEATH_IF_SUPPORTED(raw[21] = ThreeInts(), "container-overflow"); } -#endif // ADDRESS_SANITIZER +#endif // ABSL_HAVE_ADDRESS_SANITIZER TEST(FixedArrayTest, AbslHashValueWorks) { using V = absl::FixedArray; diff --git a/absl/container/internal/container_memory.h b/absl/container/internal/container_memory.h index 92a61cc0..c7777a93 100644 --- a/absl/container/internal/container_memory.h +++ b/absl/container/internal/container_memory.h @@ -15,14 +15,6 @@ #ifndef ABSL_CONTAINER_INTERNAL_CONTAINER_MEMORY_H_ #define ABSL_CONTAINER_INTERNAL_CONTAINER_MEMORY_H_ -#ifdef ADDRESS_SANITIZER -#include -#endif - -#ifdef MEMORY_SANITIZER -#include -#endif - #include #include #include @@ -30,10 +22,19 @@ #include #include +#include "absl/base/config.h" #include "absl/memory/memory.h" #include "absl/meta/type_traits.h" #include "absl/utility/utility.h" +#ifdef ABSL_HAVE_ADDRESS_SANITIZER +#include +#endif + +#ifdef ABSL_HAVE_MEMORY_SANITIZER +#include +#endif + namespace absl { ABSL_NAMESPACE_BEGIN namespace container_internal { @@ -209,10 +210,10 @@ DecomposeValue(F&& f, Arg&& arg) { // Helper functions for asan and msan. inline void SanitizerPoisonMemoryRegion(const void* m, size_t s) { -#ifdef ADDRESS_SANITIZER +#ifdef ABSL_HAVE_ADDRESS_SANITIZER ASAN_POISON_MEMORY_REGION(m, s); #endif -#ifdef MEMORY_SANITIZER +#ifdef ABSL_HAVE_MEMORY_SANITIZER __msan_poison(m, s); #endif (void)m; @@ -220,10 +221,10 @@ inline void SanitizerPoisonMemoryRegion(const void* m, size_t s) { } inline void SanitizerUnpoisonMemoryRegion(const void* m, size_t s) { -#ifdef ADDRESS_SANITIZER +#ifdef ABSL_HAVE_ADDRESS_SANITIZER ASAN_UNPOISON_MEMORY_REGION(m, s); #endif -#ifdef MEMORY_SANITIZER +#ifdef ABSL_HAVE_MEMORY_SANITIZER __msan_unpoison(m, s); #endif (void)m; diff --git a/absl/container/internal/layout.h b/absl/container/internal/layout.h index 69cc85dd..23367833 100644 --- a/absl/container/internal/layout.h +++ b/absl/container/internal/layout.h @@ -163,6 +163,7 @@ #include #include #include + #include #include #include @@ -170,15 +171,16 @@ #include #include -#ifdef ADDRESS_SANITIZER -#include -#endif - +#include "absl/base/config.h" #include "absl/meta/type_traits.h" #include "absl/strings/str_cat.h" #include "absl/types/span.h" #include "absl/utility/utility.h" +#ifdef ABSL_HAVE_ADDRESS_SANITIZER +#include +#endif + #if defined(__GXX_RTTI) #define ABSL_INTERNAL_HAS_CXA_DEMANGLE #endif @@ -614,7 +616,7 @@ class LayoutImpl, absl::index_sequence, void PoisonPadding(const Char* p) const { static_assert(N < NumOffsets, "Index out of bounds"); (void)p; -#ifdef ADDRESS_SANITIZER +#ifdef ABSL_HAVE_ADDRESS_SANITIZER PoisonPadding(p); // The `if` is an optimization. It doesn't affect the observable behaviour. if (ElementAlignment::value % ElementAlignment::value) { diff --git a/absl/container/internal/layout_test.cc b/absl/container/internal/layout_test.cc index 8f3628a1..757272f1 100644 --- a/absl/container/internal/layout_test.cc +++ b/absl/container/internal/layout_test.cc @@ -17,6 +17,7 @@ // We need ::max_align_t because some libstdc++ versions don't provide // std::max_align_t #include + #include #include #include @@ -24,6 +25,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" #include "absl/types/span.h" @@ -1314,7 +1316,7 @@ struct Region { }; void ExpectRegionPoisoned(const unsigned char* p, size_t n, bool poisoned) { -#ifdef ADDRESS_SANITIZER +#ifdef ABSL_HAVE_ADDRESS_SANITIZER for (size_t i = 0; i != n; ++i) { EXPECT_EQ(poisoned, __asan_address_is_poisoned(p + i)); } diff --git a/absl/container/internal/raw_hash_set_test.cc b/absl/container/internal/raw_hash_set_test.cc index 6befa960..e3279301 100644 --- a/absl/container/internal/raw_hash_set_test.cc +++ b/absl/container/internal/raw_hash_set_test.cc @@ -26,6 +26,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/attributes.h" +#include "absl/base/config.h" #include "absl/base/internal/cycleclock.h" #include "absl/base/internal/raw_logging.h" #include "absl/container/internal/container_memory.h" @@ -1839,7 +1840,7 @@ TEST(RawHashSamplerTest, DoNotSampleCustomAllocators) { 0.00, 0.001); } -#ifdef ADDRESS_SANITIZER +#ifdef ABSL_HAVE_ADDRESS_SANITIZER TEST(Sanitizer, PoisoningUnused) { IntTable t; t.reserve(5); @@ -1863,7 +1864,7 @@ TEST(Sanitizer, PoisoningOnErase) { t.erase(0); EXPECT_TRUE(__asan_address_is_poisoned(&v)); } -#endif // ADDRESS_SANITIZER +#endif // ABSL_HAVE_ADDRESS_SANITIZER } // namespace } // namespace container_internal diff --git a/absl/debugging/BUILD.bazel b/absl/debugging/BUILD.bazel index bdc50e09..9098ca4a 100644 --- a/absl/debugging/BUILD.bazel +++ b/absl/debugging/BUILD.bazel @@ -97,6 +97,7 @@ cc_test( ":stack_consumption", ":symbolize", "//absl/base", + "//absl/base:config", "//absl/base:core_headers", "//absl/base:raw_logging_internal", "//absl/memory", @@ -204,6 +205,7 @@ cc_test( deps = [ ":demangle_internal", ":stack_consumption", + "//absl/base:config", "//absl/base:core_headers", "//absl/base:raw_logging_internal", "//absl/memory", diff --git a/absl/debugging/CMakeLists.txt b/absl/debugging/CMakeLists.txt index c597df86..074b44cf 100644 --- a/absl/debugging/CMakeLists.txt +++ b/absl/debugging/CMakeLists.txt @@ -82,6 +82,7 @@ absl_cc_test( absl::stack_consumption absl::symbolize absl::base + absl::config absl::core_headers absl::memory absl::raw_logging_internal @@ -189,6 +190,7 @@ absl_cc_test( DEPS absl::demangle_internal absl::stack_consumption + absl::config absl::core_headers absl::memory absl::raw_logging_internal diff --git a/absl/debugging/failure_signal_handler.cc b/absl/debugging/failure_signal_handler.cc index 1f69bfa8..5d13bdbb 100644 --- a/absl/debugging/failure_signal_handler.cc +++ b/absl/debugging/failure_signal_handler.cc @@ -136,8 +136,8 @@ static bool SetupAlternateStackOnce() { const size_t page_mask = sysconf(_SC_PAGESIZE) - 1; #endif size_t stack_size = (std::max(SIGSTKSZ, 65536) + page_mask) & ~page_mask; -#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \ - defined(THREAD_SANITIZER) +#if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ + defined(ABSL_HAVE_MEMORY_SANITIZER) || defined(ABSL_HAVE_THREAD_SANITIZER) // Account for sanitizer instrumentation requiring additional stack space. stack_size *= 5; #endif diff --git a/absl/debugging/internal/demangle_test.cc b/absl/debugging/internal/demangle_test.cc index c6f1ce18..0bed7359 100644 --- a/absl/debugging/internal/demangle_test.cc +++ b/absl/debugging/internal/demangle_test.cc @@ -18,6 +18,7 @@ #include #include "gtest/gtest.h" +#include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" #include "absl/debugging/internal/stack_consumption.h" #include "absl/memory/memory.h" @@ -82,9 +83,10 @@ TEST(Demangle, Clones) { // Tests that verify that Demangle footprint is within some limit. // They are not to be run under sanitizers as the sanitizers increase // stack consumption by about 4x. -#if defined(ABSL_INTERNAL_HAVE_DEBUGGING_STACK_CONSUMPTION) && \ - !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) && \ - !defined(THREAD_SANITIZER) +#if defined(ABSL_INTERNAL_HAVE_DEBUGGING_STACK_CONSUMPTION) && \ + !defined(ABSL_HAVE_ADDRESS_SANITIZER) && \ + !defined(ABSL_HAVE_MEMORY_SANITIZER) && \ + !defined(ABSL_HAVE_THREAD_SANITIZER) static const char *g_mangled; static char g_demangle_buffer[4096]; diff --git a/absl/debugging/symbolize_test.cc b/absl/debugging/symbolize_test.cc index 43f65549..a2dd4956 100644 --- a/absl/debugging/symbolize_test.cc +++ b/absl/debugging/symbolize_test.cc @@ -27,6 +27,7 @@ #include "gtest/gtest.h" #include "absl/base/attributes.h" #include "absl/base/casts.h" +#include "absl/base/config.h" #include "absl/base/internal/per_thread_tls.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/optimization.h" @@ -220,8 +221,8 @@ static const char *SymbolizeStackConsumption(void *pc, int *stack_consumed) { static int GetStackConsumptionUpperLimit() { // Symbolize stack consumption should be within 2kB. int stack_consumption_upper_limit = 2048; -#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \ - defined(THREAD_SANITIZER) +#if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ + defined(ABSL_HAVE_MEMORY_SANITIZER) || defined(ABSL_HAVE_THREAD_SANITIZER) // Account for sanitizer instrumentation requiring additional stack space. stack_consumption_upper_limit *= 5; #endif diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index ef412639..64a13cef 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -258,6 +258,8 @@ cc_library( visibility = ["//visibility:private"], deps = [ ":strings", + "//absl/base:base_internal", + "//absl/container:compressed_tuple", "//absl/meta:type_traits", ], ) @@ -277,7 +279,6 @@ cc_library( ":str_format", ":strings", "//absl/base", - "//absl/base:base_internal", "//absl/base:core_headers", "//absl/base:endian", "//absl/base:raw_logging_internal", diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index d7237231..d6c2126d 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt @@ -548,6 +548,7 @@ absl_cc_library( DEPS absl::base absl::base_internal + absl::compressed_tuple absl::core_headers absl::endian absl::fixed_array diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index 920dcc67..763dcc45 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc @@ -61,48 +61,6 @@ enum CordRepKind { FLAT = 3, }; -namespace { - -// Type used with std::allocator for allocating and deallocating -// `CordRepExternal`. std::allocator is used because it opaquely handles the -// different new / delete overloads available on a given platform. -struct alignas(absl::cord_internal::ExternalRepAlignment()) ExternalAllocType { - unsigned char value[absl::cord_internal::ExternalRepAlignment()]; -}; - -// Returns the number of objects to pass in to std::allocator -// allocate() and deallocate() to create enough room for `CordRepExternal` with -// `releaser_size` bytes on the end. -constexpr size_t GetExternalAllocNumObjects(size_t releaser_size) { - // Be sure to round up since `releaser_size` could be smaller than - // `sizeof(ExternalAllocType)`. - return (sizeof(CordRepExternal) + releaser_size + sizeof(ExternalAllocType) - - 1) / - sizeof(ExternalAllocType); -} - -// Allocates enough memory for `CordRepExternal` and a releaser with size -// `releaser_size` bytes. -void* AllocateExternal(size_t releaser_size) { - return std::allocator().allocate( - GetExternalAllocNumObjects(releaser_size)); -} - -// Deallocates the memory for a `CordRepExternal` assuming it was allocated with -// a releaser of given size and alignment. -void DeallocateExternal(CordRepExternal* p, size_t releaser_size) { - std::allocator().deallocate( - reinterpret_cast(p), - GetExternalAllocNumObjects(releaser_size)); -} - -// Returns a pointer to the type erased releaser for the given CordRepExternal. -void* GetExternalReleaser(CordRepExternal* rep) { - return rep + 1; -} - -} // namespace - namespace cord_internal { inline CordRepConcat* CordRep::concat() { @@ -304,11 +262,7 @@ static void UnrefInternal(CordRep* rep) { } } else if (rep->tag == EXTERNAL) { CordRepExternal* rep_external = rep->external(); - absl::string_view data(rep_external->base, rep->length); - void* releaser = GetExternalReleaser(rep_external); - size_t releaser_size = rep_external->releaser_invoker(releaser, data); - rep_external->~CordRepExternal(); - DeallocateExternal(rep_external, releaser_size); + rep_external->releaser_invoker(rep_external); rep = nullptr; } else if (rep->tag == SUBSTRING) { CordRepSubstring* rep_substring = rep->substring(); @@ -458,18 +412,12 @@ static CordRep* NewTree(const char* data, namespace cord_internal { -ExternalRepReleaserPair NewExternalWithUninitializedReleaser( - absl::string_view data, ExternalReleaserInvoker invoker, - size_t releaser_size) { +void InitializeCordRepExternal(absl::string_view data, CordRepExternal* rep) { assert(!data.empty()); - - void* raw_rep = AllocateExternal(releaser_size); - auto* rep = new (raw_rep) CordRepExternal(); rep->length = data.size(); rep->tag = EXTERNAL; rep->base = data.data(); - rep->releaser_invoker = invoker; - return {VerifyTree(rep), GetExternalReleaser(rep)}; + VerifyTree(rep); } } // namespace cord_internal @@ -721,12 +669,12 @@ Cord::Cord(T&& src) { std::string data; }; const absl::string_view original_data = src; - CordRepExternal* rep = - static_cast(absl::cord_internal::NewExternalRep( - original_data, StringReleaser{std::move(src)})); + auto* rep = static_cast< + ::absl::cord_internal::CordRepExternalImpl*>( + absl::cord_internal::NewExternalRep( + original_data, StringReleaser{std::forward(src)})); // Moving src may have invalidated its data pointer, so adjust it. - rep->base = - static_cast(GetExternalReleaser(rep))->data.data(); + rep->base = rep->template get<0>().data.data(); contents_.set_tree(rep); } } @@ -775,7 +723,7 @@ Cord& Cord::operator=(T&& src) { if (src.size() <= kMaxBytesToCopy) { *this = absl::string_view(src); } else { - *this = Cord(std::move(src)); + *this = Cord(std::forward(src)); } return *this; } @@ -898,7 +846,7 @@ void Cord::Append(T&& src) { if (src.size() <= kMaxBytesToCopy) { Append(absl::string_view(src)); } else { - Append(Cord(std::move(src))); + Append(Cord(std::forward(src))); } } @@ -938,7 +886,7 @@ inline void Cord::Prepend(T&& src) { if (src.size() <= kMaxBytesToCopy) { Prepend(absl::string_view(src)); } else { - Prepend(Cord(std::move(src))); + Prepend(Cord(std::forward(src))); } } diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 8580d803..a0db2797 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -71,7 +71,6 @@ #include #include "absl/base/internal/endian.h" -#include "absl/base/internal/invoke.h" #include "absl/base/internal/per_thread_tls.h" #include "absl/base/macros.h" #include "absl/base/port.h" @@ -173,10 +172,6 @@ class Cord { // // * be move constructible // * support `void operator()(absl::string_view) const` or `void operator()` - // * not have alignment requirement greater than what is guaranteed by - // `::operator new`. This alignment is dictated by - // `alignof(std::max_align_t)` (pre-C++17 code) or - // `__STDCPP_DEFAULT_NEW_ALIGNMENT__` (C++17 code). // // Example: // @@ -842,47 +837,15 @@ inline void SmallMemmove(char* dst, const char* src, size_t n, } } -struct ExternalRepReleaserPair { - CordRep* rep; - void* releaser_address; -}; - -// Allocates a new external `CordRep` and returns a pointer to it and a pointer -// to `releaser_size` bytes where the desired releaser can be constructed. +// Does non-template-specific `CordRepExternal` initialization. // Expects `data` to be non-empty. -ExternalRepReleaserPair NewExternalWithUninitializedReleaser( - absl::string_view data, ExternalReleaserInvoker invoker, - size_t releaser_size); - -struct Rank1 {}; -struct Rank0 : Rank1 {}; - -template > -void InvokeReleaser(Rank0, Releaser&& releaser, absl::string_view data) { - ::absl::base_internal::invoke(std::forward(releaser), data); -} - -template > -void InvokeReleaser(Rank1, Releaser&& releaser, absl::string_view) { - ::absl::base_internal::invoke(std::forward(releaser)); -} +void InitializeCordRepExternal(absl::string_view data, CordRepExternal* rep); // Creates a new `CordRep` that owns `data` and `releaser` and returns a pointer // to it, or `nullptr` if `data` was empty. template // NOLINTNEXTLINE - suppress clang-tidy raw pointer return. CordRep* NewExternalRep(absl::string_view data, Releaser&& releaser) { - static_assert( -#if defined(__STDCPP_DEFAULT_NEW_ALIGNMENT__) - alignof(Releaser) <= __STDCPP_DEFAULT_NEW_ALIGNMENT__, -#else - alignof(Releaser) <= alignof(max_align_t), -#endif - "Releasers with alignment requirement greater than what is returned by " - "default `::operator new()` are not supported."); - using ReleaserType = absl::decay_t; if (data.empty()) { // Never create empty external nodes. @@ -891,18 +854,10 @@ CordRep* NewExternalRep(absl::string_view data, Releaser&& releaser) { return nullptr; } - auto releaser_invoker = [](void* type_erased_releaser, absl::string_view d) { - auto* my_releaser = static_cast(type_erased_releaser); - InvokeReleaser(Rank0{}, std::move(*my_releaser), d); - my_releaser->~ReleaserType(); - return sizeof(Releaser); - }; - - ExternalRepReleaserPair external = NewExternalWithUninitializedReleaser( - data, releaser_invoker, sizeof(releaser)); - ::new (external.releaser_address) - ReleaserType(std::forward(releaser)); - return external.rep; + CordRepExternal* rep = new CordRepExternalImpl( + std::forward(releaser), 0); + InitializeCordRepExternal(data, rep); + return rep; } // Overload for function reference types that dispatches using a function diff --git a/absl/strings/internal/cord_internal.h b/absl/strings/internal/cord_internal.h index 830ceaf4..d456eef8 100644 --- a/absl/strings/internal/cord_internal.h +++ b/absl/strings/internal/cord_internal.h @@ -21,6 +21,8 @@ #include #include +#include "absl/base/internal/invoke.h" +#include "absl/container/internal/compressed_tuple.h" #include "absl/meta/type_traits.h" #include "absl/strings/string_view.h" @@ -114,35 +116,56 @@ struct CordRepSubstring : public CordRep { CordRep* child; }; -// TODO(strel): replace the following logic (and related functions in cord.cc) -// with container_internal::Layout. - -// Alignment requirement for CordRepExternal so that the type erased releaser -// will be stored at a suitably aligned address. -constexpr size_t ExternalRepAlignment() { -#if defined(__STDCPP_DEFAULT_NEW_ALIGNMENT__) - return __STDCPP_DEFAULT_NEW_ALIGNMENT__; -#else - return alignof(max_align_t); -#endif -} - -// Type for function pointer that will invoke and destroy the type-erased -// releaser function object. Accepts a pointer to the releaser and the -// `string_view` that were passed in to `NewExternalRep` below. The return value -// is the size of the `Releaser` type. -using ExternalReleaserInvoker = size_t (*)(void*, absl::string_view); +// Type for function pointer that will invoke the releaser function and also +// delete the `CordRepExternalImpl` corresponding to the passed in +// `CordRepExternal`. +using ExternalReleaserInvoker = void (*)(CordRepExternal*); // External CordReps are allocated together with a type erased releaser. The // releaser is stored in the memory directly following the CordRepExternal. -struct alignas(ExternalRepAlignment()) CordRepExternal : public CordRep { +struct CordRepExternal : public CordRep { const char* base; // Pointer to function that knows how to call and destroy the releaser. ExternalReleaserInvoker releaser_invoker; }; -// TODO(strel): look into removing, it doesn't seem like anything relies on this -static_assert(sizeof(CordRepConcat) == sizeof(CordRepSubstring), ""); +struct Rank1 {}; +struct Rank0 : Rank1 {}; + +template > +void InvokeReleaser(Rank0, Releaser&& releaser, absl::string_view data) { + ::absl::base_internal::invoke(std::forward(releaser), data); +} + +template > +void InvokeReleaser(Rank1, Releaser&& releaser, absl::string_view) { + ::absl::base_internal::invoke(std::forward(releaser)); +} + +// We use CompressedTuple so that we can benefit from EBCO. +template +struct CordRepExternalImpl + : public CordRepExternal, + public ::absl::container_internal::CompressedTuple { + // The extra int arg is so that we can avoid interfering with copy/move + // constructors while still benefitting from perfect forwarding. + template + CordRepExternalImpl(T&& releaser, int) + : CordRepExternalImpl::CompressedTuple(std::forward(releaser)) { + this->releaser_invoker = &Release; + } + + ~CordRepExternalImpl() { + InvokeReleaser(Rank0{}, std::move(this->template get<0>()), + absl::string_view(base, length)); + } + + static void Release(CordRepExternal* rep) { + delete static_cast(rep); + } +}; } // namespace cord_internal ABSL_NAMESPACE_END diff --git a/absl/strings/string_view_test.cc b/absl/strings/string_view_test.cc index 41dbc97b..dcebb150 100644 --- a/absl/strings/string_view_test.cc +++ b/absl/strings/string_view_test.cc @@ -1177,7 +1177,7 @@ TEST(FindOneCharTest, EdgeCases) { EXPECT_EQ(absl::string_view::npos, a.rfind('x')); } -#ifndef THREAD_SANITIZER // Allocates too much memory for tsan. +#ifndef ABSL_HAVE_THREAD_SANITIZER // Allocates too much memory for tsan. TEST(HugeStringView, TwoPointTwoGB) { if (sizeof(size_t) <= 4) return; @@ -1191,7 +1191,7 @@ TEST(HugeStringView, TwoPointTwoGB) { sp.remove_suffix(2); EXPECT_EQ(size - 1 - 2, sp.length()); } -#endif // THREAD_SANITIZER +#endif // ABSL_HAVE_THREAD_SANITIZER #if !defined(NDEBUG) && !defined(ABSL_USES_STD_STRING_VIEW) TEST(NonNegativeLenTest, NonNegativeLen) { diff --git a/absl/synchronization/BUILD.bazel b/absl/synchronization/BUILD.bazel index 2cd5ae20..1a5c02ab 100644 --- a/absl/synchronization/BUILD.bazel +++ b/absl/synchronization/BUILD.bazel @@ -190,6 +190,7 @@ cc_test( ":synchronization", ":thread_pool", "//absl/base", + "//absl/base:config", "//absl/base:core_headers", "//absl/base:raw_logging_internal", "//absl/memory", @@ -211,6 +212,7 @@ cc_library( ":synchronization", ":thread_pool", "//absl/base", + "//absl/base:config", "@com_github_google_benchmark//:benchmark_main", ], alwayslink = 1, @@ -249,6 +251,7 @@ cc_library( deps = [ ":synchronization", "//absl/base", + "//absl/base:config", "//absl/strings", "//absl/time", "@com_google_googletest//:gtest", diff --git a/absl/synchronization/CMakeLists.txt b/absl/synchronization/CMakeLists.txt index dfe5d05d..e5bc52fb 100644 --- a/absl/synchronization/CMakeLists.txt +++ b/absl/synchronization/CMakeLists.txt @@ -149,6 +149,7 @@ absl_cc_test( absl::synchronization absl::thread_pool absl::base + absl::config absl::core_headers absl::memory absl::raw_logging_internal @@ -179,6 +180,7 @@ absl_cc_library( DEPS absl::synchronization absl::base + absl::config absl::strings absl::time gmock diff --git a/absl/synchronization/internal/mutex_nonprod.cc b/absl/synchronization/internal/mutex_nonprod.cc index 1732f836..334c3bc0 100644 --- a/absl/synchronization/internal/mutex_nonprod.cc +++ b/absl/synchronization/internal/mutex_nonprod.cc @@ -27,6 +27,7 @@ #include +#include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" #include "absl/time/time.h" @@ -278,7 +279,7 @@ bool CondVar::WaitWithTimeout(Mutex* mu, absl::Duration timeout) { void CondVar::EnableDebugLog(const char*) {} -#ifdef THREAD_SANITIZER +#ifdef ABSL_HAVE_THREAD_SANITIZER extern "C" void __tsan_read1(void *addr); #else #define __tsan_read1(addr) // do nothing if TSan not enabled diff --git a/absl/synchronization/internal/per_thread_sem_test.cc b/absl/synchronization/internal/per_thread_sem_test.cc index b5a2f6d4..8cf59e64 100644 --- a/absl/synchronization/internal/per_thread_sem_test.cc +++ b/absl/synchronization/internal/per_thread_sem_test.cc @@ -23,6 +23,7 @@ #include // NOLINT(build/c++11) #include "gtest/gtest.h" +#include "absl/base/config.h" #include "absl/base/internal/cycleclock.h" #include "absl/base/internal/thread_identity.h" #include "absl/strings/str_cat.h" diff --git a/absl/synchronization/internal/waiter.h b/absl/synchronization/internal/waiter.h index ae83907b..887f9b1b 100644 --- a/absl/synchronization/internal/waiter.h +++ b/absl/synchronization/internal/waiter.h @@ -100,7 +100,7 @@ class Waiter { } // How many periods to remain idle before releasing resources -#ifndef THREAD_SANITIZER +#ifndef ABSL_HAVE_THREAD_SANITIZER static constexpr int kIdlePeriods = 60; #else // Memory consumption under ThreadSanitizer is a serious concern, diff --git a/absl/synchronization/mutex.cc b/absl/synchronization/mutex.cc index aafeb674..9b7f088a 100644 --- a/absl/synchronization/mutex.cc +++ b/absl/synchronization/mutex.cc @@ -77,7 +77,7 @@ ABSL_NAMESPACE_BEGIN namespace { -#if defined(THREAD_SANITIZER) +#if defined(ABSL_HAVE_THREAD_SANITIZER) constexpr OnDeadlockCycle kDeadlockDetectionDefault = OnDeadlockCycle::kIgnore; #else constexpr OnDeadlockCycle kDeadlockDetectionDefault = OnDeadlockCycle::kAbort; @@ -705,7 +705,7 @@ static constexpr bool kDebugMode = false; static constexpr bool kDebugMode = true; #endif -#ifdef THREAD_SANITIZER +#ifdef ABSL_HAVE_THREAD_SANITIZER static unsigned TsanFlags(Mutex::MuHow how) { return how == kShared ? __tsan_mutex_read_lock : 0; } @@ -1767,7 +1767,7 @@ static inline bool EvalConditionAnnotated(const Condition *cond, Mutex *mu, // All memory accesses are ignored inside of mutex operations + for unlock // operation tsan considers that we've already released the mutex. bool res = false; -#ifdef THREAD_SANITIZER +#ifdef ABSL_HAVE_THREAD_SANITIZER const int flags = read_lock ? __tsan_mutex_read_lock : 0; const int tryflags = flags | (trylock ? __tsan_mutex_try_lock : 0); #endif @@ -2683,7 +2683,7 @@ void ReleasableMutexLock::Release() { this->mu_ = nullptr; } -#ifdef THREAD_SANITIZER +#ifdef ABSL_HAVE_THREAD_SANITIZER extern "C" void __tsan_read1(void *addr); #else #define __tsan_read1(addr) // do nothing if TSan not enabled diff --git a/absl/synchronization/mutex_benchmark.cc b/absl/synchronization/mutex_benchmark.cc index ab188001..933ea14f 100644 --- a/absl/synchronization/mutex_benchmark.cc +++ b/absl/synchronization/mutex_benchmark.cc @@ -16,6 +16,7 @@ #include // NOLINT(build/c++11) #include +#include "absl/base/config.h" #include "absl/base/internal/cycleclock.h" #include "absl/base/internal/spinlock.h" #include "absl/synchronization/blocking_counter.h" @@ -213,7 +214,7 @@ void BM_ConditionWaiters(benchmark::State& state) { } // Some configurations have higher thread limits than others. -#if defined(__linux__) && !defined(THREAD_SANITIZER) +#if defined(__linux__) && !defined(ABSL_HAVE_THREAD_SANITIZER) constexpr int kMaxConditionWaiters = 8192; #else constexpr int kMaxConditionWaiters = 1024; diff --git a/absl/synchronization/mutex_test.cc b/absl/synchronization/mutex_test.cc index afb363af..307d0e3f 100644 --- a/absl/synchronization/mutex_test.cc +++ b/absl/synchronization/mutex_test.cc @@ -30,6 +30,7 @@ #include "gtest/gtest.h" #include "absl/base/attributes.h" +#include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/internal/sysinfo.h" #include "absl/memory/memory.h" @@ -815,7 +816,7 @@ TEST(Mutex, MutexReaderDecrementBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { // Test that we correctly handle the situation when a lock is // held and then destroyed (w/o unlocking). -#ifdef THREAD_SANITIZER +#ifdef ABSL_HAVE_THREAD_SANITIZER // TSAN reports errors when locked Mutexes are destroyed. TEST(Mutex, DISABLED_LockedMutexDestructionBug) NO_THREAD_SAFETY_ANALYSIS { #else @@ -1067,7 +1068,7 @@ class ScopedDisableBazelTestWarnings { const char ScopedDisableBazelTestWarnings::kVarName[] = "TEST_WARNINGS_OUTPUT_FILE"; -#ifdef THREAD_SANITIZER +#ifdef ABSL_HAVE_THREAD_SANITIZER // This test intentionally creates deadlocks to test the deadlock detector. TEST(Mutex, DISABLED_DeadlockDetectorBazelWarning) { #else @@ -1119,7 +1120,7 @@ TEST(Mutex, DeadlockDetectorStessTest) ABSL_NO_THREAD_SAFETY_ANALYSIS { } } -#ifdef THREAD_SANITIZER +#ifdef ABSL_HAVE_THREAD_SANITIZER // TSAN reports errors when locked Mutexes are destroyed. TEST(Mutex, DISABLED_DeadlockIdBug) NO_THREAD_SAFETY_ANALYSIS { #else diff --git a/absl/time/BUILD.bazel b/absl/time/BUILD.bazel index 9ab2adb8..1d69f598 100644 --- a/absl/time/BUILD.bazel +++ b/absl/time/BUILD.bazel @@ -70,6 +70,7 @@ cc_library( ], deps = [ ":time", + "//absl/base:config", "//absl/base:raw_logging_internal", "//absl/time/internal/cctz:time_zone", "@com_google_googletest//:gtest", diff --git a/absl/time/CMakeLists.txt b/absl/time/CMakeLists.txt index 853563e8..00bdd499 100644 --- a/absl/time/CMakeLists.txt +++ b/absl/time/CMakeLists.txt @@ -99,6 +99,7 @@ absl_cc_library( ${ABSL_DEFAULT_COPTS} DEPS absl::time + absl::config absl::raw_logging_internal absl::time_zone gmock diff --git a/absl/time/internal/test_util.cc b/absl/time/internal/test_util.cc index 9bffe121..9a485a07 100644 --- a/absl/time/internal/test_util.cc +++ b/absl/time/internal/test_util.cc @@ -18,6 +18,7 @@ #include #include +#include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" #include "absl/time/internal/cctz/include/cctz/zone_info_source.h" diff --git a/ci/linux_clang-latest_libcxx_asan_bazel.sh b/ci/linux_clang-latest_libcxx_asan_bazel.sh index 0c250a62..2aed43cf 100755 --- a/ci/linux_clang-latest_libcxx_asan_bazel.sh +++ b/ci/linux_clang-latest_libcxx_asan_bazel.sh @@ -78,8 +78,6 @@ for std in ${STD}; do /usr/local/bin/bazel test ... \ --compilation_mode="${compilation_mode}" \ --copt="${exceptions_mode}" \ - --copt="-DADDRESS_SANITIZER" \ - --copt="-DUNDEFINED_BEHAVIOR_SANITIZER" \ --copt="-fsanitize=address" \ --copt="-fsanitize=float-divide-by-zero" \ --copt="-fsanitize=nullability" \ diff --git a/ci/linux_clang-latest_libcxx_tsan_bazel.sh b/ci/linux_clang-latest_libcxx_tsan_bazel.sh index c2eb5bae..b39eaf74 100755 --- a/ci/linux_clang-latest_libcxx_tsan_bazel.sh +++ b/ci/linux_clang-latest_libcxx_tsan_bazel.sh @@ -79,8 +79,6 @@ for std in ${STD}; do --build_tag_filters="-notsan" \ --compilation_mode="${compilation_mode}" \ --copt="${exceptions_mode}" \ - --copt="-DDYNAMIC_ANNOTATIONS_ENABLED=1" \ - --copt="-DTHREAD_SANITIZER" \ --copt="-fsanitize=thread" \ --copt="-fno-sanitize-blacklist" \ --copt=-Werror \ -- cgit v1.2.3 From 1995c6a3c2f9080160d9d8716504dc004e5e1ec0 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Sun, 2 Aug 2020 14:23:26 -0700 Subject: Export of internal Abseil changes -- 790f9061df340cd900e8da70e66c363f7af3c2eb by Abseil Team : Add support for rvalue reference to function types. PiperOrigin-RevId: 324508531 -- 51fe201dbb41a3ebc3d49ff65250b5f464279d43 by Abseil Team : Cleaning up function comment style; no substantive change. PiperOrigin-RevId: 324497401 -- da8595d5266577d0c170528d12f6de17b8affcc2 by Abseil Team : Add support for demangling GNU vector types. PiperOrigin-RevId: 324494559 -- 0cb0acf88c1750f6963c9cb85249f9b4f0bd5104 by Abseil Team : Add support for thread-local types. PiperOrigin-RevId: 324491183 -- c676bc8380560599cd26f7f231e04e6be532e904 by Abseil Team : Add support for demangling "Du" (char8_t). PiperOrigin-RevId: 324441607 -- b218bf6467bc62b327214782c881e8224ad91509 by Abseil Team : Update doc comments in header of `any.h` to reflect that `absl::variant` has been released. PiperOrigin-RevId: 324431690 -- e5b579f3f1aa598c1f62e71dba7103b98811de59 by Laramie Leavitt : Bugfix: Fix bounds in absl::Uniform where one of the bounds is min/max. When absl::Uniform(rng, tag, a, b) is called, the tag is used in conjunction with the type to determine whether or not to manipulate the bounds to make them inclusive or exclusive through the uniform_*_bound functions. Unfortunately, at limits of the interval the function was not well behaved. The previous implementation used wrapping arithmetic. This causes incorrect bounds computation at the extremes (numeric_limits::min / numeric_limits::max) the bound would wrap. Improve this situation by: 1/ Changing the uniform_*_bound functions to use saturating arithmetic instead of wrapping, thus in the unsigned case, the upper_bound of IntervalOpenOpen for 0 is now 0, rather than numeric_limits::max, likewise for the lower bound. 2/ Adjusting the hi/lo checks in the distributions. When the interval is empty, such as for absl::Uniform(absl::IntervalOpenOpen, gen, 1, 0), the return value is somewhat nonsensical. Now absl::Uniform more consistently returns the low input rather than any adjusted input. In the above case, that means that 1 is returned rather than 2. NOTE: Calls to absl::Uniform where the resolved upper bound is < the lower bound are still ill-formed and should be avoided. 3/ Adding better tests. The underlying uniform_*_distribution classes are not affected. PiperOrigin-RevId: 324240873 GitOrigin-RevId: 790f9061df340cd900e8da70e66c363f7af3c2eb Change-Id: I2a2208650ea3135c575e200b868ce1d275069fc8 --- absl/debugging/internal/demangle.cc | 14 +- absl/random/CMakeLists.txt | 15 ++ absl/random/distributions.h | 10 +- absl/random/distributions_test.cc | 142 ++++++-------- absl/random/internal/BUILD.bazel | 12 ++ absl/random/internal/uniform_helper.h | 38 +++- absl/random/internal/uniform_helper_test.cc | 279 ++++++++++++++++++++++++++++ absl/strings/cord.h | 28 +-- absl/types/any.h | 6 +- 9 files changed, 430 insertions(+), 114 deletions(-) create mode 100644 absl/random/internal/uniform_helper_test.cc (limited to 'absl/strings/cord.h') diff --git a/absl/debugging/internal/demangle.cc b/absl/debugging/internal/demangle.cc index 5f54ef34..87f13e50 100644 --- a/absl/debugging/internal/demangle.cc +++ b/absl/debugging/internal/demangle.cc @@ -126,6 +126,7 @@ static const AbbrevPair kBuiltinTypeList[] = { {"Dn", "std::nullptr_t", 0}, // i.e., decltype(nullptr) {"Df", "decimal32", 0}, // IEEE 754r decimal floating point (32 bits) {"Di", "char32_t", 0}, + {"Du", "char8_t", 0}, {"Ds", "char16_t", 0}, {"Dh", "float16", 0}, // IEEE 754r half-precision float (16 bits) {nullptr, nullptr, 0}, @@ -965,6 +966,7 @@ static bool ParseOperatorName(State *state, int *arity) { // ::= TT // ::= TI // ::= TS +// ::= TH # thread-local // ::= Tc <(base) encoding> // ::= GV <(object) name> // ::= T <(base) encoding> @@ -983,7 +985,7 @@ static bool ParseSpecialName(State *state) { ComplexityGuard guard(state); if (guard.IsTooComplex()) return false; ParseState copy = state->parse_state; - if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "VTIS") && + if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "VTISH") && ParseType(state)) { return true; } @@ -1142,6 +1144,7 @@ static bool ParseDecltype(State *state) { // ::= // ::= // ::= Dp # pack expansion of (C++0x) +// ::= Dv _ # GNU vector extension // static bool ParseType(State *state) { ComplexityGuard guard(state); @@ -1208,6 +1211,12 @@ static bool ParseType(State *state) { return true; } + if (ParseTwoCharToken(state, "Dv") && ParseNumber(state, nullptr) && + ParseOneCharToken(state, '_')) { + return true; + } + state->parse_state = copy; + return false; } @@ -1256,13 +1265,14 @@ static bool ParseBuiltinType(State *state) { return false; } -// ::= F [Y] E +// ::= F [Y] [O] E static bool ParseFunctionType(State *state) { ComplexityGuard guard(state); if (guard.IsTooComplex()) return false; ParseState copy = state->parse_state; if (ParseOneCharToken(state, 'F') && Optional(ParseOneCharToken(state, 'Y')) && ParseBareFunctionType(state) && + Optional(ParseOneCharToken(state, 'O')) && ParseOneCharToken(state, 'E')) { return true; } diff --git a/absl/random/CMakeLists.txt b/absl/random/CMakeLists.txt index ade9ea10..7d7bec83 100644 --- a/absl/random/CMakeLists.txt +++ b/absl/random/CMakeLists.txt @@ -1159,6 +1159,21 @@ absl_cc_library( absl::type_traits ) +# Internal-only target, do not depend on directly. +absl_cc_test( + NAME + random_internal_uniform_helper_test + SRCS + "internal/uniform_helper_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::random_internal_uniform_helper + gtest_main +) + # Internal-only target, do not depend on directly. absl_cc_test( NAME diff --git a/absl/random/distributions.h b/absl/random/distributions.h index 6775c5d6..31c79694 100644 --- a/absl/random/distributions.h +++ b/absl/random/distributions.h @@ -128,7 +128,7 @@ Uniform(TagType tag, auto a = random_internal::uniform_lower_bound(tag, lo, hi); auto b = random_internal::uniform_upper_bound(tag, lo, hi); - if (a > b) return a; + if (!random_internal::is_uniform_range_valid(a, b)) return lo; return random_internal::DistributionCaller::template Call< distribution_t>(&urbg, tag, lo, hi); @@ -144,11 +144,11 @@ Uniform(URBG&& urbg, // NOLINT(runtime/references) R lo, R hi) { using gen_t = absl::decay_t; using distribution_t = random_internal::UniformDistributionWrapper; - constexpr auto tag = absl::IntervalClosedOpen; + auto a = random_internal::uniform_lower_bound(tag, lo, hi); auto b = random_internal::uniform_upper_bound(tag, lo, hi); - if (a > b) return a; + if (!random_internal::is_uniform_range_valid(a, b)) return lo; return random_internal::DistributionCaller::template Call< distribution_t>(&urbg, lo, hi); @@ -172,7 +172,7 @@ Uniform(TagType tag, auto a = random_internal::uniform_lower_bound(tag, lo, hi); auto b = random_internal::uniform_upper_bound(tag, lo, hi); - if (a > b) return a; + if (!random_internal::is_uniform_range_valid(a, b)) return lo; return random_internal::DistributionCaller::template Call< distribution_t>(&urbg, tag, static_cast(lo), @@ -196,7 +196,7 @@ Uniform(URBG&& urbg, // NOLINT(runtime/references) constexpr auto tag = absl::IntervalClosedOpen; auto a = random_internal::uniform_lower_bound(tag, lo, hi); auto b = random_internal::uniform_upper_bound(tag, lo, hi); - if (a > b) return a; + if (!random_internal::is_uniform_range_valid(a, b)) return lo; return random_internal::DistributionCaller::template Call< distribution_t>(&urbg, static_cast(lo), diff --git a/absl/random/distributions_test.cc b/absl/random/distributions_test.cc index 2d92723a..5866a072 100644 --- a/absl/random/distributions_test.cc +++ b/absl/random/distributions_test.cc @@ -29,94 +29,6 @@ constexpr int kSize = 400000; class RandomDistributionsTest : public testing::Test {}; -TEST_F(RandomDistributionsTest, UniformBoundFunctions) { - using absl::IntervalClosedClosed; - using absl::IntervalClosedOpen; - using absl::IntervalOpenClosed; - using absl::IntervalOpenOpen; - using absl::random_internal::uniform_lower_bound; - using absl::random_internal::uniform_upper_bound; - - // absl::uniform_int_distribution natively assumes IntervalClosedClosed - // absl::uniform_real_distribution natively assumes IntervalClosedOpen - - EXPECT_EQ(uniform_lower_bound(IntervalOpenClosed, 0, 100), 1); - EXPECT_EQ(uniform_lower_bound(IntervalOpenOpen, 0, 100), 1); - EXPECT_GT(uniform_lower_bound(IntervalOpenClosed, 0, 1.0), 0); - EXPECT_GT(uniform_lower_bound(IntervalOpenOpen, 0, 1.0), 0); - EXPECT_GT(uniform_lower_bound(IntervalOpenClosed, 0, 1.0), 0); - EXPECT_GT(uniform_lower_bound(IntervalOpenOpen, 0, 1.0), 0); - - EXPECT_EQ(uniform_lower_bound(IntervalClosedClosed, 0, 100), 0); - EXPECT_EQ(uniform_lower_bound(IntervalClosedOpen, 0, 100), 0); - EXPECT_EQ(uniform_lower_bound(IntervalClosedClosed, 0, 1.0), 0); - EXPECT_EQ(uniform_lower_bound(IntervalClosedOpen, 0, 1.0), 0); - EXPECT_EQ(uniform_lower_bound(IntervalClosedClosed, 0, 1.0), 0); - EXPECT_EQ(uniform_lower_bound(IntervalClosedOpen, 0, 1.0), 0); - - EXPECT_EQ(uniform_upper_bound(IntervalOpenOpen, 0, 100), 99); - EXPECT_EQ(uniform_upper_bound(IntervalClosedOpen, 0, 100), 99); - EXPECT_EQ(uniform_upper_bound(IntervalOpenOpen, 0, 1.0), 1.0); - EXPECT_EQ(uniform_upper_bound(IntervalClosedOpen, 0, 1.0), 1.0); - EXPECT_EQ(uniform_upper_bound(IntervalOpenOpen, 0, 1.0), 1.0); - EXPECT_EQ(uniform_upper_bound(IntervalClosedOpen, 0, 1.0), 1.0); - - EXPECT_EQ(uniform_upper_bound(IntervalOpenClosed, 0, 100), 100); - EXPECT_EQ(uniform_upper_bound(IntervalClosedClosed, 0, 100), 100); - EXPECT_GT(uniform_upper_bound(IntervalOpenClosed, 0, 1.0), 1.0); - EXPECT_GT(uniform_upper_bound(IntervalClosedClosed, 0, 1.0), 1.0); - EXPECT_GT(uniform_upper_bound(IntervalOpenClosed, 0, 1.0), 1.0); - EXPECT_GT(uniform_upper_bound(IntervalClosedClosed, 0, 1.0), 1.0); - - // Negative value tests - EXPECT_EQ(uniform_lower_bound(IntervalOpenClosed, -100, -1), -99); - EXPECT_EQ(uniform_lower_bound(IntervalOpenOpen, -100, -1), -99); - EXPECT_GT(uniform_lower_bound(IntervalOpenClosed, -2.0, -1.0), -2.0); - EXPECT_GT(uniform_lower_bound(IntervalOpenOpen, -2.0, -1.0), -2.0); - EXPECT_GT(uniform_lower_bound(IntervalOpenClosed, -2.0, -1.0), -2.0); - EXPECT_GT(uniform_lower_bound(IntervalOpenOpen, -2.0, -1.0), -2.0); - - EXPECT_EQ(uniform_lower_bound(IntervalClosedClosed, -100, -1), -100); - EXPECT_EQ(uniform_lower_bound(IntervalClosedOpen, -100, -1), -100); - EXPECT_EQ(uniform_lower_bound(IntervalClosedClosed, -2.0, -1.0), -2.0); - EXPECT_EQ(uniform_lower_bound(IntervalClosedOpen, -2.0, -1.0), -2.0); - EXPECT_EQ(uniform_lower_bound(IntervalClosedClosed, -2.0, -1.0), - -2.0); - EXPECT_EQ(uniform_lower_bound(IntervalClosedOpen, -2.0, -1.0), -2.0); - - EXPECT_EQ(uniform_upper_bound(IntervalOpenOpen, -100, -1), -2); - EXPECT_EQ(uniform_upper_bound(IntervalClosedOpen, -100, -1), -2); - EXPECT_EQ(uniform_upper_bound(IntervalOpenOpen, -2.0, -1.0), -1.0); - EXPECT_EQ(uniform_upper_bound(IntervalClosedOpen, -2.0, -1.0), -1.0); - EXPECT_EQ(uniform_upper_bound(IntervalOpenOpen, -2.0, -1.0), -1.0); - EXPECT_EQ(uniform_upper_bound(IntervalClosedOpen, -2.0, -1.0), -1.0); - - EXPECT_EQ(uniform_upper_bound(IntervalOpenClosed, -100, -1), -1); - EXPECT_EQ(uniform_upper_bound(IntervalClosedClosed, -100, -1), -1); - EXPECT_GT(uniform_upper_bound(IntervalOpenClosed, -2.0, -1.0), -1.0); - EXPECT_GT(uniform_upper_bound(IntervalClosedClosed, -2.0, -1.0), -1.0); - EXPECT_GT(uniform_upper_bound(IntervalOpenClosed, -2.0, -1.0), -1.0); - EXPECT_GT(uniform_upper_bound(IntervalClosedClosed, -2.0, -1.0), - -1.0); - - // Edge cases: the next value toward itself is itself. - const double d = 1.0; - const float f = 1.0; - EXPECT_EQ(uniform_lower_bound(IntervalOpenClosed, d, d), d); - EXPECT_EQ(uniform_lower_bound(IntervalOpenClosed, f, f), f); - - EXPECT_GT(uniform_lower_bound(IntervalOpenClosed, 1.0, 2.0), 1.0); - EXPECT_LT(uniform_lower_bound(IntervalOpenClosed, 1.0, +0.0), 1.0); - EXPECT_LT(uniform_lower_bound(IntervalOpenClosed, 1.0, -0.0), 1.0); - EXPECT_LT(uniform_lower_bound(IntervalOpenClosed, 1.0, -1.0), 1.0); - - EXPECT_EQ(uniform_upper_bound(IntervalClosedClosed, 0.0f, - std::numeric_limits::max()), - std::numeric_limits::max()); - EXPECT_EQ(uniform_upper_bound(IntervalClosedClosed, 0.0, - std::numeric_limits::max()), - std::numeric_limits::max()); -} struct Invalid {}; @@ -284,7 +196,9 @@ TEST_F(RandomDistributionsTest, UniformTypeInference) { // Properly promotes float. CheckArgsInferType(); +} +TEST_F(RandomDistributionsTest, UniformExamples) { // Examples. absl::InsecureBitGen gen; EXPECT_NE(1, absl::Uniform(gen, static_cast(0), 1.0f)); @@ -307,6 +221,58 @@ TEST_F(RandomDistributionsTest, UniformNoBounds) { absl::Uniform(gen); } +TEST_F(RandomDistributionsTest, UniformNonsenseRanges) { + // The ranges used in this test are undefined behavior. + // The results are arbitrary and subject to future changes. + absl::InsecureBitGen gen; + + // + EXPECT_EQ(0, absl::Uniform(gen, 0, 0)); + EXPECT_EQ(1, absl::Uniform(gen, 1, 0)); + EXPECT_EQ(0, absl::Uniform(absl::IntervalOpenOpen, gen, 0, 0)); + EXPECT_EQ(1, absl::Uniform(absl::IntervalOpenOpen, gen, 1, 0)); + + constexpr auto m = (std::numeric_limits::max)(); + + EXPECT_EQ(m, absl::Uniform(gen, m, m)); + EXPECT_EQ(m, absl::Uniform(gen, m, m - 1)); + EXPECT_EQ(m - 1, absl::Uniform(gen, m - 1, m)); + EXPECT_EQ(m, absl::Uniform(absl::IntervalOpenOpen, gen, m, m)); + EXPECT_EQ(m, absl::Uniform(absl::IntervalOpenOpen, gen, m, m - 1)); + EXPECT_EQ(m - 1, absl::Uniform(absl::IntervalOpenOpen, gen, m - 1, m)); + + // + EXPECT_EQ(0, absl::Uniform(gen, 0, 0)); + EXPECT_EQ(1, absl::Uniform(gen, 1, 0)); + EXPECT_EQ(0, absl::Uniform(absl::IntervalOpenOpen, gen, 0, 0)); + EXPECT_EQ(1, absl::Uniform(absl::IntervalOpenOpen, gen, 1, 0)); + + constexpr auto l = (std::numeric_limits::min)(); + constexpr auto r = (std::numeric_limits::max)(); + + EXPECT_EQ(l, absl::Uniform(gen, l, l)); + EXPECT_EQ(r, absl::Uniform(gen, r, r)); + EXPECT_EQ(r, absl::Uniform(gen, r, r - 1)); + EXPECT_EQ(r - 1, absl::Uniform(gen, r - 1, r)); + EXPECT_EQ(l, absl::Uniform(absl::IntervalOpenOpen, gen, l, l)); + EXPECT_EQ(r, absl::Uniform(absl::IntervalOpenOpen, gen, r, r)); + EXPECT_EQ(r, absl::Uniform(absl::IntervalOpenOpen, gen, r, r - 1)); + EXPECT_EQ(r - 1, absl::Uniform(absl::IntervalOpenOpen, gen, r - 1, r)); + + // + const double e = std::nextafter(1.0, 2.0); // 1 + epsilon + const double f = std::nextafter(1.0, 0.0); // 1 - epsilon + const double g = std::numeric_limits::denorm_min(); + + EXPECT_EQ(1.0, absl::Uniform(gen, 1.0, e)); + EXPECT_EQ(1.0, absl::Uniform(gen, 1.0, f)); + EXPECT_EQ(0.0, absl::Uniform(gen, 0.0, g)); + + EXPECT_EQ(e, absl::Uniform(absl::IntervalOpenOpen, gen, 1.0, e)); + EXPECT_EQ(f, absl::Uniform(absl::IntervalOpenOpen, gen, 1.0, f)); + EXPECT_EQ(g, absl::Uniform(absl::IntervalOpenOpen, gen, 0.0, g)); +} + // TODO(lar): Validate properties of non-default interval-semantics. TEST_F(RandomDistributionsTest, UniformReal) { std::vector values(kSize); diff --git a/absl/random/internal/BUILD.bazel b/absl/random/internal/BUILD.bazel index 83843b4c..000cc45b 100644 --- a/absl/random/internal/BUILD.bazel +++ b/absl/random/internal/BUILD.bazel @@ -716,3 +716,15 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "uniform_helper_test", + size = "small", + srcs = ["uniform_helper_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":uniform_helper", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/absl/random/internal/uniform_helper.h b/absl/random/internal/uniform_helper.h index 5b2afecb..1243bc1c 100644 --- a/absl/random/internal/uniform_helper.h +++ b/absl/random/internal/uniform_helper.h @@ -105,7 +105,7 @@ typename absl::enable_if_t< std::is_same>>::value, IntType> uniform_lower_bound(Tag, IntType a, IntType) { - return a + 1; + return a < (std::numeric_limits::max)() ? (a + 1) : a; } template @@ -136,7 +136,7 @@ typename absl::enable_if_t< std::is_same>>::value, IntType> uniform_upper_bound(Tag, IntType, IntType b) { - return b - 1; + return b > (std::numeric_limits::min)() ? (b - 1) : b; } template @@ -172,6 +172,40 @@ uniform_upper_bound(Tag, FloatType, FloatType b) { return std::nextafter(b, (std::numeric_limits::max)()); } +// Returns whether the bounds are valid for the underlying distribution. +// Inputs must have already been resolved via uniform_*_bound calls. +// +// The c++ standard constraints in [rand.dist.uni.int] are listed as: +// requires: lo <= hi. +// +// In the uniform_int_distrubtion, {lo, hi} are closed, closed. Thus: +// [0, 0] is legal. +// [0, 0) is not legal, but [0, 1) is, which translates to [0, 0]. +// (0, 1) is not legal, but (0, 2) is, which translates to [1, 1]. +// (0, 0] is not legal, but (0, 1] is, which translates to [1, 1]. +// +// The c++ standard constraints in [rand.dist.uni.real] are listed as: +// requires: lo <= hi. +// requires: (hi - lo) <= numeric_limits::max() +// +// In the uniform_real_distribution, {lo, hi} are closed, open, Thus: +// [0, 0] is legal, which is [0, 0+epsilon). +// [0, 0) is legal. +// (0, 0) is not legal, but (0-epsilon, 0+epsilon) is. +// (0, 0] is not legal, but (0, 0+epsilon] is. +// +template +absl::enable_if_t::value, bool> +is_uniform_range_valid(FloatType a, FloatType b) { + return a <= b && std::isfinite(b - a); +} + +template +absl::enable_if_t::value, bool> +is_uniform_range_valid(IntType a, IntType b) { + return a <= b; +} + // UniformDistribution selects either absl::uniform_int_distribution // or absl::uniform_real_distribution depending on the NumType parameter. template diff --git a/absl/random/internal/uniform_helper_test.cc b/absl/random/internal/uniform_helper_test.cc new file mode 100644 index 00000000..173c49b0 --- /dev/null +++ b/absl/random/internal/uniform_helper_test.cc @@ -0,0 +1,279 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/random/internal/uniform_helper.h" + +#include +#include +#include + +#include "gtest/gtest.h" + +namespace { + +using absl::IntervalClosedClosedTag; +using absl::IntervalClosedOpenTag; +using absl::IntervalOpenClosedTag; +using absl::IntervalOpenOpenTag; +using absl::random_internal::uniform_inferred_return_t; +using absl::random_internal::uniform_lower_bound; +using absl::random_internal::uniform_upper_bound; + +class UniformHelperTest : public testing::Test {}; + +TEST_F(UniformHelperTest, UniformBoundFunctionsGeneral) { + constexpr IntervalClosedClosedTag IntervalClosedClosed; + constexpr IntervalClosedOpenTag IntervalClosedOpen; + constexpr IntervalOpenClosedTag IntervalOpenClosed; + constexpr IntervalOpenOpenTag IntervalOpenOpen; + + // absl::uniform_int_distribution natively assumes IntervalClosedClosed + // absl::uniform_real_distribution natively assumes IntervalClosedOpen + + EXPECT_EQ(uniform_lower_bound(IntervalOpenClosed, 0, 100), 1); + EXPECT_EQ(uniform_lower_bound(IntervalOpenOpen, 0, 100), 1); + EXPECT_GT(uniform_lower_bound(IntervalOpenClosed, 0, 1.0), 0); + EXPECT_GT(uniform_lower_bound(IntervalOpenOpen, 0, 1.0), 0); + EXPECT_GT(uniform_lower_bound(IntervalOpenClosed, 0, 1.0), 0); + EXPECT_GT(uniform_lower_bound(IntervalOpenOpen, 0, 1.0), 0); + + EXPECT_EQ(uniform_lower_bound(IntervalClosedClosed, 0, 100), 0); + EXPECT_EQ(uniform_lower_bound(IntervalClosedOpen, 0, 100), 0); + EXPECT_EQ(uniform_lower_bound(IntervalClosedClosed, 0, 1.0), 0); + EXPECT_EQ(uniform_lower_bound(IntervalClosedOpen, 0, 1.0), 0); + EXPECT_EQ(uniform_lower_bound(IntervalClosedClosed, 0, 1.0), 0); + EXPECT_EQ(uniform_lower_bound(IntervalClosedOpen, 0, 1.0), 0); + + EXPECT_EQ(uniform_upper_bound(IntervalOpenOpen, 0, 100), 99); + EXPECT_EQ(uniform_upper_bound(IntervalClosedOpen, 0, 100), 99); + EXPECT_EQ(uniform_upper_bound(IntervalOpenOpen, 0, 1.0), 1.0); + EXPECT_EQ(uniform_upper_bound(IntervalClosedOpen, 0, 1.0), 1.0); + EXPECT_EQ(uniform_upper_bound(IntervalOpenOpen, 0, 1.0), 1.0); + EXPECT_EQ(uniform_upper_bound(IntervalClosedOpen, 0, 1.0), 1.0); + + EXPECT_EQ(uniform_upper_bound(IntervalOpenClosed, 0, 100), 100); + EXPECT_EQ(uniform_upper_bound(IntervalClosedClosed, 0, 100), 100); + EXPECT_GT(uniform_upper_bound(IntervalOpenClosed, 0, 1.0), 1.0); + EXPECT_GT(uniform_upper_bound(IntervalClosedClosed, 0, 1.0), 1.0); + EXPECT_GT(uniform_upper_bound(IntervalOpenClosed, 0, 1.0), 1.0); + EXPECT_GT(uniform_upper_bound(IntervalClosedClosed, 0, 1.0), 1.0); + + // Negative value tests + EXPECT_EQ(uniform_lower_bound(IntervalOpenClosed, -100, -1), -99); + EXPECT_EQ(uniform_lower_bound(IntervalOpenOpen, -100, -1), -99); + EXPECT_GT(uniform_lower_bound(IntervalOpenClosed, -2.0, -1.0), -2.0); + EXPECT_GT(uniform_lower_bound(IntervalOpenOpen, -2.0, -1.0), -2.0); + EXPECT_GT(uniform_lower_bound(IntervalOpenClosed, -2.0, -1.0), -2.0); + EXPECT_GT(uniform_lower_bound(IntervalOpenOpen, -2.0, -1.0), -2.0); + + EXPECT_EQ(uniform_lower_bound(IntervalClosedClosed, -100, -1), -100); + EXPECT_EQ(uniform_lower_bound(IntervalClosedOpen, -100, -1), -100); + EXPECT_EQ(uniform_lower_bound(IntervalClosedClosed, -2.0, -1.0), -2.0); + EXPECT_EQ(uniform_lower_bound(IntervalClosedOpen, -2.0, -1.0), -2.0); + EXPECT_EQ(uniform_lower_bound(IntervalClosedClosed, -2.0, -1.0), + -2.0); + EXPECT_EQ(uniform_lower_bound(IntervalClosedOpen, -2.0, -1.0), -2.0); + + EXPECT_EQ(uniform_upper_bound(IntervalOpenOpen, -100, -1), -2); + EXPECT_EQ(uniform_upper_bound(IntervalClosedOpen, -100, -1), -2); + EXPECT_EQ(uniform_upper_bound(IntervalOpenOpen, -2.0, -1.0), -1.0); + EXPECT_EQ(uniform_upper_bound(IntervalClosedOpen, -2.0, -1.0), -1.0); + EXPECT_EQ(uniform_upper_bound(IntervalOpenOpen, -2.0, -1.0), -1.0); + EXPECT_EQ(uniform_upper_bound(IntervalClosedOpen, -2.0, -1.0), -1.0); + + EXPECT_EQ(uniform_upper_bound(IntervalOpenClosed, -100, -1), -1); + EXPECT_EQ(uniform_upper_bound(IntervalClosedClosed, -100, -1), -1); + EXPECT_GT(uniform_upper_bound(IntervalOpenClosed, -2.0, -1.0), -1.0); + EXPECT_GT(uniform_upper_bound(IntervalClosedClosed, -2.0, -1.0), -1.0); + EXPECT_GT(uniform_upper_bound(IntervalOpenClosed, -2.0, -1.0), -1.0); + EXPECT_GT(uniform_upper_bound(IntervalClosedClosed, -2.0, -1.0), + -1.0); + + EXPECT_GT(uniform_lower_bound(IntervalOpenClosed, 1.0, 2.0), 1.0); + EXPECT_LT(uniform_lower_bound(IntervalOpenClosed, 1.0, +0.0), 1.0); + EXPECT_LT(uniform_lower_bound(IntervalOpenClosed, 1.0, -0.0), 1.0); + EXPECT_LT(uniform_lower_bound(IntervalOpenClosed, 1.0, -1.0), 1.0); +} + +TEST_F(UniformHelperTest, UniformBoundFunctionsIntBounds) { + // Verifies the saturating nature of uniform_lower_bound and + // uniform_upper_bound + constexpr IntervalOpenOpenTag IntervalOpenOpen; + + // uint max. + constexpr auto m = (std::numeric_limits::max)(); + + EXPECT_EQ(1, uniform_lower_bound(IntervalOpenOpen, 0u, 0u)); + EXPECT_EQ(m, uniform_lower_bound(IntervalOpenOpen, m, m)); + EXPECT_EQ(m, uniform_lower_bound(IntervalOpenOpen, m - 1, m - 1)); + EXPECT_EQ(0, uniform_upper_bound(IntervalOpenOpen, 0u, 0u)); + EXPECT_EQ(m - 1, uniform_upper_bound(IntervalOpenOpen, m, m)); + + // int min/max + constexpr auto l = (std::numeric_limits::min)(); + constexpr auto r = (std::numeric_limits::max)(); + EXPECT_EQ(1, uniform_lower_bound(IntervalOpenOpen, 0, 0)); + EXPECT_EQ(l + 1, uniform_lower_bound(IntervalOpenOpen, l, l)); + EXPECT_EQ(r, uniform_lower_bound(IntervalOpenOpen, r - 1, r - 1)); + EXPECT_EQ(r, uniform_lower_bound(IntervalOpenOpen, r, r)); + EXPECT_EQ(-1, uniform_upper_bound(IntervalOpenOpen, 0, 0)); + EXPECT_EQ(l, uniform_upper_bound(IntervalOpenOpen, l, l)); + EXPECT_EQ(r - 1, uniform_upper_bound(IntervalOpenOpen, r, r)); +} + +TEST_F(UniformHelperTest, UniformBoundFunctionsRealBounds) { + // absl::uniform_real_distribution natively assumes IntervalClosedOpen; + // use the inverse here so each bound has to change. + constexpr IntervalOpenClosedTag IntervalOpenClosed; + + // Edge cases: the next value toward itself is itself. + EXPECT_EQ(1.0, uniform_lower_bound(IntervalOpenClosed, 1.0, 1.0)); + EXPECT_EQ(1.0f, uniform_lower_bound(IntervalOpenClosed, 1.0f, 1.0f)); + + // rightmost and leftmost finite values. + constexpr auto r = (std::numeric_limits::max)(); + const auto re = std::nexttoward(r, 0.0); + constexpr auto l = -r; + const auto le = std::nexttoward(l, 0.0); + + EXPECT_EQ(l, uniform_lower_bound(IntervalOpenClosed, l, l)); // (l,l) + EXPECT_EQ(r, uniform_lower_bound(IntervalOpenClosed, r, r)); // (r,r) + EXPECT_EQ(le, uniform_lower_bound(IntervalOpenClosed, l, r)); // (l,r) + EXPECT_EQ(le, uniform_lower_bound(IntervalOpenClosed, l, 0.0)); // (l, 0) + EXPECT_EQ(le, uniform_lower_bound(IntervalOpenClosed, l, le)); // (l, le) + EXPECT_EQ(r, uniform_lower_bound(IntervalOpenClosed, re, r)); // (re, r) + + EXPECT_EQ(le, uniform_upper_bound(IntervalOpenClosed, l, l)); // (l,l) + EXPECT_EQ(r, uniform_upper_bound(IntervalOpenClosed, r, r)); // (r,r) + EXPECT_EQ(r, uniform_upper_bound(IntervalOpenClosed, l, r)); // (l,r) + EXPECT_EQ(r, uniform_upper_bound(IntervalOpenClosed, l, re)); // (l,re) + EXPECT_EQ(r, uniform_upper_bound(IntervalOpenClosed, 0.0, r)); // (0, r) + EXPECT_EQ(r, uniform_upper_bound(IntervalOpenClosed, re, r)); // (re, r) + EXPECT_EQ(r, uniform_upper_bound(IntervalOpenClosed, le, re)); // (le, re) + + const double e = std::nextafter(1.0, 2.0); // 1 + epsilon + const double f = std::nextafter(1.0, 0.0); // 1 - epsilon + + // (1.0, 1.0 + epsilon) + EXPECT_EQ(e, uniform_lower_bound(IntervalOpenClosed, 1.0, e)); + EXPECT_EQ(std::nextafter(e, 2.0), + uniform_upper_bound(IntervalOpenClosed, 1.0, e)); + + // (1.0-epsilon, 1.0) + EXPECT_EQ(1.0, uniform_lower_bound(IntervalOpenClosed, f, 1.0)); + EXPECT_EQ(e, uniform_upper_bound(IntervalOpenClosed, f, 1.0)); + + // denorm cases. + const double g = std::numeric_limits::denorm_min(); + const double h = std::nextafter(g, 1.0); + + // (0, denorm_min) + EXPECT_EQ(g, uniform_lower_bound(IntervalOpenClosed, 0.0, g)); + EXPECT_EQ(h, uniform_upper_bound(IntervalOpenClosed, 0.0, g)); + + // (denorm_min, 1.0) + EXPECT_EQ(h, uniform_lower_bound(IntervalOpenClosed, g, 1.0)); + EXPECT_EQ(e, uniform_upper_bound(IntervalOpenClosed, g, 1.0)); + + // Edge cases: invalid bounds. + EXPECT_EQ(f, uniform_lower_bound(IntervalOpenClosed, 1.0, -1.0)); +} + +struct Invalid {}; + +template +auto InferredUniformReturnT(int) -> uniform_inferred_return_t; + +template +Invalid InferredUniformReturnT(...); + +// Given types , CheckArgsInferType() verifies that +// +// uniform_inferred_return_t and +// uniform_inferred_return_t +// +// returns the type "Expect". +// +// This interface can also be used to assert that a given inferred return types +// are invalid. Writing: +// +// CheckArgsInferType() +// +// will assert that this overload does not exist. +template +void CheckArgsInferType() { + static_assert( + absl::conjunction< + std::is_same(0))>, + std::is_same(0))>>::value, + ""); +} + +TEST_F(UniformHelperTest, UniformTypeInference) { + // Infers common types. + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + + // Properly promotes uint16_t. + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + + // Properly promotes int16_t. + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + + // Invalid (u)int16_t-pairings do not compile. + // See "CheckArgsInferType" comments above, for how this is achieved. + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + + // Properly promotes uint32_t. + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + + // Properly promotes int32_t. + CheckArgsInferType(); + CheckArgsInferType(); + + // Invalid (u)int32_t-pairings do not compile. + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + + // Invalid (u)int64_t-pairings do not compile. + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + + // Properly promotes float. + CheckArgsInferType(); +} + +} // namespace diff --git a/absl/strings/cord.h b/absl/strings/cord.h index a0db2797..b8b251b0 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -125,9 +125,9 @@ class Cord { absl::enable_if_t::value, int>; public: - // Cord::Cord() Constructors + // Cord::Cord() Constructors. - // Creates an empty Cord + // Creates an empty Cord. constexpr Cord() noexcept; // Creates a Cord from an existing Cord. Cord is copyable and efficiently @@ -153,7 +153,7 @@ class Cord { // Cord::~Cord() // - // Destructs the Cord + // Destructs the Cord. ~Cord() { if (contents_.is_tree()) DestroyCordSlow(); } @@ -587,7 +587,7 @@ class Cord { // Cord::operator[] // - // Get the "i"th character of the Cord and returns it, provided that + // Gets the "i"th character of the Cord and returns it, provided that // 0 <= i < Cord.size(). // // NOTE: This routine is reasonably efficient. It is roughly @@ -599,8 +599,8 @@ class Cord { // Cord::TryFlat() // - // If this cord's representation is a single flat array, return a - // string_view referencing that array. Otherwise return nullopt. + // If this cord's representation is a single flat array, returns a + // string_view referencing that array. Otherwise returns nullopt. absl::optional TryFlat() const; // Cord::Flatten() @@ -610,7 +610,7 @@ class Cord { // If the cord was already flat, the contents are not modified. absl::string_view Flatten(); - // Support absl::Cord as a sink object for absl::Format(). + // Supports absl::Cord as a sink object for absl::Format(). friend void AbslFormatFlush(absl::Cord* cord, absl::string_view part) { cord->Append(part); } @@ -629,7 +629,7 @@ class Cord { friend bool operator==(const Cord& lhs, const Cord& rhs); friend bool operator==(const Cord& lhs, absl::string_view rhs); - // Call the provided function once for each cord chunk, in order. Unlike + // Calls the provided function once for each cord chunk, in order. Unlike // Chunks(), this API will not allocate memory. void ForEachChunk(absl::FunctionRef) const; @@ -673,7 +673,7 @@ class Cord { void replace_tree(absl::cord_internal::CordRep* rep); // Returns non-null iff was holding a pointer absl::cord_internal::CordRep* clear(); - // Convert to pointer if necessary + // Converts to pointer if necessary. absl::cord_internal::CordRep* force_tree(size_t extra_hint); void reduce_size(size_t n); // REQUIRES: holding data void remove_prefix(size_t n); // REQUIRES: holding data @@ -731,14 +731,14 @@ class Cord { }; InlineRep contents_; - // Helper for MemoryUsage() + // Helper for MemoryUsage(). static size_t MemoryUsageAux(const absl::cord_internal::CordRep* rep); - // Helper for GetFlat() and TryFlat() + // Helper for GetFlat() and TryFlat(). static bool GetFlatAux(absl::cord_internal::CordRep* rep, absl::string_view* fragment); - // Helper for ForEachChunk() + // Helper for ForEachChunk(). static void ForEachChunkAux( absl::cord_internal::CordRep* rep, absl::FunctionRef callback); @@ -767,11 +767,11 @@ class Cord { absl::cord_internal::CordRep* TakeRep() const&; absl::cord_internal::CordRep* TakeRep() &&; - // Helper for Append() + // Helper for Append(). template void AppendImpl(C&& src); - // Helper for AbslHashValue() + // Helper for AbslHashValue(). template H HashFragmented(H hash_state) const { typename H::AbslInternalPiecewiseCombiner combiner; diff --git a/absl/types/any.h b/absl/types/any.h index 7eed5197..fc5a0746 100644 --- a/absl/types/any.h +++ b/absl/types/any.h @@ -47,9 +47,9 @@ // this abstraction, make sure that you should not instead be rewriting your // code to be more specific. // -// Abseil expects to release an `absl::variant` type shortly (a C++11 compatible -// version of the C++17 `std::variant), which is generally preferred for use -// over `absl::any`. +// Abseil has also released an `absl::variant` type (a C++11 compatible version +// of the C++17 `std::variant`), which is generally preferred for use over +// `absl::any`. #ifndef ABSL_TYPES_ANY_H_ #define ABSL_TYPES_ANY_H_ -- cgit v1.2.3 From d1de75bf540f091b4dfc860713d556e578c0f158 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Fri, 25 Sep 2020 10:29:25 -0700 Subject: Export of internal Abseil changes -- f50d25c8f8491ef7031cbbcad78edd15f98c2bd1 by Abseil Team : Add myriad2 to HAVE_MMAP Remove mutex_nonprod and associated defines. PiperOrigin-RevId: 333759830 -- 25ef4c577ea983aa3fcd6cfe2af6cdc62a06f520 by Samuel Benzaquen : Internal refactor. Represent the data with a union to allow for better constexpr support in the future. PiperOrigin-RevId: 333756733 GitOrigin-RevId: f50d25c8f8491ef7031cbbcad78edd15f98c2bd1 Change-Id: Ieecd2c47cb20de638726eb3f9fc2e5682d05dcca --- absl/base/config.h | 2 +- absl/strings/cord.cc | 85 +++---- absl/strings/cord.h | 95 ++++--- absl/strings/internal/cord_internal.h | 39 +++ absl/synchronization/BUILD.bazel | 6 +- absl/synchronization/CMakeLists.txt | 1 - absl/synchronization/internal/mutex_nonprod.cc | 325 ------------------------ absl/synchronization/internal/mutex_nonprod.inc | 249 ------------------ absl/synchronization/mutex.h | 34 --- absl/synchronization/mutex_test.cc | 4 - 10 files changed, 128 insertions(+), 712 deletions(-) delete mode 100644 absl/synchronization/internal/mutex_nonprod.cc delete mode 100644 absl/synchronization/internal/mutex_nonprod.inc (limited to 'absl/strings/cord.h') diff --git a/absl/base/config.h b/absl/base/config.h index c1d0494e..3f7f32b9 100644 --- a/absl/base/config.h +++ b/absl/base/config.h @@ -364,7 +364,7 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || #elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \ defined(__ros__) || defined(__native_client__) || defined(__asmjs__) || \ defined(__wasm__) || defined(__Fuchsia__) || defined(__sun) || \ - defined(__ASYLO__) + defined(__ASYLO__) || defined(__myriad2__) #define ABSL_HAVE_MMAP 1 #endif diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index 763dcc45..5092fd14 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc @@ -50,16 +50,10 @@ using ::absl::cord_internal::CordRepConcat; using ::absl::cord_internal::CordRepExternal; using ::absl::cord_internal::CordRepSubstring; -// Various representations that we allow -enum CordRepKind { - CONCAT = 0, - EXTERNAL = 1, - SUBSTRING = 2, - - // We have different tags for different sized flat arrays, - // starting with FLAT - FLAT = 3, -}; +using ::absl::cord_internal::CONCAT; +using ::absl::cord_internal::EXTERNAL; +using ::absl::cord_internal::FLAT; +using ::absl::cord_internal::SUBSTRING; namespace cord_internal { @@ -447,48 +441,49 @@ inline void Cord::InlineRep::set_data(const char* data, size_t n, bool nullify_tail) { static_assert(kMaxInline == 15, "set_data is hard-coded for a length of 15"); - cord_internal::SmallMemmove(data_, data, n, nullify_tail); - data_[kMaxInline] = static_cast(n); + cord_internal::SmallMemmove(data_.as_chars, data, n, nullify_tail); + set_tagged_size(static_cast(n)); } inline char* Cord::InlineRep::set_data(size_t n) { assert(n <= kMaxInline); - memset(data_, 0, sizeof(data_)); - data_[kMaxInline] = static_cast(n); - return data_; + ResetToEmpty(); + set_tagged_size(static_cast(n)); + return data_.as_chars; } inline CordRep* Cord::InlineRep::force_tree(size_t extra_hint) { - size_t len = data_[kMaxInline]; - CordRep* result; + size_t len = tagged_size(); if (len > kMaxInline) { - memcpy(&result, data_, sizeof(result)); - } else { - result = NewFlat(len + extra_hint); - result->length = len; - memcpy(result->data, data_, len); - set_tree(result); + return data_.as_tree.rep; } + + CordRep* result = NewFlat(len + extra_hint); + result->length = len; + static_assert(kMinFlatLength >= sizeof(data_.as_chars), ""); + memcpy(result->data, data_.as_chars, sizeof(data_.as_chars)); + set_tree(result); return result; } inline void Cord::InlineRep::reduce_size(size_t n) { - size_t tag = data_[kMaxInline]; + size_t tag = tagged_size(); assert(tag <= kMaxInline); assert(tag >= n); tag -= n; - memset(data_ + tag, 0, n); - data_[kMaxInline] = static_cast(tag); + memset(data_.as_chars + tag, 0, n); + set_tagged_size(static_cast(tag)); } inline void Cord::InlineRep::remove_prefix(size_t n) { - cord_internal::SmallMemmove(data_, data_ + n, data_[kMaxInline] - n); + cord_internal::SmallMemmove(data_.as_chars, data_.as_chars + n, + tagged_size() - n); reduce_size(n); } void Cord::InlineRep::AppendTree(CordRep* tree) { if (tree == nullptr) return; - size_t len = data_[kMaxInline]; + size_t len = tagged_size(); if (len == 0) { set_tree(tree); } else { @@ -498,7 +493,7 @@ void Cord::InlineRep::AppendTree(CordRep* tree) { void Cord::InlineRep::PrependTree(CordRep* tree) { assert(tree != nullptr); - size_t len = data_[kMaxInline]; + size_t len = tagged_size(); if (len == 0) { set_tree(tree); } else { @@ -554,11 +549,11 @@ void Cord::InlineRep::GetAppendRegion(char** region, size_t* size, } // Try to fit in the inline buffer if possible. - size_t inline_length = data_[kMaxInline]; + size_t inline_length = tagged_size(); if (inline_length < kMaxInline && max_length <= kMaxInline - inline_length) { - *region = data_ + inline_length; + *region = data_.as_chars + inline_length; *size = max_length; - data_[kMaxInline] = static_cast(inline_length + max_length); + set_tagged_size(static_cast(inline_length + max_length)); return; } @@ -582,11 +577,11 @@ void Cord::InlineRep::GetAppendRegion(char** region, size_t* size) { const size_t max_length = std::numeric_limits::max(); // Try to fit in the inline buffer if possible. - size_t inline_length = data_[kMaxInline]; + size_t inline_length = tagged_size(); if (inline_length < kMaxInline) { - *region = data_ + inline_length; + *region = data_.as_chars + inline_length; *size = kMaxInline - inline_length; - data_[kMaxInline] = kMaxInline; + set_tagged_size(kMaxInline); return; } @@ -621,7 +616,7 @@ static bool RepMemoryUsageLeaf(const CordRep* rep, size_t* total_mem_usage) { void Cord::InlineRep::AssignSlow(const Cord::InlineRep& src) { ClearSlow(); - memcpy(data_, src.data_, sizeof(data_)); + data_ = src.data_; if (is_tree()) { Ref(tree()); } @@ -631,7 +626,7 @@ void Cord::InlineRep::ClearSlow() { if (is_tree()) { Unref(tree()); } - memset(data_, 0, sizeof(data_)); + ResetToEmpty(); } // -------------------------------------------------------------------- @@ -735,11 +730,11 @@ template Cord& Cord::operator=(std::string&& src); void Cord::InlineRep::AppendArray(const char* src_data, size_t src_size) { if (src_size == 0) return; // memcpy(_, nullptr, 0) is undefined. // Try to fit in the inline buffer if possible. - size_t inline_length = data_[kMaxInline]; + size_t inline_length = tagged_size(); if (inline_length < kMaxInline && src_size <= kMaxInline - inline_length) { // Append new data to embedded array - data_[kMaxInline] = static_cast(inline_length + src_size); - memcpy(data_ + inline_length, src_data, src_size); + set_tagged_size(static_cast(inline_length + src_size)); + memcpy(data_.as_chars + inline_length, src_data, src_size); return; } @@ -762,7 +757,7 @@ void Cord::InlineRep::AppendArray(const char* src_data, size_t src_size) { const size_t size2 = inline_length + src_size / 10; root = NewFlat(std::max(size1, size2)); appended = std::min(src_size, TagToLength(root->tag) - inline_length); - memcpy(root->data, data_, inline_length); + memcpy(root->data, data_.as_chars, inline_length); memcpy(root->data + inline_length, src_data, appended); root->length = inline_length + appended; set_tree(root); @@ -1071,7 +1066,7 @@ Cord Cord::Subcord(size_t pos, size_t new_size) const { } else if (new_size <= InlineRep::kMaxInline) { Cord::ChunkIterator it = chunk_begin(); it.AdvanceBytes(pos); - char* dest = sub_cord.contents_.data_; + char* dest = sub_cord.contents_.data_.as_chars; size_t remaining_size = new_size; while (remaining_size > it->size()) { cord_internal::SmallMemmove(dest, it->data(), it->size()); @@ -1080,7 +1075,7 @@ Cord Cord::Subcord(size_t pos, size_t new_size) const { ++it; } cord_internal::SmallMemmove(dest, it->data(), remaining_size); - sub_cord.contents_.data_[InlineRep::kMaxInline] = new_size; + sub_cord.contents_.set_tagged_size(new_size); } else { sub_cord.contents_.set_tree(NewSubRange(tree, pos, new_size)); } @@ -1269,9 +1264,9 @@ bool ComputeCompareResult(int memcmp_res) { // Helper routine. Locates the first flat chunk of the Cord without // initializing the iterator. inline absl::string_view Cord::InlineRep::FindFlatStartPiece() const { - size_t n = data_[kMaxInline]; + size_t n = tagged_size(); if (n <= kMaxInline) { - return absl::string_view(data_, n); + return absl::string_view(data_.as_chars, n); } CordRep* node = tree(); diff --git a/absl/strings/cord.h b/absl/strings/cord.h index b8b251b0..653a1181 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -644,14 +644,12 @@ class Cord { // InlineRep holds either a tree pointer, or an array of kMaxInline bytes. class InlineRep { public: - static constexpr unsigned char kMaxInline = 15; + static constexpr unsigned char kMaxInline = cord_internal::kMaxInline; static_assert(kMaxInline >= sizeof(absl::cord_internal::CordRep*), ""); - // Tag byte & kMaxInline means we are storing a pointer. - static constexpr unsigned char kTreeFlag = 1 << 4; - // Tag byte & kProfiledFlag means we are profiling the Cord. - static constexpr unsigned char kProfiledFlag = 1 << 5; + static constexpr unsigned char kTreeFlag = cord_internal::kTreeFlag; + static constexpr unsigned char kProfiledFlag = cord_internal::kProfiledFlag; - constexpr InlineRep() : data_{} {} + constexpr InlineRep() : data_() {} InlineRep(const InlineRep& src); InlineRep(InlineRep&& src); InlineRep& operator=(const InlineRep& src); @@ -684,16 +682,16 @@ class Cord { void GetAppendRegion(char** region, size_t* size, size_t max_length); void GetAppendRegion(char** region, size_t* size); bool IsSame(const InlineRep& other) const { - return memcmp(data_, other.data_, sizeof(data_)) == 0; + return memcmp(&data_, &other.data_, sizeof(data_)) == 0; } int BitwiseCompare(const InlineRep& other) const { uint64_t x, y; - // Use memcpy to avoid anti-aliasing issues. - memcpy(&x, data_, sizeof(x)); - memcpy(&y, other.data_, sizeof(y)); + // Use memcpy to avoid aliasing issues. + memcpy(&x, &data_, sizeof(x)); + memcpy(&y, &other.data_, sizeof(y)); if (x == y) { - memcpy(&x, data_ + 8, sizeof(x)); - memcpy(&y, other.data_ + 8, sizeof(y)); + memcpy(&x, reinterpret_cast(&data_) + 8, sizeof(x)); + memcpy(&y, reinterpret_cast(&other.data_) + 8, sizeof(y)); if (x == y) return 0; } return absl::big_endian::FromHost64(x) < absl::big_endian::FromHost64(y) @@ -706,16 +704,16 @@ class Cord { // to 15 bytes does not cause a memory allocation. absl::strings_internal::STLStringResizeUninitialized(dst, sizeof(data_) - 1); - memcpy(&(*dst)[0], data_, sizeof(data_) - 1); + memcpy(&(*dst)[0], &data_, sizeof(data_) - 1); // erase is faster than resize because the logic for memory allocation is // not needed. - dst->erase(data_[kMaxInline]); + dst->erase(tagged_size()); } // Copies the inline contents into `dst`. Assumes the cord is not empty. void CopyToArray(char* dst) const; - bool is_tree() const { return data_[kMaxInline] > kMaxInline; } + bool is_tree() const { return tagged_size() > kMaxInline; } private: friend class Cord; @@ -724,10 +722,18 @@ class Cord { // Unrefs the tree, stops profiling, and zeroes the contents void ClearSlow(); - // If the data has length <= kMaxInline, we store it in data_[0..len-1], - // and store the length in data_[kMaxInline]. Else we store it in a tree - // and store a pointer to that tree in data_[0..sizeof(CordRep*)-1]. - alignas(absl::cord_internal::CordRep*) char data_[kMaxInline + 1]; + void ResetToEmpty() { data_ = {}; } + + // This uses reinterpret_cast instead of the union to avoid accessing the + // inactive union element. The tagged size is not a common prefix. + void set_tagged_size(char new_tag) { + reinterpret_cast(&data_)[kMaxInline] = new_tag; + } + char tagged_size() const { + return reinterpret_cast(&data_)[kMaxInline]; + } + + cord_internal::InlineData data_; }; InlineRep contents_; @@ -879,12 +885,12 @@ Cord MakeCordFromExternal(absl::string_view data, Releaser&& releaser) { } inline Cord::InlineRep::InlineRep(const Cord::InlineRep& src) { - cord_internal::SmallMemmove(data_, src.data_, sizeof(data_)); + data_ = src.data_; } inline Cord::InlineRep::InlineRep(Cord::InlineRep&& src) { - memcpy(data_, src.data_, sizeof(data_)); - memset(src.data_, 0, sizeof(data_)); + data_ = src.data_; + src.ResetToEmpty(); } inline Cord::InlineRep& Cord::InlineRep::operator=(const Cord::InlineRep& src) { @@ -892,7 +898,7 @@ inline Cord::InlineRep& Cord::InlineRep::operator=(const Cord::InlineRep& src) { return *this; } if (!is_tree() && !src.is_tree()) { - cord_internal::SmallMemmove(data_, src.data_, sizeof(data_)); + data_ = src.data_; return *this; } AssignSlow(src); @@ -904,8 +910,8 @@ inline Cord::InlineRep& Cord::InlineRep::operator=( if (is_tree()) { ClearSlow(); } - memcpy(data_, src.data_, sizeof(data_)); - memset(src.data_, 0, sizeof(data_)); + data_ = src.data_; + src.ResetToEmpty(); return *this; } @@ -914,43 +920,39 @@ inline void Cord::InlineRep::Swap(Cord::InlineRep* rhs) { return; } - Cord::InlineRep tmp; - cord_internal::SmallMemmove(tmp.data_, data_, sizeof(data_)); - cord_internal::SmallMemmove(data_, rhs->data_, sizeof(data_)); - cord_internal::SmallMemmove(rhs->data_, tmp.data_, sizeof(data_)); + std::swap(data_, rhs->data_); } inline const char* Cord::InlineRep::data() const { - return is_tree() ? nullptr : data_; + return is_tree() ? nullptr : data_.as_chars; } inline absl::cord_internal::CordRep* Cord::InlineRep::tree() const { if (is_tree()) { - absl::cord_internal::CordRep* rep; - memcpy(&rep, data_, sizeof(rep)); - return rep; + return data_.as_tree.rep; } else { return nullptr; } } -inline bool Cord::InlineRep::empty() const { return data_[kMaxInline] == 0; } +inline bool Cord::InlineRep::empty() const { return tagged_size() == 0; } inline size_t Cord::InlineRep::size() const { - const char tag = data_[kMaxInline]; + const char tag = tagged_size(); if (tag <= kMaxInline) return tag; return static_cast(tree()->length); } inline void Cord::InlineRep::set_tree(absl::cord_internal::CordRep* rep) { if (rep == nullptr) { - memset(data_, 0, sizeof(data_)); + ResetToEmpty(); } else { bool was_tree = is_tree(); - memcpy(data_, &rep, sizeof(rep)); - memset(data_ + sizeof(rep), 0, sizeof(data_) - sizeof(rep) - 1); + data_.as_tree = {rep, {}, tagged_size()}; if (!was_tree) { - data_[kMaxInline] = kTreeFlag; + // If we were not a tree already, set the tag. + // Otherwise, leave it alone because it might have the profile bit on. + set_tagged_size(kTreeFlag); } } } @@ -961,25 +963,20 @@ inline void Cord::InlineRep::replace_tree(absl::cord_internal::CordRep* rep) { set_tree(rep); return; } - memcpy(data_, &rep, sizeof(rep)); - memset(data_ + sizeof(rep), 0, sizeof(data_) - sizeof(rep) - 1); + data_.as_tree = {rep, {}, tagged_size()}; } inline absl::cord_internal::CordRep* Cord::InlineRep::clear() { - const char tag = data_[kMaxInline]; - absl::cord_internal::CordRep* result = nullptr; - if (tag > kMaxInline) { - memcpy(&result, data_, sizeof(result)); - } - memset(data_, 0, sizeof(data_)); // Clear the cord + absl::cord_internal::CordRep* result = tree(); + ResetToEmpty(); return result; } inline void Cord::InlineRep::CopyToArray(char* dst) const { assert(!is_tree()); - size_t n = data_[kMaxInline]; + size_t n = tagged_size(); assert(n != 0); - cord_internal::SmallMemmove(dst, data_, n); + cord_internal::SmallMemmove(dst, data_.as_chars, n); } constexpr inline Cord::Cord() noexcept {} diff --git a/absl/strings/internal/cord_internal.h b/absl/strings/internal/cord_internal.h index d456eef8..00b9baa9 100644 --- a/absl/strings/internal/cord_internal.h +++ b/absl/strings/internal/cord_internal.h @@ -85,6 +85,17 @@ struct CordRepConcat; struct CordRepSubstring; struct CordRepExternal; +// Various representations that we allow +enum CordRepKind { + CONCAT = 0, + EXTERNAL = 1, + SUBSTRING = 2, + + // We have different tags for different sized flat arrays, + // starting with FLAT + FLAT = 3, +}; + struct CordRep { // The following three fields have to be less than 32 bytes since // that is the smallest supported flat node size. @@ -167,6 +178,34 @@ struct CordRepExternalImpl } }; +enum { + kMaxInline = 15, + // Tag byte & kMaxInline means we are storing a pointer. + kTreeFlag = 1 << 4, + // Tag byte & kProfiledFlag means we are profiling the Cord. + kProfiledFlag = 1 << 5 +}; + +// If the data has length <= kMaxInline, we store it in `as_chars`, and +// store the size in `tagged_size`. +// Else we store it in a tree and store a pointer to that tree in +// `as_tree.rep` and store a tag in `tagged_size`. +struct AsTree { + absl::cord_internal::CordRep* rep; + char padding[kMaxInline + 1 - sizeof(absl::cord_internal::CordRep*) - 1]; + char tagged_size; +}; +union InlineData { + constexpr InlineData() : as_chars{} {} + explicit constexpr InlineData(AsTree tree) : as_tree(tree) {} + + AsTree as_tree; + char as_chars[kMaxInline + 1]; +}; +static_assert(sizeof(InlineData) == kMaxInline + 1, ""); +static_assert(sizeof(AsTree) == sizeof(InlineData), ""); +static_assert(offsetof(AsTree, tagged_size) == kMaxInline, ""); + } // namespace cord_internal ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/synchronization/BUILD.bazel b/absl/synchronization/BUILD.bazel index 4d4d6806..b2df4131 100644 --- a/absl/synchronization/BUILD.bazel +++ b/absl/synchronization/BUILD.bazel @@ -73,15 +73,13 @@ cc_library( "internal/create_thread_identity.cc", "internal/per_thread_sem.cc", "internal/waiter.cc", + "mutex.cc", "notification.cc", - ] + select({ - "//conditions:default": ["mutex.cc"], - }), + ], hdrs = [ "barrier.h", "blocking_counter.h", "internal/create_thread_identity.h", - "internal/mutex_nonprod.inc", "internal/per_thread_sem.h", "internal/waiter.h", "mutex.h", diff --git a/absl/synchronization/CMakeLists.txt b/absl/synchronization/CMakeLists.txt index e5bc52fb..c255b03e 100644 --- a/absl/synchronization/CMakeLists.txt +++ b/absl/synchronization/CMakeLists.txt @@ -52,7 +52,6 @@ absl_cc_library( "barrier.h" "blocking_counter.h" "internal/create_thread_identity.h" - "internal/mutex_nonprod.inc" "internal/per_thread_sem.h" "internal/waiter.h" "mutex.h" diff --git a/absl/synchronization/internal/mutex_nonprod.cc b/absl/synchronization/internal/mutex_nonprod.cc deleted file mode 100644 index 334c3bc0..00000000 --- a/absl/synchronization/internal/mutex_nonprod.cc +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright 2017 The Abseil Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Implementation of a small subset of Mutex and CondVar functionality -// for platforms where the production implementation hasn't been fully -// ported yet. - -#include "absl/synchronization/mutex.h" - -#if defined(_WIN32) -#include // NOLINT(build/c++11) -#else -#include -#include -#endif - -#include - -#include "absl/base/config.h" -#include "absl/base/internal/raw_logging.h" -#include "absl/time/time.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN - -void SetMutexDeadlockDetectionMode(OnDeadlockCycle) {} -void EnableMutexInvariantDebugging(bool) {} - -namespace synchronization_internal { - -namespace { - -// Return the current time plus the timeout. -absl::Time DeadlineFromTimeout(absl::Duration timeout) { - return absl::Now() + timeout; -} - -// Limit the deadline to a positive, 32-bit time_t value to accommodate -// implementation restrictions. This also deals with InfinitePast and -// InfiniteFuture. -absl::Time LimitedDeadline(absl::Time deadline) { - deadline = std::max(absl::FromTimeT(0), deadline); - deadline = std::min(deadline, absl::FromTimeT(0x7fffffff)); - return deadline; -} - -} // namespace - -#if defined(_WIN32) - -MutexImpl::MutexImpl() {} - -MutexImpl::~MutexImpl() { - if (locked_) { - std_mutex_.unlock(); - } -} - -void MutexImpl::Lock() { - std_mutex_.lock(); - locked_ = true; -} - -bool MutexImpl::TryLock() { - bool locked = std_mutex_.try_lock(); - if (locked) locked_ = true; - return locked; -} - -void MutexImpl::Unlock() { - locked_ = false; - released_.SignalAll(); - std_mutex_.unlock(); -} - -CondVarImpl::CondVarImpl() {} - -CondVarImpl::~CondVarImpl() {} - -void CondVarImpl::Signal() { std_cv_.notify_one(); } - -void CondVarImpl::SignalAll() { std_cv_.notify_all(); } - -void CondVarImpl::Wait(MutexImpl* mu) { - mu->released_.SignalAll(); - std_cv_.wait(mu->std_mutex_); -} - -bool CondVarImpl::WaitWithDeadline(MutexImpl* mu, absl::Time deadline) { - mu->released_.SignalAll(); - time_t when = ToTimeT(deadline); - int64_t nanos = ToInt64Nanoseconds(deadline - absl::FromTimeT(when)); - std::chrono::system_clock::time_point deadline_tp = - std::chrono::system_clock::from_time_t(when) + - std::chrono::duration_cast( - std::chrono::nanoseconds(nanos)); - auto deadline_since_epoch = - std::chrono::duration_cast>( - deadline_tp - std::chrono::system_clock::from_time_t(0)); - return std_cv_.wait_until(mu->std_mutex_, deadline_tp) == - std::cv_status::timeout; -} - -#else // ! _WIN32 - -MutexImpl::MutexImpl() { - ABSL_RAW_CHECK(pthread_mutex_init(&pthread_mutex_, nullptr) == 0, - "pthread error"); -} - -MutexImpl::~MutexImpl() { - if (locked_) { - ABSL_RAW_CHECK(pthread_mutex_unlock(&pthread_mutex_) == 0, "pthread error"); - } - ABSL_RAW_CHECK(pthread_mutex_destroy(&pthread_mutex_) == 0, "pthread error"); -} - -void MutexImpl::Lock() { - ABSL_RAW_CHECK(pthread_mutex_lock(&pthread_mutex_) == 0, "pthread error"); - locked_ = true; -} - -bool MutexImpl::TryLock() { - bool locked = (0 == pthread_mutex_trylock(&pthread_mutex_)); - if (locked) locked_ = true; - return locked; -} - -void MutexImpl::Unlock() { - locked_ = false; - released_.SignalAll(); - ABSL_RAW_CHECK(pthread_mutex_unlock(&pthread_mutex_) == 0, "pthread error"); -} - -CondVarImpl::CondVarImpl() { - ABSL_RAW_CHECK(pthread_cond_init(&pthread_cv_, nullptr) == 0, - "pthread error"); -} - -CondVarImpl::~CondVarImpl() { - ABSL_RAW_CHECK(pthread_cond_destroy(&pthread_cv_) == 0, "pthread error"); -} - -void CondVarImpl::Signal() { - ABSL_RAW_CHECK(pthread_cond_signal(&pthread_cv_) == 0, "pthread error"); -} - -void CondVarImpl::SignalAll() { - ABSL_RAW_CHECK(pthread_cond_broadcast(&pthread_cv_) == 0, "pthread error"); -} - -void CondVarImpl::Wait(MutexImpl* mu) { - mu->released_.SignalAll(); - ABSL_RAW_CHECK(pthread_cond_wait(&pthread_cv_, &mu->pthread_mutex_) == 0, - "pthread error"); -} - -bool CondVarImpl::WaitWithDeadline(MutexImpl* mu, absl::Time deadline) { - mu->released_.SignalAll(); - struct timespec ts = ToTimespec(deadline); - int rc = pthread_cond_timedwait(&pthread_cv_, &mu->pthread_mutex_, &ts); - if (rc == ETIMEDOUT) return true; - ABSL_RAW_CHECK(rc == 0, "pthread error"); - return false; -} - -#endif // ! _WIN32 - -void MutexImpl::Await(const Condition& cond) { - if (cond.Eval()) return; - released_.SignalAll(); - do { - released_.Wait(this); - } while (!cond.Eval()); -} - -bool MutexImpl::AwaitWithDeadline(const Condition& cond, absl::Time deadline) { - if (cond.Eval()) return true; - released_.SignalAll(); - while (true) { - if (released_.WaitWithDeadline(this, deadline)) return false; - if (cond.Eval()) return true; - } -} - -} // namespace synchronization_internal - -Mutex::Mutex() {} - -Mutex::~Mutex() {} - -void Mutex::Lock() { impl()->Lock(); } - -void Mutex::Unlock() { impl()->Unlock(); } - -bool Mutex::TryLock() { return impl()->TryLock(); } - -void Mutex::ReaderLock() { Lock(); } - -void Mutex::ReaderUnlock() { Unlock(); } - -void Mutex::Await(const Condition& cond) { impl()->Await(cond); } - -void Mutex::LockWhen(const Condition& cond) { - Lock(); - Await(cond); -} - -bool Mutex::AwaitWithDeadline(const Condition& cond, absl::Time deadline) { - return impl()->AwaitWithDeadline( - cond, synchronization_internal::LimitedDeadline(deadline)); -} - -bool Mutex::AwaitWithTimeout(const Condition& cond, absl::Duration timeout) { - return AwaitWithDeadline( - cond, synchronization_internal::DeadlineFromTimeout(timeout)); -} - -bool Mutex::LockWhenWithDeadline(const Condition& cond, absl::Time deadline) { - Lock(); - return AwaitWithDeadline(cond, deadline); -} - -bool Mutex::LockWhenWithTimeout(const Condition& cond, absl::Duration timeout) { - return LockWhenWithDeadline( - cond, synchronization_internal::DeadlineFromTimeout(timeout)); -} - -void Mutex::ReaderLockWhen(const Condition& cond) { - ReaderLock(); - Await(cond); -} - -bool Mutex::ReaderLockWhenWithTimeout(const Condition& cond, - absl::Duration timeout) { - return LockWhenWithTimeout(cond, timeout); -} -bool Mutex::ReaderLockWhenWithDeadline(const Condition& cond, - absl::Time deadline) { - return LockWhenWithDeadline(cond, deadline); -} - -void Mutex::EnableDebugLog(const char*) {} -void Mutex::EnableInvariantDebugging(void (*)(void*), void*) {} -void Mutex::ForgetDeadlockInfo() {} -void Mutex::AssertHeld() const {} -void Mutex::AssertReaderHeld() const {} -void Mutex::AssertNotHeld() const {} - -CondVar::CondVar() {} - -CondVar::~CondVar() {} - -void CondVar::Signal() { impl()->Signal(); } - -void CondVar::SignalAll() { impl()->SignalAll(); } - -void CondVar::Wait(Mutex* mu) { return impl()->Wait(mu->impl()); } - -bool CondVar::WaitWithDeadline(Mutex* mu, absl::Time deadline) { - return impl()->WaitWithDeadline( - mu->impl(), synchronization_internal::LimitedDeadline(deadline)); -} - -bool CondVar::WaitWithTimeout(Mutex* mu, absl::Duration timeout) { - return WaitWithDeadline(mu, absl::Now() + timeout); -} - -void CondVar::EnableDebugLog(const char*) {} - -#ifdef ABSL_HAVE_THREAD_SANITIZER -extern "C" void __tsan_read1(void *addr); -#else -#define __tsan_read1(addr) // do nothing if TSan not enabled -#endif - -// A function that just returns its argument, dereferenced -static bool Dereference(void *arg) { - // ThreadSanitizer does not instrument this file for memory accesses. - // This function dereferences a user variable that can participate - // in a data race, so we need to manually tell TSan about this memory access. - __tsan_read1(arg); - return *(static_cast(arg)); -} - -Condition::Condition() {} // null constructor, used for kTrue only -const Condition Condition::kTrue; - -Condition::Condition(bool (*func)(void *), void *arg) - : eval_(&CallVoidPtrFunction), - function_(func), - method_(nullptr), - arg_(arg) {} - -bool Condition::CallVoidPtrFunction(const Condition *c) { - return (*c->function_)(c->arg_); -} - -Condition::Condition(const bool *cond) - : eval_(CallVoidPtrFunction), - function_(Dereference), - method_(nullptr), - // const_cast is safe since Dereference does not modify arg - arg_(const_cast(cond)) {} - -bool Condition::Eval() const { - // eval_ == null for kTrue - return (this->eval_ == nullptr) || (*this->eval_)(this); -} - -void RegisterSymbolizer(bool (*)(const void*, char*, int)) {} - -ABSL_NAMESPACE_END -} // namespace absl diff --git a/absl/synchronization/internal/mutex_nonprod.inc b/absl/synchronization/internal/mutex_nonprod.inc deleted file mode 100644 index d83bc8a9..00000000 --- a/absl/synchronization/internal/mutex_nonprod.inc +++ /dev/null @@ -1,249 +0,0 @@ -// Do not include. This is an implementation detail of base/mutex.h. -// -// Declares three classes: -// -// base::internal::MutexImpl - implementation helper for Mutex -// base::internal::CondVarImpl - implementation helper for CondVar -// base::internal::SynchronizationStorage - implementation helper for -// Mutex, CondVar - -#include - -#if defined(_WIN32) -#include -#include -#else -#include -#endif - -#include "absl/base/call_once.h" -#include "absl/time/time.h" - -// Declare that Mutex::ReaderLock is actually Lock(). Intended primarily -// for tests, and even then as a last resort. -#ifdef ABSL_MUTEX_READER_LOCK_IS_EXCLUSIVE -#error ABSL_MUTEX_READER_LOCK_IS_EXCLUSIVE cannot be directly set -#else -#define ABSL_MUTEX_READER_LOCK_IS_EXCLUSIVE 1 -#endif - -// Declare that Mutex::EnableInvariantDebugging is not implemented. -// Intended primarily for tests, and even then as a last resort. -#ifdef ABSL_MUTEX_ENABLE_INVARIANT_DEBUGGING_NOT_IMPLEMENTED -#error ABSL_MUTEX_ENABLE_INVARIANT_DEBUGGING_NOT_IMPLEMENTED cannot be directly set -#else -#define ABSL_MUTEX_ENABLE_INVARIANT_DEBUGGING_NOT_IMPLEMENTED 1 -#endif - -namespace absl { -ABSL_NAMESPACE_BEGIN -class Condition; - -namespace synchronization_internal { - -class MutexImpl; - -// Do not use this implementation detail of CondVar. Provides most of the -// implementation, but should not be placed directly in static storage -// because it will not linker initialize properly. See -// SynchronizationStorage below for what we mean by linker -// initialization. -class CondVarImpl { - public: - CondVarImpl(); - CondVarImpl(const CondVarImpl&) = delete; - CondVarImpl& operator=(const CondVarImpl&) = delete; - ~CondVarImpl(); - - void Signal(); - void SignalAll(); - void Wait(MutexImpl* mutex); - bool WaitWithDeadline(MutexImpl* mutex, absl::Time deadline); - - private: -#if defined(_WIN32) - std::condition_variable_any std_cv_; -#else - pthread_cond_t pthread_cv_; -#endif -}; - -// Do not use this implementation detail of Mutex. Provides most of the -// implementation, but should not be placed directly in static storage -// because it will not linker initialize properly. See -// SynchronizationStorage below for what we mean by linker -// initialization. -class MutexImpl { - public: - MutexImpl(); - MutexImpl(const MutexImpl&) = delete; - MutexImpl& operator=(const MutexImpl&) = delete; - ~MutexImpl(); - - void Lock(); - bool TryLock(); - void Unlock(); - void Await(const Condition& cond); - bool AwaitWithDeadline(const Condition& cond, absl::Time deadline); - - private: - friend class CondVarImpl; - -#if defined(_WIN32) - std::mutex std_mutex_; -#else - pthread_mutex_t pthread_mutex_; -#endif - - // True if the underlying mutex is locked. If the destructor is entered - // while locked_, the underlying mutex is unlocked. Mutex supports - // destruction while locked, but the same is undefined behavior for both - // pthread_mutex_t and std::mutex. - bool locked_ = false; - - // Signaled before releasing the lock, in support of Await. - CondVarImpl released_; -}; - -// Do not use this implementation detail of CondVar and Mutex. A storage -// space for T that supports a LinkerInitialized constructor. T must -// have a default constructor, which is called by the first call to -// get(). T's destructor is never called if the LinkerInitialized -// constructor is called. -// -// Objects constructed with the default constructor are constructed and -// destructed like any other object, and should never be allocated in -// static storage. -// -// Objects constructed with the LinkerInitialized constructor should -// always be in static storage. For such objects, calls to get() are always -// valid, except from signal handlers. -// -// Note that this implementation relies on undefined language behavior that -// are known to hold for the set of supported compilers. An analysis -// follows. -// -// From the C++11 standard: -// -// [basic.life] says an object has non-trivial initialization if it is of -// class type and it is initialized by a constructor other than a trivial -// default constructor. (the LinkerInitialized constructor is -// non-trivial) -// -// [basic.life] says the lifetime of an object with a non-trivial -// constructor begins when the call to the constructor is complete. -// -// [basic.life] says the lifetime of an object with non-trivial destructor -// ends when the call to the destructor begins. -// -// [basic.life] p5 specifies undefined behavior when accessing non-static -// members of an instance outside its -// lifetime. (SynchronizationStorage::get() access non-static members) -// -// So, LinkerInitialized object of SynchronizationStorage uses a -// non-trivial constructor, which is called at some point during dynamic -// initialization, and is therefore subject to order of dynamic -// initialization bugs, where get() is called before the object's -// constructor is, resulting in undefined behavior. -// -// Similarly, a LinkerInitialized SynchronizationStorage object has a -// non-trivial destructor, and so its lifetime ends at some point during -// destruction of objects with static storage duration [basic.start.term] -// p4. There is a window where other exit code could call get() after this -// occurs, resulting in undefined behavior. -// -// Combined, these statements imply that LinkerInitialized instances -// of SynchronizationStorage rely on undefined behavior. -// -// However, in practice, the implementation works on all supported -// compilers. Specifically, we rely on: -// -// a) zero-initialization being sufficient to initialize -// LinkerInitialized instances for the purposes of calling -// get(), regardless of when the constructor is called. This is -// because the is_dynamic_ boolean is correctly zero-initialized to -// false. -// -// b) the LinkerInitialized constructor is a NOP, and immaterial to -// even to concurrent calls to get(). -// -// c) the destructor being a NOP for LinkerInitialized objects -// (guaranteed by a check for !is_dynamic_), and so any concurrent and -// subsequent calls to get() functioning as if the destructor were not -// called, by virtue of the instances' storage remaining valid after the -// destructor runs. -// -// d) That a-c apply transitively when SynchronizationStorage is the -// only member of a class allocated in static storage. -// -// Nothing in the language standard guarantees that a-d hold. In practice, -// these hold in all supported compilers. -// -// Future direction: -// -// Ideally, we would simply use std::mutex or a similar class, which when -// allocated statically would support use immediately after static -// initialization up until static storage is reclaimed (i.e. the properties -// we require of all "linker initialized" instances). -// -// Regarding construction in static storage, std::mutex is required to -// provide a constexpr default constructor [thread.mutex.class], which -// ensures the instance's lifetime begins with static initialization -// [basic.start.init], and so is immune to any problems caused by the order -// of dynamic initialization. However, as of this writing Microsoft's -// Visual Studio does not provide a constexpr constructor for std::mutex. -// See -// https://blogs.msdn.microsoft.com/vcblog/2015/06/02/constexpr-complete-for-vs-2015-rtm-c11-compiler-c17-stl/ -// -// Regarding destruction of instances in static storage, [basic.life] does -// say an object ends when storage in which the occupies is released, in -// the case of non-trivial destructor. However, std::mutex is not specified -// to have a trivial destructor. -// -// So, we would need a class with a constexpr default constructor and a -// trivial destructor. Today, we can achieve neither desired property using -// std::mutex directly. -template -class SynchronizationStorage { - public: - // Instances allocated on the heap or on the stack should use the default - // constructor. - SynchronizationStorage() - : destruct_(true), once_() {} - - constexpr explicit SynchronizationStorage(absl::ConstInitType) - : destruct_(false), once_(), space_{{0}} {} - - SynchronizationStorage(SynchronizationStorage&) = delete; - SynchronizationStorage& operator=(SynchronizationStorage&) = delete; - - ~SynchronizationStorage() { - if (destruct_) { - get()->~T(); - } - } - - // Retrieve the object in storage. This is fast and thread safe, but does - // incur the cost of absl::call_once(). - T* get() { - absl::call_once(once_, SynchronizationStorage::Construct, this); - return reinterpret_cast(&space_); - } - - private: - static void Construct(SynchronizationStorage* self) { - new (&self->space_) T(); - } - - // When true, T's destructor is run when this is destructed. - const bool destruct_; - - absl::once_flag once_; - - // An aligned space for the T. - alignas(T) unsigned char space_[sizeof(T)]; -}; - -} // namespace synchronization_internal -ABSL_NAMESPACE_END -} // namespace absl diff --git a/absl/synchronization/mutex.h b/absl/synchronization/mutex.h index 52401fe3..6340bd63 100644 --- a/absl/synchronization/mutex.h +++ b/absl/synchronization/mutex.h @@ -72,15 +72,6 @@ #include "absl/synchronization/internal/per_thread_sem.h" #include "absl/time/time.h" -// Decide if we should use the non-production implementation because -// the production implementation hasn't been fully ported yet. -#ifdef ABSL_INTERNAL_USE_NONPROD_MUTEX -#error ABSL_INTERNAL_USE_NONPROD_MUTEX cannot be directly set -#elif defined(ABSL_LOW_LEVEL_ALLOC_MISSING) -#define ABSL_INTERNAL_USE_NONPROD_MUTEX 1 -#include "absl/synchronization/internal/mutex_nonprod.inc" -#endif - namespace absl { ABSL_NAMESPACE_BEGIN @@ -461,15 +452,6 @@ class ABSL_LOCKABLE Mutex { static void InternalAttemptToUseMutexInFatalSignalHandler(); private: -#ifdef ABSL_INTERNAL_USE_NONPROD_MUTEX - friend class CondVar; - - synchronization_internal::MutexImpl *impl() { return impl_.get(); } - - synchronization_internal::SynchronizationStorage< - synchronization_internal::MutexImpl> - impl_; -#else std::atomic mu_; // The Mutex state. // Post()/Wait() versus associated PerThreadSem; in class for required @@ -504,7 +486,6 @@ class ABSL_LOCKABLE Mutex { void Trans(MuHow how); // used for CondVar->Mutex transfer void Fer( base_internal::PerThreadSynch *w); // used for CondVar->Mutex transfer -#endif // Catch the error of writing Mutex when intending MutexLock. Mutex(const volatile Mutex * /*ignored*/) {} // NOLINT(runtime/explicit) @@ -838,17 +819,10 @@ class CondVar { void EnableDebugLog(const char *name); private: -#ifdef ABSL_INTERNAL_USE_NONPROD_MUTEX - synchronization_internal::CondVarImpl *impl() { return impl_.get(); } - synchronization_internal::SynchronizationStorage< - synchronization_internal::CondVarImpl> - impl_; -#else bool WaitCommon(Mutex *mutex, synchronization_internal::KernelTimeout t); void Remove(base_internal::PerThreadSynch *s); void Wakeup(base_internal::PerThreadSynch *w); std::atomic cv_; // Condition variable state. -#endif CondVar(const CondVar&) = delete; CondVar& operator=(const CondVar&) = delete; }; @@ -906,12 +880,6 @@ class ABSL_SCOPED_LOCKABLE ReleasableMutexLock { ReleasableMutexLock& operator=(ReleasableMutexLock&&) = delete; }; -#ifdef ABSL_INTERNAL_USE_NONPROD_MUTEX - -inline constexpr Mutex::Mutex(absl::ConstInitType) : impl_(absl::kConstInit) {} - -#else - inline Mutex::Mutex() : mu_(0) { ABSL_TSAN_MUTEX_CREATE(this, __tsan_mutex_not_static); } @@ -920,8 +888,6 @@ inline constexpr Mutex::Mutex(absl::ConstInitType) : mu_(0) {} inline CondVar::CondVar() : cv_(0) {} -#endif // ABSL_INTERNAL_USE_NONPROD_MUTEX - // static template bool Condition::CastAndCallMethod(const Condition *c) { diff --git a/absl/synchronization/mutex_test.cc b/absl/synchronization/mutex_test.cc index 16fc9058..66d5f2aa 100644 --- a/absl/synchronization/mutex_test.cc +++ b/absl/synchronization/mutex_test.cc @@ -1002,9 +1002,6 @@ TEST(Mutex, AcquireFromCondition) { x.mu0.Unlock(); } -// The deadlock detector is not part of non-prod builds, so do not test it. -#if !defined(ABSL_INTERNAL_USE_NONPROD_MUTEX) - TEST(Mutex, DeadlockDetector) { absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort); @@ -1158,7 +1155,6 @@ TEST(Mutex, DeadlockIdBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { c.Lock(); c.Unlock(); } -#endif // !defined(ABSL_INTERNAL_USE_NONPROD_MUTEX) // -------------------------------------------------------- // Test for timeouts/deadlines on condition waits that are specified using -- cgit v1.2.3 From 4b915e70929ca2d6152240facc83d3d38c5d5f28 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Mon, 19 Oct 2020 15:25:26 -0700 Subject: Export of internal Abseil changes -- 77c85460dc3c46593b231c5161ac55273bb0c7ef by Abseil Team : Support int128 in SimpleAtoi. PiperOrigin-RevId: 337946262 -- 0be53049ccf8309650e4e22f23b290e5f75ee828 by Gennadiy Rozental : Update build scripts to use --mount instead of --volume to mount the host data into a container. This is somewhat more verbose, but more readable and flexible. More importantly this is what docker documentation is recomending to use now. PiperOrigin-RevId: 337898761 -- 3bc877f1679fdf61ecbf4365287a0403cfc9b53e by Samuel Benzaquen : Add Cord constructor for constinit instances. PiperOrigin-RevId: 337871148 -- 8b87701892b9c325e78ad4e8e4f16b785a744622 by Chris Kennelly : Use the address of kSeed, rather than its value. We initialize kSeed with &kSeed, so these are equivalent, but kSeed requires a load from memory while &kSeed can be formed as a RIP-relative lea. PiperOrigin-RevId: 337868415 GitOrigin-RevId: 77c85460dc3c46593b231c5161ac55273bb0c7ef Change-Id: I3d06c18a123f1be29dad5801e8625952dc41cd95 --- CMake/AbseilDll.cmake | 1 + absl/hash/internal/hash.h | 7 ++ absl/strings/BUILD.bazel | 14 +++ absl/strings/CMakeLists.txt | 14 +++ absl/strings/cord.cc | 2 + absl/strings/cord.h | 26 ++++++ absl/strings/cord_test.cc | 82 ++++++++++++++++++ absl/strings/internal/cord_internal.h | 82 +++++++++++++++--- absl/strings/internal/string_constant.h | 70 +++++++++++++++ absl/strings/internal/string_constant_test.cc | 60 +++++++++++++ absl/strings/numbers.cc | 120 +++++++++++++++++++++++++- absl/strings/numbers.h | 7 ++ absl/strings/numbers_test.cc | 76 +++++++++++++++- ci/cmake_install_test.sh | 2 +- ci/linux_clang-latest_libcxx_asan_bazel.sh | 6 +- ci/linux_clang-latest_libcxx_bazel.sh | 6 +- ci/linux_clang-latest_libcxx_tsan_bazel.sh | 6 +- ci/linux_clang-latest_libstdcxx_bazel.sh | 6 +- ci/linux_gcc-4.9_libstdcxx_bazel.sh | 6 +- ci/linux_gcc-latest_libstdcxx_bazel.sh | 6 +- ci/linux_gcc-latest_libstdcxx_cmake.sh | 2 +- ci/linux_gcc_alpine_cmake.sh | 2 +- 22 files changed, 567 insertions(+), 36 deletions(-) create mode 100644 absl/strings/internal/string_constant.h create mode 100644 absl/strings/internal/string_constant_test.cc (limited to 'absl/strings/cord.h') diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index d67c4399..e0ff2492 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake @@ -196,6 +196,7 @@ set(ABSL_INTERNAL_DLL_FILES "strings/internal/charconv_parse.cc" "strings/internal/charconv_parse.h" "strings/internal/stl_type_traits.h" + "strings/internal/string_constant.h" "strings/match.cc" "strings/match.h" "strings/numbers.cc" diff --git a/absl/hash/internal/hash.h b/absl/hash/internal/hash.h index 9e608f7c..b0132da2 100644 --- a/absl/hash/internal/hash.h +++ b/absl/hash/internal/hash.h @@ -855,7 +855,14 @@ class ABSL_DLL CityHashState // On other platforms this is still going to be non-deterministic but most // probably per-build and not per-process. ABSL_ATTRIBUTE_ALWAYS_INLINE static uint64_t Seed() { +#if (!defined(__clang__) || __clang_major__ > 11) && \ + !defined(__apple_build_version__) + return static_cast(reinterpret_cast(&kSeed)); +#else + // Workaround the absence of + // https://github.com/llvm/llvm-project/commit/bc15bf66dcca76cc06fe71fca35b74dc4d521021. return static_cast(reinterpret_cast(kSeed)); +#endif } static const void* const kSeed; diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index 64a13cef..30a8dd28 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -54,6 +54,7 @@ cc_library( "ascii.h", "charconv.h", "escaping.h", + "internal/string_constant.h", "match.h", "numbers.h", "str_cat.h", @@ -222,6 +223,19 @@ cc_test( ], ) +cc_test( + name = "string_constant_test", + size = "small", + srcs = ["internal/string_constant_test.cc"], + copts = ABSL_TEST_COPTS, + visibility = ["//visibility:private"], + deps = [ + ":strings", + "//absl/meta:type_traits", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "string_view_benchmark", srcs = ["string_view_benchmark.cc"], diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index d6c2126d..2b994a71 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt @@ -21,6 +21,7 @@ absl_cc_library( "ascii.h" "charconv.h" "escaping.h" + "internal/string_constant.h" "match.h" "numbers.h" "str_cat.h" @@ -158,6 +159,19 @@ absl_cc_test( gmock_main ) +absl_cc_test( + NAME + string_constant_test + SRCS + "internal/string_constant_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::strings + absl::type_traits + gmock_main +) + absl_cc_test( NAME string_view_test diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index 5092fd14..9efd1357 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc @@ -241,6 +241,7 @@ static void UnrefInternal(CordRep* rep) { absl::InlinedVector pending; while (true) { + assert(!rep->refcount.IsImmortal()); if (rep->tag == CONCAT) { CordRepConcat* rep_concat = rep->concat(); CordRep* right = rep_concat->right; @@ -256,6 +257,7 @@ static void UnrefInternal(CordRep* rep) { } } else if (rep->tag == EXTERNAL) { CordRepExternal* rep_external = rep->external(); + assert(rep_external->releaser_invoker != nullptr); rep_external->releaser_invoker(rep_external); rep = nullptr; } else if (rep->tag == SUBSTRING) { diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 653a1181..5d5c897e 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -79,6 +79,7 @@ #include "absl/meta/type_traits.h" #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/resize_uninitialized.h" +#include "absl/strings/internal/string_constant.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" @@ -624,6 +625,14 @@ class Cord { return c.HashFragmented(std::move(hash_state)); } + // Create a Cord with the contents of StringConstant::value. + // No allocations will be done and no data will be copied. + // This is an INTERNAL API and subject to change or removal. This API can only + // be used by spelling absl::strings_internal::MakeStringConstant, which is + // also an internal API. + template + explicit constexpr Cord(strings_internal::StringConstant); + private: friend class CordTestPeer; friend bool operator==(const Cord& lhs, const Cord& rhs); @@ -655,6 +664,8 @@ class Cord { InlineRep& operator=(const InlineRep& src); InlineRep& operator=(InlineRep&& src) noexcept; + explicit constexpr InlineRep(cord_internal::InlineData data); + void Swap(InlineRep* rhs); bool empty() const; size_t size() const; @@ -884,6 +895,9 @@ Cord MakeCordFromExternal(absl::string_view data, Releaser&& releaser) { return cord; } +constexpr Cord::InlineRep::InlineRep(cord_internal::InlineData data) + : data_(data) {} + inline Cord::InlineRep::InlineRep(const Cord::InlineRep& src) { data_ = src.data_; } @@ -981,6 +995,18 @@ inline void Cord::InlineRep::CopyToArray(char* dst) const { constexpr inline Cord::Cord() noexcept {} +template +constexpr Cord::Cord(strings_internal::StringConstant) + : contents_(strings_internal::StringConstant::value.size() <= + cord_internal::kMaxInline + ? cord_internal::InlineData( + strings_internal::StringConstant::value) + : cord_internal::InlineData(cord_internal::AsTree{ + &cord_internal::ConstInitExternalStorage< + strings_internal::StringConstant>::value, + {}, + cord_internal::kTreeFlag})) {} + inline Cord& Cord::operator=(const Cord& x) { contents_ = x.contents_; return *this; diff --git a/absl/strings/cord_test.cc b/absl/strings/cord_test.cc index dbed3e9a..7942bfc0 100644 --- a/absl/strings/cord_test.cc +++ b/absl/strings/cord_test.cc @@ -181,6 +181,8 @@ class CordTestPeer { const Cord& c, absl::FunctionRef callback) { c.ForEachChunk(callback); } + + static bool IsTree(const Cord& c) { return c.contents_.is_tree(); } }; ABSL_NAMESPACE_END @@ -1627,3 +1629,83 @@ TEST(CordDeathTest, Hardening) { EXPECT_DEATH_IF_SUPPORTED(static_cast(cord.chunk_end()->empty()), ""); EXPECT_DEATH_IF_SUPPORTED(++cord.chunk_end(), ""); } + +class AfterExitCordTester { + public: + bool Set(absl::Cord* cord, absl::string_view expected) { + cord_ = cord; + expected_ = expected; + return true; + } + + ~AfterExitCordTester() { + EXPECT_EQ(*cord_, expected_); + } + private: + absl::Cord* cord_; + absl::string_view expected_; +}; + +template +void TestConstinitConstructor(Str) { + const auto expected = Str::value; + // Defined before `cord` to be destroyed after it. + static AfterExitCordTester exit_tester; // NOLINT + ABSL_CONST_INIT static absl::Cord cord(Str{}); // NOLINT + static bool init_exit_tester = exit_tester.Set(&cord, expected); + (void)init_exit_tester; + + EXPECT_EQ(cord, expected); + // Copy the object and test the copy, and the original. + { + absl::Cord copy = cord; + EXPECT_EQ(copy, expected); + } + // The original still works + EXPECT_EQ(cord, expected); + + // Try making adding more structure to the tree. + { + absl::Cord copy = cord; + std::string expected_copy(expected); + for (int i = 0; i < 10; ++i) { + copy.Append(cord); + absl::StrAppend(&expected_copy, expected); + EXPECT_EQ(copy, expected_copy); + } + } + + // Make sure we are using the right branch during constant evaluation. + EXPECT_EQ(absl::CordTestPeer::IsTree(cord), cord.size() >= 16); + + for (int i = 0; i < 10; ++i) { + // Make a few more Cords from the same global rep. + // This tests what happens when the refcount for it gets below 1. + EXPECT_EQ(expected, absl::Cord(Str{})); + } +} + +constexpr int SimpleStrlen(const char* p) { + return *p ? 1 + SimpleStrlen(p + 1) : 0; +} + +struct ShortView { + constexpr absl::string_view operator()() const { + return absl::string_view("SSO string", SimpleStrlen("SSO string")); + } +}; + +struct LongView { + constexpr absl::string_view operator()() const { + return absl::string_view("String that does not fit SSO.", + SimpleStrlen("String that does not fit SSO.")); + } +}; + + +TEST(Cord, ConstinitConstructor) { + TestConstinitConstructor( + absl::strings_internal::MakeStringConstant(ShortView{})); + TestConstinitConstructor( + absl::strings_internal::MakeStringConstant(LongView{})); +} diff --git a/absl/strings/internal/cord_internal.h b/absl/strings/internal/cord_internal.h index 00b9baa9..aa91a691 100644 --- a/absl/strings/internal/cord_internal.h +++ b/absl/strings/internal/cord_internal.h @@ -33,14 +33,17 @@ namespace cord_internal { // Wraps std::atomic for reference counting. class Refcount { public: - Refcount() : count_{1} {} - ~Refcount() {} + constexpr Refcount() : count_{kRefIncrement} {} + struct Immortal {}; + explicit constexpr Refcount(Immortal) : count_(kImmortalTag) {} - // Increments the reference count by 1. Imposes no memory ordering. - inline void Increment() { count_.fetch_add(1, std::memory_order_relaxed); } + // Increments the reference count. Imposes no memory ordering. + inline void Increment() { + count_.fetch_add(kRefIncrement, std::memory_order_relaxed); + } // Asserts that the current refcount is greater than 0. If the refcount is - // greater than 1, decrements the reference count by 1. + // greater than 1, decrements the reference count. // // Returns false if there are no references outstanding; true otherwise. // Inserts barriers to ensure that state written before this method returns @@ -48,19 +51,24 @@ class Refcount { // false. inline bool Decrement() { int32_t refcount = count_.load(std::memory_order_acquire); - assert(refcount > 0); - return refcount != 1 && count_.fetch_sub(1, std::memory_order_acq_rel) != 1; + assert(refcount > 0 || refcount & kImmortalTag); + return refcount != kRefIncrement && + count_.fetch_sub(kRefIncrement, std::memory_order_acq_rel) != + kRefIncrement; } // Same as Decrement but expect that refcount is greater than 1. inline bool DecrementExpectHighRefcount() { - int32_t refcount = count_.fetch_sub(1, std::memory_order_acq_rel); - assert(refcount > 0); - return refcount != 1; + int32_t refcount = + count_.fetch_sub(kRefIncrement, std::memory_order_acq_rel); + assert(refcount > 0 || refcount & kImmortalTag); + return refcount != kRefIncrement; } // Returns the current reference count using acquire semantics. - inline int32_t Get() const { return count_.load(std::memory_order_acquire); } + inline int32_t Get() const { + return count_.load(std::memory_order_acquire) >> kImmortalShift; + } // Returns whether the atomic integer is 1. // If the reference count is used in the conventional way, a @@ -70,9 +78,27 @@ class Refcount { // performs the memory barrier needed for the owning thread // to act on the object, knowing that it has exclusive access to the // object. - inline bool IsOne() { return count_.load(std::memory_order_acquire) == 1; } + inline bool IsOne() { + return count_.load(std::memory_order_acquire) == kRefIncrement; + } + + bool IsImmortal() const { + return (count_.load(std::memory_order_relaxed) & kImmortalTag) != 0; + } private: + // We reserve the bottom bit to tag a reference count as immortal. + // By making it `1` we ensure that we never reach `0` when adding/subtracting + // `2`, thus it never looks as if it should be destroyed. + // These are used for the StringConstant constructor where we do not increase + // the refcount at construction time (due to constinit requirements) but we + // will still decrease it at destruction time to avoid branching on Unref. + enum { + kImmortalShift = 1, + kRefIncrement = 1 << kImmortalShift, + kImmortalTag = kRefIncrement - 1 + }; + std::atomic count_; }; @@ -97,6 +123,10 @@ enum CordRepKind { }; struct CordRep { + CordRep() = default; + constexpr CordRep(Refcount::Immortal immortal, size_t l) + : length(l), refcount(immortal), tag(EXTERNAL), data{} {} + // The following three fields have to be less than 32 bytes since // that is the smallest supported flat node size. size_t length; @@ -135,6 +165,12 @@ using ExternalReleaserInvoker = void (*)(CordRepExternal*); // External CordReps are allocated together with a type erased releaser. The // releaser is stored in the memory directly following the CordRepExternal. struct CordRepExternal : public CordRep { + CordRepExternal() = default; + explicit constexpr CordRepExternal(absl::string_view str) + : CordRep(Refcount::Immortal{}, str.size()), + base(str.data()), + releaser_invoker(nullptr) {} + const char* base; // Pointer to function that knows how to call and destroy the releaser. ExternalReleaserInvoker releaser_invoker; @@ -178,6 +214,14 @@ struct CordRepExternalImpl } }; +template +struct ConstInitExternalStorage { + ABSL_CONST_INIT static CordRepExternal value; +}; + +template +CordRepExternal ConstInitExternalStorage::value(Str::value); + enum { kMaxInline = 15, // Tag byte & kMaxInline means we are storing a pointer. @@ -195,9 +239,23 @@ struct AsTree { char padding[kMaxInline + 1 - sizeof(absl::cord_internal::CordRep*) - 1]; char tagged_size; }; + +constexpr char GetOrNull(absl::string_view data, size_t pos) { + return pos < data.size() ? data[pos] : '\0'; +} + union InlineData { constexpr InlineData() : as_chars{} {} explicit constexpr InlineData(AsTree tree) : as_tree(tree) {} + explicit constexpr InlineData(absl::string_view chars) + : as_chars{GetOrNull(chars, 0), GetOrNull(chars, 1), + GetOrNull(chars, 2), GetOrNull(chars, 3), + GetOrNull(chars, 4), GetOrNull(chars, 5), + GetOrNull(chars, 6), GetOrNull(chars, 7), + GetOrNull(chars, 8), GetOrNull(chars, 9), + GetOrNull(chars, 10), GetOrNull(chars, 11), + GetOrNull(chars, 12), GetOrNull(chars, 13), + GetOrNull(chars, 14), static_cast(chars.size())} {} AsTree as_tree; char as_chars[kMaxInline + 1]; diff --git a/absl/strings/internal/string_constant.h b/absl/strings/internal/string_constant.h new file mode 100644 index 00000000..b15f1d9b --- /dev/null +++ b/absl/strings/internal/string_constant.h @@ -0,0 +1,70 @@ +// Copyright 2020 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_STRINGS_INTERNAL_STRING_CONSTANT_H_ +#define ABSL_STRINGS_INTERNAL_STRING_CONSTANT_H_ + +#include "absl/meta/type_traits.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace strings_internal { + +// StringConstant represents a compile time string constant. +// It can be accessed via its `absl::string_view value` static member. +// It is guaranteed that the `string_view` returned has constant `.data()`, +// constant `.size()` and constant `value[i]` for all `0 <= i < .size()` +// +// The `T` is an opaque type. It is guaranteed that different string constants +// will have different values of `T`. This allows users to associate the string +// constant with other static state at compile time. +// +// Instances should be made using the `MakeStringConstant()` factory function +// below. +template +struct StringConstant { + private: + // Returns true if `view` points to constant data. + // Otherwise, it can't be constant evaluated. + static constexpr bool ValidateConstant(absl::string_view view) { + return view.empty() || 2 * view[0] != 1; + } + + public: + static constexpr absl::string_view value = T{}(); + constexpr absl::string_view operator()() const { return value; } + + static_assert(ValidateConstant(value), + "The input string_view must point to constant data."); +}; + +template +constexpr absl::string_view StringConstant::value; // NOLINT + +// Factory function for `StringConstant` instances. +// It supports callables that have a constexpr default constructor and a +// constexpr operator(). +// It must return an `absl::string_view` or `const char*` pointing to constant +// data. This is validated at compile time. +template +constexpr StringConstant MakeStringConstant(T) { + return {}; +} + +} // namespace strings_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_STRING_CONSTANT_H_ diff --git a/absl/strings/internal/string_constant_test.cc b/absl/strings/internal/string_constant_test.cc new file mode 100644 index 00000000..392833cf --- /dev/null +++ b/absl/strings/internal/string_constant_test.cc @@ -0,0 +1,60 @@ +// Copyright 2020 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/internal/string_constant.h" + +#include "absl/meta/type_traits.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace { + +using absl::strings_internal::MakeStringConstant; + +struct Callable { + constexpr absl::string_view operator()() const { + return absl::string_view("Callable", 8); + } +}; + +TEST(StringConstant, Traits) { + constexpr auto str = MakeStringConstant(Callable{}); + using T = decltype(str); + + EXPECT_TRUE(std::is_empty::value); + EXPECT_TRUE(std::is_trivial::value); + EXPECT_TRUE(absl::is_trivially_default_constructible::value); + EXPECT_TRUE(absl::is_trivially_copy_constructible::value); + EXPECT_TRUE(absl::is_trivially_move_constructible::value); + EXPECT_TRUE(absl::is_trivially_destructible::value); +} + +TEST(StringConstant, MakeFromCallable) { + constexpr auto str = MakeStringConstant(Callable{}); + using T = decltype(str); + EXPECT_EQ(Callable{}(), T::value); + EXPECT_EQ(Callable{}(), str()); +} + +TEST(StringConstant, MakeFromStringConstant) { + // We want to make sure the StringConstant itself is a valid input to the + // factory function. + constexpr auto str = MakeStringConstant(Callable{}); + constexpr auto str2 = MakeStringConstant(str); + using T = decltype(str2); + EXPECT_EQ(Callable{}(), T::value); + EXPECT_EQ(Callable{}(), str2()); +} + +} // namespace diff --git a/absl/strings/numbers.cc b/absl/strings/numbers.cc index 68c26dd6..3da1059c 100644 --- a/absl/strings/numbers.cc +++ b/absl/strings/numbers.cc @@ -736,9 +736,18 @@ struct LookupTables { X / 35, X / 36, \ } +// This kVmaxOverBase is generated with +// for (int base = 2; base < 37; ++base) { +// absl::uint128 max = std::numeric_limits::max(); +// auto result = max / base; +// std::cout << " MakeUint128(" << absl::Uint128High64(result) << "u, " +// << absl::Uint128Low64(result) << "u),\n"; +// } +// See https://godbolt.org/z/aneYsb +// // uint128& operator/=(uint128) is not constexpr, so hardcode the resulting // array to avoid a static initializer. -template <> +template<> const uint128 LookupTables::kVmaxOverBase[] = { 0, 0, @@ -779,6 +788,111 @@ const uint128 LookupTables::kVmaxOverBase[] = { MakeUint128(512409557603043100u, 8198552921648689607u), }; +// This kVmaxOverBase generated with +// for (int base = 2; base < 37; ++base) { +// absl::int128 max = std::numeric_limits::max(); +// auto result = max / base; +// std::cout << "\tMakeInt128(" << absl::Int128High64(result) << ", " +// << absl::Int128Low64(result) << "u),\n"; +// } +// See https://godbolt.org/z/7djYWz +// +// int128& operator/=(int128) is not constexpr, so hardcode the resulting array +// to avoid a static initializer. +template<> +const int128 LookupTables::kVmaxOverBase[] = { + 0, + 0, + MakeInt128(4611686018427387903, 18446744073709551615u), + MakeInt128(3074457345618258602, 12297829382473034410u), + MakeInt128(2305843009213693951, 18446744073709551615u), + MakeInt128(1844674407370955161, 11068046444225730969u), + MakeInt128(1537228672809129301, 6148914691236517205u), + MakeInt128(1317624576693539401, 2635249153387078802u), + MakeInt128(1152921504606846975, 18446744073709551615u), + MakeInt128(1024819115206086200, 16397105843297379214u), + MakeInt128(922337203685477580, 14757395258967641292u), + MakeInt128(838488366986797800, 13415813871788764811u), + MakeInt128(768614336404564650, 12297829382473034410u), + MakeInt128(709490156681136600, 11351842506898185609u), + MakeInt128(658812288346769700, 10540996613548315209u), + MakeInt128(614891469123651720, 9838263505978427528u), + MakeInt128(576460752303423487, 18446744073709551615u), + MakeInt128(542551296285575047, 9765923333140350855u), + MakeInt128(512409557603043100, 8198552921648689607u), + MakeInt128(485440633518672410, 17475862806672206794u), + MakeInt128(461168601842738790, 7378697629483820646u), + MakeInt128(439208192231179800, 7027331075698876806u), + MakeInt128(419244183493398900, 6707906935894382405u), + MakeInt128(401016175515425035, 2406097053092550210u), + MakeInt128(384307168202282325, 6148914691236517205u), + MakeInt128(368934881474191032, 5902958103587056517u), + MakeInt128(354745078340568300, 5675921253449092804u), + MakeInt128(341606371735362066, 17763531330238827482u), + MakeInt128(329406144173384850, 5270498306774157604u), + MakeInt128(318047311615681924, 7633135478776366185u), + MakeInt128(307445734561825860, 4919131752989213764u), + MakeInt128(297528130221121800, 4760450083537948804u), + MakeInt128(288230376151711743, 18446744073709551615u), + MakeInt128(279496122328932600, 4471937957262921603u), + MakeInt128(271275648142787523, 14106333703424951235u), + MakeInt128(263524915338707880, 4216398645419326083u), + MakeInt128(256204778801521550, 4099276460824344803u), +}; + +// This kVminOverBase generated with +// for (int base = 2; base < 37; ++base) { +// absl::int128 min = std::numeric_limits::min(); +// auto result = min / base; +// std::cout << "\tMakeInt128(" << absl::Int128High64(result) << ", " +// << absl::Int128Low64(result) << "u),\n"; +// } +// +// See https://godbolt.org/z/7djYWz +// +// int128& operator/=(int128) is not constexpr, so hardcode the resulting array +// to avoid a static initializer. +template<> +const int128 LookupTables::kVminOverBase[] = { + 0, + 0, + MakeInt128(-4611686018427387904, 0u), + MakeInt128(-3074457345618258603, 6148914691236517206u), + MakeInt128(-2305843009213693952, 0u), + MakeInt128(-1844674407370955162, 7378697629483820647u), + MakeInt128(-1537228672809129302, 12297829382473034411u), + MakeInt128(-1317624576693539402, 15811494920322472814u), + MakeInt128(-1152921504606846976, 0u), + MakeInt128(-1024819115206086201, 2049638230412172402u), + MakeInt128(-922337203685477581, 3689348814741910324u), + MakeInt128(-838488366986797801, 5030930201920786805u), + MakeInt128(-768614336404564651, 6148914691236517206u), + MakeInt128(-709490156681136601, 7094901566811366007u), + MakeInt128(-658812288346769701, 7905747460161236407u), + MakeInt128(-614891469123651721, 8608480567731124088u), + MakeInt128(-576460752303423488, 0u), + MakeInt128(-542551296285575048, 8680820740569200761u), + MakeInt128(-512409557603043101, 10248191152060862009u), + MakeInt128(-485440633518672411, 970881267037344822u), + MakeInt128(-461168601842738791, 11068046444225730970u), + MakeInt128(-439208192231179801, 11419412998010674810u), + MakeInt128(-419244183493398901, 11738837137815169211u), + MakeInt128(-401016175515425036, 16040647020617001406u), + MakeInt128(-384307168202282326, 12297829382473034411u), + MakeInt128(-368934881474191033, 12543785970122495099u), + MakeInt128(-354745078340568301, 12770822820260458812u), + MakeInt128(-341606371735362067, 683212743470724134u), + MakeInt128(-329406144173384851, 13176245766935394012u), + MakeInt128(-318047311615681925, 10813608594933185431u), + MakeInt128(-307445734561825861, 13527612320720337852u), + MakeInt128(-297528130221121801, 13686293990171602812u), + MakeInt128(-288230376151711744, 0u), + MakeInt128(-279496122328932601, 13974806116446630013u), + MakeInt128(-271275648142787524, 4340410370284600381u), + MakeInt128(-263524915338707881, 14230345428290225533u), + MakeInt128(-256204778801521551, 14347467612885206813u), +}; + template const IntType LookupTables::kVmaxOverBase[] = X_OVER_BASE_INITIALIZER(std::numeric_limits::max()); @@ -948,6 +1062,10 @@ bool safe_strto64_base(absl::string_view text, int64_t* value, int base) { return safe_int_internal(text, value, base); } +bool safe_strto128_base(absl::string_view text, int128* value, int base) { + return safe_int_internal(text, value, base); +} + bool safe_strtou32_base(absl::string_view text, uint32_t* value, int base) { return safe_uint_internal(text, value, base); } diff --git a/absl/strings/numbers.h b/absl/strings/numbers.h index d872cca5..2e004b44 100644 --- a/absl/strings/numbers.h +++ b/absl/strings/numbers.h @@ -127,6 +127,8 @@ inline void PutTwoDigits(size_t i, char* buf) { // safe_strto?() functions for implementing SimpleAtoi() bool safe_strto32_base(absl::string_view text, int32_t* value, int base); bool safe_strto64_base(absl::string_view text, int64_t* value, int base); +bool safe_strto128_base(absl::string_view text, absl::int128* value, + int base); bool safe_strtou32_base(absl::string_view text, uint32_t* value, int base); bool safe_strtou64_base(absl::string_view text, uint64_t* value, int base); bool safe_strtou128_base(absl::string_view text, absl::uint128* value, @@ -255,6 +257,11 @@ ABSL_MUST_USE_RESULT bool SimpleAtoi(absl::string_view str, int_type* out) { return numbers_internal::safe_strtoi_base(str, out, 10); } +ABSL_MUST_USE_RESULT inline bool SimpleAtoi(absl::string_view str, + absl::int128* out) { + return numbers_internal::safe_strto128_base(str, out, 10); +} + ABSL_MUST_USE_RESULT inline bool SimpleAtoi(absl::string_view str, absl::uint128* out) { return numbers_internal::safe_strtou128_base(str, out, 10); diff --git a/absl/strings/numbers_test.cc b/absl/strings/numbers_test.cc index c2f03b63..4ab67fb6 100644 --- a/absl/strings/numbers_test.cc +++ b/absl/strings/numbers_test.cc @@ -251,7 +251,7 @@ TEST(Numbers, TestFastPrints) { template void VerifySimpleAtoiGood(in_val_type in_value, int_type exp_value) { std::string s; - // uint128 can be streamed but not StrCat'd + // (u)int128 can be streamed but not StrCat'd. absl::strings_internal::OStringStream(&s) << in_value; int_type x = static_cast(~exp_value); EXPECT_TRUE(SimpleAtoi(s, &x)) @@ -264,7 +264,9 @@ void VerifySimpleAtoiGood(in_val_type in_value, int_type exp_value) { template void VerifySimpleAtoiBad(in_val_type in_value) { - std::string s = absl::StrCat(in_value); + std::string s; + // (u)int128 can be streamed but not StrCat'd. + absl::strings_internal::OStringStream(&s) << in_value; int_type x; EXPECT_FALSE(SimpleAtoi(s, &x)); EXPECT_FALSE(SimpleAtoi(s.c_str(), &x)); @@ -347,6 +349,31 @@ TEST(NumbersTest, Atoi) { std::numeric_limits::max(), std::numeric_limits::max()); + // SimpleAtoi(absl::string_view, absl::int128) + VerifySimpleAtoiGood(0, 0); + VerifySimpleAtoiGood(42, 42); + VerifySimpleAtoiGood(-42, -42); + + VerifySimpleAtoiGood(std::numeric_limits::min(), + std::numeric_limits::min()); + VerifySimpleAtoiGood(std::numeric_limits::max(), + std::numeric_limits::max()); + VerifySimpleAtoiGood(std::numeric_limits::max(), + std::numeric_limits::max()); + VerifySimpleAtoiGood(std::numeric_limits::min(), + std::numeric_limits::min()); + VerifySimpleAtoiGood(std::numeric_limits::max(), + std::numeric_limits::max()); + VerifySimpleAtoiGood(std::numeric_limits::max(), + std::numeric_limits::max()); + VerifySimpleAtoiGood( + std::numeric_limits::min(), + std::numeric_limits::min()); + VerifySimpleAtoiGood( + std::numeric_limits::max(), + std::numeric_limits::max()); + VerifySimpleAtoiBad(std::numeric_limits::max()); + // Some other types VerifySimpleAtoiGood(-42, -42); VerifySimpleAtoiGood(-42, -42); @@ -725,6 +752,51 @@ TEST(stringtest, safe_strtou128_random) { EXPECT_FALSE(parse_func(s, &parsed_value, base)); } } +TEST(stringtest, safe_strto128_random) { + // random number generators don't work for int128, and + // int128 can be streamed but not StrCat'd, so this code must be custom + // implemented for int128, but is generally the same as what's above. + // test_random_integer_parse_base( + // &absl::numbers_internal::safe_strto128_base); + using RandomEngine = std::minstd_rand0; + using IntType = absl::int128; + constexpr auto parse_func = &absl::numbers_internal::safe_strto128_base; + + std::random_device rd; + RandomEngine rng(rd()); + std::uniform_int_distribution random_int64( + std::numeric_limits::min()); + std::uniform_int_distribution random_uint64( + std::numeric_limits::min()); + std::uniform_int_distribution random_base(2, 35); + + for (size_t i = 0; i < kNumRandomTests; ++i) { + int64_t high = random_int64(rng); + uint64_t low = random_uint64(rng); + IntType value = absl::MakeInt128(high, low); + + int base = random_base(rng); + std::string str_value; + EXPECT_TRUE(Itoa(value, base, &str_value)); + IntType parsed_value; + + // Test successful parse + EXPECT_TRUE(parse_func(str_value, &parsed_value, base)); + EXPECT_EQ(parsed_value, value); + + // Test overflow + std::string s; + absl::strings_internal::OStringStream(&s) + << std::numeric_limits::max() << value; + EXPECT_FALSE(parse_func(s, &parsed_value, base)); + + // Test underflow + s.clear(); + absl::strings_internal::OStringStream(&s) + << std::numeric_limits::min() << value; + EXPECT_FALSE(parse_func(s, &parsed_value, base)); + } +} TEST(stringtest, safe_strtou32_base) { for (int i = 0; strtouint32_test_cases()[i].str != nullptr; ++i) { diff --git a/ci/cmake_install_test.sh b/ci/cmake_install_test.sh index b31e4b8c..78eb41d9 100755 --- a/ci/cmake_install_test.sh +++ b/ci/cmake_install_test.sh @@ -24,7 +24,7 @@ source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh" readonly DOCKER_CONTAINER=${LINUX_GCC_LATEST_CONTAINER} time docker run \ - --volume="${ABSEIL_ROOT}:/abseil-cpp:ro" \ + --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp,readonly \ --workdir=/abseil-cpp \ --tmpfs=/buildfs:exec \ --cap-add=SYS_PTRACE \ diff --git a/ci/linux_clang-latest_libcxx_asan_bazel.sh b/ci/linux_clang-latest_libcxx_asan_bazel.sh index 2aed43cf..da922a59 100755 --- a/ci/linux_clang-latest_libcxx_asan_bazel.sh +++ b/ci/linux_clang-latest_libcxx_asan_bazel.sh @@ -42,7 +42,7 @@ readonly DOCKER_CONTAINER=${LINUX_CLANG_LATEST_CONTAINER} # USE_BAZEL_CACHE=1 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:-}" + 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 @@ -55,7 +55,7 @@ fi # 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="--volume=${KOKORO_GFILE_DIR}/distdir:/distdir:ro ${DOCKER_EXTRA_ARGS:-}" + 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:-}" fi @@ -64,7 +64,7 @@ for std in ${STD}; do for exceptions_mode in ${EXCEPTIONS_MODE}; do echo "--------------------------------------------------------------------" time docker run \ - --volume="${ABSEIL_ROOT}:/abseil-cpp:ro" \ + --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp,readonly \ --workdir=/abseil-cpp \ --cap-add=SYS_PTRACE \ --rm \ diff --git a/ci/linux_clang-latest_libcxx_bazel.sh b/ci/linux_clang-latest_libcxx_bazel.sh index eb04e69e..06eaac55 100755 --- a/ci/linux_clang-latest_libcxx_bazel.sh +++ b/ci/linux_clang-latest_libcxx_bazel.sh @@ -42,7 +42,7 @@ readonly DOCKER_CONTAINER=${LINUX_CLANG_LATEST_CONTAINER} # USE_BAZEL_CACHE=1 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:-}" + 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 @@ -55,7 +55,7 @@ fi # 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="--volume=${KOKORO_GFILE_DIR}/distdir:/distdir:ro ${DOCKER_EXTRA_ARGS:-}" + 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:-}" fi @@ -64,7 +64,7 @@ for std in ${STD}; do for exceptions_mode in ${EXCEPTIONS_MODE}; do echo "--------------------------------------------------------------------" time docker run \ - --volume="${ABSEIL_ROOT}:/abseil-cpp-ro:ro" \ + --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp-ro,readonly \ --tmpfs=/abseil-cpp \ --workdir=/abseil-cpp \ --cap-add=SYS_PTRACE \ diff --git a/ci/linux_clang-latest_libcxx_tsan_bazel.sh b/ci/linux_clang-latest_libcxx_tsan_bazel.sh index b39eaf74..b2e722c5 100755 --- a/ci/linux_clang-latest_libcxx_tsan_bazel.sh +++ b/ci/linux_clang-latest_libcxx_tsan_bazel.sh @@ -42,7 +42,7 @@ readonly DOCKER_CONTAINER=${LINUX_CLANG_LATEST_CONTAINER} # USE_BAZEL_CACHE=1 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:-}" + 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 @@ -55,7 +55,7 @@ fi # 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="--volume=${KOKORO_GFILE_DIR}/distdir:/distdir:ro ${DOCKER_EXTRA_ARGS:-}" + 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:-}" fi @@ -64,7 +64,7 @@ for std in ${STD}; do for exceptions_mode in ${EXCEPTIONS_MODE}; do echo "--------------------------------------------------------------------" time docker run \ - --volume="${ABSEIL_ROOT}:/abseil-cpp:ro" \ + --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp,readonly \ --workdir=/abseil-cpp \ --cap-add=SYS_PTRACE \ --rm \ diff --git a/ci/linux_clang-latest_libstdcxx_bazel.sh b/ci/linux_clang-latest_libstdcxx_bazel.sh index 4e490676..f8ce3c6a 100755 --- a/ci/linux_clang-latest_libstdcxx_bazel.sh +++ b/ci/linux_clang-latest_libstdcxx_bazel.sh @@ -42,7 +42,7 @@ readonly DOCKER_CONTAINER=${LINUX_CLANG_LATEST_CONTAINER} # USE_BAZEL_CACHE=1 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:-}" + 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 @@ -55,7 +55,7 @@ fi # 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="--volume=${KOKORO_GFILE_DIR}/distdir:/distdir:ro ${DOCKER_EXTRA_ARGS:-}" + 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:-}" fi @@ -64,7 +64,7 @@ for std in ${STD}; do for exceptions_mode in ${EXCEPTIONS_MODE}; do echo "--------------------------------------------------------------------" time docker run \ - --volume="${ABSEIL_ROOT}:/abseil-cpp:ro" \ + --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp,readonly \ --workdir=/abseil-cpp \ --cap-add=SYS_PTRACE \ --rm \ diff --git a/ci/linux_gcc-4.9_libstdcxx_bazel.sh b/ci/linux_gcc-4.9_libstdcxx_bazel.sh index 8e6540cf..16f927bd 100755 --- a/ci/linux_gcc-4.9_libstdcxx_bazel.sh +++ b/ci/linux_gcc-4.9_libstdcxx_bazel.sh @@ -42,7 +42,7 @@ readonly DOCKER_CONTAINER=${LINUX_GCC_49_CONTAINER} # USE_BAZEL_CACHE=1 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:-}" + 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 @@ -55,7 +55,7 @@ fi # 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="--volume=${KOKORO_GFILE_DIR}/distdir:/distdir:ro ${DOCKER_EXTRA_ARGS:-}" + 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:-}" fi @@ -64,7 +64,7 @@ for std in ${STD}; do for exceptions_mode in ${EXCEPTIONS_MODE}; do echo "--------------------------------------------------------------------" time docker run \ - --volume="${ABSEIL_ROOT}:/abseil-cpp:ro" \ + --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp,readonly \ --workdir=/abseil-cpp \ --cap-add=SYS_PTRACE \ --rm \ diff --git a/ci/linux_gcc-latest_libstdcxx_bazel.sh b/ci/linux_gcc-latest_libstdcxx_bazel.sh index b327405c..37d89d9f 100755 --- a/ci/linux_gcc-latest_libstdcxx_bazel.sh +++ b/ci/linux_gcc-latest_libstdcxx_bazel.sh @@ -42,7 +42,7 @@ readonly DOCKER_CONTAINER=${LINUX_GCC_LATEST_CONTAINER} # USE_BAZEL_CACHE=1 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:-}" + 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 @@ -55,7 +55,7 @@ fi # 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="--volume=${KOKORO_GFILE_DIR}/distdir:/distdir:ro ${DOCKER_EXTRA_ARGS:-}" + 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:-}" fi @@ -64,7 +64,7 @@ for std in ${STD}; do for exceptions_mode in ${EXCEPTIONS_MODE}; do echo "--------------------------------------------------------------------" time docker run \ - --volume="${ABSEIL_ROOT}:/abseil-cpp-ro:ro" \ + --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp-ro,readonly \ --tmpfs=/abseil-cpp \ --workdir=/abseil-cpp \ --cap-add=SYS_PTRACE \ diff --git a/ci/linux_gcc-latest_libstdcxx_cmake.sh b/ci/linux_gcc-latest_libstdcxx_cmake.sh index 26415e23..1bf5fdab 100755 --- a/ci/linux_gcc-latest_libstdcxx_cmake.sh +++ b/ci/linux_gcc-latest_libstdcxx_cmake.sh @@ -45,7 +45,7 @@ for std in ${ABSL_CMAKE_CXX_STANDARDS}; do for compilation_mode in ${ABSL_CMAKE_BUILD_TYPES}; do for build_shared in ${ABSL_CMAKE_BUILD_SHARED}; do time docker run \ - --volume="${ABSEIL_ROOT}:/abseil-cpp:ro" \ + --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp,readonly \ --workdir=/abseil-cpp \ --tmpfs=/buildfs:exec \ --cap-add=SYS_PTRACE \ diff --git a/ci/linux_gcc_alpine_cmake.sh b/ci/linux_gcc_alpine_cmake.sh index b3b8e7a7..c8f173da 100755 --- a/ci/linux_gcc_alpine_cmake.sh +++ b/ci/linux_gcc_alpine_cmake.sh @@ -45,7 +45,7 @@ for std in ${ABSL_CMAKE_CXX_STANDARDS}; do for compilation_mode in ${ABSL_CMAKE_BUILD_TYPES}; do for build_shared in ${ABSL_CMAKE_BUILD_SHARED}; do time docker run \ - --volume="${ABSEIL_ROOT}:/abseil-cpp:ro" \ + --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp,readonly \ --workdir=/abseil-cpp \ --tmpfs=/buildfs:exec \ --cap-add=SYS_PTRACE \ -- cgit v1.2.3 From 938fd0f4e67ddb7dc321021968223317663156c5 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Thu, 10 Dec 2020 10:35:21 -0800 Subject: Export of internal Abseil changes -- 6e9f93888bbe6718997ed90bbd049f1f3572b066 by Abseil Team : Fix status to be safe for self move assignment. PiperOrigin-RevId: 346815392 -- 35cae74a977f258e81dfbe925fb5a34cb6632036 by Gennadiy Rozental : Eliminate unnecessary access to the global vars. PiperOrigin-RevId: 346777168 -- e7e786c243069060f5d6c1c945decb4b0b83f95b by Andy Getzendanner : Internal change. PiperOrigin-RevId: 346685656 -- 4ccd41c48f1a83cfa20b3ea534f743dd7d788376 by Abseil Team : Move CordRep Ref() and Unref() logic into cord_internal.h This change moves Ref() and Unref() logic out of cord.cc into cord_internal. The main purpose is to make upcoming ring buffer changes easier to isolate from existing cord.cc code. Notice that this removes the nullptr check from Unref() and now requires it to be non null (which held true most times). We may need to rethink if the 'unref unlikely one' is the common case: are cordreps most likely shared? This may be something between 'root' and non root nodes, i.e., is it more likely for leaf / flat nodes in large cords to be shared than top level cordreps being shared? Vice versa? Benchmarks say that we mostly shouldn't care, the caveat being that atomic ops seem more expensive on upcoming archs (arcadia) so we should error on the side of an extra IsOne() branch saving us single owned atomic ops. PiperOrigin-RevId: 346676121 -- f0babab103b9e60d61ba09482d468985e43eceb3 by Samuel Benzaquen : Fix iterator based constructor and `.insert` members to only require EmplaceConstructible as the standard specifies. PiperOrigin-RevId: 346616707 -- 8f48eedda02277f9c96a88ed7726e34b557cce20 by Evan Brown : Fix a bug in binary_search_impl when there's a transparent, three-way comparator that has different equivalence classes for different lookup types. Add a new can_have_multiple_equivalent_keys method to share the common logic for these cases. PiperOrigin-RevId: 346605948 -- 649183cb3cc9383431de9c81fb1c0f885d4001ae by Abseil Team : Add benchmark for accessing a Duration flag. PiperOrigin-RevId: 346594843 -- fefdb046520871af63ce2229e2f7cccfc0483dea by Abseil Team : Restructure CordReader for upcoming ring buffer changes. PiperOrigin-RevId: 346410642 -- 8b2f50e7da0ebab06ead5f94e366e984ca23cb6a by Abseil Team : Wire in an internal-only flag to toggle upcoming ring buffer changes on/off for experimentation. PiperOrigin-RevId: 346199111 GitOrigin-RevId: 6e9f93888bbe6718997ed90bbd049f1f3572b066 Change-Id: I8f34866b25a79209cb5448bbb28dd3044111d2e9 --- CMake/AbseilDll.cmake | 1 + absl/container/btree_test.cc | 40 ++++- absl/container/internal/btree.h | 42 ++--- absl/container/internal/raw_hash_set.h | 2 +- absl/container/internal/raw_hash_set_test.cc | 74 +++++++-- absl/flags/internal/usage.cc | 6 - absl/status/status.h | 8 +- absl/status/status_test.cc | 6 + absl/strings/BUILD.bazel | 10 +- absl/strings/CMakeLists.txt | 2 + absl/strings/cord.cc | 223 ++++++--------------------- absl/strings/cord.h | 1 + absl/strings/internal/cord_internal.cc | 76 +++++++++ absl/strings/internal/cord_internal.h | 107 +++++++++++++ absl/time/BUILD.bazel | 1 + absl/time/duration_benchmark.cc | 16 ++ 16 files changed, 394 insertions(+), 221 deletions(-) create mode 100644 absl/strings/internal/cord_internal.cc (limited to 'absl/strings/cord.h') diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index 68e28c22..be6e3484 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake @@ -192,6 +192,7 @@ set(ABSL_INTERNAL_DLL_FILES "strings/cord.h" "strings/escaping.cc" "strings/escaping.h" + "strings/internal/cord_internal.cc" "strings/internal/cord_internal.h" "strings/internal/cord_rep_flat.h" "strings/internal/charconv_bigint.cc" diff --git a/absl/container/btree_test.cc b/absl/container/btree_test.cc index 9b1b6436..a933386a 100644 --- a/absl/container/btree_test.cc +++ b/absl/container/btree_test.cc @@ -2696,8 +2696,36 @@ struct MultiKeyComp { bool operator()(const MultiKey a, const int b) const { return a.i1 < b; } }; -TEST(Btree, MultiKeyEqualRange) { - absl::btree_set set; +// A heterogeneous, three-way comparator that has different equivalence classes +// for different lookup types. +struct MultiKeyThreeWayComp { + using is_transparent = void; + absl::weak_ordering operator()(const MultiKey a, const MultiKey b) const { + if (a.i1 < b.i1) return absl::weak_ordering::less; + if (a.i1 > b.i1) return absl::weak_ordering::greater; + if (a.i2 < b.i2) return absl::weak_ordering::less; + if (a.i2 > b.i2) return absl::weak_ordering::greater; + return absl::weak_ordering::equivalent; + } + absl::weak_ordering operator()(const int a, const MultiKey b) const { + if (a < b.i1) return absl::weak_ordering::less; + if (a > b.i1) return absl::weak_ordering::greater; + return absl::weak_ordering::equivalent; + } + absl::weak_ordering operator()(const MultiKey a, const int b) const { + if (a.i1 < b) return absl::weak_ordering::less; + if (a.i1 > b) return absl::weak_ordering::greater; + return absl::weak_ordering::equivalent; + } +}; + +template +class BtreeMultiKeyTest : public ::testing::Test {}; +using MultiKeyComps = ::testing::Types; +TYPED_TEST_SUITE(BtreeMultiKeyTest, MultiKeyComps); + +TYPED_TEST(BtreeMultiKeyTest, EqualRange) { + absl::btree_set set; for (int i = 0; i < 100; ++i) { for (int j = 0; j < 100; ++j) { @@ -2713,15 +2741,15 @@ TEST(Btree, MultiKeyEqualRange) { } } -TEST(Btree, MultiKeyErase) { - absl::btree_set set = { +TYPED_TEST(BtreeMultiKeyTest, Erase) { + absl::btree_set set = { {1, 1}, {2, 1}, {2, 2}, {3, 1}}; EXPECT_EQ(set.erase(2), 2); EXPECT_THAT(set, ElementsAre(MultiKey{1, 1}, MultiKey{3, 1})); } -TEST(Btree, MultiKeyCount) { - const absl::btree_set set = { +TYPED_TEST(BtreeMultiKeyTest, Count) { + const absl::btree_set set = { {1, 1}, {2, 1}, {2, 2}, {3, 1}}; EXPECT_EQ(set.count(2), 2); } diff --git a/absl/container/internal/btree.h b/absl/container/internal/btree.h index dad580f5..d863cb30 100644 --- a/absl/container/internal/btree.h +++ b/absl/container/internal/btree.h @@ -220,9 +220,6 @@ struct common_params { // If Compare is a common comparator for a string-like type, then we adapt it // to use heterogeneous lookup and to be a key-compare-to comparator. using key_compare = typename key_compare_to_adapter::type; - // True when key_compare has been adapted to StringBtreeDefault{Less,Greater}. - using is_key_compare_adapted = - absl::negation>; // A type which indicates if we have a key-compare-to functor or a plain old // key-compare functor. using is_key_compare_to = btree_is_key_compare_to; @@ -232,9 +229,6 @@ struct common_params { using size_type = std::make_signed::type; using difference_type = ptrdiff_t; - // True if this is a multiset or multimap. - using is_multi_container = std::integral_constant; - using slot_policy = SlotPolicy; using slot_type = typename slot_policy::slot_type; using value_type = typename slot_policy::value_type; @@ -244,6 +238,23 @@ struct common_params { using reference = value_type &; using const_reference = const value_type &; + // For the given lookup key type, returns whether we can have multiple + // equivalent keys in the btree. If this is a multi-container, then we can. + // Otherwise, we can have multiple equivalent keys only if all of the + // following conditions are met: + // - The comparator is transparent. + // - The lookup key type is not the same as key_type. + // - The comparator is not a StringBtreeDefault{Less,Greater} comparator + // that we know has the same equivalence classes for all lookup types. + template + constexpr static bool can_have_multiple_equivalent_keys() { + return Multi || + (IsTransparent::value && + !std::is_same::value && + !std::is_same::value && + !std::is_same::value); + } + enum { kTargetNodeSize = TargetNodeSize, @@ -439,7 +450,6 @@ struct SearchResult { template class btree_node { using is_key_compare_to = typename Params::is_key_compare_to; - using is_multi_container = typename Params::is_multi_container; using field_type = typename Params::node_count_type; using allocator_type = typename Params::allocator_type; using slot_type = typename Params::slot_type; @@ -759,7 +769,7 @@ class btree_node { SearchResult binary_search_impl( const K &k, int s, int e, const CompareTo &comp, std::true_type /* IsCompareTo */) const { - if (is_multi_container::value) { + if (params_type::template can_have_multiple_equivalent_keys()) { MatchKind exact_match = MatchKind::kNe; while (s != e) { const int mid = (s + e) >> 1; @@ -770,14 +780,14 @@ class btree_node { e = mid; if (c == 0) { // Need to return the first value whose key is not less than k, - // which requires continuing the binary search if this is a - // multi-container. + // which requires continuing the binary search if there could be + // multiple equivalent keys. exact_match = MatchKind::kEq; } } } return {s, exact_match}; - } else { // Not a multi-container. + } else { // Can't have multiple equivalent keys. while (s != e) { const int mid = (s + e) >> 1; const absl::weak_ordering c = comp(key(mid), k); @@ -1054,8 +1064,6 @@ class btree { using is_key_compare_to = typename Params::is_key_compare_to; using init_type = typename Params::init_type; using field_type = typename node_type::field_type; - using is_multi_container = typename Params::is_multi_container; - using is_key_compare_adapted = typename Params::is_key_compare_adapted; // We use a static empty node for the root/leftmost/rightmost of empty btrees // in order to avoid branching in begin()/end(). @@ -1907,13 +1915,7 @@ auto btree

::equal_range(const K &key) -> std::pair { } const iterator next = std::next(lower); - // When the comparator is heterogeneous, we can't assume that comparison with - // non-`key_type` will be equivalent to `key_type` comparisons so there - // could be multiple equivalent keys even in a unique-container. But for - // heterogeneous comparisons from the default string adapted comparators, we - // don't need to worry about this. - if (!is_multi_container::value && - (std::is_same::value || is_key_compare_adapted::value)) { + if (!params_type::template can_have_multiple_equivalent_keys()) { // The next iterator after lower must point to a key greater than `key`. // Note: if this assert fails, then it may indicate that the comparator does // not meet the equivalence requirements for Compare diff --git a/absl/container/internal/raw_hash_set.h b/absl/container/internal/raw_hash_set.h index 02158c4e..a958daaf 100644 --- a/absl/container/internal/raw_hash_set.h +++ b/absl/container/internal/raw_hash_set.h @@ -1085,7 +1085,7 @@ class raw_hash_set { template void insert(InputIt first, InputIt last) { - for (; first != last; ++first) insert(*first); + for (; first != last; ++first) emplace(*first); } template = 0, RequiresInsertable = 0> diff --git a/absl/container/internal/raw_hash_set_test.cc b/absl/container/internal/raw_hash_set_test.cc index 33d2773d..0fba46ff 100644 --- a/absl/container/internal/raw_hash_set_test.cc +++ b/absl/container/internal/raw_hash_set_test.cc @@ -250,25 +250,43 @@ TEST(Group, CountLeadingEmptyOrDeleted) { } } -struct IntPolicy { - using slot_type = int64_t; - using key_type = int64_t; - using init_type = int64_t; +template +struct ValuePolicy { + using slot_type = T; + using key_type = T; + using init_type = T; - static void construct(void*, int64_t* slot, int64_t v) { *slot = v; } - static void destroy(void*, int64_t*) {} - static void transfer(void*, int64_t* new_slot, int64_t* old_slot) { - *new_slot = *old_slot; + template + static void construct(Allocator* alloc, slot_type* slot, Args&&... args) { + absl::allocator_traits::construct(*alloc, slot, + std::forward(args)...); } - static int64_t& element(slot_type* slot) { return *slot; } + template + static void destroy(Allocator* alloc, slot_type* slot) { + absl::allocator_traits::destroy(*alloc, slot); + } - template - static auto apply(F&& f, int64_t x) -> decltype(std::forward(f)(x, x)) { - return std::forward(f)(x, x); + template + static void transfer(Allocator* alloc, slot_type* new_slot, + slot_type* old_slot) { + construct(alloc, new_slot, std::move(*old_slot)); + destroy(alloc, old_slot); + } + + static T& element(slot_type* slot) { return *slot; } + + template + static decltype(absl::container_internal::DecomposeValue( + std::declval(), std::declval()...)) + apply(F&& f, Args&&... args) { + return absl::container_internal::DecomposeValue( + std::forward(f), std::forward(args)...); } }; +using IntPolicy = ValuePolicy; + class StringPolicy { template {}(v.value); + } + }; + + struct Table : raw_hash_set, H, std::equal_to, + std::allocator> { + using Base = typename Table::raw_hash_set; + using Base::Base; + }; + + std::string input[3]{"A", "B", "C"}; + + Table t(std::begin(input), std::end(input)); + EXPECT_THAT(t, UnorderedElementsAre(Value{"A"}, Value{"B"}, Value{"C"})); + + input[0] = "D"; + input[1] = "E"; + input[2] = "F"; + t.insert(std::begin(input), std::end(input)); + EXPECT_THAT(t, UnorderedElementsAre(Value{"A"}, Value{"B"}, Value{"C"}, + Value{"D"}, Value{"E"}, Value{"F"})); +} + TEST(Nodes, EmptyNodeType) { using node_type = StringTable::node_type; node_type n; diff --git a/absl/flags/internal/usage.cc b/absl/flags/internal/usage.cc index f29d7c9b..a588c7f7 100644 --- a/absl/flags/internal/usage.cc +++ b/absl/flags/internal/usage.cc @@ -437,12 +437,6 @@ void SetFlagsHelpMatchSubstr(absl::string_view substr) { HelpMode GetFlagsHelpMode() { absl::MutexLock l(&help_attributes_guard); - // Refer to dummy variales to prevent linker dropping them - if (FLAGS_help || FLAGS_helpfull || FLAGS_helpshort || FLAGS_helppackage || - FLAGS_version || FLAGS_only_check_args || FLAGS_helpon || - FLAGS_helpmatch) { - help_mode = HelpMode::kNone; - } return help_mode; } diff --git a/absl/status/status.h b/absl/status/status.h index 9019e6c2..08d3e806 100644 --- a/absl/status/status.h +++ b/absl/status/status.h @@ -705,9 +705,11 @@ inline Status::Status(Status&& x) noexcept : rep_(x.rep_) { inline Status& Status::operator=(Status&& x) { uintptr_t old_rep = rep_; - rep_ = x.rep_; - x.rep_ = MovedFromRep(); - Unref(old_rep); + if (x.rep_ != old_rep) { + rep_ = x.rep_; + x.rep_ = MovedFromRep(); + Unref(old_rep); + } return *this; } diff --git a/absl/status/status_test.cc b/absl/status/status_test.cc index ca9488ad..25333fa2 100644 --- a/absl/status/status_test.cc +++ b/absl/status/status_test.cc @@ -397,6 +397,12 @@ TEST(Status, MoveAssignment) { assignee = std::move(status); EXPECT_EQ(assignee, copy); } + { + absl::Status status(absl::StatusCode::kInvalidArgument, "message"); + absl::Status copy(status); + status = static_cast(status); + EXPECT_EQ(status, copy); + } } TEST(Status, Update) { diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index a1579a4a..aab3a286 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -267,16 +267,23 @@ cc_test( cc_library( name = "cord_internal", + srcs = [ + "internal/cord_internal.cc", + ], hdrs = [ "internal/cord_internal.h", "internal/cord_rep_flat.h", ], copts = ABSL_DEFAULT_COPTS, - visibility = ["//visibility:private"], + visibility = [ + "//visibility:private", + ], deps = [ ":strings", "//absl/base:base_internal", + "//absl/base:core_headers", "//absl/container:compressed_tuple", + "//absl/container:inlined_vector", "//absl/meta:type_traits", ], ) @@ -304,6 +311,7 @@ cc_library( "//absl/functional:function_ref", "//absl/meta:type_traits", "//absl/types:optional", + "//absl/types:variant", ], ) diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index af0b57d8..54c10151 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt @@ -556,6 +556,7 @@ absl_cc_library( "cord.h" SRCS "cord.cc" + "internal/cord_internal.cc" "internal/cord_internal.h" "internal/cord_rep_flat.h" COPTS @@ -570,6 +571,7 @@ absl_cc_library( absl::function_ref absl::inlined_vector absl::optional + absl::variant absl::raw_logging_internal absl::strings absl::strings_internal diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index badeb610..f475042c 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc @@ -60,51 +60,8 @@ using ::absl::cord_internal::EXTERNAL; using ::absl::cord_internal::FLAT; using ::absl::cord_internal::SUBSTRING; -namespace cord_internal { - -inline CordRepConcat* CordRep::concat() { - assert(tag == CONCAT); - return static_cast(this); -} - -inline const CordRepConcat* CordRep::concat() const { - assert(tag == CONCAT); - return static_cast(this); -} - -inline CordRepSubstring* CordRep::substring() { - assert(tag == SUBSTRING); - return static_cast(this); -} - -inline const CordRepSubstring* CordRep::substring() const { - assert(tag == SUBSTRING); - return static_cast(this); -} - -inline CordRepExternal* CordRep::external() { - assert(tag == EXTERNAL); - return static_cast(this); -} - -inline const CordRepExternal* CordRep::external() const { - assert(tag == EXTERNAL); - return static_cast(this); -} - -inline CordRepFlat* CordRep::flat() { - assert(tag >= FLAT); - return static_cast(this); -} -inline const CordRepFlat* CordRep::flat() const { - assert(tag >= FLAT); - return static_cast(this); -} - -} // namespace cord_internal - -// Prefer copying blocks of at most this size, otherwise reference count. -static const size_t kMaxBytesToCopy = 511; +using ::absl::cord_internal::kInlinedVectorSize; +using ::absl::cord_internal::kMaxBytesToCopy; constexpr uint64_t Fibonacci(unsigned char n, uint64_t a = 0, uint64_t b = 1) { return n == 0 ? a : Fibonacci(n - 1, b, a + b); @@ -136,17 +93,6 @@ static constexpr uint64_t min_length[] = { static const int kMinLengthSize = ABSL_ARRAYSIZE(min_length); -// The inlined size to use with absl::InlinedVector. -// -// Note: The InlinedVectors in this file (and in cord.h) do not need to use -// the same value for their inlined size. The fact that they do is historical. -// It may be desirable for each to use a different inlined size optimized for -// that InlinedVector's usage. -// -// TODO(jgm): Benchmark to see if there's a more optimal value than 47 for -// the inlined vector size (47 exists for backward compatibility). -static const int kInlinedVectorSize = 47; - static inline bool IsRootBalanced(CordRep* node) { if (node->tag != CONCAT) { return true; @@ -182,86 +128,6 @@ static inline CordRep* VerifyTree(CordRep* node) { return node; } -// -------------------------------------------------------------------- -// Memory management - -inline CordRep* Ref(CordRep* rep) { - if (rep != nullptr) { - rep->refcount.Increment(); - } - return rep; -} - -// This internal routine is called from the cold path of Unref below. Keeping it -// in a separate routine allows good inlining of Unref into many profitable call -// sites. However, the call to this function can be highly disruptive to the -// register pressure in those callers. To minimize the cost to callers, we use -// a special LLVM calling convention that preserves most registers. This allows -// the call to this routine in cold paths to not disrupt the caller's register -// pressure. This calling convention is not available on all platforms; we -// intentionally allow LLVM to ignore the attribute rather than attempting to -// hardcode the list of supported platforms. -#if defined(__clang__) && !defined(__i386__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wattributes" -__attribute__((preserve_most)) -#pragma clang diagnostic pop -#endif -static void UnrefInternal(CordRep* rep) { - assert(rep != nullptr); - - absl::InlinedVector pending; - while (true) { - assert(!rep->refcount.IsImmortal()); - if (rep->tag == CONCAT) { - CordRepConcat* rep_concat = rep->concat(); - CordRep* right = rep_concat->right; - if (!right->refcount.Decrement()) { - pending.push_back(right); - } - CordRep* left = rep_concat->left; - delete rep_concat; - rep = nullptr; - if (!left->refcount.Decrement()) { - rep = left; - continue; - } - } else if (rep->tag == EXTERNAL) { - CordRepExternal::Delete(rep); - rep = nullptr; - } else if (rep->tag == SUBSTRING) { - CordRepSubstring* rep_substring = rep->substring(); - CordRep* child = rep_substring->child; - delete rep_substring; - rep = nullptr; - if (!child->refcount.Decrement()) { - rep = child; - continue; - } - } else { - CordRepFlat::Delete(rep); - rep = nullptr; - } - - if (!pending.empty()) { - rep = pending.back(); - pending.pop_back(); - } else { - break; - } - } -} - -inline void Unref(CordRep* rep) { - // Fast-path for two common, hot cases: a null rep and a shared root. - if (ABSL_PREDICT_TRUE(rep == nullptr || - rep->refcount.DecrementExpectHighRefcount())) { - return; - } - - UnrefInternal(rep); -} - // Return the depth of a node static int Depth(const CordRep* rep) { if (rep->tag == CONCAT) { @@ -285,12 +151,14 @@ static void SetConcatChildren(CordRepConcat* concat, CordRep* left, // The returned node has a refcount of 1. static CordRep* RawConcat(CordRep* left, CordRep* right) { // Avoid making degenerate concat nodes (one child is empty) - if (left == nullptr || left->length == 0) { - Unref(left); + if (left == nullptr) return right; + if (right == nullptr) return left; + if (left->length == 0) { + CordRep::Unref(left); return right; } - if (right == nullptr || right->length == 0) { - Unref(right); + if (right->length == 0) { + CordRep::Unref(right); return left; } @@ -364,7 +232,7 @@ void InitializeCordRepExternal(absl::string_view data, CordRepExternal* rep) { static CordRep* NewSubstring(CordRep* child, size_t offset, size_t length) { // Never create empty substring nodes if (length == 0) { - Unref(child); + CordRep::Unref(child); return nullptr; } else { CordRepSubstring* rep = new CordRepSubstring(); @@ -562,13 +430,13 @@ void Cord::InlineRep::AssignSlow(const Cord::InlineRep& src) { data_ = src.data_; if (is_tree()) { - Ref(tree()); + CordRep::Ref(tree()); } } void Cord::InlineRep::ClearSlow() { if (is_tree()) { - Unref(tree()); + CordRep::Unref(tree()); } ResetToEmpty(); } @@ -577,7 +445,9 @@ void Cord::InlineRep::ClearSlow() { // Constructors and destructors Cord::Cord(const Cord& src) : contents_(src.contents_) { - Ref(contents_.tree()); // Does nothing if contents_ has embedded data + if (CordRep* tree = contents_.tree()) { + CordRep::Ref(tree); + } } Cord::Cord(absl::string_view src) { @@ -623,14 +493,18 @@ template Cord::Cord(std::string&& src); // The destruction code is separate so that the compiler can determine // that it does not need to call the destructor on a moved-from Cord. void Cord::DestroyCordSlow() { - Unref(VerifyTree(contents_.tree())); + if (CordRep* tree = contents_.tree()) { + CordRep::Unref(VerifyTree(tree)); + } } // -------------------------------------------------------------------- // Mutators void Cord::Clear() { - Unref(contents_.clear()); + if (CordRep* tree = contents_.clear()) { + CordRep::Unref(tree); + } } Cord& Cord::operator=(absl::string_view src) { @@ -641,7 +515,7 @@ Cord& Cord::operator=(absl::string_view src) { if (length <= InlineRep::kMaxInline) { // Embed into this->contents_ contents_.set_data(data, length, true); - Unref(tree); + if (tree) CordRep::Unref(tree); return *this; } if (tree != nullptr && tree->tag >= FLAT && @@ -654,7 +528,7 @@ Cord& Cord::operator=(absl::string_view src) { return *this; } contents_.set_tree(NewTree(data, length, 0)); - Unref(tree); + if (tree) CordRep::Unref(tree); return *this; } @@ -731,7 +605,7 @@ void Cord::InlineRep::AppendArray(const char* src_data, size_t src_size) { } inline CordRep* Cord::TakeRep() const& { - return Ref(contents_.tree()); + return CordRep::Ref(contents_.tree()); } inline CordRep* Cord::TakeRep() && { @@ -775,6 +649,7 @@ inline void Cord::AppendImpl(C&& src) { return; } + // Guaranteed to be a tree (kMaxBytesToCopy > kInlinedSize) contents_.AppendTree(std::forward(src).TakeRep()); } @@ -796,7 +671,7 @@ template void Cord::Append(std::string&& src); void Cord::Prepend(const Cord& src) { CordRep* src_tree = src.contents_.tree(); if (src_tree != nullptr) { - Ref(src_tree); + CordRep::Ref(src_tree); contents_.PrependTree(src_tree); return; } @@ -835,7 +710,7 @@ template void Cord::Prepend(std::string&& src); static CordRep* RemovePrefixFrom(CordRep* node, size_t n) { if (n >= node->length) return nullptr; - if (n == 0) return Ref(node); + if (n == 0) return CordRep::Ref(node); absl::InlinedVector rhs_stack; while (node->tag == CONCAT) { @@ -853,7 +728,7 @@ static CordRep* RemovePrefixFrom(CordRep* node, size_t n) { assert(n <= node->length); if (n == 0) { - Ref(node); + CordRep::Ref(node); } else { size_t start = n; size_t len = node->length - n; @@ -862,10 +737,10 @@ static CordRep* RemovePrefixFrom(CordRep* node, size_t n) { start += node->substring()->start; node = node->substring()->child; } - node = NewSubstring(Ref(node), start, len); + node = NewSubstring(CordRep::Ref(node), start, len); } while (!rhs_stack.empty()) { - node = Concat(node, Ref(rhs_stack.back())); + node = Concat(node, CordRep::Ref(rhs_stack.back())); rhs_stack.pop_back(); } return node; @@ -876,7 +751,7 @@ static CordRep* RemovePrefixFrom(CordRep* node, size_t n) { // edited in place iff that node and all its ancestors have a refcount of 1. static CordRep* RemoveSuffixFrom(CordRep* node, size_t n) { if (n >= node->length) return nullptr; - if (n == 0) return Ref(node); + if (n == 0) return CordRep::Ref(node); absl::InlinedVector lhs_stack; bool inplace_ok = node->refcount.IsOne(); @@ -896,11 +771,11 @@ static CordRep* RemoveSuffixFrom(CordRep* node, size_t n) { assert(n <= node->length); if (n == 0) { - Ref(node); + CordRep::Ref(node); } else if (inplace_ok && node->tag != EXTERNAL) { // Consider making a new buffer if the current node capacity is much // larger than the new length. - Ref(node); + CordRep::Ref(node); node->length -= n; } else { size_t start = 0; @@ -909,10 +784,10 @@ static CordRep* RemoveSuffixFrom(CordRep* node, size_t n) { start = node->substring()->start; node = node->substring()->child; } - node = NewSubstring(Ref(node), start, len); + node = NewSubstring(CordRep::Ref(node), start, len); } while (!lhs_stack.empty()) { - node = Concat(Ref(lhs_stack.back()), node); + node = Concat(CordRep::Ref(lhs_stack.back()), node); lhs_stack.pop_back(); } return node; @@ -927,7 +802,7 @@ void Cord::RemovePrefix(size_t n) { contents_.remove_prefix(n); } else { CordRep* newrep = RemovePrefixFrom(tree, n); - Unref(tree); + CordRep::Unref(tree); contents_.replace_tree(VerifyTree(newrep)); } } @@ -941,7 +816,7 @@ void Cord::RemoveSuffix(size_t n) { contents_.reduce_size(n); } else { CordRep* newrep = RemoveSuffixFrom(tree, n); - Unref(tree); + CordRep::Unref(tree); contents_.replace_tree(VerifyTree(newrep)); } } @@ -974,13 +849,13 @@ static CordRep* NewSubRange(CordRep* node, size_t pos, size_t n) { results.pop_back(); results.push_back(Concat(left, right)); } else if (pos == 0 && n == node->length) { - results.push_back(Ref(node)); + results.push_back(CordRep::Ref(node)); } else if (node->tag != CONCAT) { if (node->tag == SUBSTRING) { pos += node->substring()->start; node = node->substring()->child; } - results.push_back(NewSubstring(Ref(node), pos, n)); + results.push_back(NewSubstring(CordRep::Ref(node), pos, n)); } else if (pos + n <= node->concat()->left->length) { todo.push_back(SubRange(node->concat()->left, pos, n)); } else if (pos >= node->concat()->left->length) { @@ -1058,9 +933,9 @@ class CordForest { concat_node->left = concat_freelist_; concat_freelist_ = concat_node; } else { - Ref(concat_node->right); - Ref(concat_node->left); - Unref(concat_node); + CordRep::Ref(concat_node->right); + CordRep::Ref(concat_node->left); + CordRep::Unref(concat_node); } } else { AddNode(node); @@ -1488,7 +1363,7 @@ Cord Cord::ChunkIterator::AdvanceAndReadBytes(size_t n) { if (n < current_chunk_.size()) { // Range to read is a proper subrange of the current chunk. assert(current_leaf_ != nullptr); - CordRep* subnode = Ref(current_leaf_); + CordRep* subnode = CordRep::Ref(current_leaf_); const char* data = subnode->tag == EXTERNAL ? subnode->external()->base : subnode->data; subnode = NewSubstring(subnode, current_chunk_.data() - data, n); @@ -1500,7 +1375,7 @@ Cord Cord::ChunkIterator::AdvanceAndReadBytes(size_t n) { // Range to read begins with a proper subrange of the current chunk. assert(!current_chunk_.empty()); assert(current_leaf_ != nullptr); - CordRep* subnode = Ref(current_leaf_); + CordRep* subnode = CordRep::Ref(current_leaf_); if (current_chunk_.size() < subnode->length) { const char* data = subnode->tag == EXTERNAL ? subnode->external()->base : subnode->data; @@ -1526,7 +1401,7 @@ Cord Cord::ChunkIterator::AdvanceAndReadBytes(size_t n) { // children starting from current_subtree_ if this loop exits while staying // below current_subtree_; etc.; alternatively, push parents instead of // right children on the stack). - subnode = Concat(subnode, Ref(node)); + subnode = Concat(subnode, CordRep::Ref(node)); n -= node->length; bytes_remaining_ -= node->length; node = nullptr; @@ -1548,7 +1423,7 @@ Cord Cord::ChunkIterator::AdvanceAndReadBytes(size_t n) { node = node->concat()->left; } else { // Read left, descend right. - subnode = Concat(subnode, Ref(node->concat()->left)); + subnode = Concat(subnode, CordRep::Ref(node->concat()->left)); n -= node->concat()->left->length; bytes_remaining_ -= node->concat()->left->length; node = node->concat()->right; @@ -1567,7 +1442,9 @@ Cord Cord::ChunkIterator::AdvanceAndReadBytes(size_t n) { // chunk. assert(node->tag == EXTERNAL || node->tag >= FLAT); assert(length > n); - if (n > 0) subnode = Concat(subnode, NewSubstring(Ref(node), offset, n)); + if (n > 0) { + subnode = Concat(subnode, NewSubstring(CordRep::Ref(node), offset, n)); + } const char* data = node->tag == EXTERNAL ? node->external()->base : node->data; current_chunk_ = absl::string_view(data + offset + n, length - n); @@ -1691,7 +1568,9 @@ absl::string_view Cord::FlattenSlowPath() { s.size()); }); } - Unref(contents_.tree()); + if (CordRep* tree = contents_.tree()) { + CordRep::Unref(tree); + } contents_.set_tree(new_rep); return absl::string_view(new_buffer, total_size); } diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 5d5c897e..b9cdc435 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -82,6 +82,7 @@ #include "absl/strings/internal/string_constant.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" +#include "absl/types/variant.h" namespace absl { ABSL_NAMESPACE_BEGIN diff --git a/absl/strings/internal/cord_internal.cc b/absl/strings/internal/cord_internal.cc new file mode 100644 index 00000000..35f4d235 --- /dev/null +++ b/absl/strings/internal/cord_internal.cc @@ -0,0 +1,76 @@ +// Copyright 2020 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "absl/strings/internal/cord_internal.h" + +#include +#include +#include + +#include "absl/container/inlined_vector.h" +#include "absl/strings/internal/cord_rep_flat.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace cord_internal { + +ABSL_CONST_INIT std::atomic cord_ring_buffer_enabled(false); + +void CordRep::Destroy(CordRep* rep) { + assert(rep != nullptr); + + absl::InlinedVector pending; + while (true) { + assert(!rep->refcount.IsImmortal()); + if (rep->tag == CONCAT) { + CordRepConcat* rep_concat = rep->concat(); + CordRep* right = rep_concat->right; + if (!right->refcount.Decrement()) { + pending.push_back(right); + } + CordRep* left = rep_concat->left; + delete rep_concat; + rep = nullptr; + if (!left->refcount.Decrement()) { + rep = left; + continue; + } + } else if (rep->tag == EXTERNAL) { + CordRepExternal::Delete(rep); + rep = nullptr; + } else if (rep->tag == SUBSTRING) { + CordRepSubstring* rep_substring = rep->substring(); + CordRep* child = rep_substring->child; + delete rep_substring; + rep = nullptr; + if (!child->refcount.Decrement()) { + rep = child; + continue; + } + } else { + CordRepFlat::Delete(rep); + rep = nullptr; + } + + if (!pending.empty()) { + rep = pending.back(); + pending.pop_back(); + } else { + break; + } + } +} + +} // namespace cord_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/strings/internal/cord_internal.h b/absl/strings/internal/cord_internal.h index 6fb75c4f..f1af8e6e 100644 --- a/absl/strings/internal/cord_internal.h +++ b/absl/strings/internal/cord_internal.h @@ -22,6 +22,7 @@ #include #include "absl/base/internal/invoke.h" +#include "absl/base/optimization.h" #include "absl/container/internal/compressed_tuple.h" #include "absl/meta/type_traits.h" #include "absl/strings/string_view.h" @@ -30,6 +31,28 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace cord_internal { +extern std::atomic cord_ring_buffer_enabled; + +inline void enable_cord_ring_buffer(bool enable) { + cord_ring_buffer_enabled.store(enable, std::memory_order_relaxed); +} + +enum Constants { + // The inlined size to use with absl::InlinedVector. + // + // Note: The InlinedVectors in this file (and in cord.h) do not need to use + // the same value for their inlined size. The fact that they do is historical. + // It may be desirable for each to use a different inlined size optimized for + // that InlinedVector's usage. + // + // TODO(jgm): Benchmark to see if there's a more optimal value than 47 for + // the inlined vector size (47 exists for backward compatibility). + kInlinedVectorSize = 47, + + // Prefer copying blocks of at most this size, otherwise reference count. + kMaxBytesToCopy = 511 +}; + // Wraps std::atomic for reference counting. class Refcount { public: @@ -151,6 +174,34 @@ struct CordRep { inline const CordRepExternal* external() const; inline CordRepFlat* flat(); inline const CordRepFlat* flat() const; + + // -------------------------------------------------------------------- + // Memory management + + // This internal routine is called from the cold path of Unref below. Keeping + // it in a separate routine allows good inlining of Unref into many profitable + // call sites. However, the call to this function can be highly disruptive to + // the register pressure in those callers. To minimize the cost to callers, we + // use a special LLVM calling convention that preserves most registers. This + // allows the call to this routine in cold paths to not disrupt the caller's + // register pressure. This calling convention is not available on all + // platforms; we intentionally allow LLVM to ignore the attribute rather than + // attempting to hardcode the list of supported platforms. +#if defined(__clang__) && !defined(__i386__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wattributes" + __attribute__((preserve_most)) +#pragma clang diagnostic pop +#endif + static void Destroy(CordRep* rep); + + // Increments the reference count of `rep`. + // Requires `rep` to be a non-null pointer value. + static inline CordRep* Ref(CordRep* rep); + + // Decrements the reference count of `rep`. Destroys rep if count reaches + // zero. Requires `rep` to be a non-null pointer value. + static inline void Unref(CordRep* rep); }; struct CordRepConcat : public CordRep { @@ -284,7 +335,63 @@ static_assert(sizeof(InlineData) == kMaxInline + 1, ""); static_assert(sizeof(AsTree) == sizeof(InlineData), ""); static_assert(offsetof(AsTree, tagged_size) == kMaxInline, ""); +inline CordRepConcat* CordRep::concat() { + assert(tag == CONCAT); + return static_cast(this); +} + +inline const CordRepConcat* CordRep::concat() const { + assert(tag == CONCAT); + return static_cast(this); +} + +inline CordRepSubstring* CordRep::substring() { + assert(tag == SUBSTRING); + return static_cast(this); +} + +inline const CordRepSubstring* CordRep::substring() const { + assert(tag == SUBSTRING); + return static_cast(this); +} + +inline CordRepExternal* CordRep::external() { + assert(tag == EXTERNAL); + return static_cast(this); +} + +inline const CordRepExternal* CordRep::external() const { + assert(tag == EXTERNAL); + return static_cast(this); +} + +inline CordRepFlat* CordRep::flat() { + assert(tag >= FLAT && tag <= MAX_FLAT_TAG); + return reinterpret_cast(this); +} + +inline const CordRepFlat* CordRep::flat() const { + assert(tag >= FLAT && tag <= MAX_FLAT_TAG); + return reinterpret_cast(this); +} + +inline CordRep* CordRep::Ref(CordRep* rep) { + assert(rep != nullptr); + rep->refcount.Increment(); + return rep; +} + +inline void CordRep::Unref(CordRep* rep) { + assert(rep != nullptr); + // Expect refcount to be 0. Avoiding the cost of an atomic decrement should + // typically outweigh the cost of an extra branch checking for ref == 1. + if (ABSL_PREDICT_FALSE(!rep->refcount.DecrementExpectHighRefcount())) { + Destroy(rep); + } +} + } // namespace cord_internal + ABSL_NAMESPACE_END } // namespace absl #endif // ABSL_STRINGS_INTERNAL_CORD_INTERNAL_H_ diff --git a/absl/time/BUILD.bazel b/absl/time/BUILD.bazel index 991241a0..3e25ca26 100644 --- a/absl/time/BUILD.bazel +++ b/absl/time/BUILD.bazel @@ -119,6 +119,7 @@ cc_test( ":time", "//absl/base", "//absl/base:core_headers", + "//absl/flags:flag", "//absl/hash", "@com_github_google_benchmark//:benchmark_main", ], diff --git a/absl/time/duration_benchmark.cc b/absl/time/duration_benchmark.cc index 83a836c8..56820f37 100644 --- a/absl/time/duration_benchmark.cc +++ b/absl/time/duration_benchmark.cc @@ -18,9 +18,14 @@ #include #include "absl/base/attributes.h" +#include "absl/flags/flag.h" #include "absl/time/time.h" #include "benchmark/benchmark.h" +ABSL_FLAG(absl::Duration, absl_duration_flag_for_benchmark, + absl::Milliseconds(1), + "Flag to use for benchmarking duration flag access speed."); + namespace { // @@ -425,4 +430,15 @@ void BM_Duration_ParseDuration(benchmark::State& state) { } BENCHMARK(BM_Duration_ParseDuration)->DenseRange(0, kNumDurations - 1); +// +// Flag access +// +void BM_Duration_GetFlag(benchmark::State& state) { + while (state.KeepRunning()) { + benchmark::DoNotOptimize( + absl::GetFlag(FLAGS_absl_duration_flag_for_benchmark)); + } +} +BENCHMARK(BM_Duration_GetFlag); + } // namespace -- cgit v1.2.3 From 1918ad2ae38aa32c74b558b322479a8efdd76363 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Fri, 11 Dec 2020 14:49:17 -0800 Subject: Export of internal Abseil changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -- 0bfa836596a9c787a2f0bdc283011dd1f6810c6e by Benjamin Barenblat : Ignore missing CPU frequency on more architectures Linux on MIPS, PA-RISC, RISC-V, and SystemZ doesn’t expose the nominal CPU frequency via /sys, so don’t worry if `NominalCPUFrequency` returns 1.0 on those platforms. Some POWER machines expose the CPU frequency; others do not. Since we can’t predict which type of machine the tests will run on, simply disable testing for `NominalCPUFrequency` on POWER. PiperOrigin-RevId: 347079873 -- 492b6834ed4a07cbc3abccd846f7e37d8c556ee5 by Benjamin Barenblat : Use ABSL_HAVE_THREAD_LOCAL macro instead of copying code Reduce code duplication by checking the ABSL_HAVE_THREAD_LOCAL macro instead of copying code from base/config.h. PiperOrigin-RevId: 347079561 -- 8d656efce4da9cb032094377e58493d98427a536 by Abseil Team : Rollback PiperOrigin-RevId: 347078779 -- 221bc69ec6dd7e2777ffcff6942584f979ef6382 by Abseil Team : Add flag for 'shallow subcord' feature for experimental ring buffer rollout There is a potential trade-off of CPU cost vs over-sharing cord data for subcord of large cords. This flag allows making subcords shallow for ringbuffers (with a potential larger waste of referenced source cords), which allows us to make subcord fast for this apps that do no persist (unmodified / plain copied) sub cords. This change also introduces constants for the default settings, intended to keep the internal cord settings concistent with external flags. PiperOrigin-RevId: 347053271 -- 00a56c24293566734009f6bf2169a83fb37a35ba by Abseil Team : Revert the usage of variant<> in Cord iterator and reader. The introduction of the variant may lead to some missed compiler optimizations. PiperOrigin-RevId: 347053041 -- c7b7b5ed7e3ab46b1e75b80f1a7de0bda26c8f70 by Chris Kennelly : Release library for integer power-of-2 functions and bit counting. PiperOrigin-RevId: 347035065 -- 5a035c0d9840b251967f9e7039fc6a4e01dd52f3 by Abseil Team : Restructure Cord::ChunkIterator for future ring buffer support. PiperOrigin-RevId: 346890054 GitOrigin-RevId: 0bfa836596a9c787a2f0bdc283011dd1f6810c6e Change-Id: I3a58e2a44cb4c6f2116c43e2a4ccbc319d3ccecf --- CMake/AbseilDll.cmake | 2 + absl/base/CMakeLists.txt | 6 +- absl/base/internal/sysinfo_test.cc | 23 +- absl/container/CMakeLists.txt | 2 +- absl/container/internal/inlined_vector.h | 11 + absl/debugging/internal/stacktrace_config.h | 15 +- absl/numeric/BUILD.bazel | 29 ++ absl/numeric/CMakeLists.txt | 29 +- absl/numeric/bits.h | 177 +++++++++ absl/numeric/bits_test.cc | 565 ++++++++++++++++++++++++++++ absl/numeric/internal/bits.h | 350 +++++++++++++++++ absl/random/CMakeLists.txt | 10 +- absl/strings/CMakeLists.txt | 4 +- absl/strings/cord.cc | 45 ++- absl/strings/cord.h | 30 +- absl/strings/internal/cord_internal.cc | 5 +- absl/strings/internal/cord_internal.h | 11 + 17 files changed, 1261 insertions(+), 53 deletions(-) create mode 100644 absl/numeric/bits.h create mode 100644 absl/numeric/bits_test.cc create mode 100644 absl/numeric/internal/bits.h (limited to 'absl/strings/cord.h') diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index be6e3484..5296caf1 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake @@ -126,8 +126,10 @@ set(ABSL_INTERNAL_DLL_FILES "hash/internal/wyhash.cc" "memory/memory.h" "meta/type_traits.h" + "numeric/bits.h" "numeric/int128.cc" "numeric/int128.h" + "numeric/internal/bits.h" "random/bernoulli_distribution.h" "random/beta_distribution.h" "random/bit_gen_ref.h" diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt index 9ff5aa24..515d7b4e 100644 --- a/absl/base/CMakeLists.txt +++ b/absl/base/CMakeLists.txt @@ -520,7 +520,7 @@ absl_cc_test( absl_cc_library( NAME - bits + internal_bits HDRS "internal/bits.h" COPTS @@ -532,13 +532,13 @@ absl_cc_library( absl_cc_test( NAME - bits_test + internal_bits_test SRCS "internal/bits_test.cc" COPTS ${ABSL_TEST_COPTS} DEPS - absl::bits + absl::internal_bits gtest_main ) diff --git a/absl/base/internal/sysinfo_test.cc b/absl/base/internal/sysinfo_test.cc index fa8b88b1..5f9e45f6 100644 --- a/absl/base/internal/sysinfo_test.cc +++ b/absl/base/internal/sysinfo_test.cc @@ -37,17 +37,28 @@ TEST(SysinfoTest, NumCPUs) { << "NumCPUs() should not have the default value of 0"; } +// Ensure that NominalCPUFrequency returns a reasonable value, or 1.00 on +// platforms where the CPU frequency is not available through sysfs. +// +// POWER is particularly problematic here; some Linux kernels expose the CPU +// frequency, while others do not. Since we can't predict a priori what a given +// machine is going to do, just disable this test on POWER on Linux. +#if !(defined(__linux) && (defined(__ppc64__) || defined(__PPC64__))) TEST(SysinfoTest, NominalCPUFrequency) { -#if !(defined(__aarch64__) && defined(__linux__)) && !defined(__EMSCRIPTEN__) - EXPECT_GE(NominalCPUFrequency(), 1000.0) - << "NominalCPUFrequency() did not return a reasonable value"; -#else - // Aarch64 cannot read the CPU frequency from sysfs, so we get back 1.0. - // Emscripten does not have a sysfs to read from at all. + // Linux only exposes the CPU frequency on certain architectures, and + // Emscripten doesn't expose it at all. +#if defined(__linux__) && \ + (defined(__aarch64__) || defined(__hppa__) || defined(__mips__) || \ + defined(__riscv) || defined(__s390x__)) || \ + defined(__EMSCRIPTEN__) EXPECT_EQ(NominalCPUFrequency(), 1.0) << "CPU frequency detection was fixed! Please update unittest."; +#else + EXPECT_GE(NominalCPUFrequency(), 1000.0) + << "NominalCPUFrequency() did not return a reasonable value"; #endif } +#endif TEST(SysinfoTest, GetTID) { EXPECT_EQ(GetTID(), GetTID()); // Basic compile and equality test. diff --git a/absl/container/CMakeLists.txt b/absl/container/CMakeLists.txt index eb202c45..7562f32d 100644 --- a/absl/container/CMakeLists.txt +++ b/absl/container/CMakeLists.txt @@ -665,7 +665,7 @@ absl_cc_library( COPTS ${ABSL_DEFAULT_COPTS} DEPS - absl::bits + absl::internal_bits absl::compressed_tuple absl::config absl::container_common diff --git a/absl/container/internal/inlined_vector.h b/absl/container/internal/inlined_vector.h index 502ea3dd..120849cd 100644 --- a/absl/container/internal/inlined_vector.h +++ b/absl/container/internal/inlined_vector.h @@ -33,6 +33,12 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace inlined_vector_internal { +// GCC does not deal very well with the below code +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + template using IsAtLeastForwardIterator = std::is_convertible< typename std::iterator_traits::iterator_category, @@ -889,6 +895,11 @@ auto Storage::Swap(Storage* other_storage_ptr) -> void { swap(*GetAllocPtr(), *other_storage_ptr->GetAllocPtr()); } +// End ignore "maybe-uninitialized" +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } // namespace inlined_vector_internal ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/debugging/internal/stacktrace_config.h b/absl/debugging/internal/stacktrace_config.h index 90af8528..cca410d4 100644 --- a/absl/debugging/internal/stacktrace_config.h +++ b/absl/debugging/internal/stacktrace_config.h @@ -21,6 +21,8 @@ #ifndef ABSL_DEBUGGING_INTERNAL_STACKTRACE_CONFIG_H_ #define ABSL_DEBUGGING_INTERNAL_STACKTRACE_CONFIG_H_ +#include "absl/base/config.h" + #if defined(ABSL_STACKTRACE_INL_HEADER) #error ABSL_STACKTRACE_INL_HEADER cannot be directly set @@ -29,19 +31,8 @@ "absl/debugging/internal/stacktrace_win32-inl.inc" #elif defined(__APPLE__) +#ifdef ABSL_HAVE_THREAD_LOCAL // Thread local support required for UnwindImpl. -// Notes: -// * Xcode's clang did not support `thread_local` until version 8, and -// even then not for all iOS < 9.0. -// * Xcode 9.3 started disallowing `thread_local` for 32-bit iOS simulator -// targeting iOS 9.x. -// * Xcode 10 moves the deployment target check for iOS < 9.0 to link time -// making __has_feature unreliable there. -// -// Otherwise, `__has_feature` is only supported by Clang so it has be inside -// `defined(__APPLE__)` check. -#if __has_feature(cxx_thread_local) && \ - !(TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0) #define ABSL_STACKTRACE_INL_HEADER \ "absl/debugging/internal/stacktrace_generic-inl.inc" #endif diff --git a/absl/numeric/BUILD.bazel b/absl/numeric/BUILD.bazel index f808f5da..fb782ef3 100644 --- a/absl/numeric/BUILD.bazel +++ b/absl/numeric/BUILD.bazel @@ -24,6 +24,35 @@ package(default_visibility = ["//visibility:public"]) licenses(["notice"]) +cc_library( + name = "bits", + hdrs = [ + "bits.h", + "internal/bits.h", + ], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:config", + "//absl/base:core_headers", + ], +) + +cc_test( + name = "bits_test", + size = "small", + srcs = [ + "bits_test.cc", + ], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":bits", + "//absl/random", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "int128", srcs = [ diff --git a/absl/numeric/CMakeLists.txt b/absl/numeric/CMakeLists.txt index 1e12d80f..77e3f5ee 100644 --- a/absl/numeric/CMakeLists.txt +++ b/absl/numeric/CMakeLists.txt @@ -14,6 +14,33 @@ # limitations under the License. # +absl_cc_library( + NAME + bits + HDRS + "bits.h" + "internal/bits.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::core_headers + PUBLIC +) + +absl_cc_test( + NAME + bits_test + SRCS + "bits_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::bits + absl::core_headers + absl::random_random + gmock_main +) + absl_cc_library( NAME int128 @@ -26,9 +53,9 @@ absl_cc_library( COPTS ${ABSL_DEFAULT_COPTS} DEPS - absl::bits absl::config absl::core_headers + absl::internal_bits PUBLIC ) diff --git a/absl/numeric/bits.h b/absl/numeric/bits.h new file mode 100644 index 00000000..52013ad4 --- /dev/null +++ b/absl/numeric/bits.h @@ -0,0 +1,177 @@ +// Copyright 2020 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: bits.h +// ----------------------------------------------------------------------------- +// +// This file contains implementations of C++20's bitwise math functions, as +// defined by: +// +// P0553R4: +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0553r4.html +// P0556R3: +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0556r3.html +// P1355R2: +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1355r2.html +// P1956R1: +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1956r1.pdf +// +// When using a standard library that implements these functions, we use the +// standard library's implementation. + +#ifndef ABSL_NUMERIC_BITS_H_ +#define ABSL_NUMERIC_BITS_H_ + +#include +#include +#include + +#if (defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L) || \ + (defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L) +#include +#endif + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/numeric/internal/bits.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +#if !(defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L) +// rotating +template +ABSL_MUST_USE_RESULT constexpr + typename std::enable_if::value, T>::type + rotl(T x, int s) noexcept { + return numeric_internal::RotateLeft(x, s); +} + +template +ABSL_MUST_USE_RESULT constexpr + typename std::enable_if::value, T>::type + rotr(T x, int s) noexcept { + return numeric_internal::RotateRight(x, s); +} + +// Counting functions +// +// While these functions are typically constexpr, on some platforms, they may +// not be marked as constexpr due to constraints of the compiler/available +// intrinsics. +template +ABSL_INTERNAL_CONSTEXPR_CLZ inline + typename std::enable_if::value, int>::type + countl_zero(T x) noexcept { + return numeric_internal::CountLeadingZeroes(x); +} + +template +ABSL_INTERNAL_CONSTEXPR_CLZ inline + typename std::enable_if::value, int>::type + countl_one(T x) noexcept { + // Avoid integer promotion to a wider type + return countl_zero(static_cast(~x)); +} + +template +ABSL_INTERNAL_CONSTEXPR_CTZ inline + typename std::enable_if::value, int>::type + countr_zero(T x) noexcept { + return numeric_internal::CountTrailingZeroes(x); +} + +template +ABSL_INTERNAL_CONSTEXPR_CTZ inline + typename std::enable_if::value, int>::type + countr_one(T x) noexcept { + // Avoid integer promotion to a wider type + return countr_zero(static_cast(~x)); +} + +template +ABSL_INTERNAL_CONSTEXPR_POPCOUNT inline + typename std::enable_if::value, int>::type + popcount(T x) noexcept { + return numeric_internal::Popcount(x); +} +#else // defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L + +using std::countl_one; +using std::countl_zero; +using std::countr_one; +using std::countr_zero; +using std::popcount; +using std::rotl; +using std::rotr; + +#endif + +#if !(defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L) +// Returns: true if x is an integral power of two; false otherwise. +template +constexpr inline typename std::enable_if::value, bool>::type +has_single_bit(T x) noexcept { + return x != 0 && (x & (x - 1)) == 0; +} + +// Returns: If x == 0, 0; otherwise one plus the base-2 logarithm of x, with any +// fractional part discarded. +template +ABSL_INTERNAL_CONSTEXPR_CLZ inline + typename std::enable_if::value, T>::type + bit_width(T x) noexcept { + return std::numeric_limits::digits - countl_zero(x); +} + +// Returns: If x == 0, 0; otherwise the maximal value y such that +// has_single_bit(y) is true and y <= x. +template +ABSL_INTERNAL_CONSTEXPR_CLZ inline + typename std::enable_if::value, T>::type + bit_floor(T x) noexcept { + return x == 0 ? 0 : T{1} << (bit_width(x) - 1); +} + +// Returns: N, where N is the smallest power of 2 greater than or equal to x. +// +// Preconditions: N is representable as a value of type T. +template +ABSL_INTERNAL_CONSTEXPR_CLZ inline + typename std::enable_if::value, T>::type + bit_ceil(T x) { + // If T is narrower than unsigned, T{1} << bit_width will be promoted. We + // want to force it to wraparound so that bit_ceil of an invalid value are not + // core constant expressions. + // + // BitCeilNonPowerOf2 triggers an overflow in constexpr contexts if we would + // undergo promotion to unsigned but not fit the result into T without + // truncation. + return has_single_bit(x) ? T{1} << (bit_width(x) - 1) + : numeric_internal::BitCeilNonPowerOf2(x); +} +#else // defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L + +using std::bit_ceil; +using std::bit_floor; +using std::bit_width; +using std::has_single_bit; + +#endif + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_NUMERIC_BITS_H_ diff --git a/absl/numeric/bits_test.cc b/absl/numeric/bits_test.cc new file mode 100644 index 00000000..8bf7bc9f --- /dev/null +++ b/absl/numeric/bits_test.cc @@ -0,0 +1,565 @@ +// Copyright 2020 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/numeric/bits.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/random/random.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace { + +TEST(Rotate, Left) { + static_assert(rotl(uint8_t{0x12}, 0) == uint8_t{0x12}, ""); + static_assert(rotl(uint16_t{0x1234}, 0) == uint16_t{0x1234}, ""); + static_assert(rotl(uint32_t{0x12345678UL}, 0) == uint32_t{0x12345678UL}, ""); + static_assert(rotl(uint64_t{0x12345678ABCDEF01ULL}, 0) == + uint64_t{0x12345678ABCDEF01ULL}, + ""); + + EXPECT_EQ(rotl(uint8_t{0x12}, 0), uint8_t{0x12}); + EXPECT_EQ(rotl(uint16_t{0x1234}, 0), uint16_t{0x1234}); + EXPECT_EQ(rotl(uint32_t{0x12345678UL}, 0), uint32_t{0x12345678UL}); + EXPECT_EQ(rotl(uint64_t{0x12345678ABCDEF01ULL}, 0), + uint64_t{0x12345678ABCDEF01ULL}); + + EXPECT_EQ(rotl(uint8_t{0x12}, 8), uint8_t{0x12}); + EXPECT_EQ(rotl(uint16_t{0x1234}, 16), uint16_t{0x1234}); + EXPECT_EQ(rotl(uint32_t{0x12345678UL}, 32), uint32_t{0x12345678UL}); + EXPECT_EQ(rotl(uint64_t{0x12345678ABCDEF01ULL}, 64), + uint64_t{0x12345678ABCDEF01ULL}); + + EXPECT_EQ(rotl(uint8_t{0x12}, -8), uint8_t{0x12}); + EXPECT_EQ(rotl(uint16_t{0x1234}, -16), uint16_t{0x1234}); + EXPECT_EQ(rotl(uint32_t{0x12345678UL}, -32), uint32_t{0x12345678UL}); + EXPECT_EQ(rotl(uint64_t{0x12345678ABCDEF01ULL}, -64), + uint64_t{0x12345678ABCDEF01ULL}); + + EXPECT_EQ(rotl(uint8_t{0x12}, 4), uint8_t{0x21}); + EXPECT_EQ(rotl(uint16_t{0x1234}, 4), uint16_t{0x2341}); + EXPECT_EQ(rotl(uint32_t{0x12345678UL}, 4), uint32_t{0x23456781UL}); + EXPECT_EQ(rotl(uint64_t{0x12345678ABCDEF01ULL}, 4), + uint64_t{0x2345678ABCDEF011ULL}); + + EXPECT_EQ(rotl(uint8_t{0x12}, -4), uint8_t{0x21}); + EXPECT_EQ(rotl(uint16_t{0x1234}, -4), uint16_t{0x4123}); + EXPECT_EQ(rotl(uint32_t{0x12345678UL}, -4), uint32_t{0x81234567UL}); + EXPECT_EQ(rotl(uint64_t{0x12345678ABCDEF01ULL}, -4), + uint64_t{0x112345678ABCDEF0ULL}); +} + +TEST(Rotate, Right) { + static_assert(rotr(uint8_t{0x12}, 0) == uint8_t{0x12}, ""); + static_assert(rotr(uint16_t{0x1234}, 0) == uint16_t{0x1234}, ""); + static_assert(rotr(uint32_t{0x12345678UL}, 0) == uint32_t{0x12345678UL}, ""); + static_assert(rotr(uint64_t{0x12345678ABCDEF01ULL}, 0) == + uint64_t{0x12345678ABCDEF01ULL}, + ""); + + EXPECT_EQ(rotr(uint8_t{0x12}, 0), uint8_t{0x12}); + EXPECT_EQ(rotr(uint16_t{0x1234}, 0), uint16_t{0x1234}); + EXPECT_EQ(rotr(uint32_t{0x12345678UL}, 0), uint32_t{0x12345678UL}); + EXPECT_EQ(rotr(uint64_t{0x12345678ABCDEF01ULL}, 0), + uint64_t{0x12345678ABCDEF01ULL}); + + EXPECT_EQ(rotr(uint8_t{0x12}, 8), uint8_t{0x12}); + EXPECT_EQ(rotr(uint16_t{0x1234}, 16), uint16_t{0x1234}); + EXPECT_EQ(rotr(uint32_t{0x12345678UL}, 32), uint32_t{0x12345678UL}); + EXPECT_EQ(rotr(uint64_t{0x12345678ABCDEF01ULL}, 64), + uint64_t{0x12345678ABCDEF01ULL}); + + EXPECT_EQ(rotr(uint8_t{0x12}, -8), uint8_t{0x12}); + EXPECT_EQ(rotr(uint16_t{0x1234}, -16), uint16_t{0x1234}); + EXPECT_EQ(rotr(uint32_t{0x12345678UL}, -32), uint32_t{0x12345678UL}); + EXPECT_EQ(rotr(uint64_t{0x12345678ABCDEF01ULL}, -64), + uint64_t{0x12345678ABCDEF01ULL}); + + EXPECT_EQ(rotr(uint8_t{0x12}, 4), uint8_t{0x21}); + EXPECT_EQ(rotr(uint16_t{0x1234}, 4), uint16_t{0x4123}); + EXPECT_EQ(rotr(uint32_t{0x12345678UL}, 4), uint32_t{0x81234567UL}); + EXPECT_EQ(rotr(uint64_t{0x12345678ABCDEF01ULL}, 4), + uint64_t{0x112345678ABCDEF0ULL}); + + EXPECT_EQ(rotr(uint8_t{0x12}, -4), uint8_t{0x21}); + EXPECT_EQ(rotr(uint16_t{0x1234}, -4), uint16_t{0x2341}); + EXPECT_EQ(rotr(uint32_t{0x12345678UL}, -4), uint32_t{0x23456781UL}); + EXPECT_EQ(rotr(uint64_t{0x12345678ABCDEF01ULL}, -4), + uint64_t{0x2345678ABCDEF011ULL}); +} + +TEST(Rotate, Symmetry) { + // rotr(x, s) is equivalent to rotl(x, -s) + absl::BitGen rng; + constexpr int kTrials = 100; + + for (int i = 0; i < kTrials; ++i) { + uint8_t value = absl::Uniform(rng, std::numeric_limits::min(), + std::numeric_limits::max()); + int shift = absl::Uniform(rng, -2 * std::numeric_limits::digits, + 2 * std::numeric_limits::digits); + + EXPECT_EQ(rotl(value, shift), rotr(value, -shift)); + } + + for (int i = 0; i < kTrials; ++i) { + uint16_t value = absl::Uniform(rng, std::numeric_limits::min(), + std::numeric_limits::max()); + int shift = absl::Uniform(rng, -2 * std::numeric_limits::digits, + 2 * std::numeric_limits::digits); + + EXPECT_EQ(rotl(value, shift), rotr(value, -shift)); + } + + for (int i = 0; i < kTrials; ++i) { + uint32_t value = absl::Uniform(rng, std::numeric_limits::min(), + std::numeric_limits::max()); + int shift = absl::Uniform(rng, -2 * std::numeric_limits::digits, + 2 * std::numeric_limits::digits); + + EXPECT_EQ(rotl(value, shift), rotr(value, -shift)); + } + + for (int i = 0; i < kTrials; ++i) { + uint64_t value = absl::Uniform(rng, std::numeric_limits::min(), + std::numeric_limits::max()); + int shift = absl::Uniform(rng, -2 * std::numeric_limits::digits, + 2 * std::numeric_limits::digits); + + EXPECT_EQ(rotl(value, shift), rotr(value, -shift)); + } +} + +TEST(Counting, LeadingZeroes) { +#if ABSL_INTERNAL_HAS_CONSTEXPR_CLZ + static_assert(countl_zero(uint8_t{}) == 8, ""); + static_assert(countl_zero(static_cast(-1)) == 0, ""); + static_assert(countl_zero(uint16_t{}) == 16, ""); + static_assert(countl_zero(static_cast(-1)) == 0, ""); + static_assert(countl_zero(uint32_t{}) == 32, ""); + static_assert(countl_zero(~uint32_t{}) == 0, ""); + static_assert(countl_zero(uint64_t{}) == 64, ""); + static_assert(countl_zero(~uint64_t{}) == 0, ""); +#endif + + EXPECT_EQ(countl_zero(uint8_t{}), 8); + EXPECT_EQ(countl_zero(static_cast(-1)), 0); + EXPECT_EQ(countl_zero(uint16_t{}), 16); + EXPECT_EQ(countl_zero(static_cast(-1)), 0); + EXPECT_EQ(countl_zero(uint32_t{}), 32); + EXPECT_EQ(countl_zero(~uint32_t{}), 0); + EXPECT_EQ(countl_zero(uint64_t{}), 64); + EXPECT_EQ(countl_zero(~uint64_t{}), 0); + + for (int i = 0; i < 8; i++) { + EXPECT_EQ(countl_zero(static_cast(1u << i)), 7 - i); + } + + for (int i = 0; i < 16; i++) { + EXPECT_EQ(countl_zero(static_cast(1u << i)), 15 - i); + } + + for (int i = 0; i < 32; i++) { + EXPECT_EQ(countl_zero(uint32_t{1} << i), 31 - i); + } + + for (int i = 0; i < 64; i++) { + EXPECT_EQ(countl_zero(uint64_t{1} << i), 63 - i); + } +} + +TEST(Counting, LeadingOnes) { +#if ABSL_INTERNAL_HAS_CONSTEXPR_CLZ + static_assert(countl_one(uint8_t{}) == 0, ""); + static_assert(countl_one(static_cast(-1)) == 8, ""); + static_assert(countl_one(uint16_t{}) == 0, ""); + static_assert(countl_one(static_cast(-1)) == 16, ""); + static_assert(countl_one(uint32_t{}) == 0, ""); + static_assert(countl_one(~uint32_t{}) == 32, ""); + static_assert(countl_one(uint64_t{}) == 0, ""); + static_assert(countl_one(~uint64_t{}) == 64, ""); +#endif + + EXPECT_EQ(countl_one(uint8_t{}), 0); + EXPECT_EQ(countl_one(static_cast(-1)), 8); + EXPECT_EQ(countl_one(uint16_t{}), 0); + EXPECT_EQ(countl_one(static_cast(-1)), 16); + EXPECT_EQ(countl_one(uint32_t{}), 0); + EXPECT_EQ(countl_one(~uint32_t{}), 32); + EXPECT_EQ(countl_one(uint64_t{}), 0); + EXPECT_EQ(countl_one(~uint64_t{}), 64); +} + +TEST(Counting, TrailingZeroes) { +#if ABSL_INTERNAL_HAS_CONSTEXPR_CTZ + static_assert(countr_zero(uint8_t{}) == 8, ""); + static_assert(countr_zero(static_cast(-1)) == 0, ""); + static_assert(countr_zero(uint16_t{}) == 16, ""); + static_assert(countr_zero(static_cast(-1)) == 0, ""); + static_assert(countr_zero(uint32_t{}) == 32, ""); + static_assert(countr_zero(~uint32_t{}) == 0, ""); + static_assert(countr_zero(uint64_t{}) == 64, ""); + static_assert(countr_zero(~uint64_t{}) == 0, ""); +#endif + + EXPECT_EQ(countr_zero(uint8_t{}), 8); + EXPECT_EQ(countr_zero(static_cast(-1)), 0); + EXPECT_EQ(countr_zero(uint16_t{}), 16); + EXPECT_EQ(countr_zero(static_cast(-1)), 0); + EXPECT_EQ(countr_zero(uint32_t{}), 32); + EXPECT_EQ(countr_zero(~uint32_t{}), 0); + EXPECT_EQ(countr_zero(uint64_t{}), 64); + EXPECT_EQ(countr_zero(~uint64_t{}), 0); +} + +TEST(Counting, TrailingOnes) { +#if ABSL_INTERNAL_HAS_CONSTEXPR_CTZ + static_assert(countr_one(uint8_t{}) == 0, ""); + static_assert(countr_one(static_cast(-1)) == 8, ""); + static_assert(countr_one(uint16_t{}) == 0, ""); + static_assert(countr_one(static_cast(-1)) == 16, ""); + static_assert(countr_one(uint32_t{}) == 0, ""); + static_assert(countr_one(~uint32_t{}) == 32, ""); + static_assert(countr_one(uint64_t{}) == 0, ""); + static_assert(countr_one(~uint64_t{}) == 64, ""); +#endif + + EXPECT_EQ(countr_one(uint8_t{}), 0); + EXPECT_EQ(countr_one(static_cast(-1)), 8); + EXPECT_EQ(countr_one(uint16_t{}), 0); + EXPECT_EQ(countr_one(static_cast(-1)), 16); + EXPECT_EQ(countr_one(uint32_t{}), 0); + EXPECT_EQ(countr_one(~uint32_t{}), 32); + EXPECT_EQ(countr_one(uint64_t{}), 0); + EXPECT_EQ(countr_one(~uint64_t{}), 64); +} + +TEST(Counting, Popcount) { +#if ABSL_INTERNAL_HAS_CONSTEXPR_POPCOUNT + static_assert(popcount(uint8_t{}) == 0, ""); + static_assert(popcount(uint8_t{1}) == 1, ""); + static_assert(popcount(static_cast(-1)) == 8, ""); + static_assert(popcount(uint16_t{}) == 0, ""); + static_assert(popcount(uint16_t{1}) == 1, ""); + static_assert(popcount(static_cast(-1)) == 16, ""); + static_assert(popcount(uint32_t{}) == 0, ""); + static_assert(popcount(uint32_t{1}) == 1, ""); + static_assert(popcount(~uint32_t{}) == 32, ""); + static_assert(popcount(uint64_t{}) == 0, ""); + static_assert(popcount(uint64_t{1}) == 1, ""); + static_assert(popcount(~uint64_t{}) == 64, ""); +#endif // ABSL_INTERNAL_HAS_CONSTEXPR_POPCOUNT + + EXPECT_EQ(popcount(uint8_t{}), 0); + EXPECT_EQ(popcount(uint8_t{1}), 1); + EXPECT_EQ(popcount(static_cast(-1)), 8); + EXPECT_EQ(popcount(uint16_t{}), 0); + EXPECT_EQ(popcount(uint16_t{1}), 1); + EXPECT_EQ(popcount(static_cast(-1)), 16); + EXPECT_EQ(popcount(uint32_t{}), 0); + EXPECT_EQ(popcount(uint32_t{1}), 1); + EXPECT_EQ(popcount(~uint32_t{}), 32); + EXPECT_EQ(popcount(uint64_t{}), 0); + EXPECT_EQ(popcount(uint64_t{1}), 1); + EXPECT_EQ(popcount(~uint64_t{}), 64); + + for (int i = 0; i < 8; i++) { + EXPECT_EQ(popcount(static_cast(uint8_t{1} << i)), 1); + EXPECT_EQ(popcount(static_cast(static_cast(-1) ^ + (uint8_t{1} << i))), + 7); + } + + for (int i = 0; i < 16; i++) { + EXPECT_EQ(popcount(static_cast(uint16_t{1} << i)), 1); + EXPECT_EQ(popcount(static_cast(static_cast(-1) ^ + (uint16_t{1} << i))), + 15); + } + + for (int i = 0; i < 32; i++) { + EXPECT_EQ(popcount(uint32_t{1} << i), 1); + EXPECT_EQ(popcount(static_cast(-1) ^ (uint32_t{1} << i)), 31); + } + + for (int i = 0; i < 64; i++) { + EXPECT_EQ(popcount(uint64_t{1} << i), 1); + EXPECT_EQ(popcount(static_cast(-1) ^ (uint64_t{1} << i)), 63); + } +} + +template +struct PopcountInput { + T value = 0; + int expected = 0; +}; + +template +PopcountInput GeneratePopcountInput(absl::BitGen& gen) { + PopcountInput ret; + for (int i = 0; i < std::numeric_limits::digits; i++) { + bool coin = absl::Bernoulli(gen, 0.2); + if (coin) { + ret.value |= T{1} << i; + ret.expected++; + } + } + return ret; +} + +TEST(Counting, PopcountFuzz) { + absl::BitGen rng; + constexpr int kTrials = 100; + + for (int i = 0; i < kTrials; ++i) { + auto input = GeneratePopcountInput(rng); + EXPECT_EQ(popcount(input.value), input.expected); + } + + for (int i = 0; i < kTrials; ++i) { + auto input = GeneratePopcountInput(rng); + EXPECT_EQ(popcount(input.value), input.expected); + } + + for (int i = 0; i < kTrials; ++i) { + auto input = GeneratePopcountInput(rng); + EXPECT_EQ(popcount(input.value), input.expected); + } + + for (int i = 0; i < kTrials; ++i) { + auto input = GeneratePopcountInput(rng); + EXPECT_EQ(popcount(input.value), input.expected); + } +} + +TEST(IntegralPowersOfTwo, SingleBit) { + EXPECT_FALSE(has_single_bit(uint8_t{})); + EXPECT_FALSE(has_single_bit(static_cast(-1))); + EXPECT_FALSE(has_single_bit(uint16_t{})); + EXPECT_FALSE(has_single_bit(static_cast(-1))); + EXPECT_FALSE(has_single_bit(uint32_t{})); + EXPECT_FALSE(has_single_bit(~uint32_t{})); + EXPECT_FALSE(has_single_bit(uint64_t{})); + EXPECT_FALSE(has_single_bit(~uint64_t{})); + + static_assert(!has_single_bit(0u), ""); + static_assert(has_single_bit(1u), ""); + static_assert(has_single_bit(2u), ""); + static_assert(!has_single_bit(3u), ""); + static_assert(has_single_bit(4u), ""); + static_assert(!has_single_bit(1337u), ""); + static_assert(has_single_bit(65536u), ""); + static_assert(has_single_bit(uint32_t{1} << 30), ""); + static_assert(has_single_bit(uint64_t{1} << 42), ""); + + EXPECT_FALSE(has_single_bit(0u)); + EXPECT_TRUE(has_single_bit(1u)); + EXPECT_TRUE(has_single_bit(2u)); + EXPECT_FALSE(has_single_bit(3u)); + EXPECT_TRUE(has_single_bit(4u)); + EXPECT_FALSE(has_single_bit(1337u)); + EXPECT_TRUE(has_single_bit(65536u)); + EXPECT_TRUE(has_single_bit(uint32_t{1} << 30)); + EXPECT_TRUE(has_single_bit(uint64_t{1} << 42)); + + EXPECT_TRUE(has_single_bit( + static_cast(std::numeric_limits::max() / 2 + 1))); + EXPECT_TRUE(has_single_bit( + static_cast(std::numeric_limits::max() / 2 + 1))); + EXPECT_TRUE(has_single_bit( + static_cast(std::numeric_limits::max() / 2 + 1))); + EXPECT_TRUE(has_single_bit( + static_cast(std::numeric_limits::max() / 2 + 1))); +} + +template +bool IsBitCeilConstantExpression(int) { + return true; +} +template +bool IsBitCeilConstantExpression(char) { + return false; +} + +TEST(IntegralPowersOfTwo, Ceiling) { +#if ABSL_INTERNAL_HAS_CONSTEXPR_CLZ + static_assert(bit_ceil(0u) == 1, ""); + static_assert(bit_ceil(1u) == 1, ""); + static_assert(bit_ceil(2u) == 2, ""); + static_assert(bit_ceil(3u) == 4, ""); + static_assert(bit_ceil(4u) == 4, ""); + static_assert(bit_ceil(1337u) == 2048, ""); + static_assert(bit_ceil(65536u) == 65536, ""); + static_assert(bit_ceil(65536u - 1337u) == 65536, ""); + static_assert(bit_ceil(uint32_t{0x80000000}) == uint32_t{0x80000000}, ""); + static_assert(bit_ceil(uint64_t{0x40000000000}) == uint64_t{0x40000000000}, + ""); + static_assert( + bit_ceil(uint64_t{0x8000000000000000}) == uint64_t{0x8000000000000000}, + ""); + + EXPECT_TRUE((IsBitCeilConstantExpression(0))); + EXPECT_TRUE((IsBitCeilConstantExpression(0))); + EXPECT_FALSE((IsBitCeilConstantExpression(0))); + EXPECT_FALSE((IsBitCeilConstantExpression(0))); + + EXPECT_TRUE((IsBitCeilConstantExpression(0))); + EXPECT_TRUE((IsBitCeilConstantExpression(0))); + EXPECT_FALSE((IsBitCeilConstantExpression(0))); + EXPECT_FALSE((IsBitCeilConstantExpression(0))); + + EXPECT_TRUE((IsBitCeilConstantExpression(0))); + EXPECT_TRUE((IsBitCeilConstantExpression(0))); + EXPECT_FALSE( + (IsBitCeilConstantExpression(0))); + EXPECT_FALSE( + (IsBitCeilConstantExpression(0))); + + EXPECT_TRUE((IsBitCeilConstantExpression(0))); + EXPECT_TRUE( + (IsBitCeilConstantExpression(0))); + EXPECT_FALSE( + (IsBitCeilConstantExpression(0))); + EXPECT_FALSE( + (IsBitCeilConstantExpression(0))); +#endif + + EXPECT_EQ(bit_ceil(0u), 1); + EXPECT_EQ(bit_ceil(1u), 1); + EXPECT_EQ(bit_ceil(2u), 2); + EXPECT_EQ(bit_ceil(3u), 4); + EXPECT_EQ(bit_ceil(4u), 4); + EXPECT_EQ(bit_ceil(1337u), 2048); + EXPECT_EQ(bit_ceil(65536u), 65536); + EXPECT_EQ(bit_ceil(65536u - 1337u), 65536); + EXPECT_EQ(bit_ceil(uint64_t{0x40000000000}), uint64_t{0x40000000000}); +} + +TEST(IntegralPowersOfTwo, Floor) { +#if ABSL_INTERNAL_HAS_CONSTEXPR_CLZ + static_assert(bit_floor(0u) == 0, ""); + static_assert(bit_floor(1u) == 1, ""); + static_assert(bit_floor(2u) == 2, ""); + static_assert(bit_floor(3u) == 2, ""); + static_assert(bit_floor(4u) == 4, ""); + static_assert(bit_floor(1337u) == 1024, ""); + static_assert(bit_floor(65536u) == 65536, ""); + static_assert(bit_floor(65536u - 1337u) == 32768, ""); + static_assert(bit_floor(uint64_t{0x40000000000}) == uint64_t{0x40000000000}, + ""); +#endif + + EXPECT_EQ(bit_floor(0u), 0); + EXPECT_EQ(bit_floor(1u), 1); + EXPECT_EQ(bit_floor(2u), 2); + EXPECT_EQ(bit_floor(3u), 2); + EXPECT_EQ(bit_floor(4u), 4); + EXPECT_EQ(bit_floor(1337u), 1024); + EXPECT_EQ(bit_floor(65536u), 65536); + EXPECT_EQ(bit_floor(65536u - 1337u), 32768); + EXPECT_EQ(bit_floor(uint64_t{0x40000000000}), uint64_t{0x40000000000}); + + for (int i = 0; i < 8; i++) { + uint8_t input = uint8_t{1} << i; + EXPECT_EQ(bit_floor(input), input); + if (i > 0) { + EXPECT_EQ(bit_floor(static_cast(input + 1)), input); + } + } + + for (int i = 0; i < 16; i++) { + uint16_t input = uint16_t{1} << i; + EXPECT_EQ(bit_floor(input), input); + if (i > 0) { + EXPECT_EQ(bit_floor(static_cast(input + 1)), input); + } + } + + for (int i = 0; i < 32; i++) { + uint32_t input = uint32_t{1} << i; + EXPECT_EQ(bit_floor(input), input); + if (i > 0) { + EXPECT_EQ(bit_floor(input + 1), input); + } + } + + for (int i = 0; i < 64; i++) { + uint64_t input = uint64_t{1} << i; + EXPECT_EQ(bit_floor(input), input); + if (i > 0) { + EXPECT_EQ(bit_floor(input + 1), input); + } + } +} + +TEST(IntegralPowersOfTwo, Width) { +#if ABSL_INTERNAL_HAS_CONSTEXPR_CLZ + static_assert(bit_width(uint8_t{}) == 0, ""); + static_assert(bit_width(uint8_t{1}) == 1, ""); + static_assert(bit_width(uint8_t{3}) == 2, ""); + static_assert(bit_width(static_cast(-1)) == 8, ""); + static_assert(bit_width(uint16_t{}) == 0, ""); + static_assert(bit_width(uint16_t{1}) == 1, ""); + static_assert(bit_width(uint16_t{3}) == 2, ""); + static_assert(bit_width(static_cast(-1)) == 16, ""); + static_assert(bit_width(uint32_t{}) == 0, ""); + static_assert(bit_width(uint32_t{1}) == 1, ""); + static_assert(bit_width(uint32_t{3}) == 2, ""); + static_assert(bit_width(~uint32_t{}) == 32, ""); + static_assert(bit_width(uint64_t{}) == 0, ""); + static_assert(bit_width(uint64_t{1}) == 1, ""); + static_assert(bit_width(uint64_t{3}) == 2, ""); + static_assert(bit_width(~uint64_t{}) == 64, ""); +#endif + + EXPECT_EQ(bit_width(uint8_t{}), 0); + EXPECT_EQ(bit_width(uint8_t{1}), 1); + EXPECT_EQ(bit_width(uint8_t{3}), 2); + EXPECT_EQ(bit_width(static_cast(-1)), 8); + EXPECT_EQ(bit_width(uint16_t{}), 0); + EXPECT_EQ(bit_width(uint16_t{1}), 1); + EXPECT_EQ(bit_width(uint16_t{3}), 2); + EXPECT_EQ(bit_width(static_cast(-1)), 16); + EXPECT_EQ(bit_width(uint32_t{}), 0); + EXPECT_EQ(bit_width(uint32_t{1}), 1); + EXPECT_EQ(bit_width(uint32_t{3}), 2); + EXPECT_EQ(bit_width(~uint32_t{}), 32); + EXPECT_EQ(bit_width(uint64_t{}), 0); + EXPECT_EQ(bit_width(uint64_t{1}), 1); + EXPECT_EQ(bit_width(uint64_t{3}), 2); + EXPECT_EQ(bit_width(~uint64_t{}), 64); + + for (int i = 0; i < 8; i++) { + EXPECT_EQ(bit_width(static_cast(uint8_t{1} << i)), i + 1); + } + + for (int i = 0; i < 16; i++) { + EXPECT_EQ(bit_width(static_cast(uint16_t{1} << i)), i + 1); + } + + for (int i = 0; i < 32; i++) { + EXPECT_EQ(bit_width(uint32_t{1} << i), i + 1); + } + + for (int i = 0; i < 64; i++) { + EXPECT_EQ(bit_width(uint64_t{1} << i), i + 1); + } +} + +} // namespace +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/numeric/internal/bits.h b/absl/numeric/internal/bits.h new file mode 100644 index 00000000..60478f52 --- /dev/null +++ b/absl/numeric/internal/bits.h @@ -0,0 +1,350 @@ +// Copyright 2020 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_NUMERIC_INTERNAL_BITS_H_ +#define ABSL_NUMERIC_INTERNAL_BITS_H_ + +#include +#include +#include + +// Clang on Windows has __builtin_clzll; otherwise we need to use the +// windows intrinsic functions. +#if defined(_MSC_VER) && !defined(__clang__) +#include +#if defined(_M_X64) +#pragma intrinsic(_BitScanReverse64) +#pragma intrinsic(_BitScanForward64) +#endif +#pragma intrinsic(_BitScanReverse) +#pragma intrinsic(_BitScanForward) +#endif + +#include "absl/base/attributes.h" +#include "absl/base/config.h" + +#if ABSL_HAVE_BUILTIN(__builtin_popcountl) && \ + ABSL_HAVE_BUILTIN(__builtin_popcountll) +#define ABSL_INTERNAL_CONSTEXPR_POPCOUNT constexpr +#define ABSL_INTERNAL_HAS_CONSTEXPR_POPCOUNT 1 +#else +#define ABSL_INTERNAL_CONSTEXPR_POPCOUNT +#define ABSL_INTERNAL_HAS_CONSTEXPR_POPCOUNT 0 +#endif + +#if ABSL_HAVE_BUILTIN(__builtin_clz) && ABSL_HAVE_BUILTIN(__builtin_clzll) +#define ABSL_INTERNAL_CONSTEXPR_CLZ constexpr +#define ABSL_INTERNAL_HAS_CONSTEXPR_CLZ 1 +#else +#define ABSL_INTERNAL_CONSTEXPR_CLZ +#define ABSL_INTERNAL_HAS_CONSTEXPR_CLZ 0 +#endif + +#if ABSL_HAVE_BUILTIN(__builtin_ctz) && ABSL_HAVE_BUILTIN(__builtin_ctzll) +#define ABSL_INTERNAL_CONSTEXPR_CTZ constexpr +#define ABSL_INTERNAL_HAS_CONSTEXPR_CTZ 1 +#else +#define ABSL_INTERNAL_CONSTEXPR_CTZ +#define ABSL_INTERNAL_HAS_CONSTEXPR_CTZ 0 +#endif + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace numeric_internal { + +constexpr bool IsPowerOf2(unsigned int x) noexcept { + return x != 0 && (x & (x - 1)) == 0; +} + +template +ABSL_MUST_USE_RESULT ABSL_ATTRIBUTE_ALWAYS_INLINE constexpr T RotateRight( + T x, int s) noexcept { + static_assert(std::is_unsigned::value, "T must be unsigned"); + static_assert(IsPowerOf2(std::numeric_limits::digits), + "T must have a power-of-2 size"); + + return static_cast(x >> (s & (std::numeric_limits::digits - 1))) | + static_cast(x << ((-s) & (std::numeric_limits::digits - 1))); +} + +template +ABSL_MUST_USE_RESULT ABSL_ATTRIBUTE_ALWAYS_INLINE constexpr T RotateLeft( + T x, int s) noexcept { + static_assert(std::is_unsigned::value, "T must be unsigned"); + static_assert(IsPowerOf2(std::numeric_limits::digits), + "T must have a power-of-2 size"); + + return static_cast(x << (s & (std::numeric_limits::digits - 1))) | + static_cast(x >> ((-s) & (std::numeric_limits::digits - 1))); +} + +ABSL_INTERNAL_CONSTEXPR_POPCOUNT int Popcount32(uint32_t x) noexcept { +#if ABSL_HAVE_BUILTIN(__builtin_popcount) + static_assert(sizeof(unsigned int) == sizeof(x), + "__builtin_popcount does not take 32-bit arg"); + return __builtin_popcount(x); +#else + x -= ((x >> 1) & 0x55555555); + x = ((x >> 2) & 0x33333333) + (x & 0x33333333); + return static_cast((((x + (x >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24); +#endif +} + +ABSL_INTERNAL_CONSTEXPR_POPCOUNT int Popcount64(uint64_t x) noexcept { +#if ABSL_HAVE_BUILTIN(__builtin_popcountll) + static_assert(sizeof(unsigned long long) == sizeof(x), // NOLINT(runtime/int) + "__builtin_popcount does not take 64-bit arg"); + return __builtin_popcountll(x); +#else + x -= (x >> 1) & 0x5555555555555555ULL; + x = ((x >> 2) & 0x3333333333333333ULL) + (x & 0x3333333333333333ULL); + return static_cast( + (((x + (x >> 4)) & 0xF0F0F0F0F0F0F0FULL) * 0x101010101010101ULL) >> 56); +#endif +} + +template +ABSL_ATTRIBUTE_ALWAYS_INLINE ABSL_INTERNAL_CONSTEXPR_POPCOUNT inline int +Popcount(T x) noexcept { + static_assert(std::is_unsigned::value, "T must be unsigned"); + static_assert(IsPowerOf2(std::numeric_limits::digits), + "T must have a power-of-2 size"); + static_assert(sizeof(x) <= sizeof(uint64_t), "T is too large"); + return sizeof(x) <= sizeof(uint32_t) ? Popcount32(x) : Popcount64(x); +} + +ABSL_ATTRIBUTE_ALWAYS_INLINE ABSL_INTERNAL_CONSTEXPR_CLZ inline int +CountLeadingZeroes32(uint32_t x) { +#if ABSL_HAVE_BUILTIN(__builtin_clz) + // Use __builtin_clz, which uses the following instructions: + // x86: bsr, lzcnt + // ARM64: clz + // PPC: cntlzd + + static_assert(sizeof(unsigned int) == sizeof(x), + "__builtin_clz does not take 32-bit arg"); + // Handle 0 as a special case because __builtin_clz(0) is undefined. + return x == 0 ? 32 : __builtin_clz(x); +#elif defined(_MSC_VER) && !defined(__clang__) + unsigned long result = 0; // NOLINT(runtime/int) + if (_BitScanReverse(&result, x)) { + return 31 - result; + } + return 32; +#else + int zeroes = 28; + if (x >> 16) { + zeroes -= 16; + x >>= 16; + } + if (x >> 8) { + zeroes -= 8; + x >>= 8; + } + if (x >> 4) { + zeroes -= 4; + x >>= 4; + } + return "\4\3\2\2\1\1\1\1\0\0\0\0\0\0\0"[x] + zeroes; +#endif +} + +ABSL_ATTRIBUTE_ALWAYS_INLINE ABSL_INTERNAL_CONSTEXPR_CLZ inline int +CountLeadingZeroes16(uint16_t x) { +#if ABSL_HAVE_BUILTIN(__builtin_clzs) + static_assert(sizeof(unsigned short) == sizeof(x), // NOLINT(runtime/int) + "__builtin_clzs does not take 16-bit arg"); + return x == 0 ? 16 : __builtin_clzs(x); +#else + return CountLeadingZeroes32(x) - 16; +#endif +} + +ABSL_ATTRIBUTE_ALWAYS_INLINE ABSL_INTERNAL_CONSTEXPR_CLZ inline int +CountLeadingZeroes64(uint64_t x) { +#if ABSL_HAVE_BUILTIN(__builtin_clzll) + // Use __builtin_clzll, which uses the following instructions: + // x86: bsr, lzcnt + // ARM64: clz + // PPC: cntlzd + static_assert(sizeof(unsigned long long) == sizeof(x), // NOLINT(runtime/int) + "__builtin_clzll does not take 64-bit arg"); + + // Handle 0 as a special case because __builtin_clzll(0) is undefined. + return x == 0 ? 64 : __builtin_clzll(x); +#elif defined(_MSC_VER) && !defined(__clang__) && defined(_M_X64) + // MSVC does not have __buitin_clzll. Use _BitScanReverse64. + unsigned long result = 0; // NOLINT(runtime/int) + if (_BitScanReverse64(&result, x)) { + return 63 - result; + } + return 64; +#elif defined(_MSC_VER) && !defined(__clang__) + // MSVC does not have __buitin_clzll. Compose two calls to _BitScanReverse + unsigned long result = 0; // NOLINT(runtime/int) + if ((x >> 32) && + _BitScanReverse(&result, static_cast(x >> 32))) { + return 31 - result; + } + if (_BitScanReverse(&result, static_cast(x))) { + return 63 - result; + } + return 64; +#else + int zeroes = 60; + if (x >> 32) { + zeroes -= 32; + x >>= 32; + } + if (x >> 16) { + zeroes -= 16; + x >>= 16; + } + if (x >> 8) { + zeroes -= 8; + x >>= 8; + } + if (x >> 4) { + zeroes -= 4; + x >>= 4; + } + return "\4\3\2\2\1\1\1\1\0\0\0\0\0\0\0"[x] + zeroes; +#endif +} + +template +ABSL_ATTRIBUTE_ALWAYS_INLINE ABSL_INTERNAL_CONSTEXPR_CLZ inline int +CountLeadingZeroes(T x) { + static_assert(std::is_unsigned::value, "T must be unsigned"); + static_assert(IsPowerOf2(std::numeric_limits::digits), + "T must have a power-of-2 size"); + static_assert(sizeof(T) <= sizeof(uint64_t), "T too large"); + return sizeof(T) <= sizeof(uint16_t) + ? CountLeadingZeroes16(x) - + (std::numeric_limits::digits - + std::numeric_limits::digits) + : (sizeof(T) <= sizeof(uint32_t) + ? CountLeadingZeroes32(x) - + (std::numeric_limits::digits - + std::numeric_limits::digits) + : CountLeadingZeroes64(x)); +} + +ABSL_ATTRIBUTE_ALWAYS_INLINE ABSL_INTERNAL_CONSTEXPR_CTZ inline int +CountTrailingZeroesNonzero32(uint32_t x) { +#if ABSL_HAVE_BUILTIN(__builtin_ctz) + static_assert(sizeof(unsigned int) == sizeof(x), + "__builtin_ctz does not take 32-bit arg"); + return __builtin_ctz(x); +#elif defined(_MSC_VER) && !defined(__clang__) + unsigned long result = 0; // NOLINT(runtime/int) + _BitScanForward(&result, x); + return result; +#else + int c = 31; + x &= ~x + 1; + if (x & 0x0000FFFF) c -= 16; + if (x & 0x00FF00FF) c -= 8; + if (x & 0x0F0F0F0F) c -= 4; + if (x & 0x33333333) c -= 2; + if (x & 0x55555555) c -= 1; + return c; +#endif +} + +ABSL_ATTRIBUTE_ALWAYS_INLINE ABSL_INTERNAL_CONSTEXPR_CTZ inline int +CountTrailingZeroesNonzero64(uint64_t x) { +#if ABSL_HAVE_BUILTIN(__builtin_ctzll) + static_assert(sizeof(unsigned long long) == sizeof(x), // NOLINT(runtime/int) + "__builtin_ctzll does not take 64-bit arg"); + return __builtin_ctzll(x); +#elif defined(_MSC_VER) && !defined(__clang__) && defined(_M_X64) + unsigned long result = 0; // NOLINT(runtime/int) + _BitScanForward64(&result, x); + return result; +#elif defined(_MSC_VER) && !defined(__clang__) + unsigned long result = 0; // NOLINT(runtime/int) + if (static_cast(x) == 0) { + _BitScanForward(&result, static_cast(x >> 32)); + return result + 32; + } + _BitScanForward(&result, static_cast(x)); + return result; +#else + int c = 63; + x &= ~x + 1; + if (x & 0x00000000FFFFFFFF) c -= 32; + if (x & 0x0000FFFF0000FFFF) c -= 16; + if (x & 0x00FF00FF00FF00FF) c -= 8; + if (x & 0x0F0F0F0F0F0F0F0F) c -= 4; + if (x & 0x3333333333333333) c -= 2; + if (x & 0x5555555555555555) c -= 1; + return c; +#endif +} + +ABSL_ATTRIBUTE_ALWAYS_INLINE ABSL_INTERNAL_CONSTEXPR_CTZ inline int +CountTrailingZeroesNonzero16(uint16_t x) { +#if ABSL_HAVE_BUILTIN(__builtin_ctzs) + static_assert(sizeof(unsigned short) == sizeof(x), // NOLINT(runtime/int) + "__builtin_ctzs does not take 16-bit arg"); + return __builtin_ctzs(x); +#else + return CountTrailingZeroesNonzero32(x); +#endif +} + +template +ABSL_ATTRIBUTE_ALWAYS_INLINE ABSL_INTERNAL_CONSTEXPR_CTZ inline int +CountTrailingZeroes(T x) noexcept { + static_assert(std::is_unsigned::value, "T must be unsigned"); + static_assert(IsPowerOf2(std::numeric_limits::digits), + "T must have a power-of-2 size"); + static_assert(sizeof(T) <= sizeof(uint64_t), "T too large"); + return x == 0 ? std::numeric_limits::digits + : (sizeof(T) <= sizeof(uint16_t) + ? CountTrailingZeroesNonzero16(x) + : (sizeof(T) <= sizeof(uint32_t) + ? CountTrailingZeroesNonzero32(x) + : CountTrailingZeroesNonzero64(x))); +} + +// If T is narrower than unsigned, T{1} << bit_width will be promoted. We +// want to force it to wraparound so that bit_ceil of an invalid value are not +// core constant expressions. +template +ABSL_ATTRIBUTE_ALWAYS_INLINE ABSL_INTERNAL_CONSTEXPR_CLZ inline + typename std::enable_if::value, T>::type + BitCeilPromotionHelper(T x, T promotion) { + return (T{1} << (x + promotion)) >> promotion; +} + +template +ABSL_ATTRIBUTE_ALWAYS_INLINE ABSL_INTERNAL_CONSTEXPR_CLZ inline + typename std::enable_if::value, T>::type + BitCeilNonPowerOf2(T x) { + // If T is narrower than unsigned, it undergoes promotion to unsigned when we + // shift. We calcualte the number of bits added by the wider type. + return BitCeilPromotionHelper( + static_cast(std::numeric_limits::digits - CountLeadingZeroes(x)), + T{sizeof(T) >= sizeof(unsigned) ? 0 + : std::numeric_limits::digits - + std::numeric_limits::digits}); +} + +} // namespace numeric_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_NUMERIC_INTERNAL_BITS_H_ diff --git a/absl/random/CMakeLists.txt b/absl/random/CMakeLists.txt index 7d7bec83..d717a5ce 100644 --- a/absl/random/CMakeLists.txt +++ b/absl/random/CMakeLists.txt @@ -673,7 +673,7 @@ absl_cc_library( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS - absl::bits + absl::internal_bits absl::random_internal_fastmath absl::random_internal_traits absl::type_traits @@ -690,7 +690,7 @@ absl_cc_library( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS - absl::bits + absl::internal_bits absl::config absl::int128 ) @@ -706,7 +706,7 @@ absl_cc_library( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS - absl::bits + absl::internal_bits ) # Internal-only target, do not depend on directly. @@ -902,7 +902,7 @@ absl_cc_test( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS - absl::bits + absl::internal_bits absl::flags absl::random_internal_generate_real gtest_main @@ -1201,7 +1201,7 @@ absl_cc_test( ${ABSL_DEFAULT_LINKOPTS} DEPS absl::random_internal_wide_multiply - absl::bits + absl::internal_bits absl::int128 gtest_main ) diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index 54c10151..6cdab257 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt @@ -56,7 +56,7 @@ absl_cc_library( DEPS absl::strings_internal absl::base - absl::bits + absl::internal_bits absl::config absl::core_headers absl::endian @@ -406,7 +406,7 @@ absl_cc_library( COPTS ${ABSL_DEFAULT_COPTS} DEPS - absl::bits + absl::internal_bits absl::strings absl::config absl::core_headers diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index f475042c..f87f4882 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc @@ -1298,26 +1298,23 @@ void Cord::CopyToArraySlowPath(char* dst) const { } } -Cord::ChunkIterator& Cord::ChunkIterator::operator++() { - ABSL_HARDENING_ASSERT(bytes_remaining_ > 0 && - "Attempted to iterate past `end()`"); - assert(bytes_remaining_ >= current_chunk_.size()); - bytes_remaining_ -= current_chunk_.size(); - - if (stack_of_right_children_.empty()) { +Cord::ChunkIterator& Cord::ChunkIterator::AdvanceStack() { + assert(absl::holds_alternative(context_)); + auto& stack_of_right_children = absl::get(context_); + if (stack_of_right_children.empty()) { assert(!current_chunk_.empty()); // Called on invalid iterator. // We have reached the end of the Cord. return *this; } // Process the next node on the stack. - CordRep* node = stack_of_right_children_.back(); - stack_of_right_children_.pop_back(); + CordRep* node = stack_of_right_children.back(); + stack_of_right_children.pop_back(); // Walk down the left branches until we hit a non-CONCAT node. Save the // right children to the stack for subsequent traversal. while (node->tag == CONCAT) { - stack_of_right_children_.push_back(node->concat()->right); + stack_of_right_children.push_back(node->concat()->right); node = node->concat()->left; } @@ -1360,6 +1357,8 @@ Cord Cord::ChunkIterator::AdvanceAndReadBytes(size_t n) { } return subcord; } + assert(absl::holds_alternative(context_)); + auto& stack_of_right_children = absl::get(context_); if (n < current_chunk_.size()) { // Range to read is a proper subrange of the current chunk. assert(current_leaf_ != nullptr); @@ -1388,13 +1387,13 @@ Cord Cord::ChunkIterator::AdvanceAndReadBytes(size_t n) { // Process the next node(s) on the stack, reading whole subtrees depending on // their length and how many bytes we are advancing. CordRep* node = nullptr; - while (!stack_of_right_children_.empty()) { - node = stack_of_right_children_.back(); - stack_of_right_children_.pop_back(); + while (!stack_of_right_children.empty()) { + node = stack_of_right_children.back(); + stack_of_right_children.pop_back(); if (node->length > n) break; // TODO(qrczak): This might unnecessarily recreate existing concat nodes. // Avoiding that would need pretty complicated logic (instead of - // current_leaf_, keep current_subtree_ which points to the highest node + // current_leaf, keep current_subtree_ which points to the highest node // such that the current leaf can be found on the path of left children // starting from current_subtree_; delay creating subnode while node is // below current_subtree_; find the proper node along the path of left @@ -1419,7 +1418,7 @@ Cord Cord::ChunkIterator::AdvanceAndReadBytes(size_t n) { while (node->tag == CONCAT) { if (node->concat()->left->length > n) { // Push right, descend left. - stack_of_right_children_.push_back(node->concat()->right); + stack_of_right_children.push_back(node->concat()->right); node = node->concat()->left; } else { // Read left, descend right. @@ -1462,12 +1461,20 @@ void Cord::ChunkIterator::AdvanceBytesSlowPath(size_t n) { n -= current_chunk_.size(); bytes_remaining_ -= current_chunk_.size(); + if (!absl::holds_alternative(context_)) { + // We have reached the end of the Cord. + assert(bytes_remaining_ == 0); + return; + } + // Process the next node(s) on the stack, skipping whole subtrees depending on // their length and how many bytes we are advancing. CordRep* node = nullptr; - while (!stack_of_right_children_.empty()) { - node = stack_of_right_children_.back(); - stack_of_right_children_.pop_back(); + assert(absl::holds_alternative(context_)); + auto& stack_of_right_children = absl::get(context_); + while (!stack_of_right_children.empty()) { + node = stack_of_right_children.back(); + stack_of_right_children.pop_back(); if (node->length > n) break; n -= node->length; bytes_remaining_ -= node->length; @@ -1485,7 +1492,7 @@ void Cord::ChunkIterator::AdvanceBytesSlowPath(size_t n) { while (node->tag == CONCAT) { if (node->concat()->left->length > n) { // Push right, descend left. - stack_of_right_children_.push_back(node->concat()->right); + stack_of_right_children.push_back(node->concat()->right); node = node->concat()->left; } else { // Skip left, descend right. diff --git a/absl/strings/cord.h b/absl/strings/cord.h index b9cdc435..5fc61a73 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -362,6 +362,8 @@ class Cord { friend class CharIterator; private: + using Stack = absl::InlinedVector; + // Constructs a `begin()` iterator from `cord`. explicit ChunkIterator(const Cord* cord); @@ -370,6 +372,10 @@ class Cord { void RemoveChunkPrefix(size_t n); Cord AdvanceAndReadBytes(size_t n); void AdvanceBytes(size_t n); + + // Stack specific operator++ + ChunkIterator& AdvanceStack(); + // Iterates `n` bytes, where `n` is expected to be greater than or equal to // `current_chunk_.size()`. void AdvanceBytesSlowPath(size_t n); @@ -383,8 +389,10 @@ class Cord { absl::cord_internal::CordRep* current_leaf_ = nullptr; // The number of bytes left in the `Cord` over which we are iterating. size_t bytes_remaining_ = 0; - absl::InlinedVector - stack_of_right_children_; + // Context of this chunk iterator, can be one of: + // - monostate: iterator holds only one chunk or is empty. + // - Stack : iterator holds a concat / substring tree + absl::variant context_; }; // Cord::ChunkIterator::chunk_begin() @@ -1100,13 +1108,29 @@ inline Cord::ChunkIterator::ChunkIterator(const Cord* cord) : bytes_remaining_(cord->size()) { if (cord->empty()) return; if (cord->contents_.is_tree()) { - stack_of_right_children_.push_back(cord->contents_.tree()); + Stack& stack_of_right_children = context_.emplace(); + stack_of_right_children.push_back(cord->contents_.tree()); operator++(); } else { current_chunk_ = absl::string_view(cord->contents_.data(), cord->size()); } } +inline Cord::ChunkIterator& Cord::ChunkIterator::operator++() { + ABSL_HARDENING_ASSERT(bytes_remaining_ > 0 && + "Attempted to iterate past `end()`"); + assert(bytes_remaining_ >= current_chunk_.size()); + bytes_remaining_ -= current_chunk_.size(); + if (bytes_remaining_ > 0) { + if (absl::holds_alternative(context_)) { + return AdvanceStack(); + } + } else { + current_chunk_ = {}; + } + return *this; +} + inline Cord::ChunkIterator Cord::ChunkIterator::operator++(int) { ChunkIterator tmp(*this); operator++(); diff --git a/absl/strings/internal/cord_internal.cc b/absl/strings/internal/cord_internal.cc index 35f4d235..59f2e4d9 100644 --- a/absl/strings/internal/cord_internal.cc +++ b/absl/strings/internal/cord_internal.cc @@ -24,7 +24,10 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace cord_internal { -ABSL_CONST_INIT std::atomic cord_ring_buffer_enabled(false); +ABSL_CONST_INIT std::atomic cord_ring_buffer_enabled( + kCordEnableRingBufferDefault); +ABSL_CONST_INIT std::atomic shallow_subcords_enabled( + kCordShallowSubcordsDefault); void CordRep::Destroy(CordRep* rep) { assert(rep != nullptr); diff --git a/absl/strings/internal/cord_internal.h b/absl/strings/internal/cord_internal.h index f1af8e6e..57e7046a 100644 --- a/absl/strings/internal/cord_internal.h +++ b/absl/strings/internal/cord_internal.h @@ -31,12 +31,23 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace cord_internal { +// Default feature enable states for cord ring buffers +enum CordFeatureDefaults { + kCordEnableRingBufferDefault = false, + kCordShallowSubcordsDefault = false +}; + extern std::atomic cord_ring_buffer_enabled; +extern std::atomic shallow_subcords_enabled; inline void enable_cord_ring_buffer(bool enable) { cord_ring_buffer_enabled.store(enable, std::memory_order_relaxed); } +inline void enable_shallow_subcords(bool enable) { + shallow_subcords_enabled.store(enable, std::memory_order_relaxed); +} + enum Constants { // The inlined size to use with absl::InlinedVector. // -- cgit v1.2.3 From 52acfe6fc6b8bb9b1b7854993dd0fb33f0f3c4af Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Mon, 14 Dec 2020 08:53:54 -0800 Subject: Export of internal Abseil changes -- 751781aa5b9e998f84a8a7e00789c80d3338730e by Abseil Team : Fix attributes.h compilation with -Wundef flag in C PiperOrigin-RevId: 347394915 -- 66070a8166b0e1a61236b954d07fbb378f4f990b by Abseil Team : Revert the usage of variant<> in Cord iterator and reader. The introduction of the variant may lead to some missed compiler optimizations. PiperOrigin-RevId: 347384869 GitOrigin-RevId: 751781aa5b9e998f84a8a7e00789c80d3338730e Change-Id: Ibf1190d498a6f968f2ea9b89467ccfb5224dafa8 --- absl/base/attributes.h | 2 +- absl/strings/BUILD.bazel | 1 - absl/strings/CMakeLists.txt | 1 - absl/strings/cord.cc | 11 ++++------- absl/strings/cord.h | 20 +++++++++----------- 5 files changed, 14 insertions(+), 21 deletions(-) (limited to 'absl/strings/cord.h') diff --git a/absl/base/attributes.h b/absl/base/attributes.h index f1d3cfe4..0ba1e7da 100644 --- a/absl/base/attributes.h +++ b/absl/base/attributes.h @@ -646,7 +646,7 @@ // Every usage of a deprecated entity will trigger a warning when compiled with // clang's `-Wdeprecated-declarations` option. This option is turned off by // default, but the warnings will be reported by clang-tidy. -#if defined(__clang__) && __cplusplus >= 201103L +#if defined(__clang__) && defined(__cplusplus) && __cplusplus >= 201103L #define ABSL_DEPRECATED(message) __attribute__((deprecated(message))) #endif diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index aab3a286..3154d80d 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -311,7 +311,6 @@ cc_library( "//absl/functional:function_ref", "//absl/meta:type_traits", "//absl/types:optional", - "//absl/types:variant", ], ) diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index 6cdab257..fa61ff61 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt @@ -571,7 +571,6 @@ absl_cc_library( absl::function_ref absl::inlined_vector absl::optional - absl::variant absl::raw_logging_internal absl::strings absl::strings_internal diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index f87f4882..8d51c750 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc @@ -1299,8 +1299,7 @@ void Cord::CopyToArraySlowPath(char* dst) const { } Cord::ChunkIterator& Cord::ChunkIterator::AdvanceStack() { - assert(absl::holds_alternative(context_)); - auto& stack_of_right_children = absl::get(context_); + auto& stack_of_right_children = stack_of_right_children_; if (stack_of_right_children.empty()) { assert(!current_chunk_.empty()); // Called on invalid iterator. // We have reached the end of the Cord. @@ -1357,8 +1356,7 @@ Cord Cord::ChunkIterator::AdvanceAndReadBytes(size_t n) { } return subcord; } - assert(absl::holds_alternative(context_)); - auto& stack_of_right_children = absl::get(context_); + auto& stack_of_right_children = stack_of_right_children_; if (n < current_chunk_.size()) { // Range to read is a proper subrange of the current chunk. assert(current_leaf_ != nullptr); @@ -1461,7 +1459,7 @@ void Cord::ChunkIterator::AdvanceBytesSlowPath(size_t n) { n -= current_chunk_.size(); bytes_remaining_ -= current_chunk_.size(); - if (!absl::holds_alternative(context_)) { + if (stack_of_right_children_.empty()) { // We have reached the end of the Cord. assert(bytes_remaining_ == 0); return; @@ -1470,8 +1468,7 @@ void Cord::ChunkIterator::AdvanceBytesSlowPath(size_t n) { // Process the next node(s) on the stack, skipping whole subtrees depending on // their length and how many bytes we are advancing. CordRep* node = nullptr; - assert(absl::holds_alternative(context_)); - auto& stack_of_right_children = absl::get(context_); + auto& stack_of_right_children = stack_of_right_children_; while (!stack_of_right_children.empty()) { node = stack_of_right_children.back(); stack_of_right_children.pop_back(); diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 5fc61a73..7136f034 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -82,7 +82,6 @@ #include "absl/strings/internal/string_constant.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" -#include "absl/types/variant.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -362,7 +361,11 @@ class Cord { friend class CharIterator; private: - using Stack = absl::InlinedVector; + // Stack of right children of concat nodes that we have to visit. + // Keep this at the end of the structure to avoid cache-thrashing. + // TODO(jgm): Benchmark to see if there's a more optimal value than 47 for + // the inlined vector size (47 exists for backward compatibility). + using Stack = absl::InlinedVector; // Constructs a `begin()` iterator from `cord`. explicit ChunkIterator(const Cord* cord); @@ -389,10 +392,8 @@ class Cord { absl::cord_internal::CordRep* current_leaf_ = nullptr; // The number of bytes left in the `Cord` over which we are iterating. size_t bytes_remaining_ = 0; - // Context of this chunk iterator, can be one of: - // - monostate: iterator holds only one chunk or is empty. - // - Stack : iterator holds a concat / substring tree - absl::variant context_; + // See 'Stack' alias definition. + Stack stack_of_right_children_; }; // Cord::ChunkIterator::chunk_begin() @@ -1108,8 +1109,7 @@ inline Cord::ChunkIterator::ChunkIterator(const Cord* cord) : bytes_remaining_(cord->size()) { if (cord->empty()) return; if (cord->contents_.is_tree()) { - Stack& stack_of_right_children = context_.emplace(); - stack_of_right_children.push_back(cord->contents_.tree()); + stack_of_right_children_.push_back(cord->contents_.tree()); operator++(); } else { current_chunk_ = absl::string_view(cord->contents_.data(), cord->size()); @@ -1122,9 +1122,7 @@ inline Cord::ChunkIterator& Cord::ChunkIterator::operator++() { assert(bytes_remaining_ >= current_chunk_.size()); bytes_remaining_ -= current_chunk_.size(); if (bytes_remaining_ > 0) { - if (absl::holds_alternative(context_)) { - return AdvanceStack(); - } + return AdvanceStack(); } else { current_chunk_ = {}; } -- cgit v1.2.3 From 22771d471930ce88e1e75d0ca9dd8c65a7b0f895 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Wed, 20 Jan 2021 12:39:22 -0800 Subject: Export of internal Abseil changes -- 642ab296a2c9629c44f3f2ce6911cd2488bcf416 by Derek Mauro : Remove an obsolete check in CMakeLists.txt PiperOrigin-RevId: 352852564 -- ce78cb96bcfd162737dbcf35005da3d1d6a3486b by Abseil Team : Clarify that the calling *thread* must have locked the mutex in order to unlock it. PiperOrigin-RevId: 352801804 -- 24e1f5f72756046f5265abf618e951c341f09b8d by Derek Mauro : Fixes failing CMake string comparisons https://cmake.org/cmake/help/latest/policy/CMP0054.html Fixes #791 PiperOrigin-RevId: 352791054 -- 0ac10bc3f4dca2c4c4b51d7b8196a2eaee9537a1 by Abseil Team : Introduce CordRepRing class This change introduces the CordRepRing class that implements all the lower level / internal implementation for upcoming CordRepRing ring buffer support in cord. PiperOrigin-RevId: 352771994 -- 4bd36dda61760785844f0f29f26d90cc18046f75 by Abseil Team : Optimize InlineData representation for cord sampling (cordz) This CL changes InlineData to allow us to store a (future) Cordz Info pointer directly into the inline representation: - make InlineData a class that provides a public API to set the active union members (tree or chars) and safely access that data. - change 'tree' and 'profiled' bits to be the 2 least significant bits, allowing us 62 continquous bits for storing a Cordz Info pointer. PiperOrigin-RevId: 352642411 -- dc55ba71bbce0e6a83e05a453990c51ac3d68426 by Mark Barolak : Add unit test coverage for the mutating overload of absl::AsciiStrToLower. PiperOrigin-RevId: 352626006 GitOrigin-RevId: 642ab296a2c9629c44f3f2ce6911cd2488bcf416 Change-Id: I6c5929dd830d3c630e14e7fd5387fc3e25a69100 --- CMake/AbseilDll.cmake | 2 + CMake/AbseilHelpers.cmake | 10 +- CMakeLists.txt | 2 +- absl/copts/AbseilConfigureCopts.cmake | 16 +- absl/strings/BUILD.bazel | 23 + absl/strings/CMakeLists.txt | 21 + absl/strings/ascii_test.cc | 4 + absl/strings/cord.cc | 119 +-- absl/strings/cord.h | 63 +- absl/strings/cord_ring_test.cc | 1572 ++++++++++++++++++++++++++++++++ absl/strings/internal/cord_internal.cc | 4 + absl/strings/internal/cord_internal.h | 206 ++++- absl/strings/internal/cord_rep_flat.h | 4 +- absl/strings/internal/cord_rep_ring.cc | 895 ++++++++++++++++++ absl/strings/internal/cord_rep_ring.h | 576 ++++++++++++ absl/synchronization/mutex.h | 2 +- absl/types/CMakeLists.txt | 4 - 17 files changed, 3375 insertions(+), 148 deletions(-) create mode 100644 absl/strings/cord_ring_test.cc create mode 100644 absl/strings/internal/cord_rep_ring.cc create mode 100644 absl/strings/internal/cord_rep_ring.h (limited to 'absl/strings/cord.h') diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index 7c98b21a..ac2f325c 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake @@ -196,6 +196,8 @@ set(ABSL_INTERNAL_DLL_FILES "strings/internal/cord_internal.cc" "strings/internal/cord_internal.h" "strings/internal/cord_rep_flat.h" + "strings/internal/cord_rep_ring.cc" + "strings/internal/cord_rep_ring.h" "strings/internal/charconv_bigint.cc" "strings/internal/charconv_bigint.h" "strings/internal/charconv_parse.cc" diff --git a/CMake/AbseilHelpers.cmake b/CMake/AbseilHelpers.cmake index e88507de..588d4fa2 100644 --- a/CMake/AbseilHelpers.cmake +++ b/CMake/AbseilHelpers.cmake @@ -104,7 +104,7 @@ function(absl_cc_library) endif() endforeach() - if("${ABSL_CC_SRCS}" STREQUAL "") + if(ABSL_CC_SRCS STREQUAL "") set(ABSL_CC_LIB_IS_INTERFACE 1) else() set(ABSL_CC_LIB_IS_INTERFACE 0) @@ -142,7 +142,7 @@ function(absl_cc_library) endif() # Generate a pkg-config file for every library: - if(${_build_type} STREQUAL "static" OR ${_build_type} STREQUAL "shared") + if(_build_type STREQUAL "static" OR _build_type STREQUAL "shared") if(NOT ABSL_CC_LIB_TESTONLY) if(absl_VERSION) set(PC_VERSION "${absl_VERSION}") @@ -183,7 +183,7 @@ Cflags: -I\${includedir}${PC_CFLAGS}\n") endif() if(NOT ABSL_CC_LIB_IS_INTERFACE) - if(${_build_type} STREQUAL "dll_dep") + if(_build_type STREQUAL "dll_dep") # This target depends on the DLL. When adding dependencies to this target, # any depended-on-target which is contained inside the DLL is replaced # with a dependency on the DLL. @@ -212,7 +212,7 @@ Cflags: -I\${includedir}${PC_CFLAGS}\n") "${_gtest_link_define}" ) - elseif(${_build_type} STREQUAL "static" OR ${_build_type} STREQUAL "shared") + elseif(_build_type STREQUAL "static" OR _build_type STREQUAL "shared") add_library(${_NAME} "") target_sources(${_NAME} PRIVATE ${ABSL_CC_LIB_SRCS} ${ABSL_CC_LIB_HDRS}) target_link_libraries(${_NAME} @@ -273,7 +273,7 @@ Cflags: -I\${includedir}${PC_CFLAGS}\n") $ ) - if (${_build_type} STREQUAL "dll") + if (_build_type STREQUAL "dll") set(ABSL_CC_LIB_DEPS abseil_dll) endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index c1ae8d56..d8c3b580 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,7 +51,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) # when absl is included as subproject (i.e. using add_subdirectory(abseil-cpp)) # in the source tree of a project that uses it, install rules are disabled. -if(NOT "^${CMAKE_SOURCE_DIR}$" STREQUAL "^${PROJECT_SOURCE_DIR}$") +if(NOT CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) option(ABSL_ENABLE_INSTALL "Enable install rule" OFF) else() option(ABSL_ENABLE_INSTALL "Enable install rule" ON) diff --git a/absl/copts/AbseilConfigureCopts.cmake b/absl/copts/AbseilConfigureCopts.cmake index acd46d04..90a1449d 100644 --- a/absl/copts/AbseilConfigureCopts.cmake +++ b/absl/copts/AbseilConfigureCopts.cmake @@ -12,16 +12,16 @@ else() set(ABSL_BUILD_DLL FALSE) endif() -if("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "x86_64|amd64|AMD64") +if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64") if (MSVC) set(ABSL_RANDOM_RANDEN_COPTS "${ABSL_RANDOM_HWAES_MSVC_X64_FLAGS}") else() set(ABSL_RANDOM_RANDEN_COPTS "${ABSL_RANDOM_HWAES_X64_FLAGS}") endif() -elseif("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "arm.*|aarch64") - if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm.*|aarch64") + if (CMAKE_SIZEOF_VOID_P STREQUAL "8") set(ABSL_RANDOM_RANDEN_COPTS "${ABSL_RANDOM_HWAES_ARM64_FLAGS}") - elseif("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") + elseif(CMAKE_SIZEOF_VOID_P STREQUAL "4") set(ABSL_RANDOM_RANDEN_COPTS "${ABSL_RANDOM_HWAES_ARM32_FLAGS}") else() message(WARNING "Value of CMAKE_SIZEOF_VOID_P (${CMAKE_SIZEOF_VOID_P}) is not supported.") @@ -32,10 +32,10 @@ else() endif() -if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(ABSL_DEFAULT_COPTS "${ABSL_GCC_FLAGS}") set(ABSL_TEST_COPTS "${ABSL_GCC_FLAGS};${ABSL_GCC_TEST_FLAGS}") -elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") # MATCHES so we get both Clang and AppleClang if(MSVC) # clang-cl is half MSVC, half LLVM @@ -45,7 +45,7 @@ elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") else() set(ABSL_DEFAULT_COPTS "${ABSL_LLVM_FLAGS}") set(ABSL_TEST_COPTS "${ABSL_LLVM_FLAGS};${ABSL_LLVM_TEST_FLAGS}") - if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") # AppleClang doesn't have lsan # https://developer.apple.com/documentation/code_diagnostics if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.5) @@ -54,7 +54,7 @@ elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") endif() endif() endif() -elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") set(ABSL_DEFAULT_COPTS "${ABSL_MSVC_FLAGS}") set(ABSL_TEST_COPTS "${ABSL_MSVC_FLAGS};${ABSL_MSVC_TEST_FLAGS}") set(ABSL_DEFAULT_LINKOPTS "${ABSL_MSVC_LINKOPTS}") diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index 70016791..d7c322f6 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -269,10 +269,12 @@ cc_library( name = "cord_internal", srcs = [ "internal/cord_internal.cc", + "internal/cord_rep_ring.cc", ], hdrs = [ "internal/cord_internal.h", "internal/cord_rep_flat.h", + "internal/cord_rep_ring.h", ], copts = ABSL_DEFAULT_COPTS, visibility = [ @@ -281,9 +283,13 @@ cc_library( deps = [ ":strings", "//absl/base:base_internal", + "//absl/base:config", "//absl/base:core_headers", + "//absl/base:raw_logging_internal", + "//absl/base:throw_delegate", "//absl/container:compressed_tuple", "//absl/container:inlined_vector", + "//absl/container:layout", "//absl/meta:type_traits", ], ) @@ -347,6 +353,23 @@ cc_test( ], ) +cc_test( + name = "cord_ring_test", + size = "medium", + srcs = ["cord_ring_test.cc"], + copts = ABSL_TEST_COPTS, + visibility = ["//visibility:private"], + deps = [ + ":cord_internal", + ":strings", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:raw_logging_internal", + "//absl/debugging:leak_check", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "substitute_test", size = "small", diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index 654d0fd6..a6204bf6 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt @@ -558,6 +558,8 @@ absl_cc_library( "cord.cc" "internal/cord_internal.cc" "internal/cord_internal.h" + "internal/cord_rep_ring.h" + "internal/cord_rep_ring.cc" "internal/cord_rep_flat.h" COPTS ${ABSL_DEFAULT_COPTS} @@ -565,6 +567,7 @@ absl_cc_library( absl::base absl::base_internal absl::compressed_tuple + absl::config absl::core_headers absl::endian absl::fixed_array @@ -574,6 +577,7 @@ absl_cc_library( absl::raw_logging_internal absl::strings absl::strings_internal + absl::throw_delegate absl::type_traits PUBLIC ) @@ -609,3 +613,20 @@ absl_cc_test( absl::fixed_array gmock_main ) + +absl_cc_test( + NAME + cord_ring_test + SRCS + "cord_ring_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::config + absl::cord + absl::strings + absl::base + absl::core_headers + absl::raw_logging_internal + gmock_main +) diff --git a/absl/strings/ascii_test.cc b/absl/strings/ascii_test.cc index 5ecd23f8..83af7825 100644 --- a/absl/strings/ascii_test.cc +++ b/absl/strings/ascii_test.cc @@ -197,11 +197,15 @@ TEST(AsciiStrTo, Lower) { const std::string str("GHIJKL"); const std::string str2("MNOPQR"); const absl::string_view sp(str2); + std::string mutable_str("STUVWX"); EXPECT_EQ("abcdef", absl::AsciiStrToLower(buf)); EXPECT_EQ("ghijkl", absl::AsciiStrToLower(str)); EXPECT_EQ("mnopqr", absl::AsciiStrToLower(sp)); + absl::AsciiStrToLower(&mutable_str); + EXPECT_EQ("stuvwx", mutable_str); + char mutable_buf[] = "Mutable"; std::transform(mutable_buf, mutable_buf + strlen(mutable_buf), mutable_buf, absl::ascii_tolower); diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index c118bdd8..df8c26d4 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc @@ -255,50 +255,49 @@ inline void Cord::InlineRep::set_data(const char* data, size_t n, bool nullify_tail) { static_assert(kMaxInline == 15, "set_data is hard-coded for a length of 15"); - cord_internal::SmallMemmove(data_.as_chars, data, n, nullify_tail); - set_tagged_size(static_cast(n)); + cord_internal::SmallMemmove(data_.as_chars(), data, n, nullify_tail); + set_inline_size(n); } inline char* Cord::InlineRep::set_data(size_t n) { assert(n <= kMaxInline); ResetToEmpty(); - set_tagged_size(static_cast(n)); - return data_.as_chars; + set_inline_size(n); + return data_.as_chars(); } inline CordRep* Cord::InlineRep::force_tree(size_t extra_hint) { - size_t len = tagged_size(); - if (len > kMaxInline) { - return data_.as_tree.rep; + if (data_.is_tree()) { + return data_.as_tree(); } + size_t len = inline_size(); CordRepFlat* result = CordRepFlat::New(len + extra_hint); result->length = len; - static_assert(kMinFlatLength >= sizeof(data_.as_chars), ""); - memcpy(result->Data(), data_.as_chars, sizeof(data_.as_chars)); + static_assert(kMinFlatLength >= sizeof(data_), ""); + memcpy(result->Data(), data_.as_chars(), sizeof(data_)); set_tree(result); return result; } inline void Cord::InlineRep::reduce_size(size_t n) { - size_t tag = tagged_size(); + size_t tag = inline_size(); assert(tag <= kMaxInline); assert(tag >= n); tag -= n; - memset(data_.as_chars + tag, 0, n); - set_tagged_size(static_cast(tag)); + memset(data_.as_chars() + tag, 0, n); + set_inline_size(static_cast(tag)); } inline void Cord::InlineRep::remove_prefix(size_t n) { - cord_internal::SmallMemmove(data_.as_chars, data_.as_chars + n, - tagged_size() - n); + cord_internal::SmallMemmove(data_.as_chars(), data_.as_chars() + n, + inline_size() - n); reduce_size(n); } void Cord::InlineRep::AppendTree(CordRep* tree) { if (tree == nullptr) return; - size_t len = tagged_size(); - if (len == 0) { + if (data_.is_empty()) { set_tree(tree); } else { set_tree(Concat(force_tree(0), tree)); @@ -307,8 +306,7 @@ void Cord::InlineRep::AppendTree(CordRep* tree) { void Cord::InlineRep::PrependTree(CordRep* tree) { assert(tree != nullptr); - size_t len = tagged_size(); - if (len == 0) { + if (data_.is_empty()) { set_tree(tree); } else { set_tree(Concat(tree, force_tree(0))); @@ -363,12 +361,14 @@ void Cord::InlineRep::GetAppendRegion(char** region, size_t* size, } // Try to fit in the inline buffer if possible. - size_t inline_length = tagged_size(); - if (inline_length < kMaxInline && max_length <= kMaxInline - inline_length) { - *region = data_.as_chars + inline_length; - *size = max_length; - set_tagged_size(static_cast(inline_length + max_length)); - return; + if (!is_tree()) { + size_t inline_length = inline_size(); + if (max_length <= kMaxInline - inline_length) { + *region = data_.as_chars() + inline_length; + *size = max_length; + set_inline_size(inline_length + max_length); + return; + } } CordRep* root = force_tree(max_length); @@ -390,12 +390,14 @@ void Cord::InlineRep::GetAppendRegion(char** region, size_t* size) { const size_t max_length = std::numeric_limits::max(); // Try to fit in the inline buffer if possible. - size_t inline_length = tagged_size(); - if (inline_length < kMaxInline) { - *region = data_.as_chars + inline_length; - *size = kMaxInline - inline_length; - set_tagged_size(kMaxInline); - return; + if (!data_.is_tree()) { + size_t inline_length = inline_size(); + if (inline_length < kMaxInline) { + *region = data_.as_chars() + inline_length; + *size = kMaxInline - inline_length; + set_inline_size(kMaxInline); + return; + } } CordRep* root = force_tree(max_length); @@ -549,24 +551,25 @@ template Cord& Cord::operator=(std::string&& src); // we keep it here to make diffs easier. void Cord::InlineRep::AppendArray(const char* src_data, size_t src_size) { if (src_size == 0) return; // memcpy(_, nullptr, 0) is undefined. - // Try to fit in the inline buffer if possible. - size_t inline_length = tagged_size(); - if (inline_length < kMaxInline && src_size <= kMaxInline - inline_length) { - // Append new data to embedded array - set_tagged_size(static_cast(inline_length + src_size)); - memcpy(data_.as_chars + inline_length, src_data, src_size); - return; - } - - CordRep* root = tree(); size_t appended = 0; - if (root) { + CordRep* root = nullptr; + if (is_tree()) { + root = data_.as_tree(); char* region; if (PrepareAppendRegion(root, ®ion, &appended, src_size)) { memcpy(region, src_data, appended); } } else { + // Try to fit in the inline buffer if possible. + size_t inline_length = inline_size(); + if (src_size <= kMaxInline - inline_length) { + // Append new data to embedded array + memcpy(data_.as_chars() + inline_length, src_data, src_size); + set_inline_size(inline_length + src_size); + return; + } + // It is possible that src_data == data_, but when we transition from an // InlineRep to a tree we need to assign data_ = root via set_tree. To // avoid corrupting the source data before we copy it, delay calling @@ -578,7 +581,7 @@ void Cord::InlineRep::AppendArray(const char* src_data, size_t src_size) { root = CordRepFlat::New(std::max(size1, size2)); appended = std::min( src_size, root->flat()->Capacity() - inline_length); - memcpy(root->flat()->Data(), data_.as_chars, inline_length); + memcpy(root->flat()->Data(), data_.as_chars(), inline_length); memcpy(root->flat()->Data() + inline_length, src_data, appended); root->length = inline_length + appended; set_tree(root); @@ -684,18 +687,19 @@ void Cord::Prepend(const Cord& src) { void Cord::Prepend(absl::string_view src) { if (src.empty()) return; // memcpy(_, nullptr, 0) is undefined. - size_t cur_size = contents_.size(); - if (!contents_.is_tree() && cur_size + src.size() <= InlineRep::kMaxInline) { - // Use embedded storage. - char data[InlineRep::kMaxInline + 1] = {0}; - data[InlineRep::kMaxInline] = cur_size + src.size(); // set size - memcpy(data, src.data(), src.size()); - memcpy(data + src.size(), contents_.data(), cur_size); - memcpy(reinterpret_cast(&contents_), data, - InlineRep::kMaxInline + 1); - } else { - contents_.PrependTree(NewTree(src.data(), src.size(), 0)); + if (!contents_.is_tree()) { + size_t cur_size = contents_.inline_size(); + if (cur_size + src.size() <= InlineRep::kMaxInline) { + // Use embedded storage. + char data[InlineRep::kMaxInline + 1] = {0}; + memcpy(data, src.data(), src.size()); + memcpy(data + src.size(), contents_.data(), cur_size); + memcpy(contents_.data_.as_chars(), data, InlineRep::kMaxInline + 1); + contents_.set_inline_size(cur_size + src.size()); + return; + } } + contents_.PrependTree(NewTree(src.data(), src.size(), 0)); } template > @@ -888,7 +892,7 @@ Cord Cord::Subcord(size_t pos, size_t new_size) const { } else if (new_size <= InlineRep::kMaxInline) { Cord::ChunkIterator it = chunk_begin(); it.AdvanceBytes(pos); - char* dest = sub_cord.contents_.data_.as_chars; + char* dest = sub_cord.contents_.data_.as_chars(); size_t remaining_size = new_size; while (remaining_size > it->size()) { cord_internal::SmallMemmove(dest, it->data(), it->size()); @@ -897,7 +901,7 @@ Cord Cord::Subcord(size_t pos, size_t new_size) const { ++it; } cord_internal::SmallMemmove(dest, it->data(), remaining_size); - sub_cord.contents_.set_tagged_size(new_size); + sub_cord.contents_.set_inline_size(new_size); } else { sub_cord.contents_.set_tree(NewSubRange(tree, pos, new_size)); } @@ -1086,9 +1090,8 @@ bool ComputeCompareResult(int memcmp_res) { // Helper routine. Locates the first flat chunk of the Cord without // initializing the iterator. inline absl::string_view Cord::InlineRep::FindFlatStartPiece() const { - size_t n = tagged_size(); - if (n <= kMaxInline) { - return absl::string_view(data_.as_chars, n); + if (!is_tree()) { + return absl::string_view(data_.as_chars(), data_.inline_size()); } CordRep* node = tree(); diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 7136f034..9d8764a0 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -665,8 +665,6 @@ class Cord { public: static constexpr unsigned char kMaxInline = cord_internal::kMaxInline; static_assert(kMaxInline >= sizeof(absl::cord_internal::CordRep*), ""); - static constexpr unsigned char kTreeFlag = cord_internal::kTreeFlag; - static constexpr unsigned char kProfiledFlag = cord_internal::kProfiledFlag; constexpr InlineRep() : data_() {} InlineRep(const InlineRep& src); @@ -685,6 +683,7 @@ class Cord { char* set_data(size_t n); // Write data to the result // Returns nullptr if holding bytes absl::cord_internal::CordRep* tree() const; + absl::cord_internal::CordRep* as_tree() const; // Discards old pointer, if any void set_tree(absl::cord_internal::CordRep* rep); // Replaces a tree with a new root. This is faster than set_tree, but it @@ -728,13 +727,13 @@ class Cord { memcpy(&(*dst)[0], &data_, sizeof(data_) - 1); // erase is faster than resize because the logic for memory allocation is // not needed. - dst->erase(tagged_size()); + dst->erase(inline_size()); } // Copies the inline contents into `dst`. Assumes the cord is not empty. void CopyToArray(char* dst) const; - bool is_tree() const { return tagged_size() > kMaxInline; } + bool is_tree() const { return data_.is_tree(); } private: friend class Cord; @@ -745,14 +744,8 @@ class Cord { void ResetToEmpty() { data_ = {}; } - // This uses reinterpret_cast instead of the union to avoid accessing the - // inactive union element. The tagged size is not a common prefix. - void set_tagged_size(char new_tag) { - reinterpret_cast(&data_)[kMaxInline] = new_tag; - } - char tagged_size() const { - return reinterpret_cast(&data_)[kMaxInline]; - } + void set_inline_size(size_t size) { data_.set_inline_size(size); } + size_t inline_size() const { return data_.inline_size(); } cord_internal::InlineData data_; }; @@ -948,35 +941,39 @@ inline void Cord::InlineRep::Swap(Cord::InlineRep* rhs) { } inline const char* Cord::InlineRep::data() const { - return is_tree() ? nullptr : data_.as_chars; + return is_tree() ? nullptr : data_.as_chars(); +} + +inline absl::cord_internal::CordRep* Cord::InlineRep::as_tree() const { + assert(data_.is_tree()); + return data_.as_tree(); } inline absl::cord_internal::CordRep* Cord::InlineRep::tree() const { if (is_tree()) { - return data_.as_tree.rep; + return as_tree(); } else { return nullptr; } } -inline bool Cord::InlineRep::empty() const { return tagged_size() == 0; } +inline bool Cord::InlineRep::empty() const { return data_.is_empty(); } inline size_t Cord::InlineRep::size() const { - const char tag = tagged_size(); - if (tag <= kMaxInline) return tag; - return static_cast(tree()->length); + return is_tree() ? as_tree()->length : inline_size(); } inline void Cord::InlineRep::set_tree(absl::cord_internal::CordRep* rep) { if (rep == nullptr) { ResetToEmpty(); } else { - bool was_tree = is_tree(); - data_.as_tree = {rep, {}, tagged_size()}; - if (!was_tree) { - // If we were not a tree already, set the tag. - // Otherwise, leave it alone because it might have the profile bit on. - set_tagged_size(kTreeFlag); + if (data_.is_tree()) { + // `data_` already holds a 'tree' value and an optional cordz_info value. + // Replace the tree value only, leaving the cordz_info value unchanged. + data_.set_tree(rep); + } else { + // `data_` contains inlined data: initialize data_ to tree value `rep`. + data_.make_tree(rep); } } } @@ -987,7 +984,7 @@ inline void Cord::InlineRep::replace_tree(absl::cord_internal::CordRep* rep) { set_tree(rep); return; } - data_.as_tree = {rep, {}, tagged_size()}; + data_.set_tree(rep); } inline absl::cord_internal::CordRep* Cord::InlineRep::clear() { @@ -998,9 +995,9 @@ inline absl::cord_internal::CordRep* Cord::InlineRep::clear() { inline void Cord::InlineRep::CopyToArray(char* dst) const { assert(!is_tree()); - size_t n = tagged_size(); + size_t n = inline_size(); assert(n != 0); - cord_internal::SmallMemmove(dst, data_.as_chars, n); + cord_internal::SmallMemmove(dst, data_.as_chars(), n); } constexpr inline Cord::Cord() noexcept {} @@ -1011,11 +1008,9 @@ constexpr Cord::Cord(strings_internal::StringConstant) cord_internal::kMaxInline ? cord_internal::InlineData( strings_internal::StringConstant::value) - : cord_internal::InlineData(cord_internal::AsTree{ + : cord_internal::InlineData( &cord_internal::ConstInitExternalStorage< - strings_internal::StringConstant>::value, - {}, - cord_internal::kTreeFlag})) {} + strings_internal::StringConstant>::value)) {} inline Cord& Cord::operator=(const Cord& x) { contents_ = x.contents_; @@ -1107,12 +1102,12 @@ inline bool Cord::StartsWith(absl::string_view rhs) const { inline Cord::ChunkIterator::ChunkIterator(const Cord* cord) : bytes_remaining_(cord->size()) { - if (cord->empty()) return; if (cord->contents_.is_tree()) { - stack_of_right_children_.push_back(cord->contents_.tree()); + stack_of_right_children_.push_back(cord->contents_.as_tree()); operator++(); } else { - current_chunk_ = absl::string_view(cord->contents_.data(), cord->size()); + current_chunk_ = + absl::string_view(cord->contents_.data(), bytes_remaining_); } } diff --git a/absl/strings/cord_ring_test.cc b/absl/strings/cord_ring_test.cc new file mode 100644 index 00000000..7d75e106 --- /dev/null +++ b/absl/strings/cord_ring_test.cc @@ -0,0 +1,1572 @@ +// Copyright 2020 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/base/macros.h" +#include "absl/debugging/leak_check.h" +#include "absl/strings/internal/cord_internal.h" +#include "absl/strings/internal/cord_rep_ring.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" + +extern thread_local bool cord_ring; + +// TOOD(b/177688959): weird things happened with the original test +#define ASAN_BUG_177688959_FIXED false + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace { + +using RandomEngine = std::mt19937_64; + +using ::absl::cord_internal::CordRep; +using ::absl::cord_internal::CordRepConcat; +using ::absl::cord_internal::CordRepExternal; +using ::absl::cord_internal::CordRepFlat; +using ::absl::cord_internal::CordRepRing; +using ::absl::cord_internal::CordRepSubstring; + +using ::absl::cord_internal::CONCAT; +using ::absl::cord_internal::EXTERNAL; +using ::absl::cord_internal::SUBSTRING; + +using testing::ElementsAre; +using testing::ElementsAreArray; +using testing::Eq; +using testing::Ge; +using testing::Le; +using testing::Lt; +using testing::Ne; +using testing::SizeIs; + +using index_type = CordRepRing::index_type; + +enum InputShareMode { kPrivate, kShared, kSharedIndirect }; + +// TestParam class used by all test fixtures. +// Not all fixtures use all possible input combinations +struct TestParam { + TestParam() = default; + explicit TestParam(InputShareMode input_share_mode) + : input_share_mode(input_share_mode) {} + + // Run the test with the 'rep under test' to be privately owned. + // Otherwise, the rep has a shared ref count of 2 or higher. + bool refcount_is_one = true; + + // Run the test with the 'rep under test' being allocated with enough capacity + // to accommodate any modifications made to it. Otherwise, the rep has zero + // extra (reserve) capacity. + bool with_capacity = true; + + // For test providing possibly shared input such as Append(.., CordpRep*), + // this field defines if that input is adopted with a refcount of one + // (privately owned / donated), or shared. For composite inputs such as + // 'substring of flat', we also have the 'shared indirect' value which means + // the top level node is not shared, but the contained child node is shared. + InputShareMode input_share_mode = kPrivate; + + std::string ToString() const { + return absl::StrCat(refcount_is_one ? "Private" : "Shared", + with_capacity ? "" : "_NoCapacity", + (input_share_mode == kPrivate) ? "" + : (input_share_mode == kShared) + ? "_SharedInput" + : "_IndirectSharedInput"); + } +}; +using TestParams = std::vector; + +// Matcher validating when mutable copies are required / performed. +MATCHER_P2(EqIfPrivate, param, rep, + absl::StrCat("Equal 0x", absl::Hex(rep), " if private")) { + return param.refcount_is_one ? arg == rep : arg != rep; +} + +// Matcher validating when mutable copies are required / performed. +MATCHER_P2(EqIfPrivateAndCapacity, param, rep, + absl::StrCat("Equal 0x", absl::Hex(rep), + " if private and capacity")) { + return (param.refcount_is_one && param.with_capacity) ? arg == rep + : arg != rep; +} + +MATCHER_P2(EqIfInputPrivate, param, rep, "Equal if input is private") { + return param.input_share_mode == kPrivate ? arg == rep : arg != rep; +} + +// Matcher validating the core in-variants of the CordRepRing instance. +MATCHER(IsValidRingBuffer, "RingBuffer is valid") { + std::stringstream ss; + if (!arg->IsValid(ss)) { + *result_listener << "\nERROR: " << ss.str() << "\nRING = " << *arg; + return false; + } + return true; +} + +// Returns the flats contained in the provided CordRepRing +std::vector ToFlats(const CordRepRing* r) { + std::vector flats; + flats.reserve(r->entries()); + index_type pos = r->head(); + do { + flats.push_back(r->entry_data(pos)); + } while ((pos = r->advance(pos)) != r->tail()); + return flats; +} + +class not_a_string_view { + public: + explicit not_a_string_view(absl::string_view s) + : data_(s.data()), size_(s.size()) {} + explicit not_a_string_view(const void* data, size_t size) + : data_(data), size_(size) {} + + not_a_string_view remove_prefix(size_t n) const { + return not_a_string_view(static_cast(data_) + n, size_ - n); + } + + not_a_string_view remove_suffix(size_t n) const { + return not_a_string_view(data_, size_ - n); + } + + const void* data() const { return data_; } + size_t size() const { return size_; } + + private: + const void* data_; + size_t size_; +}; + +bool operator==(not_a_string_view lhs, not_a_string_view rhs) { + return lhs.data() == rhs.data() && lhs.size() == rhs.size(); +} + +std::ostream& operator<<(std::ostream& s, not_a_string_view rhs) { + return s << "{ data: " << rhs.data() << " size: " << rhs.size() << "}"; +} + +std::vector ToRawFlats(const CordRepRing* r) { + std::vector flats; + flats.reserve(r->entries()); + index_type pos = r->head(); + do { + flats.emplace_back(r->entry_data(pos)); + } while ((pos = r->advance(pos)) != r->tail()); + return flats; +} + +// Returns the value contained in the provided CordRepRing +std::string ToString(const CordRepRing* r) { + std::string value; + value.reserve(r->length); + index_type pos = r->head(); + do { + absl::string_view sv = r->entry_data(pos); + value.append(sv.data(), sv.size()); + } while ((pos = r->advance(pos)) != r->tail()); + return value; +} + +// Creates a flat for testing +CordRep* MakeFlat(absl::string_view s, size_t extra = 0) { + CordRepFlat* flat = CordRepFlat::New(s.length() + extra); + memcpy(flat->Data(), s.data(), s.length()); + flat->length = s.length(); + return flat; +} + +// Creates an external node for testing +CordRepExternal* MakeExternal(absl::string_view s) { + struct Rep : public CordRepExternal { + std::string s; + explicit Rep(absl::string_view s) : s(s) { + this->tag = EXTERNAL; + this->base = s.data(); + this->length = s.length(); + this->releaser_invoker = [](CordRepExternal* self) { + delete static_cast(self); + }; + } + }; + return new Rep(s); +} + +CordRepExternal* MakeFakeExternal(size_t length) { + struct Rep : public CordRepExternal { + std::string s; + explicit Rep(size_t len) { + this->tag = EXTERNAL; + this->base = this->storage; + this->length = len; + this->releaser_invoker = [](CordRepExternal* self) { + delete static_cast(self); + }; + } + }; + return new Rep(length); +} + +// Creates a flat or an external node for testing depending on the size. +CordRep* MakeLeaf(absl::string_view s, size_t extra = 0) { + if (s.size() <= absl::cord_internal::kMaxFlatLength) { + return MakeFlat(s, extra); + } else { + return MakeExternal(s); + } +} + +// Creates a substring node +CordRepSubstring* MakeSubstring(size_t start, size_t len, CordRep* rep) { + auto* sub = new CordRepSubstring; + sub->tag = SUBSTRING; + sub->start = start; + sub->length = (len <= 0) ? rep->length - start + len : len; + sub->child = rep; + return sub; +} + +// Creates a substring node removing the specified prefix +CordRepSubstring* RemovePrefix(size_t start, CordRep* rep) { + return MakeSubstring(start, rep->length - start, rep); +} + +// Creates a substring node removing the specified suffix +CordRepSubstring* RemoveSuffix(size_t length, CordRep* rep) { + return MakeSubstring(0, rep->length - length, rep); +} + +CordRepConcat* MakeConcat(CordRep* left, CordRep* right, int depth = 0) { + auto* concat = new CordRepConcat; + concat->tag = CONCAT; + concat->length = left->length + right->length; + concat->left = left; + concat->right = right; + concat->set_depth(depth); + return concat; +} + +enum Composition { kMix, kAppend, kPrepend }; + +Composition RandomComposition() { + RandomEngine rng(testing::GTEST_FLAG(random_seed)); + return (rng() & 1) ? kMix : ((rng() & 1) ? kAppend : kPrepend); +} + +absl::string_view ToString(Composition composition) { + switch (composition) { + case kAppend: + return "Append"; + case kPrepend: + return "Prepend"; + case kMix: + return "Mix"; + } + assert(false); + return "???"; +} + +constexpr const char* kFox = "The quick brown fox jumps over the lazy dog"; +constexpr const char* kFoxFlats[] = {"The ", "quick ", "brown ", + "fox ", "jumps ", "over ", + "the ", "lazy ", "dog"}; +constexpr const char* kAlphabet = "abcdefghijklmnopqrstuvwxyz"; + +CordRepRing* FromFlats(Span flats, + Composition composition = kAppend) { + if (flats.empty()) return nullptr; + CordRepRing* ring = nullptr; + switch (composition) { + case kAppend: + ring = CordRepRing::Create(MakeLeaf(flats.front()), flats.size() - 1); + for (int i = 1; i < flats.size(); ++i) { + ring = CordRepRing::Append(ring, MakeLeaf(flats[i])); + } + break; + case kPrepend: + ring = CordRepRing::Create(MakeLeaf(flats.back()), flats.size() - 1); + for (int i = static_cast(flats.size() - 2); i >= 0; --i) { + ring = CordRepRing::Prepend(ring, MakeLeaf(flats[i])); + } + break; + case kMix: + size_t middle1 = flats.size() / 2, middle2 = middle1; + ring = CordRepRing::Create(MakeLeaf(flats[middle1]), flats.size() - 1); + if (!flats.empty()) { + if ((flats.size() & 1) == 0) { + ring = CordRepRing::Prepend(ring, MakeLeaf(flats[--middle1])); + } + for (int i = 1; i <= middle1; ++i) { + ring = CordRepRing::Prepend(ring, MakeLeaf(flats[middle1 - i])); + ring = CordRepRing::Append(ring, MakeLeaf(flats[middle2 + i])); + } + } + break; + } + EXPECT_THAT(ToFlats(ring), ElementsAreArray(flats)); + return ring; +} + +std::ostream& operator<<(std::ostream& s, const TestParam& param) { + return s << param.ToString(); +} + +std::string TestParamToString(const testing::TestParamInfo& info) { + return info.param.ToString(); +} + +class CordRingTest : public testing::Test { + public: + ~CordRingTest() override { +#if ASAN_BUG_177688959_FIXED + for (CordRep* rep : unrefs_) { + CordRep::Unref(rep); + } +#endif + } + + template + CordRepType* NeedsUnref(CordRepType* rep) { + assert(rep); +#if ASAN_BUG_177688959_FIXED + unrefs_.push_back(rep); +#endif + return rep; + } + + template + CordRepType* Ref(CordRepType* rep) { + CordRep::Ref(rep); + return NeedsUnref(rep); + } + + void Unref(CordRep* rep) { +#if !ASAN_BUG_177688959_FIXED + CordRep::Unref(rep); +#endif + } + + private: +#if ASAN_BUG_177688959_FIXED + std::vector unrefs_; +#endif +}; + +class CordRingTestWithParam : public testing::TestWithParam { + public: + ~CordRingTestWithParam() override { +#if ASAN_BUG_177688959_FIXED + for (CordRep* rep : unrefs_) { + CordRep::Unref(rep); + } +#endif + } + + CordRepRing* CreateWithCapacity(CordRep* child, size_t extra_capacity) { + if (!GetParam().with_capacity) extra_capacity = 0; + CordRepRing* ring = CordRepRing::Create(child, extra_capacity); + ring->SetCapacityForTesting(1 + extra_capacity); + return RefIfShared(ring); + } + + bool Shared() const { return !GetParam().refcount_is_one; } + bool InputShared() const { return GetParam().input_share_mode == kShared; } + bool InputSharedIndirect() const { + return GetParam().input_share_mode == kSharedIndirect; + } + + template + CordRepType* NeedsUnref(CordRepType* rep) { + assert(rep); +#if ASAN_BUG_177688959_FIXED + unrefs_.push_back(rep); +#endif + return rep; + } + + template + CordRepType* Ref(CordRepType* rep) { + CordRep::Ref(rep); + return NeedsUnref(rep); + } + + void Unref(CordRep* rep) { +#if !ASAN_BUG_177688959_FIXED + CordRep::Unref(rep); +#endif + } + + template + CordRepType* RefIfShared(CordRepType* rep) { + return Shared() ? Ref(rep) : rep; + } + + void UnrefIfShared(CordRep* rep) { + if (Shared()) Unref(rep); + } + + template + CordRepType* RefIfInputShared(CordRepType* rep) { + return InputShared() ? Ref(rep) : rep; + } + + void UnrefIfInputShared(CordRep* rep) { + if (InputShared()) Unref(rep); + } + + template + CordRepType* RefIfInputSharedIndirect(CordRepType* rep) { + return InputSharedIndirect() ? Ref(rep) : rep; + } + + void UnrefIfInputSharedIndirect(CordRep* rep) { + if (InputSharedIndirect()) Unref(rep); + } + + private: +#if ASAN_BUG_177688959_FIXED + std::vector unrefs_; +#endif +}; + +class CordRingCreateTest : public CordRingTestWithParam { + public: + static TestParams CreateTestParams() { + TestParams params; + params.emplace_back(InputShareMode::kPrivate); + params.emplace_back(InputShareMode::kShared); + return params; + } +}; + +class CordRingSubTest : public CordRingTestWithParam { + public: + static TestParams CreateTestParams() { + TestParams params; + for (bool refcount_is_one : {true, false}) { + TestParam param; + param.refcount_is_one = refcount_is_one; + params.push_back(param); + } + return params; + } +}; + +class CordRingBuildTest : public CordRingTestWithParam { + public: + static TestParams CreateTestParams() { + TestParams params; + for (bool refcount_is_one : {true, false}) { + for (bool with_capacity : {true, false}) { + TestParam param; + param.refcount_is_one = refcount_is_one; + param.with_capacity = with_capacity; + params.push_back(param); + } + } + return params; + } +}; + +class CordRingCreateFromTreeTest : public CordRingTestWithParam { + public: + static TestParams CreateTestParams() { + TestParams params; + params.emplace_back(InputShareMode::kPrivate); + params.emplace_back(InputShareMode::kShared); + params.emplace_back(InputShareMode::kSharedIndirect); + return params; + } +}; + +class CordRingBuildInputTest : public CordRingTestWithParam { + public: + static TestParams CreateTestParams() { + TestParams params; + for (bool refcount_is_one : {true, false}) { + for (bool with_capacity : {true, false}) { + for (InputShareMode share_mode : {kPrivate, kShared, kSharedIndirect}) { + TestParam param; + param.refcount_is_one = refcount_is_one; + param.with_capacity = with_capacity; + param.input_share_mode = share_mode; + params.push_back(param); + } + } + } + return params; + } +}; + +INSTANTIATE_TEST_CASE_P(WithParam, CordRingSubTest, + testing::ValuesIn(CordRingSubTest::CreateTestParams()), + TestParamToString); + +INSTANTIATE_TEST_CASE_P( + WithParam, CordRingCreateTest, + testing::ValuesIn(CordRingCreateTest::CreateTestParams()), + TestParamToString); + +INSTANTIATE_TEST_CASE_P( + WithParam, CordRingCreateFromTreeTest, + testing::ValuesIn(CordRingCreateFromTreeTest::CreateTestParams()), + TestParamToString); + +INSTANTIATE_TEST_CASE_P( + WithParam, CordRingBuildTest, + testing::ValuesIn(CordRingBuildTest::CreateTestParams()), + TestParamToString); + +INSTANTIATE_TEST_CASE_P( + WithParam, CordRingBuildInputTest, + testing::ValuesIn(CordRingBuildInputTest::CreateTestParams()), + TestParamToString); + +TEST_P(CordRingCreateTest, CreateFromFlat) { + absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; + CordRepRing* result = NeedsUnref(CordRepRing::Create(MakeFlat(str1))); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result->length, Eq(str1.size())); + EXPECT_THAT(ToFlats(result), ElementsAre(str1)); + Unref(result); +} + +TEST_P(CordRingCreateTest, CreateFromRing) { + CordRepRing* ring = RefIfShared(FromFlats(kFoxFlats)); + CordRepRing* result = NeedsUnref(CordRepRing::Create(ring)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivate(GetParam(), ring)); + EXPECT_THAT(ToFlats(result), ElementsAreArray(kFoxFlats)); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringRing) { + CordRepRing* ring = RefIfInputSharedIndirect(FromFlats(kFoxFlats)); + CordRep* sub = RefIfInputShared(MakeSubstring(2, 11, ring)); + CordRepRing* result = NeedsUnref(CordRepRing::Create(sub)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfInputPrivate(GetParam(), ring)); + EXPECT_THAT(ToString(result), string_view(kFox).substr(2, 11)); + UnrefIfInputSharedIndirect(ring); + UnrefIfInputShared(sub); + Unref(result); +} + +TEST_F(CordRingTest, CreateWithIllegalExtraCapacity) { + CordRep* flat = NeedsUnref(MakeFlat("Hello world")); +#if defined(ABSL_HAVE_EXCEPTIONS) + try { + CordRepRing::Create(flat, CordRepRing::kMaxCapacity); + GTEST_FAIL() << "expected std::length_error exception"; + } catch (const std::length_error&) { + } +#elif defined(GTEST_HAS_DEATH_TEST) + EXPECT_DEATH(CordRepRing::Create(flat, CordRepRing::kMaxCapacity), ".*"); +#endif + Unref(flat); +} + +TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringOfFlat) { + absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; + auto* flat = RefIfInputShared(MakeFlat(str1)); + auto* child = RefIfInputSharedIndirect(MakeSubstring(4, 20, flat)); + CordRepRing* result = NeedsUnref(CordRepRing::Create(child)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result->length, Eq(20)); + EXPECT_THAT(ToFlats(result), ElementsAre(str1.substr(4, 20))); + Unref(result); + UnrefIfInputShared(flat); + UnrefIfInputSharedIndirect(child); +} + +TEST_P(CordRingCreateTest, CreateFromExternal) { + absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; + auto* child = RefIfInputShared(MakeExternal(str1)); + CordRepRing* result = NeedsUnref(CordRepRing::Create(child)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result->length, Eq(str1.size())); + EXPECT_THAT(ToFlats(result), ElementsAre(str1)); + Unref(result); + UnrefIfInputShared(child); +} + +TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringOfExternal) { + absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; + auto* external = RefIfInputShared(MakeExternal(str1)); + auto* child = RefIfInputSharedIndirect(MakeSubstring(1, 24, external)); + CordRepRing* result = NeedsUnref(CordRepRing::Create(child)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result->length, Eq(24)); + EXPECT_THAT(ToFlats(result), ElementsAre(str1.substr(1, 24))); + Unref(result); + UnrefIfInputShared(external); + UnrefIfInputSharedIndirect(child); +} + +TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringOfLargeExternal) { + auto* external = RefIfInputShared(MakeFakeExternal(1 << 20)); + auto str = not_a_string_view(external->base, 1 << 20) + .remove_prefix(1 << 19) + .remove_suffix(6); + auto* child = + RefIfInputSharedIndirect(MakeSubstring(1 << 19, (1 << 19) - 6, external)); + CordRepRing* result = NeedsUnref(CordRepRing::Create(child)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result->length, Eq(str.size())); + EXPECT_THAT(ToRawFlats(result), ElementsAre(str)); + Unref(result); + UnrefIfInputShared(external); + UnrefIfInputSharedIndirect(child); +} + +TEST_P(CordRingBuildInputTest, CreateFromConcat) { + CordRep* flats[] = {MakeFlat("abcdefgh"), MakeFlat("ijklm"), + MakeFlat("nopqrstuv"), MakeFlat("wxyz")}; + auto* left = MakeConcat(RefIfInputSharedIndirect(flats[0]), flats[1]); + auto* right = MakeConcat(flats[2], RefIfInputSharedIndirect(flats[3])); + auto* concat = RefIfInputShared(MakeConcat(left, right)); + CordRepRing* result = NeedsUnref(CordRepRing::Create(concat)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result->length, Eq(26)); + EXPECT_THAT(ToString(result), Eq(kAlphabet)); + UnrefIfInputSharedIndirect(flats[0]); + UnrefIfInputSharedIndirect(flats[3]); + UnrefIfInputShared(concat); + Unref(result); +} + +TEST_P(CordRingBuildInputTest, CreateFromSubstringConcat) { + for (size_t off = 0; off < 26; ++off) { + for (size_t len = 1; len < 26 - off; ++len) { + CordRep* flats[] = {MakeFlat("abcdefgh"), MakeFlat("ijklm"), + MakeFlat("nopqrstuv"), MakeFlat("wxyz")}; + auto* left = MakeConcat(RefIfInputSharedIndirect(flats[0]), flats[1]); + auto* right = MakeConcat(flats[2], RefIfInputSharedIndirect(flats[3])); + auto* concat = MakeConcat(left, right); + auto* child = RefIfInputShared(MakeSubstring(off, len, concat)); + CordRepRing* result = NeedsUnref(CordRepRing::Create(child)); + ASSERT_THAT(result, IsValidRingBuffer()); + ASSERT_THAT(result->length, Eq(len)); + ASSERT_THAT(ToString(result), string_view(kAlphabet).substr(off, len)); + UnrefIfInputSharedIndirect(flats[0]); + UnrefIfInputSharedIndirect(flats[3]); + UnrefIfInputShared(child); + Unref(result); + } + } +} + +TEST_P(CordRingCreateTest, Properties) { + absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; + CordRepRing* result = NeedsUnref(CordRepRing::Create(MakeFlat(str1), 120)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result->head(), Eq(0)); + EXPECT_THAT(result->tail(), Eq(1)); + EXPECT_THAT(result->capacity(), Ge(120 + 1)); + EXPECT_THAT(result->capacity(), Le(2 * 120 + 1)); + EXPECT_THAT(result->entries(), Eq(1)); + EXPECT_THAT(result->begin_pos(), Eq(0)); + Unref(result); +} + +TEST_P(CordRingCreateTest, EntryForNewFlat) { + absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; + CordRep* child = MakeFlat(str1); + CordRepRing* result = NeedsUnref(CordRepRing::Create(child, 120)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result->entry_child(0), Eq(child)); + EXPECT_THAT(result->entry_end_pos(0), Eq(str1.length())); + EXPECT_THAT(result->entry_data_offset(0), Eq(0)); + Unref(result); +} + +TEST_P(CordRingCreateTest, EntryForNewFlatSubstring) { + absl::string_view str1 = "1234567890abcdefghijklmnopqrstuvwxyz"; + CordRep* child = MakeFlat(str1); + CordRep* substring = MakeSubstring(10, 26, child); + CordRepRing* result = NeedsUnref(CordRepRing::Create(substring, 1)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result->entry_child(0), Eq(child)); + EXPECT_THAT(result->entry_end_pos(0), Eq(26)); + EXPECT_THAT(result->entry_data_offset(0), Eq(10)); + Unref(result); +} + +TEST_P(CordRingBuildTest, AppendFlat) { + absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; + absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1); + CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, MakeFlat(str2))); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(result->length, Eq(str1.size() + str2.size())); + EXPECT_THAT(ToFlats(result), ElementsAre(str1, str2)); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildTest, PrependFlat) { + absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; + absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1); + CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, MakeFlat(str2))); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(result->length, Eq(str1.size() + str2.size())); + EXPECT_THAT(ToFlats(result), ElementsAre(str2, str1)); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildTest, AppendString) { + absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; + absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1); + CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(result->length, Eq(str1.size() + str2.size())); + EXPECT_THAT(ToFlats(result), ElementsAre(str1, str2)); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildTest, AppendStringHavingExtra) { + absl::string_view str1 = "1234"; + absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + CordRepRing* ring = CreateWithCapacity(MakeFlat(str1, 26), 0); + CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result->length, Eq(str1.size() + str2.size())); + EXPECT_THAT(result, EqIfPrivate(GetParam(), ring)); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildTest, AppendStringHavingPartialExtra) { + absl::string_view str1 = "1234"; + absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + // Create flat with at least one extra byte. We don't expect to have sized + // alloc and capacity rounding to grant us enough to not make it partial. + auto* flat = MakeFlat(str1, 1); + size_t avail = flat->flat()->Capacity() - flat->length; + ASSERT_THAT(avail, Lt(str2.size())) << " adjust test for larger flats!"; + + // Construct the flats we do expect using all of `avail`. + absl::string_view str1a = str2.substr(0, avail); + absl::string_view str2a = str2.substr(avail); + + CordRepRing* ring = CreateWithCapacity(flat, 1); + CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result->length, Eq(str1.size() + str2.size())); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + if (GetParam().refcount_is_one) { + EXPECT_THAT(ToFlats(result), ElementsAre(StrCat(str1, str1a), str2a)); + } else { + EXPECT_THAT(ToFlats(result), ElementsAre(str1, str2)); + } + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildTest, AppendStringHavingExtraInSubstring) { + absl::string_view str1 = "123456789_1234"; + absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + CordRep* flat = RemovePrefix(10, MakeFlat(str1, 26)); + CordRepRing* ring = CreateWithCapacity(flat, 0); + CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivate(GetParam(), ring)); + EXPECT_THAT(result->length, Eq(4 + str2.size())); + if (GetParam().refcount_is_one) { + EXPECT_THAT(ToFlats(result), ElementsAre(StrCat("1234", str2))); + } else { + EXPECT_THAT(ToFlats(result), ElementsAre("1234", str2)); + } + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildTest, AppendStringHavingSharedExtra) { + absl::string_view str1 = "123456789_1234"; + absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + for (int shared_type = 0; shared_type < 2; ++shared_type) { + SCOPED_TRACE(absl::StrCat("Shared extra type ", shared_type)); + + // Create a flat that is shared in some way. + CordRep* flat = nullptr; + CordRep* flat1 = nullptr; + if (shared_type == 0) { + // Shared flat + flat = CordRep::Ref(MakeFlat(str1.substr(10), 100)); + } else if (shared_type == 1) { + // Shared flat inside private substring + flat1 = CordRep::Ref(MakeFlat(str1)); + flat = RemovePrefix(10, flat1); + } else { + // Private flat inside shared substring + flat = CordRep::Ref(RemovePrefix(10, MakeFlat(str1, 100))); + } + + CordRepRing* ring = CreateWithCapacity(flat, 1); + CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(result->length, Eq(4 + str2.size())); + EXPECT_THAT(ToFlats(result), ElementsAre("1234", str2)); + UnrefIfShared(ring); + Unref(result); + + CordRep::Unref(shared_type == 1 ? flat1 : flat); + } +} + +TEST_P(CordRingBuildTest, AppendStringWithExtra) { + absl::string_view str1 = "1234"; + absl::string_view str2 = "1234567890"; + absl::string_view str3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1); + CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2, 26)); + result = CordRepRing::Append(result, str3); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result->length, Eq(str1.size() + str2.size() + str3.size())); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(ToFlats(result), ElementsAre(str1, StrCat(str2, str3))); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildTest, PrependString) { + absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; + absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + // Use external rep to avoid appending to first flat + CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1); + CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2)); + ASSERT_THAT(result, IsValidRingBuffer()); + if (GetParam().with_capacity && GetParam().refcount_is_one) { + EXPECT_THAT(result, Eq(ring)); + } else { + EXPECT_THAT(result, Ne(ring)); + } + EXPECT_THAT(result->length, Eq(str1.size() + str2.size())); + EXPECT_THAT(ToFlats(result), ElementsAre(str2, str1)); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildTest, PrependStringHavingExtra) { + absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz1234"; + absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + CordRep* flat = RemovePrefix(26, MakeFlat(str1)); + CordRepRing* ring = CreateWithCapacity(flat, 0); + CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivate(GetParam(), ring)); + EXPECT_THAT(result->length, Eq(4 + str2.size())); + if (GetParam().refcount_is_one) { + EXPECT_THAT(ToFlats(result), ElementsAre(StrCat(str2, "1234"))); + } else { + EXPECT_THAT(ToFlats(result), ElementsAre(str2, "1234")); + } + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildTest, PrependStringHavingSharedExtra) { + absl::string_view str1 = "123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + absl::string_view str2 = "abcdefghij"; + absl::string_view str1a = str1.substr(10); + for (int shared_type = 1; shared_type < 2; ++shared_type) { + SCOPED_TRACE(absl::StrCat("Shared extra type ", shared_type)); + + // Create a flat that is shared in some way. + CordRep* flat = nullptr; + CordRep* flat1 = nullptr; + if (shared_type == 1) { + // Shared flat inside private substring + flat = RemovePrefix(10, flat1 = CordRep::Ref(MakeFlat(str1))); + } else { + // Private flat inside shared substring + flat = CordRep::Ref(RemovePrefix(10, MakeFlat(str1, 100))); + } + + CordRepRing* ring = CreateWithCapacity(flat, 1); + CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result->length, Eq(str1a.size() + str2.size())); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(ToFlats(result), ElementsAre(str2, str1a)); + UnrefIfShared(ring); + Unref(result); + CordRep::Unref(shared_type == 1 ? flat1 : flat); + } +} + +TEST_P(CordRingBuildTest, PrependStringWithExtra) { + absl::string_view str1 = "1234"; + absl::string_view str2 = "1234567890"; + absl::string_view str3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1); + CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2, 26)); + ASSERT_THAT(result, IsValidRingBuffer()); + result = CordRepRing::Prepend(result, str3); + EXPECT_THAT(result->length, Eq(str1.size() + str2.size() + str3.size())); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(ToFlats(result), ElementsAre(StrCat(str3, str2), str1)); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildTest, AppendPrependStringMix) { + const auto& flats = kFoxFlats; + CordRepRing* ring = CreateWithCapacity(MakeFlat(flats[4]), 8); + CordRepRing* result = ring; + for (int i = 1; i <= 4; ++i) { + result = CordRepRing::Prepend(result, flats[4 - i]); + result = CordRepRing::Append(result, flats[4 + i]); + } + UnrefIfShared(ring); + NeedsUnref(result); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(ToString(result), kFox); + Unref(result); +} + +TEST_P(CordRingBuildTest, AppendPrependStringMixWithExtra) { + const auto& flats = kFoxFlats; + CordRepRing* ring = CreateWithCapacity(MakeFlat(flats[4], 100), 8); + CordRepRing* result = ring; + for (int i = 1; i <= 4; ++i) { + result = CordRepRing::Prepend(result, flats[4 - i], 100); + result = CordRepRing::Append(result, flats[4 + i], 100); + } + NeedsUnref(result); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + if (GetParam().refcount_is_one) { + EXPECT_THAT(ToFlats(result), + ElementsAre("The quick brown fox ", "jumps over the lazy dog")); + } else { + EXPECT_THAT(ToFlats(result), ElementsAre("The quick brown fox ", "jumps ", + "over the lazy dog")); + } + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildTest, AppendPrependStringMixWithPrependedExtra) { + const auto& flats = kFoxFlats; + CordRep* flat = MakeFlat(StrCat(std::string(50, '.'), flats[4]), 50); + CordRepRing* ring = CreateWithCapacity(RemovePrefix(50, flat), 0); + CordRepRing* result = ring; + for (int i = 1; i <= 4; ++i) { + result = CordRepRing::Prepend(result, flats[4 - i], 100); + result = CordRepRing::Append(result, flats[4 + i], 100); + } + result = NeedsUnref(result); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivate(GetParam(), ring)); + if (GetParam().refcount_is_one) { + EXPECT_THAT(ToFlats(result), ElementsAre(kFox)); + } else { + EXPECT_THAT(ToFlats(result), ElementsAre("The quick brown fox ", "jumps ", + "over the lazy dog")); + } + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingSubTest, SubRing) { + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + auto flats = MakeSpan(kFoxFlats); + string_view all = kFox; + for (size_t offset = 0; offset < all.size() - 1; ++offset) { + CordRepRing* ring = RefIfShared(FromFlats(flats, composition)); + CordRepRing* result = CordRepRing::SubRing(ring, offset, 0); + EXPECT_THAT(result, nullptr); + UnrefIfShared(ring); + + for (size_t len = 1; len < all.size() - offset; ++len) { + ring = RefIfShared(FromFlats(flats, composition)); + result = NeedsUnref(CordRepRing::SubRing(ring, offset, len)); + ASSERT_THAT(result, IsValidRingBuffer()); + ASSERT_THAT(result, EqIfPrivate(GetParam(), ring)); + ASSERT_THAT(ToString(result), Eq(all.substr(offset, len))); + UnrefIfShared(ring); + Unref(result); + } + } +} + +TEST_P(CordRingSubTest, SubRingFromLargeExternal) { + auto composition = RandomComposition(); + std::string large_string(1 << 20, '.'); + const char* flats[] = { + "abcdefghijklmnopqrstuvwxyz", + large_string.c_str(), + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + }; + std::string buffer = absl::StrCat(flats[0], flats[1], flats[2]); + absl::string_view all = buffer; + for (size_t offset = 0; offset < 30; ++offset) { + CordRepRing* ring = RefIfShared(FromFlats(flats, composition)); + CordRepRing* result = CordRepRing::SubRing(ring, offset, 0); + EXPECT_THAT(result, nullptr); + UnrefIfShared(ring); + + for (size_t len = all.size() - 30; len < all.size() - offset; ++len) { + ring = RefIfShared(FromFlats(flats, composition)); + result = NeedsUnref(CordRepRing::SubRing(ring, offset, len)); + ASSERT_THAT(result, IsValidRingBuffer()); + ASSERT_THAT(result, EqIfPrivate(GetParam(), ring)); + auto str = ToString(result); + ASSERT_THAT(str, SizeIs(len)); + ASSERT_THAT(str, Eq(all.substr(offset, len))); + UnrefIfShared(ring); + Unref(result); + } + } +} + +TEST_P(CordRingSubTest, RemovePrefix) { + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + auto flats = MakeSpan(kFoxFlats); + string_view all = kFox; + CordRepRing* ring = RefIfShared(FromFlats(flats, composition)); + CordRepRing* result = CordRepRing::RemovePrefix(ring, all.size()); + EXPECT_THAT(result, nullptr); + UnrefIfShared(ring); + + for (size_t len = 1; len < all.size(); ++len) { + ring = RefIfShared(FromFlats(flats, composition)); + result = NeedsUnref(CordRepRing::RemovePrefix(ring, len)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivate(GetParam(), ring)); + EXPECT_THAT(ToString(result), Eq(all.substr(len))); + UnrefIfShared(ring); + Unref(result); + } +} + +TEST_P(CordRingSubTest, RemovePrefixFromLargeExternal) { + CordRepExternal* external1 = MakeFakeExternal(1 << 20); + CordRepExternal* external2 = MakeFakeExternal(1 << 20); + CordRepRing* ring = CordRepRing::Create(external1, 1); + ring = CordRepRing::Append(ring, external2); + CordRepRing* result = NeedsUnref(CordRepRing::RemovePrefix(ring, 1 << 16)); + EXPECT_THAT( + ToRawFlats(result), + ElementsAre( + not_a_string_view(external1->base, 1 << 20).remove_prefix(1 << 16), + not_a_string_view(external2->base, 1 << 20))); + Unref(result); +} + +TEST_P(CordRingSubTest, RemoveSuffix) { + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + auto flats = MakeSpan(kFoxFlats); + string_view all = kFox; + CordRepRing* ring = RefIfShared(FromFlats(flats, composition)); + CordRepRing* result = CordRepRing::RemoveSuffix(ring, all.size()); + EXPECT_THAT(result, nullptr); + UnrefIfShared(ring); + + for (size_t len = 1; len < all.size(); ++len) { + ring = RefIfShared(FromFlats(flats, composition)); + result = NeedsUnref(CordRepRing::RemoveSuffix(ring, len)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivate(GetParam(), ring)); + EXPECT_THAT(ToString(result), Eq(all.substr(0, all.size() - len))); + UnrefIfShared(ring); + Unref(result); + } +} + +TEST_P(CordRingSubTest, AppendRing) { + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + auto flats = MakeSpan(kFoxFlats).subspan(1); + CordRepRing* ring = CreateWithCapacity(MakeFlat(kFoxFlats[0]), flats.size()); + CordRepRing* child = FromFlats(flats, composition); + CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, child)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivate(GetParam(), ring)); + EXPECT_THAT(ToFlats(result), ElementsAreArray(kFoxFlats)); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildInputTest, AppendRingWithFlatOffset) { + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + auto flats = MakeSpan(kFoxFlats); + CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size()); + CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition)); + CordRep* stripped = RemovePrefix(10, child); + CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(ToFlats(result), ElementsAre("Head", "brown ", "fox ", "jumps ", + "over ", "the ", "lazy ", "dog")); + UnrefIfInputSharedIndirect(child); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildInputTest, AppendRingWithBrokenOffset) { + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + auto flats = MakeSpan(kFoxFlats); + CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size()); + CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition)); + CordRep* stripped = RemovePrefix(21, child); + CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(ToFlats(result), + ElementsAre("Head", "umps ", "over ", "the ", "lazy ", "dog")); + UnrefIfInputSharedIndirect(child); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildInputTest, AppendRingWithFlatLength) { + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + auto flats = MakeSpan(kFoxFlats); + CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size()); + CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition)); + CordRep* stripped = RemoveSuffix(8, child); + CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(ToFlats(result), ElementsAre("Head", "The ", "quick ", "brown ", + "fox ", "jumps ", "over ", "the ")); + UnrefIfInputSharedIndirect(child); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildTest, AppendRingWithBrokenFlatLength) { + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + auto flats = MakeSpan(kFoxFlats); + CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size()); + CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition)); + CordRep* stripped = RemoveSuffix(15, child); + CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(ToFlats(result), ElementsAre("Head", "The ", "quick ", "brown ", + "fox ", "jumps ", "ov")); + UnrefIfInputSharedIndirect(child); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildTest, AppendRingMiddlePiece) { + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + auto flats = MakeSpan(kFoxFlats); + CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size()); + CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition)); + CordRep* stripped = MakeSubstring(7, child->length - 27, child); + CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(ToFlats(result), + ElementsAre("Head", "ck ", "brown ", "fox ", "jum")); + UnrefIfInputSharedIndirect(child); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildTest, AppendRingSinglePiece) { + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + auto flats = MakeSpan(kFoxFlats); + CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size()); + CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition)); + CordRep* stripped = RefIfInputShared(MakeSubstring(11, 3, child)); + CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(ToFlats(result), ElementsAre("Head", "row")); + UnrefIfInputSharedIndirect(child); + UnrefIfInputShared(stripped); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildInputTest, AppendRingSinglePieceWithPrefix) { + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + auto flats = MakeSpan(kFoxFlats); + size_t extra_capacity = 1 + (GetParam().with_capacity ? flats.size() : 0); + CordRepRing* ring = CordRepRing::Create(MakeFlat("Head"), extra_capacity); + ring->SetCapacityForTesting(1 + extra_capacity); + ring = RefIfShared(CordRepRing::Prepend(ring, MakeFlat("Prepend"))); + assert(ring->IsValid(std::cout)); + CordRepRing* child = RefIfInputSharedIndirect(FromFlats(flats, composition)); + CordRep* stripped = RefIfInputShared(MakeSubstring(11, 3, child)); + CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(ToFlats(result), ElementsAre("Prepend", "Head", "row")); + UnrefIfInputSharedIndirect(child); + UnrefIfInputShared(stripped); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildInputTest, PrependRing) { + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + auto fox = MakeSpan(kFoxFlats); + auto flats = MakeSpan(fox).subspan(0, fox.size() - 1); + CordRepRing* ring = CreateWithCapacity(MakeFlat(fox.back()), flats.size()); + CordRepRing* child = RefIfInputShared(FromFlats(flats, composition)); + CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, child)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(ToFlats(result), ElementsAreArray(kFoxFlats)); + UnrefIfInputShared(child); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildInputTest, PrependRingWithFlatOffset) { + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + auto flats = MakeSpan(kFoxFlats); + CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size()); + CordRep* child = RefIfInputShared(FromFlats(flats, composition)); + CordRep* stripped = RefIfInputSharedIndirect(RemovePrefix(10, child)); + CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(ToFlats(result), ElementsAre("brown ", "fox ", "jumps ", "over ", + "the ", "lazy ", "dog", "Tail")); + UnrefIfInputShared(child); + UnrefIfInputSharedIndirect(stripped); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildInputTest, PrependRingWithBrokenOffset) { + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + auto flats = MakeSpan(kFoxFlats); + CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size()); + CordRep* child = RefIfInputShared(FromFlats(flats, composition)); + CordRep* stripped = RefIfInputSharedIndirect(RemovePrefix(21, child)); + CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped)); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(ToFlats(result), + ElementsAre("umps ", "over ", "the ", "lazy ", "dog", "Tail")); + UnrefIfInputShared(child); + UnrefIfInputSharedIndirect(stripped); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildInputTest, PrependRingWithFlatLength) { + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + auto flats = MakeSpan(kFoxFlats); + CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size()); + CordRep* child = RefIfInputShared(FromFlats(flats, composition)); + CordRep* stripped = RefIfInputSharedIndirect(RemoveSuffix(8, child)); + CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(ToFlats(result), ElementsAre("The ", "quick ", "brown ", "fox ", + "jumps ", "over ", "the ", "Tail")); + UnrefIfShared(ring); + UnrefIfInputShared(child); + UnrefIfInputSharedIndirect(stripped); + Unref(result); +} + +TEST_P(CordRingBuildInputTest, PrependRingWithBrokenFlatLength) { + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + auto flats = MakeSpan(kFoxFlats); + CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size()); + CordRep* child = RefIfInputShared(FromFlats(flats, composition)); + CordRep* stripped = RefIfInputSharedIndirect(RemoveSuffix(15, child)); + CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(ToFlats(result), ElementsAre("The ", "quick ", "brown ", "fox ", + "jumps ", "ov", "Tail")); + UnrefIfInputShared(child); + UnrefIfInputSharedIndirect(stripped); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildInputTest, PrependRingMiddlePiece) { + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + auto flats = MakeSpan(kFoxFlats); + CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size()); + CordRep* child = RefIfInputShared(FromFlats(flats, composition)); + CordRep* stripped = + RefIfInputSharedIndirect(MakeSubstring(7, child->length - 27, child)); + CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(ToFlats(result), + ElementsAre("ck ", "brown ", "fox ", "jum", "Tail")); + UnrefIfInputShared(child); + UnrefIfInputSharedIndirect(stripped); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildInputTest, PrependRingSinglePiece) { + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + auto flats = MakeSpan(kFoxFlats); + CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size()); + CordRep* child = RefIfInputShared(FromFlats(flats, composition)); + CordRep* stripped = RefIfInputSharedIndirect(MakeSubstring(11, 3, child)); + CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(ToFlats(result), ElementsAre("row", "Tail")); + UnrefIfInputShared(child); + UnrefIfInputSharedIndirect(stripped); + UnrefIfShared(ring); + Unref(result); +} + +TEST_P(CordRingBuildInputTest, PrependRingSinglePieceWithPrefix) { + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + auto flats = MakeSpan(kFoxFlats); + size_t extra_capacity = 1 + (GetParam().with_capacity ? flats.size() : 0); + CordRepRing* ring = CordRepRing::Create(MakeFlat("Tail"), extra_capacity); + ring->SetCapacityForTesting(1 + extra_capacity); + ring = RefIfShared(CordRepRing::Prepend(ring, MakeFlat("Prepend"))); + CordRep* child = RefIfInputShared(FromFlats(flats, composition)); + CordRep* stripped = RefIfInputSharedIndirect(MakeSubstring(11, 3, child)); + CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped)); + ASSERT_THAT(result, IsValidRingBuffer()); + EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); + EXPECT_THAT(ToFlats(result), ElementsAre("row", "Prepend", "Tail")); + UnrefIfInputShared(child); + UnrefIfInputSharedIndirect(stripped); + UnrefIfShared(ring); + Unref(result); +} + +TEST_F(CordRingTest, Find) { + constexpr const char* flats[] = { + "abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ", + "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_", + "+-=", "[]\\{}|;':", ",/<>?", "."}; + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + CordRepRing* ring = NeedsUnref(FromFlats(flats, composition)); + std::string value = ToString(ring); + for (int i = 0; i < value.length(); ++i) { + CordRepRing::Position found = ring->Find(i); + auto data = ring->entry_data(found.index); + ASSERT_THAT(found.offset, Lt(data.length())); + ASSERT_THAT(data[found.offset], Eq(value[i])); + } + Unref(ring); +} + +TEST_F(CordRingTest, FindWithHint) { + constexpr const char* flats[] = { + "abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ", + "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_", + "+-=", "[]\\{}|;':", ",/<>?", "."}; + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + CordRepRing* ring = NeedsUnref(FromFlats(flats, composition)); + std::string value = ToString(ring); + +#if defined(GTEST_HAS_DEATH_TEST) + // Test hint beyond valid position + index_type head = ring->head(); + EXPECT_DEBUG_DEATH(ring->Find(ring->advance(head), 0), ".*"); + EXPECT_DEBUG_DEATH(ring->Find(ring->advance(head), 9), ".*"); + EXPECT_DEBUG_DEATH(ring->Find(ring->advance(head, 3), 24), ".*"); +#endif + + int flat_pos = 0; + size_t flat_offset = 0; + for (auto sflat : flats) { + string_view flat(sflat); + for (int offset = 0; offset < flat.length(); ++offset) { + for (int start = 0; start <= flat_pos; ++start) { + index_type hint = ring->advance(ring->head(), start); + CordRepRing::Position found = ring->Find(hint, flat_offset + offset); + ASSERT_THAT(found.index, Eq(ring->advance(ring->head(), flat_pos))); + ASSERT_THAT(found.offset, Eq(offset)); + } + } + ++flat_pos; + flat_offset += flat.length(); + } + Unref(ring); +} + +TEST_F(CordRingTest, FindInLargeRing) { + constexpr const char* flats[] = { + "abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ", + "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_", + "+-=", "[]\\{}|;':", ",/<>?", "."}; + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + CordRepRing* ring = FromFlats(flats, composition); + for (int i = 0; i < 13; ++i) { + ring = CordRepRing::Append(ring, FromFlats(flats, composition)); + } + NeedsUnref(ring); + std::string value = ToString(ring); + for (int i = 0; i < value.length(); ++i) { + CordRepRing::Position pos = ring->Find(i); + auto data = ring->entry_data(pos.index); + ASSERT_THAT(pos.offset, Lt(data.length())); + ASSERT_THAT(data[pos.offset], Eq(value[i])); + } + Unref(ring); +} + +TEST_F(CordRingTest, FindTail) { + constexpr const char* flats[] = { + "abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ", + "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_", + "+-=", "[]\\{}|;':", ",/<>?", "."}; + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + CordRepRing* ring = NeedsUnref(FromFlats(flats, composition)); + std::string value = ToString(ring); + + for (int i = 0; i < value.length(); ++i) { + CordRepRing::Position pos = ring->FindTail(i + 1); + auto data = ring->entry_data(ring->retreat(pos.index)); + ASSERT_THAT(pos.offset, Lt(data.length())); + ASSERT_THAT(data[data.length() - pos.offset - 1], Eq(value[i])); + } + Unref(ring); +} + +TEST_F(CordRingTest, FindTailWithHint) { + constexpr const char* flats[] = { + "abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ", + "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_", + "+-=", "[]\\{}|;':", ",/<>?", "."}; + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + CordRepRing* ring = NeedsUnref(FromFlats(flats, composition)); + std::string value = ToString(ring); + + // Test hint beyond valid position +#if defined(GTEST_HAS_DEATH_TEST) + index_type head = ring->head(); + EXPECT_DEBUG_DEATH(ring->FindTail(ring->advance(head), 1), ".*"); + EXPECT_DEBUG_DEATH(ring->FindTail(ring->advance(head), 10), ".*"); + EXPECT_DEBUG_DEATH(ring->FindTail(ring->advance(head, 3), 26), ".*"); +#endif + + for (int i = 0; i < value.length(); ++i) { + CordRepRing::Position pos = ring->FindTail(i + 1); + auto data = ring->entry_data(ring->retreat(pos.index)); + ASSERT_THAT(pos.offset, Lt(data.length())); + ASSERT_THAT(data[data.length() - pos.offset - 1], Eq(value[i])); + } + Unref(ring); +} + +TEST_F(CordRingTest, FindTailInLargeRing) { + constexpr const char* flats[] = { + "abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ", + "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_", + "+-=", "[]\\{}|;':", ",/<>?", "."}; + auto composition = RandomComposition(); + SCOPED_TRACE(ToString(composition)); + CordRepRing* ring = FromFlats(flats, composition); + for (int i = 0; i < 13; ++i) { + ring = CordRepRing::Append(ring, FromFlats(flats, composition)); + } + NeedsUnref(ring); + std::string value = ToString(ring); + for (int i = 0; i < value.length(); ++i) { + CordRepRing::Position pos = ring->FindTail(i + 1); + auto data = ring->entry_data(ring->retreat(pos.index)); + ASSERT_THAT(pos.offset, Lt(data.length())); + ASSERT_THAT(data[data.length() - pos.offset - 1], Eq(value[i])); + } + Unref(ring); +} + +TEST_F(CordRingTest, GetCharacter) { + auto flats = MakeSpan(kFoxFlats); + CordRepRing* ring = CordRepRing::Create(MakeFlat("Tail"), flats.size()); + CordRep* child = FromFlats(flats, kAppend); + CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, child)); + std::string value = ToString(result); + for (int i = 0; i < value.length(); ++i) { + ASSERT_THAT(result->GetCharacter(i), Eq(value[i])); + } + Unref(result); +} + +TEST_F(CordRingTest, GetCharacterWithSubstring) { + absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; + auto* child = MakeSubstring(4, 20, MakeFlat(str1)); + CordRepRing* result = NeedsUnref(CordRepRing::Create(child)); + ASSERT_THAT(result, IsValidRingBuffer()); + std::string value = ToString(result); + for (int i = 0; i < value.length(); ++i) { + ASSERT_THAT(result->GetCharacter(i), Eq(value[i])); + } + Unref(result); +} + +TEST_F(CordRingTest, Dump) { + std::stringstream ss; + auto flats = MakeSpan(kFoxFlats); + CordRepRing* ring = NeedsUnref(FromFlats(flats, kPrepend)); + ss << *ring; + Unref(ring); +} + +} // namespace +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/strings/internal/cord_internal.cc b/absl/strings/internal/cord_internal.cc index 59f2e4d9..905ffd0c 100644 --- a/absl/strings/internal/cord_internal.cc +++ b/absl/strings/internal/cord_internal.cc @@ -19,6 +19,7 @@ #include "absl/container/inlined_vector.h" #include "absl/strings/internal/cord_rep_flat.h" +#include "absl/strings/internal/cord_rep_ring.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -48,6 +49,9 @@ void CordRep::Destroy(CordRep* rep) { rep = left; continue; } + } else if (rep->tag == RING) { + CordRepRing::Destroy(rep->ring()); + rep = nullptr; } else if (rep->tag == EXTERNAL) { CordRepExternal::Delete(rep); rep = nullptr; diff --git a/absl/strings/internal/cord_internal.h b/absl/strings/internal/cord_internal.h index b586ea37..011b49d3 100644 --- a/absl/strings/internal/cord_internal.h +++ b/absl/strings/internal/cord_internal.h @@ -1,4 +1,4 @@ -// Copyright 2020 The Abseil Authors. +// Copyright 2021 The Abseil Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ #include #include +#include "absl/base/config.h" #include "absl/base/internal/invoke.h" #include "absl/base/optimization.h" #include "absl/container/internal/compressed_tuple.h" @@ -145,13 +146,14 @@ struct CordRepConcat; struct CordRepExternal; struct CordRepFlat; struct CordRepSubstring; +class CordRepRing; // Various representations that we allow enum CordRepKind { - CONCAT = 0, - EXTERNAL = 1, - SUBSTRING = 2, - RING = 3, + CONCAT = 0, + EXTERNAL = 1, + SUBSTRING = 2, + RING = 3, // We have different tags for different sized flat arrays, // starting with FLAT, and limited to MAX_FLAT_TAG. The 224 value is based on @@ -160,7 +162,7 @@ enum CordRepKind { // as the Tag <---> Size logic so that FLAT stil represents the minimum flat // allocation size. (32 bytes as of now). FLAT = 4, - MAX_FLAT_TAG = 224, + MAX_FLAT_TAG = 224 }; struct CordRep { @@ -177,6 +179,8 @@ struct CordRep { uint8_t tag; char storage[1]; // Starting point for flat array: MUST BE LAST FIELD + inline CordRepRing* ring(); + inline const CordRepRing* ring() const; inline CordRepConcat* concat(); inline const CordRepConcat* concat() const; inline CordRepSubstring* substring(); @@ -306,45 +310,165 @@ CordRepExternal ConstInitExternalStorage::value(Str::value); enum { kMaxInline = 15, - // Tag byte & kMaxInline means we are storing a pointer. - kTreeFlag = 1 << 4, - // Tag byte & kProfiledFlag means we are profiling the Cord. - kProfiledFlag = 1 << 5 -}; - -// If the data has length <= kMaxInline, we store it in `as_chars`, and -// store the size in `tagged_size`. -// Else we store it in a tree and store a pointer to that tree in -// `as_tree.rep` and store a tag in `tagged_size`. -struct AsTree { - absl::cord_internal::CordRep* rep; - char padding[kMaxInline + 1 - sizeof(absl::cord_internal::CordRep*) - 1]; - char tagged_size; }; constexpr char GetOrNull(absl::string_view data, size_t pos) { return pos < data.size() ? data[pos] : '\0'; } -union InlineData { - constexpr InlineData() : as_chars{} {} - explicit constexpr InlineData(AsTree tree) : as_tree(tree) {} +// We store cordz_info as 64 bit pointer value in big endian format. This +// guarantees that the least significant byte of cordz_info matches the last +// byte of the inline data representation in as_chars_, which holds the inlined +// size or the 'is_tree' bit. +using cordz_info_t = int64_t; + +// Assert that the `cordz_info` pointer value perfectly overlaps the last half +// of `as_chars_` and can hold a pointer value. +static_assert(sizeof(cordz_info_t) * 2 == kMaxInline + 1, ""); +static_assert(sizeof(cordz_info_t) >= sizeof(intptr_t), ""); + +// BigEndianByte() creates a big endian representation of 'value', i.e.: a big +// endian value where the last byte in the host's representation holds 'value`, +// with all other bytes being 0. +static constexpr cordz_info_t BigEndianByte(unsigned char value) { +#if defined(ABSL_IS_BIG_ENDIAN) + return value; +#else + return static_cast(value) << ((sizeof(cordz_info_t) - 1) * 8); +#endif +} + +class InlineData { + public: + // kNullCordzInfo holds the big endian representation of intptr_t(1) + // This is the 'null' / initial value of 'cordz_info'. The null value + // is specifically big endian 1 as with 64-bit pointers, the last + // byte of cordz_info overlaps with the last byte holding the tag. + static constexpr cordz_info_t kNullCordzInfo = BigEndianByte(1); + + // kFakeCordzInfo holds a 'fake', non-null cordz-info value we use to + // emulate the previous 'kProfiled' tag logic in 'set_profiled' until + // cord code is changed to store cordz_info values in InlineData. + static constexpr cordz_info_t kFakeCordzInfo = BigEndianByte(9); + + constexpr InlineData() : as_chars_{0} {} + explicit constexpr InlineData(CordRep* rep) : as_tree_(rep) {} explicit constexpr InlineData(absl::string_view chars) - : as_chars{GetOrNull(chars, 0), GetOrNull(chars, 1), - GetOrNull(chars, 2), GetOrNull(chars, 3), - GetOrNull(chars, 4), GetOrNull(chars, 5), - GetOrNull(chars, 6), GetOrNull(chars, 7), - GetOrNull(chars, 8), GetOrNull(chars, 9), - GetOrNull(chars, 10), GetOrNull(chars, 11), - GetOrNull(chars, 12), GetOrNull(chars, 13), - GetOrNull(chars, 14), static_cast(chars.size())} {} - - AsTree as_tree; - char as_chars[kMaxInline + 1]; + : as_chars_{ + GetOrNull(chars, 0), GetOrNull(chars, 1), + GetOrNull(chars, 2), GetOrNull(chars, 3), + GetOrNull(chars, 4), GetOrNull(chars, 5), + GetOrNull(chars, 6), GetOrNull(chars, 7), + GetOrNull(chars, 8), GetOrNull(chars, 9), + GetOrNull(chars, 10), GetOrNull(chars, 11), + GetOrNull(chars, 12), GetOrNull(chars, 13), + GetOrNull(chars, 14), static_cast((chars.size() << 1))} {} + + // Returns true if the current instance is empty. + // The 'empty value' is an inlined data value of zero length. + bool is_empty() const { return tag() == 0; } + + // Returns true if the current instance holds a tree value. + bool is_tree() const { return (tag() & 1) != 0; } + + // Returns true if the current instance holds a cordz_info value. + // Requires the current instance to hold a tree value. + bool is_profiled() const { + assert(is_tree()); + return as_tree_.cordz_info != kNullCordzInfo; + } + + // Returns a read only pointer to the character data inside this instance. + // Requires the current instance to hold inline data. + const char* as_chars() const { + assert(!is_tree()); + return as_chars_; + } + + // Returns a mutable pointer to the character data inside this instance. + // Should be used for 'write only' operations setting an inlined value. + // Applications can set the value of inlined data either before or after + // setting the inlined size, i.e., both of the below are valid: + // + // // Set inlined data and inline size + // memcpy(data_.as_chars(), data, size); + // data_.set_inline_size(size); + // + // // Set inlined size and inline data + // data_.set_inline_size(size); + // memcpy(data_.as_chars(), data, size); + // + // It's an error to read from the returned pointer without a preceding write + // if the current instance does not hold inline data, i.e.: is_tree() == true. + char* as_chars() { return as_chars_; } + + // Returns the tree value of this value. + // Requires the current instance to hold a tree value. + CordRep* as_tree() const { + assert(is_tree()); + return as_tree_.rep; + } + + // Initialize this instance to holding the tree value `rep`, + // initializing the cordz_info to null, i.e.: 'not profiled'. + void make_tree(CordRep* rep) { + as_tree_.rep = rep; + as_tree_.cordz_info = kNullCordzInfo; + } + + // Set the tree value of this instance to 'rep`. + // Requires the current instance to already hold a tree value. + // Does not affect the value of cordz_info. + void set_tree(CordRep* rep) { + assert(is_tree()); + as_tree_.rep = rep; + } + + // Returns the size of the inlined character data inside this instance. + // Requires the current instance to hold inline data. + size_t inline_size() const { + assert(!is_tree()); + return tag() >> 1; + } + + // Sets the size of the inlined character data inside this instance. + // Requires `size` to be <= kMaxInline. + // See the documentation on 'as_chars()' for more information and examples. + void set_inline_size(size_t size) { + ABSL_ASSERT(size <= kMaxInline); + tag() = static_cast(size << 1); + } + + // Sets or unsets the 'is_profiled' state of this instance. + // Requires the current instance to hold a tree value. + void set_profiled(bool profiled) { + assert(is_tree()); + as_tree_.cordz_info = profiled ? kFakeCordzInfo : kNullCordzInfo; + } + + private: + // See cordz_info_t for forced alignment and size of `cordz_info` details. + struct AsTree { + explicit constexpr AsTree(absl::cord_internal::CordRep* tree) + : rep(tree), cordz_info(kNullCordzInfo) {} + absl::cord_internal::CordRep* rep; + alignas(sizeof(cordz_info_t)) cordz_info_t cordz_info; + }; + + char& tag() { return reinterpret_cast(this)[kMaxInline]; } + char tag() const { return reinterpret_cast(this)[kMaxInline]; } + + // If the data has length <= kMaxInline, we store it in `as_chars_`, and + // store the size in the last char of `as_chars_` shifted left + 1. + // Else we store it in a tree and store a pointer to that tree in + // `as_tree_.rep` and store a tag in `tagged_size`. + union { + char as_chars_[kMaxInline + 1]; + AsTree as_tree_; + }; }; + static_assert(sizeof(InlineData) == kMaxInline + 1, ""); -static_assert(sizeof(AsTree) == sizeof(InlineData), ""); -static_assert(offsetof(AsTree, tagged_size) == kMaxInline, ""); inline CordRepConcat* CordRep::concat() { assert(tag == CONCAT); @@ -386,6 +510,16 @@ inline const CordRepFlat* CordRep::flat() const { return reinterpret_cast(this); } +inline CordRepRing* CordRep::ring() { + assert(tag == RING); + return reinterpret_cast(this); +} + +inline const CordRepRing* CordRep::ring() const { + assert(tag == RING); + return reinterpret_cast(this); +} + inline CordRep* CordRep::Ref(CordRep* rep) { assert(rep != nullptr); rep->refcount.Increment(); diff --git a/absl/strings/internal/cord_rep_flat.h b/absl/strings/internal/cord_rep_flat.h index 8c7d160e..5f7d55ce 100644 --- a/absl/strings/internal/cord_rep_flat.h +++ b/absl/strings/internal/cord_rep_flat.h @@ -104,7 +104,8 @@ struct CordRepFlat : public CordRep { // Flat CordReps are allocated and constructed with raw ::operator new and // placement new, and must be destructed and deallocated accordingly. static void Delete(CordRep*rep) { - assert(rep->tag >= FLAT); + assert(rep->tag >= FLAT && rep->tag <= MAX_FLAT_TAG); + #if defined(__cpp_sized_deallocation) size_t size = TagToAllocatedSize(rep->tag); rep->~CordRep(); @@ -115,6 +116,7 @@ struct CordRepFlat : public CordRep { #endif } + // Returns a pointer to the data inside this flat rep. char* Data() { return storage; } const char* Data() const { return storage; } diff --git a/absl/strings/internal/cord_rep_ring.cc b/absl/strings/internal/cord_rep_ring.cc new file mode 100644 index 00000000..358b0d92 --- /dev/null +++ b/absl/strings/internal/cord_rep_ring.cc @@ -0,0 +1,895 @@ +// Copyright 2020 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "absl/strings/internal/cord_rep_ring.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/base/internal/raw_logging.h" +#include "absl/base/internal/throw_delegate.h" +#include "absl/base/macros.h" +#include "absl/container/inlined_vector.h" +#include "absl/strings/internal/cord_internal.h" +#include "absl/strings/internal/cord_rep_flat.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace cord_internal { + +// See https://bugs.llvm.org/show_bug.cgi?id=48477 +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wshadow" +#pragma clang diagnostic ignored "-Wshadow-field" +#endif + +namespace { + +using index_type = CordRepRing::index_type; + +enum class Direction { kForward, kReversed }; + +inline bool IsFlatOrExternal(CordRep* rep) { + return rep->tag >= FLAT || rep->tag == EXTERNAL; +} + +// Verifies that n + extra <= kMaxCapacity: throws std::length_error otherwise. +inline void CheckCapacity(size_t n, size_t extra) { + if (ABSL_PREDICT_FALSE(extra > CordRepRing::kMaxCapacity - n)) { + base_internal::ThrowStdLengthError("Maximum capacity exceeded"); + } +} + +// Removes a reference from `rep` only. +// Asserts that the refcount after decrement is not zero. +inline bool UnrefNeverOne(CordRep* rep) { + bool result = rep->refcount.Decrement(); + assert(result); + return result; +} + +// Creates a flat from the provided string data, allocating up to `extra` +// capacity in the returned flat depending on kMaxFlatLength limitations. +// Requires `len` to be less or equal to `kMaxFlatLength` +CordRepFlat* CreateFlat(const char* s, size_t n, size_t extra = 0) { // NOLINT + assert(n <= kMaxFlatLength); + auto* rep = CordRepFlat::New(n + extra); + rep->length = n; + memcpy(rep->Data(), s, n); + return rep; +} + +// Unrefs the provided `substring`, and returns `substring->child` +// Adds or assumes a reference on `substring->child` +CordRep* ClipSubstring(CordRepSubstring* substring) { + CordRep* child = substring->child; + if (substring->refcount.IsOne()) { + delete substring; + } else { + CordRep::Ref(child); + if (ABSL_PREDICT_FALSE(!substring->refcount.Decrement())) { + UnrefNeverOne(child); + delete substring; + } + } + return child; +} + +// Unrefs the provided `concat`, and returns `{concat->left, concat->right}` +// Adds or assumes a reference on `concat->left` and `concat->right`. +std::pair ClipConcat(CordRepConcat* concat) { + auto result = std::make_pair(concat->left, concat->right); + if (concat->refcount.IsOne()) { + delete concat; + } else { + CordRep::Ref(result.first); + CordRep::Ref(result.second); + if (ABSL_PREDICT_FALSE(!concat->refcount.Decrement())) { + UnrefNeverOne(result.first); + UnrefNeverOne(result.second); + delete concat; + } + } + return result; +} + +// Unrefs the entries in `[head, tail)`. +// Requires all entries to be a FLAT or EXTERNAL node. +void UnrefEntries(const CordRepRing* rep, index_type head, index_type tail) { + rep->ForEach(head, tail, [rep](index_type ix) { + CordRep* child = rep->entry_child(ix); + if (!child->refcount.Decrement()) { + if (child->tag >= FLAT) { + CordRepFlat::Delete(child->flat()); + } else { + CordRepExternal::Delete(child->external()); + } + } + }); +} + +template +void Consume(Direction direction, CordRep* rep, F&& fn) { + size_t offset = 0; + size_t length = rep->length; + struct Entry { + CordRep* rep; + size_t offset; + size_t length; + }; + absl::InlinedVector stack; + + for (;;) { + if (rep->tag >= FLAT || rep->tag == EXTERNAL || rep->tag == RING) { + fn(rep, offset, length); + if (stack.empty()) return; + + rep = stack.back().rep; + offset = stack.back().offset; + length = stack.back().length; + stack.pop_back(); + } else if (rep->tag == SUBSTRING) { + offset += rep->substring()->start; + rep = ClipSubstring(rep->substring()); + } else if (rep->tag == CONCAT) { + auto res = ClipConcat(rep->concat()); + CordRep* left = res.first; + CordRep* right = res.second; + + if (left->length <= offset) { + // Don't need left node + offset -= left->length; + CordRep::Unref(left); + rep = right; + continue; + } + + size_t length_left = left->length - offset; + if (length_left >= length) { + // Don't need right node + CordRep::Unref(right); + rep = left; + continue; + } + + // Need both nodes + size_t length_right = length - length_left; + if (direction == Direction::kReversed) { + stack.push_back({left, offset, length_left}); + rep = right; + offset = 0; + length = length_right; + } else { + stack.push_back({right, 0, length_right}); + rep = left; + length = length_left; + } + } else { + assert("Valid tag" == nullptr); + return; + } + } +} + +template +void Consume(CordRep* rep, F&& fn) { + return Consume(Direction::kForward, rep, std::forward(fn)); +} + +template +void RConsume(CordRep* rep, F&& fn) { + return Consume(Direction::kReversed, rep, std::forward(fn)); +} + +} // namespace + +std::ostream& operator<<(std::ostream& s, const CordRepRing& rep) { + // Note: 'pos' values are defined as size_t (for overflow reasons), but that + // prints really awkward for small prepended values such as -5. ssize_t is not + // portable (POSIX), so we use ptrdiff_t instead to cast to signed values. + s << " CordRepRing(" << &rep << ", length = " << rep.length + << ", head = " << rep.head_ << ", tail = " << rep.tail_ + << ", cap = " << rep.capacity_ << ", rc = " << rep.refcount.Get() + << ", begin_pos_ = " << static_cast(rep.begin_pos_) << ") {\n"; + CordRepRing::index_type head = rep.head(); + do { + CordRep* child = rep.entry_child(head); + s << " entry[" << head << "] length = " << rep.entry_length(head) + << ", child " << child << ", clen = " << child->length + << ", tag = " << static_cast(child->tag) + << ", rc = " << child->refcount.Get() + << ", offset = " << rep.entry_data_offset(head) + << ", end_pos = " << static_cast(rep.entry_end_pos(head)) + << "\n"; + head = rep.advance(head); + } while (head != rep.tail()); + return s << "}\n"; +} + +void CordRepRing::AddDataOffset(index_type index, size_t n) { + entry_data_offset()[index] += static_cast(n); +} + +void CordRepRing::SubLength(index_type index, size_t n) { + entry_end_pos()[index] -= n; +} + +class CordRepRing::Filler { + public: + Filler(CordRepRing* rep, index_type pos) : rep_(rep), head_(pos), pos_(pos) {} + + index_type head() const { return head_; } + index_type pos() const { return pos_; } + + void Add(CordRep* child, size_t offset, pos_type end_pos) { + rep_->entry_end_pos()[pos_] = end_pos; + rep_->entry_child()[pos_] = child; + rep_->entry_data_offset()[pos_] = static_cast(offset); + pos_ = rep_->advance(pos_); + } + + private: + CordRepRing* rep_; + index_type head_; + index_type pos_; +}; + +constexpr size_t CordRepRing::kMaxCapacity; // NOLINT: needed for c++11 + +bool CordRepRing::IsValid(std::ostream& output) const { + if (capacity_ == 0) { + output << "capacity == 0"; + return false; + } + + if (head_ >= capacity_ || tail_ >= capacity_) { + output << "head " << head_ << " and/or tail " << tail_ << "exceed capacity " + << capacity_; + return false; + } + + const index_type back = retreat(tail_); + size_t pos_length = Distance(begin_pos_, entry_end_pos(back)); + if (pos_length != length) { + output << "length " << length << " does not match positional length " + << pos_length << " from begin_pos " << begin_pos_ << " and entry[" + << back << "].end_pos " << entry_end_pos(back); + return false; + } + + index_type head = head_; + pos_type begin_pos = begin_pos_; + do { + pos_type end_pos = entry_end_pos(head); + size_t entry_length = Distance(begin_pos, end_pos); + if (entry_length == 0) { + output << "entry[" << head << "] has an invalid length " << entry_length + << " from begin_pos " << begin_pos << " and end_pos " << end_pos; + return false; + } + + CordRep* child = entry_child(head); + if (child == nullptr) { + output << "entry[" << head << "].child == nullptr"; + return false; + } + if (child->tag < FLAT && child->tag != EXTERNAL) { + output << "entry[" << head << "].child has an invalid tag " + << static_cast(child->tag); + return false; + } + + size_t offset = entry_data_offset(head); + if (offset >= child->length || entry_length > child->length - offset) { + output << "entry[" << head << "] has offset " << offset + << " and entry length " << entry_length + << " which are outside of the childs length of " << child->length; + return false; + } + + begin_pos = end_pos; + head = advance(head); + } while (head != tail_); + + return true; +} + +#ifdef EXTRA_CORD_RING_VALIDATION +CordRepRing* CordRepRing::Validate(CordRepRing* rep, const char* file, + int line) { + if (!rep->IsValid(std::cerr)) { + std::cerr << "\nERROR: CordRepRing corrupted"; + if (line) std::cerr << " at line " << line; + if (file) std::cerr << " in file " << file; + std::cerr << "\nContent = " << *rep; + abort(); + } + return rep; +} +#endif // EXTRA_CORD_RING_VALIDATION + +CordRepRing* CordRepRing::New(size_t capacity, size_t extra) { + CheckCapacity(capacity, extra); + + size_t size = AllocSize(capacity += extra); + void* mem = ::operator new(size); + auto* rep = new (mem) CordRepRing(static_cast(capacity)); + rep->tag = RING; + rep->capacity_ = static_cast(capacity); + rep->begin_pos_ = 0; + return rep; +} + +void CordRepRing::SetCapacityForTesting(size_t capacity) { + // Adjust for the changed layout + assert(capacity <= capacity_); + assert(head() == 0 || head() < tail()); + memmove(Layout::Partial(capacity).Pointer<1>(data_) + head(), + Layout::Partial(capacity_).Pointer<1>(data_) + head(), + entries() * sizeof(Layout::ElementType<1>)); + memmove(Layout::Partial(capacity, capacity).Pointer<2>(data_) + head(), + Layout::Partial(capacity_, capacity_).Pointer<2>(data_) + head(), + entries() * sizeof(Layout::ElementType<2>)); + capacity_ = static_cast(capacity); +} + +void CordRepRing::Delete(CordRepRing* rep) { + assert(rep != nullptr && rep->tag == RING); +#if defined(__cpp_sized_deallocation) + size_t size = AllocSize(rep->capacity_); + rep->~CordRepRing(); + ::operator delete(rep, size); +#else + rep->~CordRepRing(); + ::operator delete(rep); +#endif +} + +void CordRepRing::Destroy(CordRepRing* rep) { + UnrefEntries(rep, rep->head(), rep->tail()); + Delete(rep); +} + +template +void CordRepRing::Fill(const CordRepRing* src, index_type head, + index_type tail) { + this->length = src->length; + head_ = 0; + tail_ = advance(0, src->entries(head, tail)); + begin_pos_ = src->begin_pos_; + + // TODO(mvels): there may be opportunities here for large buffers. + auto* dst_pos = entry_end_pos(); + auto* dst_child = entry_child(); + auto* dst_offset = entry_data_offset(); + src->ForEach(head, tail, [&](index_type index) { + *dst_pos++ = src->entry_end_pos(index); + CordRep* child = src->entry_child(index); + *dst_child++ = ref ? CordRep::Ref(child) : child; + *dst_offset++ = src->entry_data_offset(index); + }); +} + +CordRepRing* CordRepRing::Copy(CordRepRing* rep, index_type head, + index_type tail, size_t extra) { + CordRepRing* newrep = CordRepRing::New(rep->entries(head, tail), extra); + newrep->Fill(rep, head, tail); + CordRep::Unref(rep); + return newrep; +} + +CordRepRing* CordRepRing::Mutable(CordRepRing* rep, size_t extra) { + // Get current number of entries, and check for max capacity. + size_t entries = rep->entries(); + + size_t min_extra = (std::max)(extra, rep->capacity() * 2 - entries); + if (!rep->refcount.IsOne()) { + return Copy(rep, rep->head(), rep->tail(), min_extra); + } else if (entries + extra > rep->capacity()) { + CordRepRing* newrep = CordRepRing::New(entries, min_extra); + newrep->Fill(rep, rep->head(), rep->tail()); + CordRepRing::Delete(rep); + return newrep; + } else { + return rep; + } +} + +Span CordRepRing::GetAppendBuffer(size_t size) { + assert(refcount.IsOne()); + index_type back = retreat(tail_); + CordRep* child = entry_child(back); + if (child->tag >= FLAT && child->refcount.IsOne()) { + size_t capacity = child->flat()->Capacity(); + pos_type end_pos = entry_end_pos(back); + size_t data_offset = entry_data_offset(back); + size_t entry_length = Distance(entry_begin_pos(back), end_pos); + size_t used = data_offset + entry_length; + if (size_t n = (std::min)(capacity - used, size)) { + child->length = data_offset + entry_length + n; + entry_end_pos()[back] = end_pos + n; + this->length += n; + return {child->flat()->Data() + used, n}; + } + } + return {nullptr, 0}; +} + +Span CordRepRing::GetPrependBuffer(size_t size) { + assert(refcount.IsOne()); + CordRep* child = entry_child(head_); + size_t data_offset = entry_data_offset(head_); + if (data_offset && child->refcount.IsOne() && child->tag >= FLAT) { + size_t n = (std::min)(data_offset, size); + this->length += n; + begin_pos_ -= n; + data_offset -= n; + entry_data_offset()[head_] = static_cast(data_offset); + return {child->flat()->Data() + data_offset, n}; + } + return {nullptr, 0}; +} + +CordRepRing* CordRepRing::CreateFromLeaf(CordRep* child, size_t offset, + size_t length, size_t extra) { + CordRepRing* rep = CordRepRing::New(1, extra); + rep->head_ = 0; + rep->tail_ = rep->advance(0); + rep->length = length; + rep->entry_end_pos()[0] = length; + rep->entry_child()[0] = child; + rep->entry_data_offset()[0] = static_cast(offset); + return Validate(rep); +} + +CordRepRing* CordRepRing::CreateSlow(CordRep* child, size_t extra) { + CordRepRing* rep = nullptr; + Consume(child, [&](CordRep* child, size_t offset, size_t length) { + if (IsFlatOrExternal(child)) { + rep = rep ? AppendLeaf(rep, child, offset, length) + : CreateFromLeaf(child, offset, length, extra); + } else if (rep) { + rep = AddRing(rep, child->ring(), offset, length); + } else if (offset == 0 && child->length == length) { + rep = Mutable(child->ring(), extra); + } else { + rep = SubRing(child->ring(), offset, length, extra); + } + }); + return Validate(rep, nullptr, __LINE__); +} + +CordRepRing* CordRepRing::Create(CordRep* child, size_t extra) { + size_t length = child->length; + if (IsFlatOrExternal(child)) { + return CreateFromLeaf(child, 0, length, extra); + } + if (child->tag == RING) { + return Mutable(child->ring(), extra); + } + return CreateSlow(child, extra); +} + +template +CordRepRing* CordRepRing::AddRing(CordRepRing* rep, CordRepRing* ring, + size_t offset, size_t length) { + assert(offset < ring->length); + constexpr bool append = mode == AddMode::kAppend; + Position head = ring->Find(offset); + Position tail = ring->FindTail(head.index, offset + length); + const index_type entries = ring->entries(head.index, tail.index); + + rep = Mutable(rep, entries); + + // The delta for making ring[head].end_pos into 'len - offset' + const pos_type delta_length = + (append ? rep->begin_pos_ + rep->length : rep->begin_pos_ - length) - + ring->entry_begin_pos(head.index) - head.offset; + + // Start filling at `tail`, or `entries` before `head` + Filler filler(rep, append ? rep->tail_ : rep->retreat(rep->head_, entries)); + + if (ring->refcount.IsOne()) { + // Copy entries from source stealing the ref and adjusting the end position. + // Commit the filler as this is no-op. + ring->ForEach(head.index, tail.index, [&](index_type ix) { + filler.Add(ring->entry_child(ix), ring->entry_data_offset(ix), + ring->entry_end_pos(ix) + delta_length); + }); + + // Unref entries we did not copy over, and delete source. + if (head.index != ring->head_) UnrefEntries(ring, ring->head_, head.index); + if (tail.index != ring->tail_) UnrefEntries(ring, tail.index, ring->tail_); + CordRepRing::Delete(ring); + } else { + ring->ForEach(head.index, tail.index, [&](index_type ix) { + CordRep* child = ring->entry_child(ix); + filler.Add(child, ring->entry_data_offset(ix), + ring->entry_end_pos(ix) + delta_length); + CordRep::Ref(child); + }); + CordRepRing::Unref(ring); + } + + if (head.offset) { + // Increase offset of first 'source' entry appended or prepended. + // This is always the entry in `filler.head()` + rep->AddDataOffset(filler.head(), head.offset); + } + + if (tail.offset) { + // Reduce length of last 'source' entry appended or prepended. + // This is always the entry tailed by `filler.pos()` + rep->SubLength(rep->retreat(filler.pos()), tail.offset); + } + + // Commit changes + rep->length += length; + if (append) { + rep->tail_ = filler.pos(); + } else { + rep->head_ = filler.head(); + rep->begin_pos_ -= length; + } + + return Validate(rep); +} + +CordRepRing* CordRepRing::AppendSlow(CordRepRing* rep, CordRep* child) { + Consume(child, [&rep](CordRep* child, size_t offset, size_t length) { + if (child->tag == RING) { + rep = AddRing(rep, child->ring(), offset, length); + } else { + rep = AppendLeaf(rep, child, offset, length); + } + }); + return rep; +} + +CordRepRing* CordRepRing::AppendLeaf(CordRepRing* rep, CordRep* child, + size_t offset, size_t length) { + rep = Mutable(rep, 1); + index_type back = rep->tail_; + const pos_type begin_pos = rep->begin_pos_ + rep->length; + rep->tail_ = rep->advance(rep->tail_); + rep->length += length; + rep->entry_end_pos()[back] = begin_pos + length; + rep->entry_child()[back] = child; + rep->entry_data_offset()[back] = static_cast(offset); + return Validate(rep, nullptr, __LINE__); +} + +CordRepRing* CordRepRing::Append(CordRepRing* rep, CordRep* child) { + size_t length = child->length; + if (IsFlatOrExternal(child)) { + return AppendLeaf(rep, child, 0, length); + } + if (child->tag == RING) { + return AddRing(rep, child->ring(), 0, length); + } + return AppendSlow(rep, child); +} + +CordRepRing* CordRepRing::PrependSlow(CordRepRing* rep, CordRep* child) { + RConsume(child, [&](CordRep* child, size_t offset, size_t length) { + if (IsFlatOrExternal(child)) { + rep = PrependLeaf(rep, child, offset, length); + } else { + rep = AddRing(rep, child->ring(), offset, length); + } + }); + return Validate(rep); +} + +CordRepRing* CordRepRing::PrependLeaf(CordRepRing* rep, CordRep* child, + size_t offset, size_t length) { + rep = Mutable(rep, 1); + index_type head = rep->retreat(rep->head_); + pos_type end_pos = rep->begin_pos_; + rep->head_ = head; + rep->length += length; + rep->begin_pos_ -= length; + rep->entry_end_pos()[head] = end_pos; + rep->entry_child()[head] = child; + rep->entry_data_offset()[head] = static_cast(offset); + return Validate(rep); +} + +CordRepRing* CordRepRing::Prepend(CordRepRing* rep, CordRep* child) { + size_t length = child->length; + if (IsFlatOrExternal(child)) { + return PrependLeaf(rep, child, 0, length); + } + if (child->tag == RING) { + return AddRing(rep, child->ring(), 0, length); + } + return PrependSlow(rep, child); +} + +CordRepRing* CordRepRing::Append(CordRepRing* rep, absl::string_view data, + size_t extra) { + if (rep->refcount.IsOne()) { + Span avail = rep->GetAppendBuffer(data.length()); + if (!avail.empty()) { + memcpy(avail.data(), data.data(), avail.length()); + data.remove_prefix(avail.length()); + } + } + if (data.empty()) return Validate(rep); + + const size_t flats = (data.length() - 1) / kMaxFlatLength + 1; + rep = Mutable(rep, flats); + + Filler filler(rep, rep->tail_); + pos_type pos = rep->begin_pos_ + rep->length; + + while (data.length() >= kMaxFlatLength) { + auto* flat = CreateFlat(data.data(), kMaxFlatLength); + filler.Add(flat, 0, pos += kMaxFlatLength); + data.remove_prefix(kMaxFlatLength); + } + + if (data.length()) { + auto* flat = CreateFlat(data.data(), data.length(), extra); + filler.Add(flat, 0, pos += data.length()); + } + + rep->length = pos - rep->begin_pos_; + rep->tail_ = filler.pos(); + + return Validate(rep); +} + +CordRepRing* CordRepRing::Prepend(CordRepRing* rep, absl::string_view data, + size_t extra) { + if (rep->refcount.IsOne()) { + Span avail = rep->GetPrependBuffer(data.length()); + if (!avail.empty()) { + const char* tail = data.data() + data.length() - avail.length(); + memcpy(avail.data(), tail, avail.length()); + data.remove_suffix(avail.length()); + } + } + if (data.empty()) return rep; + + const size_t flats = (data.length() - 1) / kMaxFlatLength + 1; + rep = Mutable(rep, flats); + pos_type pos = rep->begin_pos_; + Filler filler(rep, rep->retreat(rep->head_, static_cast(flats))); + + size_t first_size = data.size() - (flats - 1) * kMaxFlatLength; + CordRepFlat* flat = CordRepFlat::New(first_size + extra); + flat->length = first_size + extra; + memcpy(flat->Data() + extra, data.data(), first_size); + data.remove_prefix(first_size); + filler.Add(flat, extra, pos); + pos -= first_size; + + while (!data.empty()) { + assert(data.size() >= kMaxFlatLength); + flat = CreateFlat(data.data(), kMaxFlatLength); + filler.Add(flat, 0, pos); + pos -= kMaxFlatLength; + data.remove_prefix(kMaxFlatLength); + } + + rep->head_ = filler.head(); + rep->length += rep->begin_pos_ - pos; + rep->begin_pos_ = pos; + + return Validate(rep); +} + +// 32 entries is 32 * sizeof(pos_type) = 4 cache lines on x86 +static constexpr index_type kBinarySearchThreshold = 32; +static constexpr index_type kBinarySearchEndCount = 8; + +template +CordRepRing::index_type CordRepRing::FindBinary(index_type head, + index_type tail, + size_t offset) const { + index_type count = tail + (wrap ? capacity_ : 0) - head; + do { + count = (count - 1) / 2; + assert(count < entries(head, tail_)); + index_type mid = wrap ? advance(head, count) : head + count; + index_type after_mid = wrap ? advance(mid) : mid + 1; + bool larger = (offset >= entry_end_offset(mid)); + head = larger ? after_mid : head; + tail = larger ? tail : mid; + assert(head != tail); + } while (ABSL_PREDICT_TRUE(count > kBinarySearchEndCount)); + return head; +} + +CordRepRing::Position CordRepRing::FindSlow(index_type head, + size_t offset) const { + index_type tail = tail_; + + // Binary search until we are good for linear search + // Optimize for branchless / non wrapping ops + if (tail > head) { + index_type count = tail - head; + if (count > kBinarySearchThreshold) { + head = FindBinary(head, tail, offset); + } + } else { + index_type count = capacity_ + tail - head; + if (count > kBinarySearchThreshold) { + head = FindBinary(head, tail, offset); + } + } + + pos_type pos = entry_begin_pos(head); + pos_type end_pos = entry_end_pos(head); + while (offset >= Distance(begin_pos_, end_pos)) { + head = advance(head); + pos = end_pos; + end_pos = entry_end_pos(head); + } + + return {head, offset - Distance(begin_pos_, pos)}; +} + +CordRepRing::Position CordRepRing::FindTailSlow(index_type head, + size_t offset) const { + index_type tail = tail_; + const size_t tail_offset = offset - 1; + + // Binary search until we are good for linear search + // Optimize for branchless / non wrapping ops + if (tail > head) { + index_type count = tail - head; + if (count > kBinarySearchThreshold) { + head = FindBinary(head, tail, tail_offset); + } + } else { + index_type count = capacity_ + tail - head; + if (count > kBinarySearchThreshold) { + head = FindBinary(head, tail, tail_offset); + } + } + + size_t end_offset = entry_end_offset(head); + while (tail_offset >= end_offset) { + head = advance(head); + end_offset = entry_end_offset(head); + } + + return {advance(head), end_offset - offset}; +} + +char CordRepRing::GetCharacter(size_t offset) const { + assert(offset < length); + + Position pos = Find(offset); + size_t data_offset = entry_data_offset(pos.index) + pos.offset; + return GetRepData(entry_child(pos.index))[data_offset]; +} + +CordRepRing* CordRepRing::SubRing(CordRepRing* rep, size_t offset, + size_t length, size_t extra) { + assert(offset <= rep->length); + assert(offset <= rep->length - length); + + if (length == 0) { + CordRep::Unref(rep); + return nullptr; + } + + // Find position of first byte + Position head = rep->Find(offset); + Position tail = rep->FindTail(head.index, offset + length); + const size_t new_entries = rep->entries(head.index, tail.index); + + if (rep->refcount.IsOne() && extra <= (rep->capacity() - new_entries)) { + // We adopt a privately owned rep and no extra entries needed. + if (head.index != rep->head_) UnrefEntries(rep, rep->head_, head.index); + if (tail.index != rep->tail_) UnrefEntries(rep, tail.index, rep->tail_); + rep->head_ = head.index; + rep->tail_ = tail.index; + } else { + // Copy subset to new rep + rep = Copy(rep, head.index, tail.index, extra); + head.index = rep->head_; + tail.index = rep->tail_; + } + + // Adjust begin_pos and length + rep->length = length; + rep->begin_pos_ += offset; + + // Adjust head and tail blocks + if (head.offset) { + rep->AddDataOffset(head.index, head.offset); + } + if (tail.offset) { + rep->SubLength(rep->retreat(tail.index), tail.offset); + } + + return Validate(rep); +} + +CordRepRing* CordRepRing::RemovePrefix(CordRepRing* rep, size_t len, + size_t extra) { + assert(len <= rep->length); + if (len == rep->length) { + CordRep::Unref(rep); + return nullptr; + } + + Position head = rep->Find(len); + if (rep->refcount.IsOne()) { + if (head.index != rep->head_) UnrefEntries(rep, rep->head_, head.index); + rep->head_ = head.index; + } else { + rep = Copy(rep, head.index, rep->tail_, extra); + head.index = rep->head_; + } + + // Adjust begin_pos and length + rep->length -= len; + rep->begin_pos_ += len; + + // Adjust head block + if (head.offset) { + rep->AddDataOffset(head.index, head.offset); + } + + return Validate(rep); +} + +CordRepRing* CordRepRing::RemoveSuffix(CordRepRing* rep, size_t len, + size_t extra) { + assert(len <= rep->length); + + if (len == rep->length) { + CordRep::Unref(rep); + return nullptr; + } + + Position tail = rep->FindTail(rep->length - len); + if (rep->refcount.IsOne()) { + // We adopt a privately owned rep, scrub. + if (tail.index != rep->tail_) UnrefEntries(rep, tail.index, rep->tail_); + rep->tail_ = tail.index; + } else { + // Copy subset to new rep + rep = Copy(rep, rep->head_, tail.index, extra); + tail.index = rep->tail_; + } + + // Adjust length + rep->length -= len; + + // Adjust tail block + if (tail.offset) { + rep->SubLength(rep->retreat(tail.index), tail.offset); + } + + return Validate(rep); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +} // namespace cord_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/strings/internal/cord_rep_ring.h b/absl/strings/internal/cord_rep_ring.h new file mode 100644 index 00000000..e6f6b59c --- /dev/null +++ b/absl/strings/internal/cord_rep_ring.h @@ -0,0 +1,576 @@ +// Copyright 2020 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_STRINGS_INTERNAL_CORD_REP_RING_H_ +#define ABSL_STRINGS_INTERNAL_CORD_REP_RING_H_ + +#include +#include +#include +#include +#include +#include + +#include "absl/container/internal/layout.h" +#include "absl/strings/internal/cord_internal.h" +#include "absl/strings/internal/cord_rep_flat.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace cord_internal { + +// See https://bugs.llvm.org/show_bug.cgi?id=48477 +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wshadow" +#pragma clang diagnostic ignored "-Wshadow-field" +#endif + +// All operations modifying a ring buffer are implemented as static methods +// requiring a CordRepRing instance with a reference adopted by the method. +// +// The methods return the modified ring buffer, which may be equal to the input +// if the input was not shared, and having large enough capacity to accommodate +// any newly added node(s). Otherwise, a copy of the input rep with the new +// node(s) added is returned. +// +// Any modification on non shared ring buffers with enough capacity will then +// require minimum atomic operations. Caller should where possible provide +// reasonable `extra` hints for both anticipated extra `flat` byte space, as +// well as anticipated extra nodes required for complex operations. +// +// Example of code creating a ring buffer, adding some data to it, +// and discarding the buffer when done: +// +// void FunWithRings() { +// // Create ring with 3 flats +// CordRep* flat = CreateFlat("Hello"); +// CordRepRing* ring = CordRepRing::Create(flat, 2); +// ring = CordRepRing::Append(ring, CreateFlat(" ")); +// ring = CordRepRing::Append(ring, CreateFlat("world")); +// DoSomethingWithRing(ring); +// CordRep::Unref(ring); +// } +// +// Example of code Copying an existing ring buffer and modifying it: +// +// void MoreFunWithRings(CordRepRing* src) { +// CordRepRing* ring = CordRep::Ref(src)->ring(); +// ring = CordRepRing::Append(ring, CreateFlat("Hello")); +// ring = CordRepRing::Append(ring, CreateFlat(" ")); +// ring = CordRepRing::Append(ring, CreateFlat("world")); +// DoSomethingWithRing(ring); +// CordRep::Unref(ring); +// } +// +class CordRepRing : public CordRep { + public: + // `pos_type` represents a 'logical position'. A CordRepRing instance has a + // `begin_pos` (default 0), and each node inside the buffer will have an + // `end_pos` which is the `end_pos` of the previous node (or `begin_pos`) plus + // this node's length. The purpose is to allow for a binary search on this + // position, while allowing O(1) prepend and append operations. + using pos_type = uint64_t; + + // `index_type` is the type for the `head`, `tail` and `capacity` indexes. + // Ring buffers are limited to having no more than four billion entries. + using index_type = uint32_t; + + // `offset_type` is the type for the data offset inside a child rep's data. + using offset_type = uint32_t; + + // Position holds the node index and relative offset into the node for + // some physical offset in the contained data as returned by the Find() + // and FindTail() methods. + struct Position { + index_type index; + size_t offset; + }; + + // The maximum # of child nodes that can be hosted inside a CordRepRing. + static constexpr size_t kMaxCapacity = (std::numeric_limits::max)(); + + // CordRepring can not be default constructed, moved, copied or assigned. + CordRepRing() = delete; + CordRepRing(const CordRepRing&) = delete; + CordRepRing& operator=(const CordRepRing&) = delete; + + // Returns true if this instance is valid, false if some or all of the + // invariants are broken. Intended for debug purposes only. + // `output` receives an explanation of the broken invariants. + bool IsValid(std::ostream& output) const; + + // Returns the size in bytes for a CordRepRing with `capacity' entries. + static constexpr size_t AllocSize(size_t capacity); + + // Returns the distance in bytes from `pos` to `end_pos`. + static constexpr size_t Distance(pos_type pos, pos_type end_pos); + + // Creates a new ring buffer from the provided `rep`. Adopts a reference + // on `rep`. The returned ring buffer has a capacity of at least `extra + 1` + static CordRepRing* Create(CordRep* child, size_t extra = 0); + + // `head`, `tail` and `capacity` indexes defining the ring buffer boundaries. + index_type head() const { return head_; } + index_type tail() const { return tail_; } + index_type capacity() const { return capacity_; } + + // Returns the number of entries in this instance. + index_type entries() const { return entries(head_, tail_); } + + // Returns the logical begin position of this instance. + pos_type begin_pos() const { return begin_pos_; } + + // Returns the number of entries for a given head-tail range. + // Requires `head` and `tail` values to be less than `capacity()`. + index_type entries(index_type head, index_type tail) const { + assert(head < capacity_ && tail < capacity_); + return tail - head + ((tail > head) ? 0 : capacity_); + } + + // Returns the logical end position of entry `index`. + pos_type const& entry_end_pos(index_type index) const { + assert(IsValidIndex(index)); + return Layout::Partial().Pointer<0>(data_)[index]; + } + + // Returns the child pointer of entry `index`. + CordRep* const& entry_child(index_type index) const { + assert(IsValidIndex(index)); + return Layout::Partial(capacity()).Pointer<1>(data_)[index]; + } + + // Returns the data offset of entry `index` + offset_type const& entry_data_offset(index_type index) const { + assert(IsValidIndex(index)); + return Layout::Partial(capacity(), capacity()).Pointer<2>(data_)[index]; + } + + // Appends the provided child node to the `rep` instance. + // Adopts a reference from `rep` and `child` which may not be null. + // If the provided child is a FLAT or EXTERNAL node, or a SUBSTRING node + // containing a FLAT or EXTERNAL node, then flat or external the node is added + // 'as is', with an offset added for the SUBSTRING case. + // If the provided child is a RING or CONCAT tree, or a SUBSTRING of a RING or + // CONCAT tree, then all child nodes not excluded by any start offset or + // length values are added recursively. + static CordRepRing* Append(CordRepRing* rep, CordRep* child); + + // Appends the provided string data to the `rep` instance. + // This function will attempt to utilize any remaining capacity in the last + // node of the input if that node is not shared (directly or indirectly), and + // of type FLAT. Remaining data will be added as one or more FLAT nodes. + // Any last node added to the ring buffer will be allocated with up to + // `extra` bytes of capacity for (anticipated) subsequent append actions. + static CordRepRing* Append(CordRepRing* rep, string_view data, + size_t extra = 0); + + // Prepends the provided child node to the `rep` instance. + // Adopts a reference from `rep` and `child` which may not be null. + // If the provided child is a FLAT or EXTERNAL node, or a SUBSTRING node + // containing a FLAT or EXTERNAL node, then flat or external the node is + // prepended 'as is', with an optional offset added for the SUBSTRING case. + // If the provided child is a RING or CONCAT tree, or a SUBSTRING of a RING + // or CONCAT tree, then all child nodes not excluded by any start offset or + // length values are added recursively. + static CordRepRing* Prepend(CordRepRing* rep, CordRep* child); + + // Prepends the provided string data to the `rep` instance. + // This function will attempt to utilize any remaining capacity in the first + // node of the input if that node is not shared (directly or indirectly), and + // of type FLAT. Remaining data will be added as one or more FLAT nodes. + // Any first node prepnded to the ring buffer will be allocated with up to + // `extra` bytes of capacity for (anticipated) subsequent prepend actions. + static CordRepRing* Prepend(CordRepRing* rep, string_view data, + size_t extra = 0); + + // Returns a span referencing potentially unused capacity in the last node. + // The returned span may be empty if no such capacity is available, or if the + // current instance is shared. Else, a span of size `n <= size` is returned. + // If non empty, the ring buffer is adjusted to the new length, with the newly + // added capacity left uninitialized. Callers should assign a value to the + // entire span before any other operations on this instance. + Span GetAppendBuffer(size_t size); + + // Returns a span referencing potentially unused capacity in the first node. + // This function is identical to GetAppendBuffer except that it returns a span + // referencing up to `size` capacity directly before the existing data. + Span GetPrependBuffer(size_t size); + + // Returns a cord ring buffer containing `length` bytes of data starting at + // `offset`. If the input is not shared, this function will remove all head + // and tail child nodes outside of the requested range, and adjust the new + // head and tail nodes as required. If the input is shared, this function + // returns a new instance sharing some or all of the nodes from the input. + static CordRepRing* SubRing(CordRepRing* r, size_t offset, size_t length, + size_t extra = 0); + + // Returns a cord ring buffer with the first `length` bytes removed. + // If the input is not shared, this function will remove all head child nodes + // fully inside the first `length` bytes, and adjust the new head as required. + // If the input is shared, this function returns a new instance sharing some + // or all of the nodes from the input. + static CordRepRing* RemoveSuffix(CordRepRing* r, size_t length, + size_t extra = 0); + + // Returns a cord ring buffer with the last `length` bytes removed. + // If the input is not shared, this function will remove all head child nodes + // fully inside the first `length` bytes, and adjust the new head as required. + // If the input is shared, this function returns a new instance sharing some + // or all of the nodes from the input. + static CordRepRing* RemovePrefix(CordRepRing* r, size_t len, + size_t extra = 0); + + // Returns the character at `offset`. Requires that `offset < length`. + char GetCharacter(size_t offset) const; + + // Testing only: set capacity to requested capacity. + void SetCapacityForTesting(size_t capacity); + + // Returns the CordRep data pointer for the provided CordRep. + // Requires that the provided `rep` is either a FLAT or EXTERNAL CordRep. + static const char* GetLeafData(const CordRep* rep); + + // Returns the CordRep data pointer for the provided CordRep. + // Requires that `rep` is either a FLAT, EXTERNAL, or SUBSTRING CordRep. + static const char* GetRepData(const CordRep* rep); + + // Advances the provided position, wrapping around capacity as needed. + // Requires `index` < capacity() + inline index_type advance(index_type index) const; + + // Advances the provided position by 'n`, wrapping around capacity as needed. + // Requires `index` < capacity() and `n` <= capacity. + inline index_type advance(index_type index, index_type n) const; + + // Retreats the provided position, wrapping around 0 as needed. + // Requires `index` < capacity() + inline index_type retreat(index_type index) const; + + // Retreats the provided position by 'n', wrapping around 0 as needed. + // Requires `index` < capacity() + inline index_type retreat(index_type index, index_type n) const; + + // Returns the logical begin position of entry `index` + pos_type const& entry_begin_pos(index_type index) const { + return (index == head_) ? begin_pos_ : entry_end_pos(retreat(index)); + } + + // Returns the physical start offset of entry `index` + size_t entry_start_offset(index_type index) const { + return Distance(begin_pos_, entry_begin_pos(index)); + } + + // Returns the physical end offset of entry `index` + size_t entry_end_offset(index_type index) const { + return Distance(begin_pos_, entry_end_pos(index)); + } + + // Returns the data length for entry `index` + size_t entry_length(index_type index) const { + return Distance(entry_begin_pos(index), entry_end_pos(index)); + } + + // Returns the data for entry `index` + absl::string_view entry_data(index_type index) const; + + // Returns the position for `offset` as {index, prefix}. `index` holds the + // index of the entry at the specified offset and `prefix` holds the relative + // offset inside that entry. + // Requires `offset` < length. + // + // For example we can implement GetCharacter(offset) as: + // char GetCharacter(size_t offset) { + // Position pos = this->Find(offset); + // return this->entry_data(pos.pos)[pos.offset]; + // } + inline Position Find(size_t offset) const; + + // Find starting at `head` + inline Position Find(index_type head, size_t offset) const; + + // Returns the tail position for `offset` as {tail index, suffix}. + // `tail index` holds holds the index of the entry holding the offset directly + // before 'offset` advanced by one. 'suffix` holds the relative offset from + // that relative offset in the entry to the end of the entry. + // For example, FindTail(length) will return {tail(), 0}, FindTail(length - 5) + // will return {retreat(tail), 5)} provided the preceding entry contains at + // least 5 bytes of data. + // Requires offset >= 1 && offset <= length. + // + // This function is very useful in functions that need to clip the end of some + // ring buffer such as 'RemovePrefix'. + // For example, we could implement RemovePrefix for non shared instances as: + // void RemoveSuffix(size_t n) { + // Position pos = FindTail(length - n); + // UnrefEntries(pos.pos, this->tail_); + // this->tail_ = pos.pos; + // entry(retreat(pos.pos)).end_pos -= pos.offset; + // } + inline Position FindTail(size_t offset) const; + + // Find tail starting at `head` + inline Position FindTail(index_type head, size_t offset) const; + + // Invokes f(index_type index) for each entry inside the range [head, tail> + template + void ForEach(index_type head, index_type tail, F&& f) const { + index_type n1 = (tail > head) ? tail : capacity_; + for (index_type i = head; i < n1; ++i) f(i); + if (tail <= head) { + for (index_type i = 0; i < tail; ++i) f(i); + } + } + + // Invokes f(index_type index) for each entry inside this instance. + template + void ForEach(F&& f) const { + ForEach(head_, tail_, std::forward(f)); + } + + // Dump this instance's data tp stream `s` in human readable format, excluding + // the actual data content itself. Intended for debug purposes only. + friend std::ostream& operator<<(std::ostream& s, const CordRepRing& rep); + + private: + enum class AddMode { kAppend, kPrepend }; + + using Layout = container_internal::Layout; + + class Filler; + class Transaction; + class CreateTransaction; + + static constexpr size_t kLayoutAlignment = Layout::Partial().Alignment(); + + // Creates a new CordRepRing. + explicit CordRepRing(index_type capacity) : capacity_(capacity) {} + + // Returns true if `index` is a valid index into this instance. + bool IsValidIndex(index_type index) const; + + // Debug use only: validates the provided CordRepRing invariants. + // Verification of all CordRepRing methods can be enabled by defining + // EXTRA_CORD_RING_VALIDATION, i.e.: `--copts=-DEXTRA_CORD_RING_VALIDATION` + // Verification is VERY expensive, so only do it for debugging purposes. + static CordRepRing* Validate(CordRepRing* rep, const char* file = nullptr, + int line = 0); + + // Allocates a CordRepRing large enough to hold `capacity + extra' entries. + // The returned capacity may be larger if the allocated memory allows for it. + // The maximum capacity of a CordRepRing is capped at kMaxCapacity. + // Throws `std::length_error` if `capacity + extra' exceeds kMaxCapacity. + static CordRepRing* New(size_t capacity, size_t extra); + + // Deallocates (but does not destroy) the provided ring buffer. + static void Delete(CordRepRing* rep); + + // Destroys the provided ring buffer, decrementing the reference count of all + // contained child CordReps. The provided 1\`rep` should have a ref count of + // one (pre decrement destroy call observing `refcount.IsOne()`) or zero (post + // decrement destroy call observing `!refcount.Decrement()`). + static void Destroy(CordRepRing* rep); + + // Returns a mutable reference to the logical end position array. + pos_type* entry_end_pos() { + return Layout::Partial().Pointer<0>(data_); + } + + // Returns a mutable reference to the child pointer array. + CordRep** entry_child() { + return Layout::Partial(capacity()).Pointer<1>(data_); + } + + // Returns a mutable reference to the data offset array. + offset_type* entry_data_offset() { + return Layout::Partial(capacity(), capacity()).Pointer<2>(data_); + } + + // Find implementations for the non fast path 0 / length cases. + Position FindSlow(index_type head, size_t offset) const; + Position FindTailSlow(index_type head, size_t offset) const; + + // Finds the index of the first node that is inside a reasonable distance + // of the node at `offset` from which we can continue with a linear search. + template + index_type FindBinary(index_type head, index_type tail, size_t offset) const; + + // Fills the current (initialized) instance from the provided source, copying + // entries [head, tail). Adds a reference to copied entries if `ref` is true. + template + void Fill(const CordRepRing* src, index_type head, index_type tail); + + // Create a copy of 'rep', copying all entries [head, tail), allocating room + // for `extra` entries. Adds a reference on all copied entries. + static CordRepRing* Copy(CordRepRing* rep, index_type head, index_type tail, + size_t extra = 0); + + // Returns a Mutable CordRepRing reference from `rep` with room for at least + // `extra` additional nodes. Adopts a reference count from `rep`. + // This function will return `rep` if, and only if: + // - rep.entries + extra <= rep.capacity + // - rep.refcount == 1 + // Otherwise, this function will create a new copy of `rep` with additional + // capacity to satisfy `extra` extra nodes, and unref the old `rep` instance. + // + // If a new CordRepRing can not be allocated, or the new capacity would exceed + // the maxmimum capacity, then the input is consumed only, and an exception is + // thrown. + static CordRepRing* Mutable(CordRepRing* rep, size_t extra); + + // Slow path for Append(CordRepRing* rep, CordRep* child). This function is + // exercised if the provided `child` in Append() is not a leaf node, i.e., a + // ring buffer or old (concat) cord tree. + static CordRepRing* AppendSlow(CordRepRing* rep, CordRep* child); + + // Appends the provided leaf node. Requires `child` to be FLAT or EXTERNAL. + static CordRepRing* AppendLeaf(CordRepRing* rep, CordRep* child, + size_t offset, size_t length); + + // Prepends the provided leaf node. Requires `child` to be FLAT or EXTERNAL. + static CordRepRing* PrependLeaf(CordRepRing* rep, CordRep* child, + size_t offset, size_t length); + + // Slow path for Prepend(CordRepRing* rep, CordRep* child). This function is + // exercised if the provided `child` in Prepend() is not a leaf node, i.e., a + // ring buffer or old (concat) cord tree. + static CordRepRing* PrependSlow(CordRepRing* rep, CordRep* child); + + // Slow path for Create(CordRep* child, size_t extra). This function is + // exercised if the provided `child` in Prepend() is not a leaf node, i.e., a + // ring buffer or old (concat) cord tree. + static CordRepRing* CreateSlow(CordRep* child, size_t extra); + + // Creates a new ring buffer from the provided `child` leaf node. Requires + // `child` to be FLAT or EXTERNAL. on `rep`. + // The returned ring buffer has a capacity of at least `1 + extra` + static CordRepRing* CreateFromLeaf(CordRep* child, size_t offset, + size_t length, size_t extra); + + // Appends or prepends (depending on AddMode) the ring buffer in `ring' to + // `rep` starting at `offset` with length `length`. + template + static CordRepRing* AddRing(CordRepRing* rep, CordRepRing* ring, + size_t offset, size_t length); + + // Increases the data offset for entry `index` by `n`. + void AddDataOffset(index_type index, size_t n); + + // Descreases the length for entry `index` by `n`. + void SubLength(index_type index, size_t n); + + index_type head_; + index_type tail_; + index_type capacity_; + pos_type begin_pos_; + + alignas(kLayoutAlignment) char data_[kLayoutAlignment]; + + friend struct CordRep; +}; + +constexpr size_t CordRepRing::AllocSize(size_t capacity) { + return sizeof(CordRepRing) - sizeof(data_) + + Layout(capacity, capacity, capacity).AllocSize(); +} + +inline constexpr size_t CordRepRing::Distance(pos_type pos, pos_type end_pos) { + return (end_pos - pos); +} + +inline const char* CordRepRing::GetLeafData(const CordRep* rep) { + return rep->tag != EXTERNAL ? rep->flat()->Data() : rep->external()->base; +} + +inline const char* CordRepRing::GetRepData(const CordRep* rep) { + if (rep->tag >= FLAT) return rep->flat()->Data(); + if (rep->tag == EXTERNAL) return rep->external()->base; + return GetLeafData(rep->substring()->child) + rep->substring()->start; +} + +inline CordRepRing::index_type CordRepRing::advance(index_type index) const { + assert(index < capacity_); + return ++index == capacity_ ? 0 : index; +} + +inline CordRepRing::index_type CordRepRing::advance(index_type index, + index_type n) const { + assert(index < capacity_ && n <= capacity_); + return (index += n) >= capacity_ ? index - capacity_ : index; +} + +inline CordRepRing::index_type CordRepRing::retreat(index_type index) const { + assert(index < capacity_); + return (index > 0 ? index : capacity_) - 1; +} + +inline CordRepRing::index_type CordRepRing::retreat(index_type index, + index_type n) const { + assert(index < capacity_ && n <= capacity_); + return index >= n ? index - n : capacity_ - n + index; +} + +inline absl::string_view CordRepRing::entry_data(index_type index) const { + size_t data_offset = entry_data_offset(index); + return {GetRepData(entry_child(index)) + data_offset, entry_length(index)}; +} + +inline bool CordRepRing::IsValidIndex(index_type index) const { + if (index >= capacity_) return false; + return (tail_ > head_) ? (index >= head_ && index < tail_) + : (index >= head_ || index < tail_); +} + +#ifndef EXTRA_CORD_RING_VALIDATION +inline CordRepRing* CordRepRing::Validate(CordRepRing* rep, + const char* /*file*/, int /*line*/) { + return rep; +} +#endif + +inline CordRepRing::Position CordRepRing::Find(size_t offset) const { + assert(offset < length); + return (offset == 0) ? Position{head_, 0} : FindSlow(head_, offset); +} + +inline CordRepRing::Position CordRepRing::Find(index_type head, + size_t offset) const { + assert(offset < length); + assert(IsValidIndex(head) && offset >= entry_start_offset(head)); + return (offset == 0) ? Position{head_, 0} : FindSlow(head, offset); +} + +inline CordRepRing::Position CordRepRing::FindTail(size_t offset) const { + assert(offset > 0 && offset <= length); + return (offset == length) ? Position{tail_, 0} : FindTailSlow(head_, offset); +} + +inline CordRepRing::Position CordRepRing::FindTail(index_type head, + size_t offset) const { + assert(offset > 0 && offset <= length); + assert(IsValidIndex(head) && offset >= entry_start_offset(head) + 1); + return (offset == length) ? Position{tail_, 0} : FindTailSlow(head, offset); +} + +std::ostream& operator<<(std::ostream& s, const CordRepRing& rep); + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +} // namespace cord_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_CORD_REP_RING_H_ diff --git a/absl/synchronization/mutex.h b/absl/synchronization/mutex.h index 598d1e06..4dd51fec 100644 --- a/absl/synchronization/mutex.h +++ b/absl/synchronization/mutex.h @@ -162,7 +162,7 @@ class ABSL_LOCKABLE Mutex { // Mutex::Unlock() // // Releases this `Mutex` and returns it from the exclusive/write state to the - // free state. Caller must hold the `Mutex` exclusively. + // free state. Calling thread must hold the `Mutex` exclusively. void Unlock() ABSL_UNLOCK_FUNCTION(); // Mutex::TryLock() diff --git a/absl/types/CMakeLists.txt b/absl/types/CMakeLists.txt index 3f99ad8a..c356b211 100644 --- a/absl/types/CMakeLists.txt +++ b/absl/types/CMakeLists.txt @@ -353,9 +353,6 @@ absl_cc_test( gmock_main ) -# TODO(cohenjon,zhangxy) Figure out why this test is failing on gcc 4.8 -if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9) -else() absl_cc_test( NAME variant_exception_safety_test @@ -370,4 +367,3 @@ absl_cc_test( absl::memory gmock_main ) -endif() -- cgit v1.2.3 From 3a2d6572d06709da32a17f053ca1e3c8e2af90df Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Thu, 21 Jan 2021 12:04:50 -0800 Subject: Export of internal Abseil changes -- 3b43586da865534cf86401d2cae09c65c60b8474 by Abseil Team : Introduce CordRepRingReader class PiperOrigin-RevId: 353070937 -- 0bff6e4bcca34fdd1e6610da5fb3c37fd49b2940 by Abseil Team : Fix docstring typo "Exmaple" -> "Example" PiperOrigin-RevId: 352927688 -- 1ef4e0a1100cfa7bc9d9e8f155acf0e469348b56 by Abseil Team : Refactor tree initialization of ChunkIterator and CordReader PiperOrigin-RevId: 352916786 -- 919c3eb175b87294184a405785eef4fab520d47e by Abseil Team : Disable `preserve_most` when compiling with sanitizers. PiperOrigin-RevId: 352890630 GitOrigin-RevId: 3b43586da865534cf86401d2cae09c65c60b8474 Change-Id: I8a733494b353af69a46862a4019a7f9b40148f49 --- CMake/AbseilDll.cmake | 1 + absl/base/log_severity.h | 2 +- absl/strings/BUILD.bazel | 16 +++ absl/strings/CMakeLists.txt | 16 +++ absl/strings/cord.h | 20 +++- absl/strings/cord_ring_reader_test.cc | 173 +++++++++++++++++++++++++++ absl/strings/internal/cord_internal.h | 5 + absl/strings/internal/cord_rep_ring_reader.h | 114 ++++++++++++++++++ 8 files changed, 344 insertions(+), 3 deletions(-) create mode 100644 absl/strings/cord_ring_reader_test.cc create mode 100644 absl/strings/internal/cord_rep_ring_reader.h (limited to 'absl/strings/cord.h') diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index ac2f325c..b8c38d68 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake @@ -198,6 +198,7 @@ set(ABSL_INTERNAL_DLL_FILES "strings/internal/cord_rep_flat.h" "strings/internal/cord_rep_ring.cc" "strings/internal/cord_rep_ring.h" + "strings/internal/cord_rep_ring_reader.h" "strings/internal/charconv_bigint.cc" "strings/internal/charconv_bigint.h" "strings/internal/charconv_parse.cc" diff --git a/absl/base/log_severity.h b/absl/base/log_severity.h index 045f17f8..22364224 100644 --- a/absl/base/log_severity.h +++ b/absl/base/log_severity.h @@ -36,7 +36,7 @@ ABSL_NAMESPACE_BEGIN // such values to a defined severity level, however in some cases values other // than the defined levels are useful for comparison. // -// Exmaple: +// Example: // // // Effectively disables all logging: // SetMinLogLevel(static_cast(100)); diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index d7c322f6..794cf43a 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -275,6 +275,7 @@ cc_library( "internal/cord_internal.h", "internal/cord_rep_flat.h", "internal/cord_rep_ring.h", + "internal/cord_rep_ring_reader.h", ], copts = ABSL_DEFAULT_COPTS, visibility = [ @@ -370,6 +371,21 @@ cc_test( ], ) +cc_test( + name = "cord_ring_reader_test", + size = "medium", + srcs = ["cord_ring_reader_test.cc"], + copts = ABSL_TEST_COPTS, + visibility = ["//visibility:private"], + deps = [ + ":cord_internal", + ":strings", + "//absl/base:core_headers", + "//absl/debugging:leak_check", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "substitute_test", size = "small", diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index a6204bf6..12f322a9 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt @@ -560,6 +560,7 @@ absl_cc_library( "internal/cord_internal.h" "internal/cord_rep_ring.h" "internal/cord_rep_ring.cc" + "internal/cord_rep_ring_reader.h" "internal/cord_rep_flat.h" COPTS ${ABSL_DEFAULT_COPTS} @@ -630,3 +631,18 @@ absl_cc_test( absl::raw_logging_internal gmock_main ) + +absl_cc_test( + NAME + cord_ring_reader_test + SRCS + "cord_ring_reader_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::cord + absl::strings + absl::base + absl::core_headers + gmock_main +) diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 9d8764a0..abef98fe 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -367,9 +367,15 @@ class Cord { // the inlined vector size (47 exists for backward compatibility). using Stack = absl::InlinedVector; + // Constructs a `begin()` iterator from `tree`. `tree` must not be null. + explicit ChunkIterator(cord_internal::CordRep* tree); + // Constructs a `begin()` iterator from `cord`. explicit ChunkIterator(const Cord* cord); + // Initializes this instance from a tree. Invoked by constructors. + void InitTree(cord_internal::CordRep* tree); + // Removes `n` bytes from `current_chunk_`. Expects `n` to be smaller than // `current_chunk_.size()`. void RemoveChunkPrefix(size_t n); @@ -1100,11 +1106,20 @@ inline bool Cord::StartsWith(absl::string_view rhs) const { return EqualsImpl(rhs, rhs_size); } +inline void Cord::ChunkIterator::InitTree(cord_internal::CordRep* tree) { + stack_of_right_children_.push_back(tree); + operator++(); +} + +inline Cord::ChunkIterator::ChunkIterator(cord_internal::CordRep* tree) + : bytes_remaining_(tree->length) { + InitTree(tree); +} + inline Cord::ChunkIterator::ChunkIterator(const Cord* cord) : bytes_remaining_(cord->size()) { if (cord->contents_.is_tree()) { - stack_of_right_children_.push_back(cord->contents_.as_tree()); - operator++(); + InitTree(cord->contents_.as_tree()); } else { current_chunk_ = absl::string_view(cord->contents_.data(), bytes_remaining_); @@ -1155,6 +1170,7 @@ inline void Cord::ChunkIterator::RemoveChunkPrefix(size_t n) { } inline void Cord::ChunkIterator::AdvanceBytes(size_t n) { + assert(bytes_remaining_ >= n); if (ABSL_PREDICT_TRUE(n < current_chunk_.size())) { RemoveChunkPrefix(n); } else if (n != 0) { diff --git a/absl/strings/cord_ring_reader_test.cc b/absl/strings/cord_ring_reader_test.cc new file mode 100644 index 00000000..585616f3 --- /dev/null +++ b/absl/strings/cord_ring_reader_test.cc @@ -0,0 +1,173 @@ +// Copyright 2020 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/debugging/leak_check.h" +#include "absl/strings/internal/cord_internal.h" +#include "absl/strings/internal/cord_rep_ring.h" +#include "absl/strings/internal/cord_rep_ring_reader.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace cord_internal { +namespace { + +using testing::Eq; + +// Creates a flat for testing +CordRep* MakeFlat(absl::string_view s) { + CordRepFlat* flat = CordRepFlat::New(s.length()); + memcpy(flat->Data(), s.data(), s.length()); + flat->length = s.length(); + return flat; +} + +CordRepRing* FromFlats(Span flats) { + CordRepRing* ring = CordRepRing::Create(MakeFlat(flats[0]), flats.size() - 1); + for (int i = 1; i < flats.size(); ++i) { + ring = CordRepRing::Append(ring, MakeFlat(flats[i])); + } + return ring; +} + +std::array TestFlats() { + return {"abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ", + "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_", + "+-=", "[]\\{}|;':", ",/<>?", "."}; +} + +TEST(CordRingReaderTest, DefaultInstance) { + CordRepRingReader reader; + EXPECT_FALSE(static_cast(reader)); + EXPECT_THAT(reader.ring(), Eq(nullptr)); +#ifndef NDEBUG + EXPECT_DEATH_IF_SUPPORTED(reader.length(), ".*"); + EXPECT_DEATH_IF_SUPPORTED(reader.consumed(), ".*"); + EXPECT_DEATH_IF_SUPPORTED(reader.remaining(), ".*"); + EXPECT_DEATH_IF_SUPPORTED(reader.Next(), ".*"); + EXPECT_DEATH_IF_SUPPORTED(reader.Seek(0), ".*"); +#endif +} + +TEST(CordRingReaderTest, Reset) { + CordRepRingReader reader; + auto flats = TestFlats(); + CordRepRing* ring = FromFlats(flats); + + absl::string_view first = reader.Reset(ring); + EXPECT_THAT(first, Eq(flats[0])); + EXPECT_TRUE(static_cast(reader)); + EXPECT_THAT(reader.ring(), Eq(ring)); + EXPECT_THAT(reader.index(), Eq(ring->head())); + EXPECT_THAT(reader.length(), Eq(ring->length)); + EXPECT_THAT(reader.consumed(), Eq(flats[0].length())); + EXPECT_THAT(reader.remaining(), Eq(ring->length - reader.consumed())); + + reader.Reset(); + EXPECT_FALSE(static_cast(reader)); + EXPECT_THAT(reader.ring(), Eq(nullptr)); + + CordRep::Unref(ring); +} + +TEST(CordRingReaderTest, Next) { + CordRepRingReader reader; + auto flats = TestFlats(); + CordRepRing* ring = FromFlats(flats); + CordRepRing::index_type head = ring->head(); + + reader.Reset(ring); + size_t consumed = reader.consumed(); + size_t remaining = reader.remaining(); + for (int i = 1; i < flats.size(); ++i) { + consumed += flats[i].length(); + remaining -= flats[i].length(); + absl::string_view next = reader.Next(); + ASSERT_THAT(next, Eq(flats[i])); + ASSERT_THAT(reader.index(), Eq(ring->advance(head, i))); + ASSERT_THAT(reader.consumed(), Eq(consumed)); + ASSERT_THAT(reader.remaining(), Eq(remaining)); + } + +#ifndef NDEBUG + EXPECT_DEATH_IF_SUPPORTED(reader.Next(), ".*"); +#endif + + CordRep::Unref(ring); +} + +TEST(CordRingReaderTest, SeekForward) { + CordRepRingReader reader; + auto flats = TestFlats(); + CordRepRing* ring = FromFlats(flats); + CordRepRing::index_type head = ring->head(); + + reader.Reset(ring); + size_t consumed = 0; + size_t remaining = ring->length;; + for (int i = 0; i < flats.size(); ++i) { + size_t offset = consumed; + consumed += flats[i].length(); + remaining -= flats[i].length(); + for (int off = 0; off < flats[i].length(); ++off) { + absl::string_view chunk = reader.Seek(offset + off); + ASSERT_THAT(chunk, Eq(flats[i].substr(off))); + ASSERT_THAT(reader.index(), Eq(ring->advance(head, i))); + ASSERT_THAT(reader.consumed(), Eq(consumed)); + ASSERT_THAT(reader.remaining(), Eq(remaining)); + } + } + + CordRep::Unref(ring); +} + +TEST(CordRingReaderTest, SeekBackward) { + CordRepRingReader reader; + auto flats = TestFlats(); + CordRepRing* ring = FromFlats(flats); + CordRepRing::index_type head = ring->head(); + + reader.Reset(ring); + size_t consumed = ring->length; + size_t remaining = 0; + for (int i = flats.size() - 1; i >= 0; --i) { + size_t offset = consumed - flats[i].length(); + for (int off = 0; off < flats[i].length(); ++off) { + absl::string_view chunk = reader.Seek(offset + off); + ASSERT_THAT(chunk, Eq(flats[i].substr(off))); + ASSERT_THAT(reader.index(), Eq(ring->advance(head, i))); + ASSERT_THAT(reader.consumed(), Eq(consumed)); + ASSERT_THAT(reader.remaining(), Eq(remaining)); + } + consumed -= flats[i].length(); + remaining += flats[i].length(); + } +#ifndef NDEBUG + EXPECT_DEATH_IF_SUPPORTED(reader.Seek(ring->length), ".*"); +#endif + CordRep::Unref(ring); +} + +} // namespace +} // namespace cord_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/strings/internal/cord_internal.h b/absl/strings/internal/cord_internal.h index 011b49d3..7a1ef6b1 100644 --- a/absl/strings/internal/cord_internal.h +++ b/absl/strings/internal/cord_internal.h @@ -203,10 +203,15 @@ struct CordRep { // platforms; we intentionally allow LLVM to ignore the attribute rather than // attempting to hardcode the list of supported platforms. #if defined(__clang__) && !defined(__i386__) +#if !(defined(ABSL_HAVE_MEMORY_SANITIZER) || \ + defined(ABSL_HAVE_THREAD_SANITIZER) || \ + defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ + defined(UNDEFINED_BEHAVIOR_SANITIZER)) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wattributes" __attribute__((preserve_most)) #pragma clang diagnostic pop +#endif // *_SANITIZER #endif static void Destroy(CordRep* rep); diff --git a/absl/strings/internal/cord_rep_ring_reader.h b/absl/strings/internal/cord_rep_ring_reader.h new file mode 100644 index 00000000..396c0e2c --- /dev/null +++ b/absl/strings/internal/cord_rep_ring_reader.h @@ -0,0 +1,114 @@ +// Copyright 2021 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_STRINGS_INTERNAL_CORD_REP_RING_READER_H_ +#define ABSL_STRINGS_INTERNAL_CORD_REP_RING_READER_H_ + +#include +#include +#include + +#include "absl/strings/internal/cord_internal.h" +#include "absl/strings/internal/cord_rep_ring.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace cord_internal { + +// CordRepRingReader provides basic navigation over CordRepRing data. +class CordRepRingReader { + public: + // Returns true if this instance is not empty. + explicit operator bool() const { return ring_ != nullptr; } + + // Returns the ring buffer reference for this instance, or nullptr if empty. + CordRepRing* ring() const { return ring_; } + + // Returns the current node index inside the ring buffer for this instance. + // The returned value is undefined if this instance is empty. + CordRepRing::index_type index() const { return index_; } + + // Returns the length of the referenced ring buffer. + // Requires the current instance to be non empty. + size_t length() const { + assert(ring_); + return ring_->length; + } + + // Returns the end offset of the last navigated-to chunk, which represents the + // total bytes 'consumed' relative to the start of the ring. The returned + // value is never zero. For example, initializing a reader with a ring buffer + // with a first chunk of 19 bytes will return consumed() = 19. + // Requires the current instance to be non empty. + size_t consumed() const { + assert(ring_); + return ring_->entry_end_offset(index_); + } + + // Returns the number of bytes remaining beyond the last navigated-to chunk. + // Requires the current instance to be non empty. + size_t remaining() const { + assert(ring_); + return length() - consumed(); + } + + // Resets this instance to an empty value + void Reset() { ring_ = nullptr; } + + // Resets this instance to the start of `ring`. `ring` must not be null. + // Returns a reference into the first chunk of the provided ring. + absl::string_view Reset(CordRepRing* ring) { + assert(ring); + ring_ = ring; + index_ = ring_->head(); + return ring_->entry_data(index_); + } + + // Navigates to the next chunk inside the reference ring buffer. + // Returns a reference into the navigated-to chunk. + // Requires remaining() to be non zero. + absl::string_view Next() { + assert(remaining()); + index_ = ring_->advance(index_); + return ring_->entry_data(index_); + } + + // Navigates to the chunk at offset `offset`. + // Returns a reference into the navigated-to chunk, adjusted for the relative + // position of `offset` into that chunk. For example, calling Seek(13) on a + // ring buffer containing 2 chunks of 10 and 20 bytes respectively will return + // a string view into the second chunk starting at offset 3 with a size of 17. + // Requires `offset` to be less than `length()` + absl::string_view Seek(size_t offset) { + assert(offset < length()); + size_t current = ring_->entry_end_offset(index_); + CordRepRing::index_type hint = (offset >= current) ? index_ : ring_->head(); + const CordRepRing::Position head = ring_->Find(hint, offset); + index_ = head.index; + auto data = ring_->entry_data(head.index); + data.remove_prefix(head.offset); + return data; + } + + private: + CordRepRing* ring_ = nullptr; + CordRepRing::index_type index_; +}; + +} // namespace cord_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_CORD_REP_RING_READER_H_ -- cgit v1.2.3 From a9a49560208484f1f99efdde889da6147e8722fe Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Fri, 29 Jan 2021 12:09:30 -0800 Subject: Export of internal Abseil changes -- c68f1886f5e8fd90eb0c2d2e68feaf00a7cdacda by CJ Johnson : Introduce absl::Cleanup to the OSS repo PiperOrigin-RevId: 354583156 -- 17030cf388e10f7eb959e3e566326d1072ce392e by Abseil Team : Internal change only PiperOrigin-RevId: 354574953 -- e979d7236d4f3252e79ddda6739b67a9a326bf6d by CJ Johnson : Internal change PiperOrigin-RevId: 354545297 -- 7ea02b3783f7f49ef97d86a8f6580a19cc57df14 by Abseil Team : Pre-allocate memory for vectors where the size is known. PiperOrigin-RevId: 354344576 -- 9246c7cb11f1d6444f79ebe25acc69a8a9b870e0 by Matt Kulukundis : Add support for Elbrus 2000 (e2k) Import of https://github.com/abseil/abseil-cpp/pull/889 PiperOrigin-RevId: 354344013 -- 0fc93d359cc1fb307552e917b37b7b2e7eed822f by Abseil Team : Integrate CordRepRing logic into cord (but do not enable it) PiperOrigin-RevId: 354312238 -- eda05622f7da71466723acb33403f783529df24b by Abseil Team : Protect ignore diagnostic with "__has_warning". PiperOrigin-RevId: 354112334 -- 47716c5d8fb10efa4fdd801d28bac414c6f8ec32 by Abseil Team : Rearrange InlinedVector copy constructor and destructor to treat a few special cases inline and then tail-call a non-inlined routine for the rest. In particular, we optimize for empty vectors in both cases. Added a couple of benchmarks that copy either an InlVec or an InlVec>. Speed difference: ``` BM_CopyTrivial/0 0.92ns +- 0% 0.47ns +- 0% -48.91% (p=0.000 n=11+12) BM_CopyTrivial/1 0.92ns +- 0% 1.15ns +- 0% +25.00% (p=0.000 n=10+9) BM_CopyTrivial/8 8.57ns +- 0% 10.72ns +- 1% +25.16% (p=0.000 n=10+12) BM_CopyNonTrivial/0 3.21ns +- 0% 0.70ns +- 0% -78.23% (p=0.000 n=12+10) BM_CopyNonTrivial/1 5.88ns +- 1% 5.51ns +- 0% -6.28% (p=0.000 n=10+8) BM_CopyNonTrivial/8 21.5ns +- 1% 15.2ns +- 2% -29.23% (p=0.000 n=12+12) ``` Note: the slowdowns are a few cycles which is expected given the procedure call added in that case. We decided this is a good tradeoff given the code size reductions and the more significant speedups for empty vectors. Size difference (as measured by nm): ``` BM_CopyTrivial from 1048 bytes to 326 bytes. BM_CopyNonTrivial from 749 bytes to 470 bytes. ``` Code size for a large binary drops by ~500KB (from 349415719 to 348906015 348906191). All of the benchmarks that showed a significant difference: Ones that improve with this CL: ``` BM_CopyNonTrivial/0 3.21ns +- 0% 0.70ns +- 0% -78.23% (p=0.000 n=12+10) BM_InlinedVectorFillString/0 0.93ns +- 0% 0.24ns +- 0% -74.19% (p=0.000 n=12+10) BM_InlinedVectorAssignments/1 10.5ns +- 0% 4.1ns +- 0% -60.64% (p=0.000 n=11+10) BM_InlinedVectorAssignments/2 10.7ns +- 0% 4.4ns +- 0% -59.08% (p=0.000 n=11+11) BM_CopyTrivial/0 0.92ns +- 0% 0.47ns +- 0% -48.91% (p=0.000 n=11+12) BM_CopyNonTrivial/8 21.5ns +- 1% 15.2ns +- 2% -29.23% (p=0.000 n=12+12) BM_StdVectorEmpty 0.47ns +- 1% 0.35ns +- 0% -24.73% (p=0.000 n=12+12) BM_StdVectorSize 0.46ns +- 2% 0.35ns +- 0% -24.32% (p=0.000 n=12+12) BM_SwapElements/0 3.44ns +- 0% 2.76ns +- 1% -19.83% (p=0.000 n=11+11) BM_InlinedVectorFillRange/256 20.7ns +- 1% 17.8ns +- 0% -14.08% (p=0.000 n=12+9) BM_CopyNonTrivial/1 5.88ns +- 1% 5.51ns +- 0% -6.28% (p=0.000 n=10+8) BM_SwapElements/1 4.19ns +- 0% 3.95ns +- 1% -5.63% (p=0.000 n=11+12) BM_SwapElements/1 4.18ns +- 0% 3.99ns +- 0% -4.70% (p=0.000 n=9+11) BM_SwapElements/0 2.41ns +- 0% 2.31ns +- 0% -4.45% (p=0.000 n=12+12) BM_InlinedVectorFillRange/64 8.25ns +- 0% 8.04ns +- 0% -2.51% (p=0.000 n=12+11) BM_SwapElements/1 82.4ns +- 0% 81.5ns +- 0% -1.06% (p=0.000 n=12+12) ``` Ones that get worse with this CL: ``` BM_CopyTrivial/1 0.92ns +- 0% 1.15ns +- 0% +25.00% (p=0.000 n=10+9) BM_CopyTrivial/8 8.57ns +- 0% 10.72ns +- 1% +25.16% (p=0.000 n=10+12) BM_SwapElements/512 1.48ns +- 1% 1.66ns +- 1% +11.88% (p=0.000 n=12+12) BM_InlinedVectorFillString/1 11.5ns +- 0% 12.8ns +- 1% +11.62% (p=0.000 n=12+11) BM_SwapElements/64 1.48ns +- 2% 1.66ns +- 1% +11.66% (p=0.000 n=12+11) BM_SwapElements/1k 1.48ns +- 1% 1.65ns +- 2% +11.32% (p=0.000 n=12+12) BM_SwapElements/512 1.48ns +- 2% 1.58ns +- 4% +6.62% (p=0.000 n=11+12) BM_SwapElements/1k 1.49ns +- 2% 1.58ns +- 3% +6.05% (p=0.000 n=12+12) BM_SwapElements/64 1.48ns +- 2% 1.57ns +- 4% +6.04% (p=0.000 n=11+12) BM_InlinedVectorFillRange/1 4.81ns +- 0% 5.05ns +- 0% +4.83% (p=0.000 n=11+11) BM_InlinedVectorFillString/8 79.4ns +- 1% 83.1ns +- 1% +4.64% (p=0.000 n=10+12) BM_StdVectorFillString/1 16.3ns +- 0% 16.6ns +- 0% +2.13% (p=0.000 n=11+8) ``` PiperOrigin-RevId: 353906786 -- 8e26518b3cec9c598e5e9573c46c3bd1b03a67ef by Abseil Team : Internal change PiperOrigin-RevId: 353737330 -- f206ae0983e58c9904ed8b8f05f9caf564a446be by Matt Kulukundis : Import of CCTZ from GitHub. PiperOrigin-RevId: 353682256 GitOrigin-RevId: c68f1886f5e8fd90eb0c2d2e68feaf00a7cdacda Change-Id: I5790c1036c4f543c701d1039848fabf7ae881ad8 --- CMake/AbseilDll.cmake | 2 + README.md | 3 + absl/base/config.h | 9 + absl/base/spinlock_test_common.cc | 1 + absl/cleanup/BUILD.bazel | 66 ++++++ absl/cleanup/CMakeLists.txt | 55 +++++ absl/cleanup/cleanup.h | 129 +++++++++++ absl/cleanup/cleanup_test.cc | 240 +++++++++++++++++++++ absl/cleanup/internal/cleanup.h | 77 +++++++ absl/container/inlined_vector.h | 8 +- absl/container/inlined_vector_benchmark.cc | 22 ++ absl/container/internal/inlined_vector.h | 66 +++++- absl/status/status.cc | 16 +- absl/status/status.h | 9 +- absl/strings/cord.cc | 141 +++++++++++- absl/strings/cord.h | 43 +++- absl/strings/cord_test.cc | 6 +- absl/strings/internal/cord_rep_flat.h | 7 +- absl/strings/internal/cord_rep_ring.cc | 2 + absl/strings/internal/cord_rep_ring.h | 4 +- absl/time/internal/cctz/testdata/version | 2 +- .../internal/cctz/testdata/zoneinfo/Africa/Juba | Bin 449 -> 458 bytes 22 files changed, 876 insertions(+), 32 deletions(-) create mode 100644 absl/cleanup/BUILD.bazel create mode 100644 absl/cleanup/CMakeLists.txt create mode 100644 absl/cleanup/cleanup.h create mode 100644 absl/cleanup/cleanup_test.cc create mode 100644 absl/cleanup/internal/cleanup.h (limited to 'absl/strings/cord.h') diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index b8c38d68..47707dfd 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake @@ -60,6 +60,8 @@ set(ABSL_INTERNAL_DLL_FILES "base/policy_checks.h" "base/port.h" "base/thread_annotations.h" + "cleanup/cleanup.h" + "cleanup/internal/cleanup.h" "container/btree_map.h" "container/btree_set.h" "container/fixed_array.h" diff --git a/README.md b/README.md index 76f1d1e1..264c4b3f 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,9 @@ Abseil contains the following C++ library components: * [`algorithm`](absl/algorithm/)
The `algorithm` library contains additions to the C++ `` library and container-based versions of such algorithms. +* [`cleanup`](absl/cleanup/) +
The `cleanup` library contains the control-flow-construct-like type + `absl::Cleanup` which is used for executing a callback on scope exit. * [`container`](absl/container/)
The `container` library contains additional STL-style containers, including Abseil's unordered "Swiss table" containers. diff --git a/absl/base/config.h b/absl/base/config.h index f6ddf0c5..3d6aa178 100644 --- a/absl/base/config.h +++ b/absl/base/config.h @@ -722,4 +722,13 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || #define ABSL_HAVE_ADDRESS_SANITIZER 1 #endif +// ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION +// +// Class template argument deduction is a language feature added in C++17. +#ifdef ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION +#error "ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION cannot be directly set." +#elif defined(__cpp_deduction_guides) +#define ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION 1 +#endif + #endif // ABSL_BASE_CONFIG_H_ diff --git a/absl/base/spinlock_test_common.cc b/absl/base/spinlock_test_common.cc index dee266e4..2b572c5b 100644 --- a/absl/base/spinlock_test_common.cc +++ b/absl/base/spinlock_test_common.cc @@ -92,6 +92,7 @@ static void TestFunction(int thread_salt, SpinLock* spinlock) { static void ThreadedTest(SpinLock* spinlock) { std::vector threads; + threads.reserve(kNumThreads); for (int i = 0; i < kNumThreads; ++i) { threads.push_back(std::thread(TestFunction, i, spinlock)); } diff --git a/absl/cleanup/BUILD.bazel b/absl/cleanup/BUILD.bazel new file mode 100644 index 00000000..5cca898f --- /dev/null +++ b/absl/cleanup/BUILD.bazel @@ -0,0 +1,66 @@ +# Copyright 2021 The Abseil Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") +load( + "//absl:copts/configure_copts.bzl", + "ABSL_DEFAULT_COPTS", + "ABSL_DEFAULT_LINKOPTS", + "ABSL_TEST_COPTS", +) + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +cc_library( + name = "cleanup_internal", + hdrs = ["internal/cleanup.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:base_internal", + "//absl/base:core_headers", + "//absl/utility", + ], +) + +cc_library( + name = "cleanup", + hdrs = [ + "cleanup.h", + ], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":cleanup_internal", + "//absl/base:config", + "//absl/base:core_headers", + ], +) + +cc_test( + name = "cleanup_test", + size = "small", + srcs = [ + "cleanup_test.cc", + ], + copts = ABSL_TEST_COPTS, + deps = [ + ":cleanup", + "//absl/base:config", + "//absl/utility", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/absl/cleanup/CMakeLists.txt b/absl/cleanup/CMakeLists.txt new file mode 100644 index 00000000..a2dd78a8 --- /dev/null +++ b/absl/cleanup/CMakeLists.txt @@ -0,0 +1,55 @@ +# Copyright 2021 The Abseil Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +absl_cc_library( + NAME + cleanup_internal + HDRS + "internal/cleanup.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::base_internal + absl::core_headers + absl::utility + PUBLIC +) + +absl_cc_library( + NAME + cleanup + HDRS + "cleanup.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::cleanup_internal + absl::config + absl::core_headers + PUBLIC +) + +absl_cc_test( + NAME + cleanup_test + SRCS + "cleanup_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::cleanup + absl::config + absl::utility + gmock_main +) diff --git a/absl/cleanup/cleanup.h b/absl/cleanup/cleanup.h new file mode 100644 index 00000000..eba04b33 --- /dev/null +++ b/absl/cleanup/cleanup.h @@ -0,0 +1,129 @@ +// Copyright 2021 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: cleanup.h +// ----------------------------------------------------------------------------- +// +// `absl::Cleanup` implements the scope guard idiom, invoking `operator()() &&` +// on the callback it was constructed with, on scope exit. +// +// Example: +// +// ``` +// void CopyGoodData(const char* input_path, const char* output_path) { +// FILE* in_file = fopen(input_path, "r"); +// FILE* out_file = fopen(output_path, "w"); +// if (in_file == nullptr || out_file == nullptr) return; +// +// // C++17 style using class template argument deduction +// absl::Cleanup in_closer = [&in_file] { fclose(in_file); }; +// +// // C++11 style using the factory function +// auto out_closer = absl::MakeCleanup([&out_file] { fclose(out_file); }); +// +// // `fclose` will be called on all exit paths by the cleanup instances +// +// Data data; +// while (ReadData(in_file, &data)) { +// if (data.IsBad()) { +// LOG(ERROR) << "Found bad data."; +// return; // `in_closer` and `out_closer` will call their callbacks +// } +// SaveData(out_file, &data); +// } +// return; // `in_closer` and `out_closer` will call their callbacks +// } +// ``` +// +// `std::move(cleanup).Invoke()` will execute the callback early, before +// destruction, and prevent the callback from executing in the destructor. +// +// Alternatively, `std::move(cleanup).Cancel()` will prevent the callback from +// ever executing at all. +// +// Once a cleanup object has been `std::move(...)`-ed, it may not be used again. + +#ifndef ABSL_CLEANUP_CLEANUP_H_ +#define ABSL_CLEANUP_CLEANUP_H_ + +#include + +#include "absl/base/config.h" +#include "absl/base/macros.h" +#include "absl/cleanup/internal/cleanup.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +template +class ABSL_MUST_USE_RESULT Cleanup { + static_assert(cleanup_internal::WasDeduced(), + "Explicit template parameters are not supported."); + + static_assert(cleanup_internal::ReturnsVoid(), + "Callbacks that return values are not supported."); + + public: + Cleanup(Callback callback) : storage_(std::move(callback)) {} // NOLINT + + Cleanup(Cleanup&& other) : storage_(std::move(other.storage_)) {} + + void Cancel() && { + ABSL_HARDENING_ASSERT(storage_.IsCallbackEngaged()); + storage_.DisengageCallback(); + } + + void Invoke() && { + ABSL_HARDENING_ASSERT(storage_.IsCallbackEngaged()); + storage_.DisengageCallback(); + storage_.InvokeCallback(); + } + + ~Cleanup() { + if (storage_.IsCallbackEngaged()) { + storage_.InvokeCallback(); + } + } + + private: + cleanup_internal::Storage storage_; +}; + +// `auto c = absl::MakeCleanup(/* callback */);` +// +// C++11 type deduction API for creating an instance of `absl::Cleanup`. +template +absl::Cleanup MakeCleanup(Callback callback) { + static_assert(cleanup_internal::WasDeduced(), + "Explicit template parameters are not supported."); + + static_assert(cleanup_internal::ReturnsVoid(), + "Callbacks that return values are not supported."); + + return {std::move(callback)}; +} + +// `absl::Cleanup c = /* callback */;` +// +// C++17 type deduction API for creating an instance of `absl::Cleanup`. +#if defined(ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION) +template +Cleanup(Callback callback) -> Cleanup; +#endif // defined(ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION) + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CLEANUP_CLEANUP_H_ diff --git a/absl/cleanup/cleanup_test.cc b/absl/cleanup/cleanup_test.cc new file mode 100644 index 00000000..34e3bfad --- /dev/null +++ b/absl/cleanup/cleanup_test.cc @@ -0,0 +1,240 @@ +// Copyright 2021 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/cleanup/cleanup.h" + +#include +#include +#include + +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/utility/utility.h" + +namespace { + +using Tag = absl::cleanup_internal::Tag; + +template +void AssertSameType() { + static_assert(std::is_same::value, ""); +} + +struct IdentityFactory { + template + static Callback AsCallback(Callback callback) { + return Callback(std::move(callback)); + } +}; + +// `FunctorClass` is a type used for testing `absl::Cleanup`. It is intended to +// represent users that make their own move-only callback types outside of +// `std::function` and lambda literals. +class FunctorClass { + using Callback = std::function; + + public: + explicit FunctorClass(Callback callback) : callback_(std::move(callback)) {} + + FunctorClass(FunctorClass&& other) + : callback_(absl::exchange(other.callback_, Callback())) {} + + FunctorClass(const FunctorClass&) = delete; + + FunctorClass& operator=(const FunctorClass&) = delete; + + FunctorClass& operator=(FunctorClass&&) = delete; + + void operator()() const& = delete; + + void operator()() && { + ASSERT_TRUE(callback_); + callback_(); + callback_ = nullptr; + } + + private: + Callback callback_; +}; + +struct FunctorClassFactory { + template + static FunctorClass AsCallback(Callback callback) { + return FunctorClass(std::move(callback)); + } +}; + +struct StdFunctionFactory { + template + static std::function AsCallback(Callback callback) { + return std::function(std::move(callback)); + } +}; + +using CleanupTestParams = + ::testing::Types; +template +struct CleanupTest : public ::testing::Test {}; +TYPED_TEST_SUITE(CleanupTest, CleanupTestParams); + +bool function_pointer_called = false; +void FunctionPointerFunction() { function_pointer_called = true; } + +TYPED_TEST(CleanupTest, FactoryProducesCorrectType) { + { + auto callback = TypeParam::AsCallback([] {}); + auto cleanup = absl::MakeCleanup(std::move(callback)); + + AssertSameType, decltype(cleanup)>(); + } + + { + auto cleanup = absl::MakeCleanup(&FunctionPointerFunction); + + AssertSameType, decltype(cleanup)>(); + } + + { + auto cleanup = absl::MakeCleanup(FunctionPointerFunction); + + AssertSameType, decltype(cleanup)>(); + } +} + +#if defined(ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION) +TYPED_TEST(CleanupTest, CTADProducesCorrectType) { + { + auto callback = TypeParam::AsCallback([] {}); + absl::Cleanup cleanup = std::move(callback); + + AssertSameType, decltype(cleanup)>(); + } + + { + absl::Cleanup cleanup = &FunctionPointerFunction; + + AssertSameType, decltype(cleanup)>(); + } + + { + absl::Cleanup cleanup = FunctionPointerFunction; + + AssertSameType, decltype(cleanup)>(); + } +} + +TYPED_TEST(CleanupTest, FactoryAndCTADProduceSameType) { + { + auto callback = IdentityFactory::AsCallback([] {}); + auto factory_cleanup = absl::MakeCleanup(callback); + absl::Cleanup deduction_cleanup = callback; + + AssertSameType(); + } + + { + auto factory_cleanup = + absl::MakeCleanup(FunctorClassFactory::AsCallback([] {})); + absl::Cleanup deduction_cleanup = FunctorClassFactory::AsCallback([] {}); + + AssertSameType(); + } + + { + auto factory_cleanup = + absl::MakeCleanup(StdFunctionFactory::AsCallback([] {})); + absl::Cleanup deduction_cleanup = StdFunctionFactory::AsCallback([] {}); + + AssertSameType(); + } + + { + auto factory_cleanup = absl::MakeCleanup(&FunctionPointerFunction); + absl::Cleanup deduction_cleanup = &FunctionPointerFunction; + + AssertSameType(); + } + + { + auto factory_cleanup = absl::MakeCleanup(FunctionPointerFunction); + absl::Cleanup deduction_cleanup = FunctionPointerFunction; + + AssertSameType(); + } +} +#endif // defined(ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION) + +TYPED_TEST(CleanupTest, BasicUsage) { + bool called = false; + + { + EXPECT_FALSE(called); + + auto cleanup = + absl::MakeCleanup(TypeParam::AsCallback([&called] { called = true; })); + + EXPECT_FALSE(called); + } + + EXPECT_TRUE(called); +} + +TYPED_TEST(CleanupTest, BasicUsageWithFunctionPointer) { + function_pointer_called = false; + + { + EXPECT_FALSE(function_pointer_called); + + auto cleanup = + absl::MakeCleanup(TypeParam::AsCallback(&FunctionPointerFunction)); + + EXPECT_FALSE(function_pointer_called); + } + + EXPECT_TRUE(function_pointer_called); +} + +TYPED_TEST(CleanupTest, Cancel) { + bool called = false; + + { + EXPECT_FALSE(called); + + auto cleanup = + absl::MakeCleanup(TypeParam::AsCallback([&called] { called = true; })); + std::move(cleanup).Cancel(); + + EXPECT_FALSE(called); + } + + EXPECT_FALSE(called); +} + +TYPED_TEST(CleanupTest, Invoke) { + bool called = false; + + { + EXPECT_FALSE(called); + + auto cleanup = + absl::MakeCleanup(TypeParam::AsCallback([&called] { called = true; })); + std::move(cleanup).Invoke(); + + EXPECT_TRUE(called); + } + + EXPECT_TRUE(called); +} + +} // namespace diff --git a/absl/cleanup/internal/cleanup.h b/absl/cleanup/internal/cleanup.h new file mode 100644 index 00000000..a126ff30 --- /dev/null +++ b/absl/cleanup/internal/cleanup.h @@ -0,0 +1,77 @@ +// Copyright 2021 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_CLEANUP_INTERNAL_CLEANUP_H_ +#define ABSL_CLEANUP_INTERNAL_CLEANUP_H_ + +#include +#include + +#include "absl/base/internal/invoke.h" +#include "absl/base/thread_annotations.h" +#include "absl/utility/utility.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +namespace cleanup_internal { + +struct Tag {}; + +template +constexpr bool WasDeduced() { + return (std::is_same::value) && + (sizeof...(Args) == 0); +} + +template +constexpr bool ReturnsVoid() { + return (std::is_same, void>::value); +} + +template +class Storage { + public: + explicit Storage(Callback callback) + : engaged_(true), callback_(std::move(callback)) {} + + Storage(Storage&& other) + : engaged_(absl::exchange(other.engaged_, false)), + callback_(std::move(other.callback_)) {} + + Storage(const Storage& other) = delete; + + Storage& operator=(Storage&& other) = delete; + + Storage& operator=(const Storage& other) = delete; + + bool IsCallbackEngaged() const { return engaged_; } + + void DisengageCallback() { engaged_ = false; } + + void InvokeCallback() ABSL_NO_THREAD_SAFETY_ANALYSIS { + std::move(callback_)(); + } + + private: + bool engaged_; + Callback callback_; +}; + +} // namespace cleanup_internal + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CLEANUP_INTERNAL_CLEANUP_H_ diff --git a/absl/container/inlined_vector.h b/absl/container/inlined_vector.h index 90bb96e8..7c182342 100644 --- a/absl/container/inlined_vector.h +++ b/absl/container/inlined_vector.h @@ -167,11 +167,13 @@ class InlinedVector { // Creates an inlined vector by copying the contents of `other` using `alloc`. InlinedVector(const InlinedVector& other, const allocator_type& alloc) : storage_(alloc) { - if (IsMemcpyOk::value && !other.storage_.GetIsAllocated()) { + if (other.empty()) { + // Empty; nothing to do. + } else if (IsMemcpyOk::value && !other.storage_.GetIsAllocated()) { + // Memcpy-able and do not need allocation. storage_.MemcpyFrom(other.storage_); } else { - storage_.Initialize(IteratorValueAdapter(other.data()), - other.size()); + storage_.InitFrom(other.storage_); } } diff --git a/absl/container/inlined_vector_benchmark.cc b/absl/container/inlined_vector_benchmark.cc index b8dafe93..e256fad6 100644 --- a/absl/container/inlined_vector_benchmark.cc +++ b/absl/container/inlined_vector_benchmark.cc @@ -534,6 +534,28 @@ void BM_ConstructFromMove(benchmark::State& state) { ABSL_INTERNAL_BENCHMARK_ONE_SIZE(BM_ConstructFromMove, TrivialType); ABSL_INTERNAL_BENCHMARK_ONE_SIZE(BM_ConstructFromMove, NontrivialType); +// Measure cost of copy-constructor+destructor. +void BM_CopyTrivial(benchmark::State& state) { + const int n = state.range(0); + InlVec src(n); + for (auto s : state) { + InlVec copy(src); + benchmark::DoNotOptimize(copy); + } +} +BENCHMARK(BM_CopyTrivial)->Arg(0)->Arg(1)->Arg(kLargeSize); + +// Measure cost of copy-constructor+destructor. +void BM_CopyNonTrivial(benchmark::State& state) { + const int n = state.range(0); + InlVec> src(n); + for (auto s : state) { + InlVec> copy(src); + benchmark::DoNotOptimize(copy); + } +} +BENCHMARK(BM_CopyNonTrivial)->Arg(0)->Arg(1)->Arg(kLargeSize); + template void BM_AssignSizeRef(benchmark::State& state) { auto size = ToSize; diff --git a/absl/container/internal/inlined_vector.h b/absl/container/internal/inlined_vector.h index 120849cd..b8aec45b 100644 --- a/absl/container/internal/inlined_vector.h +++ b/absl/container/internal/inlined_vector.h @@ -81,6 +81,23 @@ void DestroyElements(AllocatorType* alloc_ptr, Pointer destroy_first, } } +// If kUseMemcpy is true, memcpy(dst, src, n); else do nothing. +// Useful to avoid compiler warnings when memcpy() is used for T values +// that are not trivially copyable in non-reachable code. +template +inline void MemcpyIfAllowed(void* dst, const void* src, size_t n); + +// memcpy when allowed. +template <> +inline void MemcpyIfAllowed(void* dst, const void* src, size_t n) { + memcpy(dst, src, n); +} + +// Do nothing for types that are not memcpy-able. This function is only +// called from non-reachable branches. +template <> +inline void MemcpyIfAllowed(void*, const void*, size_t) {} + template void ConstructElements(AllocatorType* alloc_ptr, Pointer construct_first, @@ -310,9 +327,14 @@ class Storage { : metadata_(alloc, /* size and is_allocated */ 0) {} ~Storage() { - pointer data = GetIsAllocated() ? GetAllocatedData() : GetInlinedData(); - inlined_vector_internal::DestroyElements(GetAllocPtr(), data, GetSize()); - DeallocateIfAllocated(); + if (GetSizeAndIsAllocated() == 0) { + // Empty and not allocated; nothing to do. + } else if (IsMemcpyOk::value) { + // No destructors need to be run; just deallocate if necessary. + DeallocateIfAllocated(); + } else { + DestroyContents(); + } } // --------------------------------------------------------------------------- @@ -370,6 +392,8 @@ class Storage { // Storage Member Mutators // --------------------------------------------------------------------------- + ABSL_ATTRIBUTE_NOINLINE void InitFrom(const Storage& other); + template void Initialize(ValueAdapter values, size_type new_size); @@ -452,6 +476,8 @@ class Storage { } private: + ABSL_ATTRIBUTE_NOINLINE void DestroyContents(); + using Metadata = container_internal::CompressedTuple; @@ -476,6 +502,40 @@ class Storage { Data data_; }; +template +void Storage::DestroyContents() { + pointer data = GetIsAllocated() ? GetAllocatedData() : GetInlinedData(); + inlined_vector_internal::DestroyElements(GetAllocPtr(), data, GetSize()); + DeallocateIfAllocated(); +} + +template +void Storage::InitFrom(const Storage& other) { + const auto n = other.GetSize(); + assert(n > 0); // Empty sources handled handled in caller. + const_pointer src; + pointer dst; + if (!other.GetIsAllocated()) { + dst = GetInlinedData(); + src = other.GetInlinedData(); + } else { + // Because this is only called from the `InlinedVector` constructors, it's + // safe to take on the allocation with size `0`. If `ConstructElements(...)` + // throws, deallocation will be automatically handled by `~Storage()`. + size_type new_capacity = ComputeCapacity(GetInlinedCapacity(), n); + dst = AllocatorTraits::allocate(*GetAllocPtr(), new_capacity); + SetAllocatedData(dst, new_capacity); + src = other.GetAllocatedData(); + } + if (IsMemcpyOk::value) { + MemcpyIfAllowed(dst, src, sizeof(dst[0]) * n); + } else { + auto values = IteratorValueAdapter(src); + inlined_vector_internal::ConstructElements(GetAllocPtr(), dst, &values, n); + } + GetSizeAndIsAllocated() = other.GetSizeAndIsAllocated(); +} + template template auto Storage::Initialize(ValueAdapter values, size_type new_size) diff --git a/absl/status/status.cc b/absl/status/status.cc index c71de846..7962bb9e 100644 --- a/absl/status/status.cc +++ b/absl/status/status.cc @@ -207,10 +207,12 @@ void Status::UnrefNonInlined(uintptr_t rep) { } } -uintptr_t Status::NewRep(absl::StatusCode code, absl::string_view msg, - std::unique_ptr payloads) { +uintptr_t Status::NewRep( + absl::StatusCode code, absl::string_view msg, + std::unique_ptr payloads) { status_internal::StatusRep* rep = new status_internal::StatusRep( - code, std::string(msg.data(), msg.size()), std::move(payloads)); + code, std::string(msg.data(), msg.size()), + std::move(payloads)); return PointerToRep(rep); } @@ -236,8 +238,9 @@ absl::StatusCode Status::code() const { void Status::PrepareToModify() { ABSL_RAW_CHECK(!ok(), "PrepareToModify shouldn't be called on OK status."); if (IsInlined(rep_)) { - rep_ = NewRep(static_cast(raw_code()), - absl::string_view(), nullptr); + rep_ = + NewRep(static_cast(raw_code()), absl::string_view(), + nullptr); return; } @@ -248,7 +251,8 @@ void Status::PrepareToModify() { if (rep->payloads) { payloads = absl::make_unique(*rep->payloads); } - rep_ = NewRep(rep->code, message(), std::move(payloads)); + rep_ = NewRep(rep->code, message(), + std::move(payloads)); UnrefNonInlined(rep_i); } } diff --git a/absl/status/status.h b/absl/status/status.h index 08d3e806..118f64fb 100644 --- a/absl/status/status.h +++ b/absl/status/status.h @@ -371,10 +371,10 @@ class ABSL_MUST_USE_RESULT Status final { Status(); // Creates a status in the canonical error space with the specified - // `absl::StatusCode` and error message. If `code == absl::StatusCode::kOk`, + // `absl::StatusCode` and error message. If `code == absl::StatusCode::kOk`, // NOLINT // `msg` is ignored and an object identical to an OK status is constructed. // - // The `msg` string must be in UTF-8. The implementation may complain (e.g., + // The `msg` string must be in UTF-8. The implementation may complain (e.g., // NOLINT // by printing a warning) if it is not. Status(absl::StatusCode code, absl::string_view msg); @@ -551,8 +551,9 @@ class ABSL_MUST_USE_RESULT Status final { status_internal::Payloads* GetPayloads(); // Takes ownership of payload. - static uintptr_t NewRep(absl::StatusCode code, absl::string_view msg, - std::unique_ptr payload); + static uintptr_t NewRep( + absl::StatusCode code, absl::string_view msg, + std::unique_ptr payload); static bool EqualsSlow(const absl::Status& a, const absl::Status& b); // MSVC 14.0 limitation requires the const. diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index df8c26d4..39191ef5 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc @@ -37,6 +37,7 @@ #include "absl/strings/escaping.h" #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_rep_flat.h" +#include "absl/strings/internal/cord_rep_ring.h" #include "absl/strings/internal/resize_uninitialized.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" @@ -50,8 +51,8 @@ using ::absl::cord_internal::CordRep; using ::absl::cord_internal::CordRepConcat; using ::absl::cord_internal::CordRepExternal; using ::absl::cord_internal::CordRepFlat; +using ::absl::cord_internal::CordRepRing; using ::absl::cord_internal::CordRepSubstring; - using ::absl::cord_internal::kMinFlatLength; using ::absl::cord_internal::kMaxFlatLength; @@ -94,6 +95,11 @@ static constexpr uint64_t min_length[] = { static const int kMinLengthSize = ABSL_ARRAYSIZE(min_length); +static inline bool cord_ring_enabled() { + return cord_internal::cord_ring_buffer_enabled.load( + std::memory_order_relaxed); +} + static inline bool IsRootBalanced(CordRep* node) { if (node->tag != CONCAT) { return true; @@ -109,7 +115,8 @@ static inline bool IsRootBalanced(CordRep* node) { } static CordRep* Rebalance(CordRep* node); -static void DumpNode(CordRep* rep, bool include_data, std::ostream* os); +static void DumpNode(CordRep* rep, bool include_data, std::ostream* os, + int indent = 0); static bool VerifyNode(CordRep* root, CordRep* start_node, bool full_validation); @@ -198,12 +205,38 @@ static CordRep* MakeBalancedTree(CordRep** reps, size_t n) { return reps[0]; } +static CordRepFlat* CreateFlat(const char* data, size_t length, + size_t alloc_hint) { + CordRepFlat* flat = CordRepFlat::New(length + alloc_hint); + flat->length = length; + memcpy(flat->Data(), data, length); + return flat; +} + +// Creates a new flat or ringbuffer out of the specified array. +// The returned node has a refcount of 1. +static CordRep* RingNewTree(const char* data, size_t length, + size_t alloc_hint) { + if (length <= kMaxFlatLength) { + return CreateFlat(data, length, alloc_hint); + } + CordRepFlat* flat = CreateFlat(data, kMaxFlatLength, 0); + data += kMaxFlatLength; + length -= kMaxFlatLength; + size_t extra = (length - 1) / kMaxFlatLength + 1; + auto* root = CordRepRing::Create(flat, extra); + return CordRepRing::Append(root, {data, length}, alloc_hint); +} + // Create a new tree out of the specified array. // The returned node has a refcount of 1. static CordRep* NewTree(const char* data, size_t length, size_t alloc_hint) { if (length == 0) return nullptr; + if (cord_ring_enabled()) { + return RingNewTree(data, length, alloc_hint); + } absl::FixedArray reps((length - 1) / kMaxFlatLength + 1); size_t n = 0; do { @@ -295,10 +328,18 @@ inline void Cord::InlineRep::remove_prefix(size_t n) { reduce_size(n); } +// Returns `rep` converted into a CordRepRing. +// Directly returns `rep` if `rep` is already a CordRepRing. +static CordRepRing* ForceRing(CordRep* rep, size_t extra) { + return (rep->tag == RING) ? rep->ring() : CordRepRing::Create(rep, extra); +} + void Cord::InlineRep::AppendTree(CordRep* tree) { if (tree == nullptr) return; if (data_.is_empty()) { set_tree(tree); + } else if (cord_ring_enabled()) { + set_tree(CordRepRing::Append(ForceRing(force_tree(0), 1), tree)); } else { set_tree(Concat(force_tree(0), tree)); } @@ -308,6 +349,8 @@ void Cord::InlineRep::PrependTree(CordRep* tree) { assert(tree != nullptr); if (data_.is_empty()) { set_tree(tree); + } else if (cord_ring_enabled()) { + set_tree(CordRepRing::Prepend(ForceRing(force_tree(0), 1), tree)); } else { set_tree(Concat(tree, force_tree(0))); } @@ -319,6 +362,15 @@ void Cord::InlineRep::PrependTree(CordRep* tree) { // written to region and the actual size increase will be written to size. static inline bool PrepareAppendRegion(CordRep* root, char** region, size_t* size, size_t max_length) { + if (root->tag == RING && root->refcount.IsOne()) { + Span span = root->ring()->GetAppendBuffer(max_length); + if (!span.empty()) { + *region = span.data(); + *size = span.size(); + return true; + } + } + // Search down the right-hand path for a non-full FLAT node. CordRep* dst = root; while (dst->tag == CONCAT && dst->refcount.IsOne()) { @@ -383,6 +435,11 @@ void Cord::InlineRep::GetAppendRegion(char** region, size_t* size, new_node->length = std::min(new_node->Capacity(), max_length); *region = new_node->Data(); *size = new_node->length; + + if (cord_ring_enabled()) { + replace_tree(CordRepRing::Append(ForceRing(root, 1), new_node)); + return; + } replace_tree(Concat(root, new_node)); } @@ -411,6 +468,11 @@ void Cord::InlineRep::GetAppendRegion(char** region, size_t* size) { new_node->length = new_node->Capacity(); *region = new_node->Data(); *size = new_node->length; + + if (cord_ring_enabled()) { + replace_tree(CordRepRing::Append(ForceRing(root, 1), new_node)); + return; + } replace_tree(Concat(root, new_node)); } @@ -593,6 +655,13 @@ void Cord::InlineRep::AppendArray(const char* src_data, size_t src_size) { return; } + if (cord_ring_enabled()) { + absl::string_view data(src_data, src_size); + root = ForceRing(root, (data.size() - 1) / kMaxFlatLength + 1); + replace_tree(CordRepRing::Append(root->ring(), data)); + return; + } + // Use new block(s) for any remaining bytes that were not handled above. // Alloc extra memory only if the right child of the root of the new tree is // going to be a FLAT node, which will permit further inplace appends. @@ -805,6 +874,8 @@ void Cord::RemovePrefix(size_t n) { CordRep* tree = contents_.tree(); if (tree == nullptr) { contents_.remove_prefix(n); + } else if (tree->tag == RING) { + contents_.replace_tree(CordRepRing::RemovePrefix(tree->ring(), n)); } else { CordRep* newrep = RemovePrefixFrom(tree, n); CordRep::Unref(tree); @@ -819,6 +890,8 @@ void Cord::RemoveSuffix(size_t n) { CordRep* tree = contents_.tree(); if (tree == nullptr) { contents_.reduce_size(n); + } else if (tree->tag == RING) { + contents_.replace_tree(CordRepRing::RemoveSuffix(tree->ring(), n)); } else { CordRep* newrep = RemoveSuffixFrom(tree, n); CordRep::Unref(tree); @@ -902,6 +975,9 @@ Cord Cord::Subcord(size_t pos, size_t new_size) const { } cord_internal::SmallMemmove(dest, it->data(), remaining_size); sub_cord.contents_.set_inline_size(new_size); + } else if (tree->tag == RING) { + tree = CordRepRing::SubRing(CordRep::Ref(tree)->ring(), pos, new_size); + sub_cord.contents_.set_tree(tree); } else { sub_cord.contents_.set_tree(NewSubRange(tree, pos, new_size)); } @@ -1103,6 +1179,10 @@ inline absl::string_view Cord::InlineRep::FindFlatStartPiece() const { return absl::string_view(node->external()->base, node->length); } + if (node->tag == RING) { + return node->ring()->entry_data(node->ring()->head()); + } + // Walk down the left branches until we hit a non-CONCAT node. while (node->tag == CONCAT) { node = node->concat()->left; @@ -1360,6 +1440,25 @@ Cord Cord::ChunkIterator::AdvanceAndReadBytes(size_t n) { } return subcord; } + + if (ring_reader_) { + size_t chunk_size = current_chunk_.size(); + if (n <= chunk_size && n <= kMaxBytesToCopy) { + subcord = Cord(current_chunk_.substr(0, n)); + } else { + auto* ring = CordRep::Ref(ring_reader_.ring())->ring(); + size_t offset = ring_reader_.length() - bytes_remaining_; + subcord.contents_.set_tree(CordRepRing::SubRing(ring, offset, n)); + } + if (n < chunk_size) { + bytes_remaining_ -= n; + current_chunk_.remove_prefix(n); + } else { + AdvanceBytesRing(n); + } + return subcord; + } + auto& stack_of_right_children = stack_of_right_children_; if (n < current_chunk_.size()) { // Range to read is a proper subrange of the current chunk. @@ -1533,6 +1632,8 @@ char Cord::operator[](size_t i) const { if (rep->tag >= FLAT) { // Get the "i"th character directly from the flat array. return rep->flat()->Data()[offset]; + } else if (rep->tag == RING) { + return rep->ring()->GetCharacter(offset); } else if (rep->tag == EXTERNAL) { // Get the "i"th character from the external array. return rep->external()->base[offset]; @@ -1609,6 +1710,15 @@ absl::string_view Cord::FlattenSlowPath() { /* static */ void Cord::ForEachChunkAux( absl::cord_internal::CordRep* rep, absl::FunctionRef callback) { + if (rep->tag == RING) { + ChunkIterator it(rep), end; + while (it != end) { + callback(*it); + ++it; + } + return; + } + assert(rep != nullptr); int stack_pos = 0; constexpr int stack_max = 128; @@ -1650,9 +1760,9 @@ absl::string_view Cord::FlattenSlowPath() { } } -static void DumpNode(CordRep* rep, bool include_data, std::ostream* os) { +static void DumpNode(CordRep* rep, bool include_data, std::ostream* os, + int indent) { const int kIndentStep = 1; - int indent = 0; absl::InlinedVector stack; absl::InlinedVector indents; for (;;) { @@ -1673,18 +1783,28 @@ static void DumpNode(CordRep* rep, bool include_data, std::ostream* os) { *os << "SUBSTRING @ " << rep->substring()->start << "\n"; indent += kIndentStep; rep = rep->substring()->child; - } else { // Leaf + } else { // Leaf or ring if (rep->tag == EXTERNAL) { *os << "EXTERNAL ["; if (include_data) *os << absl::CEscape(std::string(rep->external()->base, rep->length)); *os << "]\n"; - } else { + } else if (rep->tag >= FLAT) { *os << "FLAT cap=" << rep->flat()->Capacity() << " ["; if (include_data) *os << absl::CEscape(std::string(rep->flat()->Data(), rep->length)); *os << "]\n"; + } else { + assert(rep->tag == RING); + auto* ring = rep->ring(); + *os << "RING, entries = " << ring->entries() << "\n"; + CordRepRing::index_type head = ring->head(); + do { + DumpNode(ring->entry_child(head), include_data, os, + indent + kIndentStep); + head = ring->advance(head);; + } while (head != ring->tail()); } if (stack.empty()) break; rep = stack.back(); @@ -1778,6 +1898,15 @@ static bool VerifyNode(CordRep* root, CordRep* start_node, } next_node = right; } + } else if (cur_node->tag == RING) { + total_mem_usage += CordRepRing::AllocSize(cur_node->ring()->capacity()); + const CordRepRing* ring = cur_node->ring(); + CordRepRing::index_type pos = ring->head(), tail = ring->tail(); + do { + CordRep* node = ring->entry_child(pos); + assert(node->tag >= FLAT || node->tag == EXTERNAL); + RepMemoryUsageLeaf(node, &total_mem_usage); + } while ((pos = ring->advance(pos)) != tail); } else { // Since cur_node is not a leaf or a concat node it must be a substring. assert(cur_node->tag == SUBSTRING); diff --git a/absl/strings/cord.h b/absl/strings/cord.h index abef98fe..17341bda 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -78,6 +78,8 @@ #include "absl/functional/function_ref.h" #include "absl/meta/type_traits.h" #include "absl/strings/internal/cord_internal.h" +#include "absl/strings/internal/cord_rep_ring.h" +#include "absl/strings/internal/cord_rep_ring_reader.h" #include "absl/strings/internal/resize_uninitialized.h" #include "absl/strings/internal/string_constant.h" #include "absl/strings/string_view.h" @@ -361,6 +363,10 @@ class Cord { friend class CharIterator; private: + using CordRep = absl::cord_internal::CordRep; + using CordRepRing = absl::cord_internal::CordRepRing; + using CordRepRingReader = absl::cord_internal::CordRepRingReader; + // Stack of right children of concat nodes that we have to visit. // Keep this at the end of the structure to avoid cache-thrashing. // TODO(jgm): Benchmark to see if there's a more optimal value than 47 for @@ -385,6 +391,10 @@ class Cord { // Stack specific operator++ ChunkIterator& AdvanceStack(); + // Ring buffer specific operator++ + ChunkIterator& AdvanceRing(); + void AdvanceBytesRing(size_t n); + // Iterates `n` bytes, where `n` is expected to be greater than or equal to // `current_chunk_.size()`. void AdvanceBytesSlowPath(size_t n); @@ -398,6 +408,10 @@ class Cord { absl::cord_internal::CordRep* current_leaf_ = nullptr; // The number of bytes left in the `Cord` over which we are iterating. size_t bytes_remaining_ = 0; + + // Cord reader for ring buffers. Empty if not traversing a ring buffer. + CordRepRingReader ring_reader_; + // See 'Stack' alias definition. Stack stack_of_right_children_; }; @@ -1107,6 +1121,11 @@ inline bool Cord::StartsWith(absl::string_view rhs) const { } inline void Cord::ChunkIterator::InitTree(cord_internal::CordRep* tree) { + if (tree->tag == cord_internal::RING) { + current_chunk_ = ring_reader_.Reset(tree->ring()); + return; + } + stack_of_right_children_.push_back(tree); operator++(); } @@ -1126,13 +1145,33 @@ inline Cord::ChunkIterator::ChunkIterator(const Cord* cord) } } +inline Cord::ChunkIterator& Cord::ChunkIterator::AdvanceRing() { + current_chunk_ = ring_reader_.Next(); + return *this; +} + +inline void Cord::ChunkIterator::AdvanceBytesRing(size_t n) { + assert(n >= current_chunk_.size()); + bytes_remaining_ -= n; + if (bytes_remaining_) { + if (n == current_chunk_.size()) { + current_chunk_ = ring_reader_.Next(); + } else { + size_t offset = ring_reader_.length() - bytes_remaining_; + current_chunk_ = ring_reader_.Seek(offset); + } + } else { + current_chunk_ = {}; + } +} + inline Cord::ChunkIterator& Cord::ChunkIterator::operator++() { ABSL_HARDENING_ASSERT(bytes_remaining_ > 0 && "Attempted to iterate past `end()`"); assert(bytes_remaining_ >= current_chunk_.size()); bytes_remaining_ -= current_chunk_.size(); if (bytes_remaining_ > 0) { - return AdvanceStack(); + return ring_reader_ ? AdvanceRing() : AdvanceStack(); } else { current_chunk_ = {}; } @@ -1174,7 +1213,7 @@ inline void Cord::ChunkIterator::AdvanceBytes(size_t n) { if (ABSL_PREDICT_TRUE(n < current_chunk_.size())) { RemoveChunkPrefix(n); } else if (n != 0) { - AdvanceBytesSlowPath(n); + ring_reader_ ? AdvanceBytesRing(n) : AdvanceBytesSlowPath(n); } } diff --git a/absl/strings/cord_test.cc b/absl/strings/cord_test.cc index 7942bfc0..bf7a6820 100644 --- a/absl/strings/cord_test.cc +++ b/absl/strings/cord_test.cc @@ -367,7 +367,7 @@ TEST(Cord, Subcord) { for (size_t end_pos : positions) { if (end_pos < pos || end_pos > a.size()) continue; absl::Cord sa = a.Subcord(pos, end_pos - pos); - EXPECT_EQ(absl::string_view(s).substr(pos, end_pos - pos), + ASSERT_EQ(absl::string_view(s).substr(pos, end_pos - pos), std::string(sa)) << a; } @@ -379,7 +379,7 @@ TEST(Cord, Subcord) { for (size_t pos = 0; pos <= sh.size(); ++pos) { for (size_t n = 0; n <= sh.size() - pos; ++n) { absl::Cord sc = c.Subcord(pos, n); - EXPECT_EQ(sh.substr(pos, n), std::string(sc)) << c; + ASSERT_EQ(sh.substr(pos, n), std::string(sc)) << c; } } @@ -389,7 +389,7 @@ TEST(Cord, Subcord) { while (sa.size() > 1) { sa = sa.Subcord(1, sa.size() - 2); ss = ss.substr(1, ss.size() - 2); - EXPECT_EQ(ss, std::string(sa)) << a; + ASSERT_EQ(ss, std::string(sa)) << a; if (HasFailure()) break; // halt cascade } diff --git a/absl/strings/internal/cord_rep_flat.h b/absl/strings/internal/cord_rep_flat.h index 55418153..a98aa9df 100644 --- a/absl/strings/internal/cord_rep_flat.h +++ b/absl/strings/internal/cord_rep_flat.h @@ -43,8 +43,9 @@ static constexpr size_t kMaxFlatSize = 4096; static constexpr size_t kMaxFlatLength = kMaxFlatSize - kFlatOverhead; static constexpr size_t kMinFlatLength = kMinFlatSize - kFlatOverhead; -constexpr size_t AllocatedSizeToTagUnchecked(size_t size) { - return (size <= 1024) ? size / 8 : 128 + size / 32 - 1024 / 32; +constexpr uint8_t AllocatedSizeToTagUnchecked(size_t size) { + return static_cast((size <= 1024) ? size / 8 + : 128 + size / 32 - 1024 / 32); } static_assert(kMinFlatSize / 8 >= FLAT, ""); @@ -65,7 +66,7 @@ inline size_t RoundUpForTag(size_t size) { // undefined if the size exceeds the maximum size that can be encoded in // a tag, i.e., if size is larger than TagToAllocatedSize(). inline uint8_t AllocatedSizeToTag(size_t size) { - const size_t tag = AllocatedSizeToTagUnchecked(size); + const uint8_t tag = AllocatedSizeToTagUnchecked(size); assert(tag <= MAX_FLAT_TAG); return tag; } diff --git a/absl/strings/internal/cord_rep_ring.cc b/absl/strings/internal/cord_rep_ring.cc index 358b0d92..4d31d1d9 100644 --- a/absl/strings/internal/cord_rep_ring.cc +++ b/absl/strings/internal/cord_rep_ring.cc @@ -36,8 +36,10 @@ namespace cord_internal { #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wshadow" +#if __has_warning("-Wshadow-field") #pragma clang diagnostic ignored "-Wshadow-field" #endif +#endif namespace { diff --git a/absl/strings/internal/cord_rep_ring.h b/absl/strings/internal/cord_rep_ring.h index 55cba8b4..c74d3353 100644 --- a/absl/strings/internal/cord_rep_ring.h +++ b/absl/strings/internal/cord_rep_ring.h @@ -34,8 +34,10 @@ namespace cord_internal { #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wshadow" +#if __has_warning("-Wshadow-field") #pragma clang diagnostic ignored "-Wshadow-field" #endif +#endif // All operations modifying a ring buffer are implemented as static methods // requiring a CordRepRing instance with a reference adopted by the method. @@ -81,7 +83,7 @@ class CordRepRing : public CordRep { // `end_pos` which is the `end_pos` of the previous node (or `begin_pos`) plus // this node's length. The purpose is to allow for a binary search on this // position, while allowing O(1) prepend and append operations. - using pos_type = uint64_t; + using pos_type = size_t; // `index_type` is the type for the `head`, `tail` and `capacity` indexes. // Ring buffers are limited to having no more than four billion entries. diff --git a/absl/time/internal/cctz/testdata/version b/absl/time/internal/cctz/testdata/version index a481df8c..1d590958 100644 --- a/absl/time/internal/cctz/testdata/version +++ b/absl/time/internal/cctz/testdata/version @@ -1 +1 @@ -2020f +2021a diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Juba b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Juba index 36b05220..0aba9ffd 100644 Binary files a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Juba and b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Juba differ -- cgit v1.2.3 From 20869f89edbc38139f46bdc49c8b6c3a1f7bee4c Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Mon, 1 Feb 2021 14:09:21 -0800 Subject: Export of internal Abseil changes -- cea62ebc5d31c62aabcb94c066d9be506f34baf6 by Abseil Team : Fix typo in `Cord::EndsWith()` docs PiperOrigin-RevId: 355023067 -- f89225a55476478ec167be50dea543f5414836f9 by Abseil Team : Add set_cordz_info() and get_cordz_info() methods to InlineData This change has preparations for future (optional) integration of CordzInfo sampling data into Cord's InlineData for non inlined cords. PiperOrigin-RevId: 354965340 -- 324057574aeb697bd3327cb905eb5bca16ade768 by Abseil Team : Fix two comment typos. PiperOrigin-RevId: 354952568 -- 5bb93ca3d57ead3633e1efde4aa28718987ef64f by CJ Johnson : Clarify doc comment for absl::Cleanup by using absl::Status return type and clarify the engaged state by surfacing the initial value in the public header. PiperOrigin-RevId: 354935253 -- ec95424594b24a1aec9bf7972b2355f37285506a by Abseil Team : Remove `preserve_most` attribute from CordRep::Destroy() PiperOrigin-RevId: 354921927 GitOrigin-RevId: cea62ebc5d31c62aabcb94c066d9be506f34baf6 Change-Id: Ibe1d66197db7ce9554594e07b1c6e7c6dea3c9da --- absl/cleanup/cleanup.h | 51 +++++++++++++++++++++-------------- absl/cleanup/internal/cleanup.h | 10 +++---- absl/hash/internal/wyhash_test.cc | 4 +-- absl/strings/BUILD.bazel | 1 + absl/strings/cord.h | 2 +- absl/strings/internal/cord_internal.h | 44 ++++++++++++++++-------------- 6 files changed, 64 insertions(+), 48 deletions(-) (limited to 'absl/strings/cord.h') diff --git a/absl/cleanup/cleanup.h b/absl/cleanup/cleanup.h index f606b3f4..5a4bc546 100644 --- a/absl/cleanup/cleanup.h +++ b/absl/cleanup/cleanup.h @@ -16,35 +16,39 @@ // File: cleanup.h // ----------------------------------------------------------------------------- // -// `absl::Cleanup` implements the scope guard idiom, invoking `operator()() &&` -// on the callback it was constructed with, on scope exit. +// `absl::Cleanup` implements the scope guard idiom, invoking the contained +// callback's `operator()() &&` on scope exit. // // Example: // // ``` -// void CopyGoodData(const char* input_path, const char* output_path) { -// FILE* in_file = fopen(input_path, "r"); -// if (in_file == nullptr) return; +// absl::Status CopyGoodData(const char* source_path, const char* sink_path) { +// FILE* source_file = fopen(source_path, "r"); +// if (source_file == nullptr) { +// return absl::NotFoundError("No source file"); // No cleanups execute +// } // -// // C++17 style using class template argument deduction -// absl::Cleanup in_closer = [in_file] { fclose(in_file); }; +// // C++17 style cleanup using class template argument deduction +// absl::Cleanup source_closer = [source_file] { fclose(source_file); }; // -// FILE* out_file = fopen(output_path, "w"); -// if (out_file == nullptr) return; // `in_closer` will run +// FILE* sink_file = fopen(sink_path, "w"); +// if (sink_file == nullptr) { +// return absl::NotFoundError("No sink file"); // First cleanup executes +// } // -// // C++11 style using the factory function -// auto out_closer = absl::MakeCleanup([out_file] { fclose(out_file); }); +// // C++11 style cleanup using the factory function +// auto sink_closer = absl::MakeCleanup([sink_file] { fclose(sink_file); }); // // Data data; -// while (ReadData(in_file, &data)) { +// while (ReadData(source_file, &data)) { // if (data.IsBad()) { -// LOG(ERROR) << "Found bad data."; -// return; // `in_closer` and `out_closer` will run +// absl::Status result = absl::FailedPreconditionError("Read bad data"); +// return result; // Both cleanups execute // } -// SaveData(out_file, &data); +// SaveData(sink_file, &data); // } // -// // `in_closer` and `out_closer` will run +// return absl::OkStatus(); // Both cleanups execute // } // ``` // @@ -54,6 +58,12 @@ // // `std::move(cleanup).Invoke()` will execute the callback early, before // destruction, and prevent the callback from executing in the destructor. +// +// Usage: +// +// `absl::Cleanup` is not an interface type. It is only intended to be used +// within the body of a function. It is not a value type and instead models a +// control flow construct. Check out `defer` in Golang for something similar. #ifndef ABSL_CLEANUP_CLEANUP_H_ #define ABSL_CLEANUP_CLEANUP_H_ @@ -76,9 +86,10 @@ class ABSL_MUST_USE_RESULT Cleanup { "Callbacks that return values are not supported."); public: - Cleanup(Callback callback) : storage_(std::move(callback)) {} // NOLINT + Cleanup(Callback callback) // NOLINT + : storage_(std::move(callback), /*engaged=*/true) {} - Cleanup(Cleanup&& other) : storage_(std::move(other.storage_)) {} + Cleanup(Cleanup&& other) = default; void Cancel() && { ABSL_HARDENING_ASSERT(storage_.IsCallbackEngaged()); @@ -103,7 +114,7 @@ class ABSL_MUST_USE_RESULT Cleanup { // `absl::Cleanup c = /* callback */;` // -// C++17 type deduction API for creating an instance of `absl::Cleanup`. +// C++17 type deduction API for creating an instance of `absl::Cleanup` #if defined(ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION) template Cleanup(Callback callback) -> Cleanup; @@ -111,7 +122,7 @@ Cleanup(Callback callback) -> Cleanup; // `auto c = absl::MakeCleanup(/* callback */);` // -// C++11 type deduction API for creating an instance of `absl::Cleanup`. +// C++11 type deduction API for creating an instance of `absl::Cleanup` template absl::Cleanup MakeCleanup(Callback callback) { static_assert(cleanup_internal::WasDeduced(), diff --git a/absl/cleanup/internal/cleanup.h b/absl/cleanup/internal/cleanup.h index 8fbca5bd..b68e3dd3 100644 --- a/absl/cleanup/internal/cleanup.h +++ b/absl/cleanup/internal/cleanup.h @@ -45,12 +45,12 @@ class Storage { public: Storage() = delete; - explicit Storage(Callback callback) - : engaged_(true), callback_(std::move(callback)) {} + Storage(Callback callback, bool engaged) + : callback_(std::move(callback)), engaged_(engaged) {} Storage(Storage&& other) - : engaged_(absl::exchange(other.engaged_, false)), - callback_(std::move(other.callback_)) {} + : callback_(std::move(other.callback_)), + engaged_(absl::exchange(other.engaged_, false)) {} Storage(const Storage& other) = delete; @@ -67,8 +67,8 @@ class Storage { } private: - bool engaged_; Callback callback_; + bool engaged_; }; } // namespace cleanup_internal diff --git a/absl/hash/internal/wyhash_test.cc b/absl/hash/internal/wyhash_test.cc index 30dc9e34..9fb06d23 100644 --- a/absl/hash/internal/wyhash_test.cc +++ b/absl/hash/internal/wyhash_test.cc @@ -1,14 +1,14 @@ // Copyright 2020 The Abseil Authors // // Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in cokSaltliance with the License. +// you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or ikSaltlied. +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index 794cf43a..5efaf896 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -286,6 +286,7 @@ cc_library( "//absl/base:base_internal", "//absl/base:config", "//absl/base:core_headers", + "//absl/base:endian", "//absl/base:raw_logging_internal", "//absl/base:throw_delegate", "//absl/container:compressed_tuple", diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 17341bda..aefb5e53 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -289,7 +289,7 @@ class Cord { bool StartsWith(const Cord& rhs) const; bool StartsWith(absl::string_view rhs) const; - // Cord::EndsWidth() + // Cord::EndsWith() // // Determines whether the Cord ends with the passed string data `rhs`. bool EndsWith(absl::string_view rhs) const; diff --git a/absl/strings/internal/cord_internal.h b/absl/strings/internal/cord_internal.h index 96502433..cda00a44 100644 --- a/absl/strings/internal/cord_internal.h +++ b/absl/strings/internal/cord_internal.h @@ -22,6 +22,7 @@ #include #include "absl/base/config.h" +#include "absl/base/internal/endian.h" #include "absl/base/internal/invoke.h" #include "absl/base/optimization.h" #include "absl/container/internal/compressed_tuple.h" @@ -32,6 +33,8 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace cord_internal { +class CordzInfo; + // Default feature enable states for cord ring buffers enum CordFeatureDefaults { kCordEnableRingBufferDefault = false, @@ -193,26 +196,7 @@ struct CordRep { // -------------------------------------------------------------------- // Memory management - // This internal routine is called from the cold path of Unref below. Keeping - // it in a separate routine allows good inlining of Unref into many profitable - // call sites. However, the call to this function can be highly disruptive to - // the register pressure in those callers. To minimize the cost to callers, we - // use a special LLVM calling convention that preserves most registers. This - // allows the call to this routine in cold paths to not disrupt the caller's - // register pressure. This calling convention is not available on all - // platforms; we intentionally allow LLVM to ignore the attribute rather than - // attempting to hardcode the list of supported platforms. -#if defined(__clang__) && !defined(__i386__) -#if !(defined(ABSL_HAVE_MEMORY_SANITIZER) || \ - defined(ABSL_HAVE_THREAD_SANITIZER) || \ - defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ - defined(UNDEFINED_BEHAVIOR_SANITIZER)) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wattributes" - __attribute__((preserve_most)) -#pragma clang diagnostic pop -#endif // *_SANITIZER -#endif + // Destroys the provided `rep`. static void Destroy(CordRep* rep); // Increments the reference count of `rep`. @@ -383,6 +367,26 @@ class InlineData { return as_tree_.cordz_info != kNullCordzInfo; } + // Returns the cordz_info sampling instance for this instance, or nullptr + // if the current instance is not sampled and does not have CordzInfo data. + // Requires the current instance to hold a tree value. + CordzInfo* cordz_info() const { + assert(is_tree()); + intptr_t info = + static_cast(absl::big_endian::ToHost64(as_tree_.cordz_info)); + assert(info & 1); + return reinterpret_cast(info - 1); + } + + // Sets the current cordz_info sampling instance for this instance, or nullptr + // if the current instance is not sampled and does not have CordzInfo data. + // Requires the current instance to hold a tree value. + void set_cordz_info(CordzInfo* cordz_info) { + assert(is_tree()); + intptr_t info = reinterpret_cast(cordz_info) | 1; + as_tree_.cordz_info = absl::big_endian::FromHost64(info); + } + // Returns a read only pointer to the character data inside this instance. // Requires the current instance to hold inline data. const char* as_chars() const { -- cgit v1.2.3 From 9c6a50fdd80bb39fabd95faeda84f04062685ff3 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Wed, 3 Feb 2021 10:18:15 -0800 Subject: Export of internal Abseil changes -- 4ff721439234e91caf6f7b772e5f554e7dd423c8 by Benjamin Barenblat : Remove endian-sensitivity from hash slow path Prior to this commit, the Abseil hash fast path was endian-agnostic, but the slow path assumed a little-endian platform. Change the slow path to be endian-correct, ensuring that values produced by the fast and slow paths are equal even on big-endian systems. PiperOrigin-RevId: 355424258 -- 7f4fe1aa4de46ad0a2ef19fa9c061fc12a7391ed by Abseil Team : Directly store CordzInfo in the InlineData data contents of InlineRep This greatly reduces the cost of coping and moving cords. Especially the move constructor and move assignment are now back to lean loads and stores without needing any CordzInfo lookups for tracked cords. PiperOrigin-RevId: 355409161 -- 3ca4ca84ed6d98f1e383ffd8d12c28876e905bb3 by Abseil Team : Add #include PiperOrigin-RevId: 355386114 -- 30b0ffad0621971b3135148fcc9e183b0dd2a6bb by Abseil Team : Optimize Cord copy constructor This change avoids double stores of the Cord copy constructor from the zero init of the InlineData / InlineRep contents followed by the assignment and inlines the copy constructor. PiperOrigin-RevId: 355287939 -- 0c043fa7b6e41ca7cefc5edc1e17ad46223e4e77 by CJ Johnson : Now that the absl::Cleanup example returns absl::Status, since we decided on absl::FailedPreconditionError, the precondition should be a positive statement and then the check should be failure to adhere to that positive statement PiperOrigin-RevId: 355216923 -- 9ed922ca5d28fe8790ec6bc0837cf39fbcc92896 by Gennadiy Rozental : Do not set mvsc linker flags for clang-cl (fixes #874) Import of https://github.com/abseil/abseil-cpp/pull/891 PiperOrigin-RevId: 355199380 GitOrigin-RevId: 4ff721439234e91caf6f7b772e5f554e7dd423c8 Change-Id: I3d9d2383549720d7a91f9108dfcd979ad6632fce --- absl/cleanup/cleanup.h | 4 +- absl/cleanup/internal/cleanup.h | 14 +++--- .../internal/unordered_map_constructor_test.h | 1 + absl/hash/BUILD.bazel | 1 + absl/hash/CMakeLists.txt | 1 + absl/hash/internal/hash.h | 51 +++++++++++++++++----- absl/strings/cord.cc | 8 +--- absl/strings/cord.h | 28 ++++++++++-- absl/strings/cord_test.cc | 4 ++ absl/strings/internal/cord_internal.h | 6 +++ 10 files changed, 90 insertions(+), 28 deletions(-) (limited to 'absl/strings/cord.h') diff --git a/absl/cleanup/cleanup.h b/absl/cleanup/cleanup.h index 5a4bc546..8ebf1e9b 100644 --- a/absl/cleanup/cleanup.h +++ b/absl/cleanup/cleanup.h @@ -41,7 +41,7 @@ // // Data data; // while (ReadData(source_file, &data)) { -// if (data.IsBad()) { +// if (!data.IsGood()) { // absl::Status result = absl::FailedPreconditionError("Read bad data"); // return result; // Both cleanups execute // } @@ -87,7 +87,7 @@ class ABSL_MUST_USE_RESULT Cleanup { public: Cleanup(Callback callback) // NOLINT - : storage_(std::move(callback), /*engaged=*/true) {} + : storage_(std::move(callback), /* is_callback_engaged = */ true) {} Cleanup(Cleanup&& other) = default; diff --git a/absl/cleanup/internal/cleanup.h b/absl/cleanup/internal/cleanup.h index b68e3dd3..b4c40737 100644 --- a/absl/cleanup/internal/cleanup.h +++ b/absl/cleanup/internal/cleanup.h @@ -45,12 +45,14 @@ class Storage { public: Storage() = delete; - Storage(Callback callback, bool engaged) - : callback_(std::move(callback)), engaged_(engaged) {} + Storage(Callback callback, bool is_callback_engaged) + : callback_(std::move(callback)), + is_callback_engaged_(is_callback_engaged) {} Storage(Storage&& other) : callback_(std::move(other.callback_)), - engaged_(absl::exchange(other.engaged_, false)) {} + is_callback_engaged_( + absl::exchange(other.is_callback_engaged_, false)) {} Storage(const Storage& other) = delete; @@ -58,9 +60,9 @@ class Storage { Storage& operator=(const Storage& other) = delete; - bool IsCallbackEngaged() const { return engaged_; } + bool IsCallbackEngaged() const { return is_callback_engaged_; } - void DisengageCallback() { engaged_ = false; } + void DisengageCallback() { is_callback_engaged_ = false; } void InvokeCallback() ABSL_NO_THREAD_SAFETY_ANALYSIS { std::move(callback_)(); @@ -68,7 +70,7 @@ class Storage { private: Callback callback_; - bool engaged_; + bool is_callback_engaged_; }; } // namespace cleanup_internal diff --git a/absl/container/internal/unordered_map_constructor_test.h b/absl/container/internal/unordered_map_constructor_test.h index 76ee95e6..3f90ad7c 100644 --- a/absl/container/internal/unordered_map_constructor_test.h +++ b/absl/container/internal/unordered_map_constructor_test.h @@ -16,6 +16,7 @@ #define ABSL_CONTAINER_INTERNAL_UNORDERED_MAP_CONSTRUCTOR_TEST_H_ #include +#include #include #include "gmock/gmock.h" diff --git a/absl/hash/BUILD.bazel b/absl/hash/BUILD.bazel index 90c6c8a8..4b2c220f 100644 --- a/absl/hash/BUILD.bazel +++ b/absl/hash/BUILD.bazel @@ -38,6 +38,7 @@ cc_library( deps = [ ":city", ":wyhash", + "//absl/base:config", "//absl/base:core_headers", "//absl/base:endian", "//absl/container:fixed_array", diff --git a/absl/hash/CMakeLists.txt b/absl/hash/CMakeLists.txt index 6d198775..b43bfa54 100644 --- a/absl/hash/CMakeLists.txt +++ b/absl/hash/CMakeLists.txt @@ -26,6 +26,7 @@ absl_cc_library( ${ABSL_DEFAULT_COPTS} DEPS absl::city + absl::config absl::core_headers absl::endian absl::fixed_array diff --git a/absl/hash/internal/hash.h b/absl/hash/internal/hash.h index eb3471d8..7fb0af0b 100644 --- a/absl/hash/internal/hash.h +++ b/absl/hash/internal/hash.h @@ -38,7 +38,8 @@ #include #include -#include "absl/base/internal/endian.h" +#include "absl/base/config.h" +#include "absl/base/internal/unaligned_access.h" #include "absl/base/port.h" #include "absl/container/fixed_array.h" #include "absl/hash/internal/wyhash.h" @@ -804,26 +805,54 @@ class ABSL_DLL HashState : public HashStateBase { size_t len); // Reads 9 to 16 bytes from p. - // The first 8 bytes are in .first, the rest (zero padded) bytes are in - // .second. + // The least significant 8 bytes are in .first, the rest (zero padded) bytes + // are in .second. static std::pair Read9To16(const unsigned char* p, size_t len) { - uint64_t high = little_endian::Load64(p + len - 8); - return {little_endian::Load64(p), high >> (128 - len * 8)}; + uint64_t low_mem = absl::base_internal::UnalignedLoad64(p); + uint64_t high_mem = absl::base_internal::UnalignedLoad64(p + len - 8); +#ifdef ABSL_IS_LITTLE_ENDIAN + uint64_t most_significant = high_mem; + uint64_t least_significant = low_mem; +#else + uint64_t most_significant = low_mem; + uint64_t least_significant = high_mem; +#endif + return {least_significant, most_significant >> (128 - len * 8)}; } // Reads 4 to 8 bytes from p. Zero pads to fill uint64_t. static uint64_t Read4To8(const unsigned char* p, size_t len) { - return (static_cast(little_endian::Load32(p + len - 4)) - << (len - 4) * 8) | - little_endian::Load32(p); + uint32_t low_mem = absl::base_internal::UnalignedLoad32(p); + uint32_t high_mem = absl::base_internal::UnalignedLoad32(p + len - 4); +#ifdef ABSL_IS_LITTLE_ENDIAN + uint32_t most_significant = high_mem; + uint32_t least_significant = low_mem; +#else + uint32_t most_significant = low_mem; + uint32_t least_significant = high_mem; +#endif + return (static_cast(most_significant) << (len - 4) * 8) | + least_significant; } // Reads 1 to 3 bytes from p. Zero pads to fill uint32_t. static uint32_t Read1To3(const unsigned char* p, size_t len) { - return static_cast((p[0]) | // - (p[len / 2] << (len / 2 * 8)) | // - (p[len - 1] << ((len - 1) * 8))); + unsigned char mem0 = p[0]; + unsigned char mem1 = p[len / 2]; + unsigned char mem2 = p[len - 1]; +#ifdef ABSL_IS_LITTLE_ENDIAN + unsigned char significant2 = mem2; + unsigned char significant1 = mem1; + unsigned char significant0 = mem0; +#else + unsigned char significant2 = mem0; + unsigned char significant1 = mem1; + unsigned char significant0 = mem2; +#endif + return static_cast(significant0 | // + (significant1 << (len / 2 * 8)) | // + (significant2 << ((len - 1) * 8))); } ABSL_ATTRIBUTE_ALWAYS_INLINE static uint64_t Mix(uint64_t state, uint64_t v) { diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index 39191ef5..93533757 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc @@ -495,7 +495,9 @@ void Cord::InlineRep::AssignSlow(const Cord::InlineRep& src) { data_ = src.data_; if (is_tree()) { + data_.set_profiled(false); CordRep::Ref(tree()); + clear_cordz_info(); } } @@ -509,12 +511,6 @@ void Cord::InlineRep::ClearSlow() { // -------------------------------------------------------------------- // Constructors and destructors -Cord::Cord(const Cord& src) : contents_(src.contents_) { - if (CordRep* tree = contents_.tree()) { - CordRep::Ref(tree); - } -} - Cord::Cord(absl::string_view src) { const size_t n = src.size(); if (n <= InlineRep::kMaxInline) { diff --git a/absl/strings/cord.h b/absl/strings/cord.h index aefb5e53..320226d2 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -755,6 +755,23 @@ class Cord { bool is_tree() const { return data_.is_tree(); } + // Returns true if the Cord is being profiled by cordz. + bool is_profiled() const { return data_.is_tree() && data_.is_profiled(); } + + // Returns the profiled CordzInfo, or nullptr if not sampled. + absl::cord_internal::CordzInfo* cordz_info() const { + return data_.cordz_info(); + } + + // Sets the profiled CordzInfo. `cordz_info` must not be null. + void set_cordz_info(cord_internal::CordzInfo* cordz_info) { + assert(cordz_info != nullptr); + data_.set_cordz_info(cordz_info); + } + + // Resets the current cordz_info to null / empty. + void clear_cordz_info() { data_.clear_cordz_info(); } + private: friend class Cord; @@ -921,8 +938,12 @@ Cord MakeCordFromExternal(absl::string_view data, Releaser&& releaser) { constexpr Cord::InlineRep::InlineRep(cord_internal::InlineData data) : data_(data) {} -inline Cord::InlineRep::InlineRep(const Cord::InlineRep& src) { - data_ = src.data_; +inline Cord::InlineRep::InlineRep(const Cord::InlineRep& src) + : data_(src.data_) { + if (is_tree()) { + data_.clear_cordz_info(); + absl::cord_internal::CordRep::Ref(as_tree()); + } } inline Cord::InlineRep::InlineRep(Cord::InlineRep&& src) { @@ -956,7 +977,6 @@ inline void Cord::InlineRep::Swap(Cord::InlineRep* rhs) { if (rhs == this) { return; } - std::swap(data_, rhs->data_); } @@ -1037,6 +1057,8 @@ inline Cord& Cord::operator=(const Cord& x) { return *this; } +inline Cord::Cord(const Cord& src) : contents_(src.contents_) {} + inline Cord::Cord(Cord&& src) noexcept : contents_(std::move(src.contents_)) {} inline void Cord::swap(Cord& other) noexcept { diff --git a/absl/strings/cord_test.cc b/absl/strings/cord_test.cc index bf7a6820..f9982428 100644 --- a/absl/strings/cord_test.cc +++ b/absl/strings/cord_test.cc @@ -183,6 +183,10 @@ class CordTestPeer { } static bool IsTree(const Cord& c) { return c.contents_.is_tree(); } + + static cord_internal::CordzInfo* GetCordzInfo(const Cord& c) { + return c.contents_.cordz_info(); + } }; ABSL_NAMESPACE_END diff --git a/absl/strings/internal/cord_internal.h b/absl/strings/internal/cord_internal.h index cda00a44..a1ba67fe 100644 --- a/absl/strings/internal/cord_internal.h +++ b/absl/strings/internal/cord_internal.h @@ -387,6 +387,12 @@ class InlineData { as_tree_.cordz_info = absl::big_endian::FromHost64(info); } + // Resets the current cordz_info to null / empty. + void clear_cordz_info() { + assert(is_tree()); + as_tree_.cordz_info = kNullCordzInfo; + } + // Returns a read only pointer to the character data inside this instance. // Requires the current instance to hold inline data. const char* as_chars() const { -- cgit v1.2.3 From c36d825d9a5443f81d2656685ae021d6326da90c Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Sun, 7 Feb 2021 17:35:10 -0800 Subject: Export of internal Abseil changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -- 756156bf03da050e8b27539a8247d9af7e44c6a2 by Abseil Team : Fix a typo in cord.h: "accomodate" => "accommodate" PiperOrigin-RevId: 356168875 -- 638befdb342b608ec28910ee931ee200fdbe1fef by Samuel Benzaquen : Fix float conversion for PPC. In PPC `long double` is a double-double representation which behaves weirdly wrt numeric_limits. Don't take `long double` into account when we are not handling `long double` natively anyway. Fix the convert test to always run the conversion even if we are not going to compare against libc's printf result. This allows exercising the code itself to make sure we don't trigger assertions or UB found by sanitizers. PiperOrigin-RevId: 355857729 -- ff5f893319fa76b273c7785b76ef6c95b1791076 by Abseil Team : Example usage tweak PiperOrigin-RevId: 355695750 -- 0efc454f90023fa651b226e5e3ba7395a3b60c6d by Benjamin Barenblat : Remove endian-sensitivity from Abseil’s RNG Ensure that the Abseil random number generator produces identical output on both big- and little-endian platforms by byte-swapping appropriately on big-endian systems. PiperOrigin-RevId: 355635051 GitOrigin-RevId: 756156bf03da050e8b27539a8247d9af7e44c6a2 Change-Id: Iaaa69767b8e85d626742b9ba56fefb75f07c69ee --- absl/base/BUILD.bazel | 1 + absl/base/CMakeLists.txt | 1 + absl/base/internal/endian.h | 61 +++++++++++++++ absl/random/CMakeLists.txt | 3 + absl/random/internal/BUILD.bazel | 7 +- absl/random/internal/explicit_seed_seq.h | 3 +- absl/random/internal/randen_engine.h | 8 +- absl/random/internal/randen_slow_test.cc | 3 +- absl/strings/cord.h | 2 +- absl/strings/internal/str_format/convert_test.cc | 88 +++++++++++----------- .../internal/str_format/float_conversion.cc | 33 ++++---- absl/synchronization/mutex.h | 2 +- 12 files changed, 146 insertions(+), 66 deletions(-) (limited to 'absl/strings/cord.h') diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel index 5d67a507..7d2ff308 100644 --- a/absl/base/BUILD.bazel +++ b/absl/base/BUILD.bazel @@ -479,6 +479,7 @@ cc_library( copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ + ":base", ":config", ":core_headers", ], diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt index 3d930b85..981b8cc0 100644 --- a/absl/base/CMakeLists.txt +++ b/absl/base/CMakeLists.txt @@ -418,6 +418,7 @@ absl_cc_library( COPTS ${ABSL_DEFAULT_COPTS} DEPS + absl::base absl::config absl::core_headers PUBLIC diff --git a/absl/base/internal/endian.h b/absl/base/internal/endian.h index 9677530e..dad0e9ae 100644 --- a/absl/base/internal/endian.h +++ b/absl/base/internal/endian.h @@ -26,6 +26,7 @@ #endif #include +#include "absl/base/casts.h" #include "absl/base/config.h" #include "absl/base/internal/unaligned_access.h" #include "absl/base/port.h" @@ -173,6 +174,36 @@ inline constexpr bool IsLittleEndian() { return false; } #endif /* ENDIAN */ +inline uint8_t FromHost(uint8_t x) { return x; } +inline uint16_t FromHost(uint16_t x) { return FromHost16(x); } +inline uint32_t FromHost(uint32_t x) { return FromHost32(x); } +inline uint64_t FromHost(uint64_t x) { return FromHost64(x); } +inline uint8_t ToHost(uint8_t x) { return x; } +inline uint16_t ToHost(uint16_t x) { return ToHost16(x); } +inline uint32_t ToHost(uint32_t x) { return ToHost32(x); } +inline uint64_t ToHost(uint64_t x) { return ToHost64(x); } + +inline int8_t FromHost(int8_t x) { return x; } +inline int16_t FromHost(int16_t x) { + return bit_cast(FromHost16(bit_cast(x))); +} +inline int32_t FromHost(int32_t x) { + return bit_cast(FromHost32(bit_cast(x))); +} +inline int64_t FromHost(int64_t x) { + return bit_cast(FromHost64(bit_cast(x))); +} +inline int8_t ToHost(int8_t x) { return x; } +inline int16_t ToHost(int16_t x) { + return bit_cast(ToHost16(bit_cast(x))); +} +inline int32_t ToHost(int32_t x) { + return bit_cast(ToHost32(bit_cast(x))); +} +inline int64_t ToHost(int64_t x) { + return bit_cast(ToHost64(bit_cast(x))); +} + // Functions to do unaligned loads and stores in little-endian order. inline uint16_t Load16(const void *p) { return ToHost16(ABSL_INTERNAL_UNALIGNED_LOAD16(p)); @@ -233,6 +264,36 @@ inline constexpr bool IsLittleEndian() { return false; } #endif /* ENDIAN */ +inline uint8_t FromHost(uint8_t x) { return x; } +inline uint16_t FromHost(uint16_t x) { return FromHost16(x); } +inline uint32_t FromHost(uint32_t x) { return FromHost32(x); } +inline uint64_t FromHost(uint64_t x) { return FromHost64(x); } +inline uint8_t ToHost(uint8_t x) { return x; } +inline uint16_t ToHost(uint16_t x) { return ToHost16(x); } +inline uint32_t ToHost(uint32_t x) { return ToHost32(x); } +inline uint64_t ToHost(uint64_t x) { return ToHost64(x); } + +inline int8_t FromHost(int8_t x) { return x; } +inline int16_t FromHost(int16_t x) { + return bit_cast(FromHost16(bit_cast(x))); +} +inline int32_t FromHost(int32_t x) { + return bit_cast(FromHost32(bit_cast(x))); +} +inline int64_t FromHost(int64_t x) { + return bit_cast(FromHost64(bit_cast(x))); +} +inline int8_t ToHost(int8_t x) { return x; } +inline int16_t ToHost(int16_t x) { + return bit_cast(ToHost16(bit_cast(x))); +} +inline int32_t ToHost(int32_t x) { + return bit_cast(ToHost32(bit_cast(x))); +} +inline int64_t ToHost(int64_t x) { + return bit_cast(ToHost64(bit_cast(x))); +} + // Functions to do unaligned loads and stores in big-endian order. inline uint16_t Load16(const void *p) { return ToHost16(ABSL_INTERNAL_UNALIGNED_LOAD16(p)); diff --git a/absl/random/CMakeLists.txt b/absl/random/CMakeLists.txt index 7d7bec83..13093d6d 100644 --- a/absl/random/CMakeLists.txt +++ b/absl/random/CMakeLists.txt @@ -611,6 +611,7 @@ absl_cc_library( ${ABSL_DEFAULT_LINKOPTS} DEPS absl::config + absl::endian TESTONLY ) @@ -758,6 +759,7 @@ absl_cc_library( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS + absl::endian absl::random_internal_iostream_state_saver absl::random_internal_randen absl::raw_logging_internal @@ -1119,6 +1121,7 @@ absl_cc_test( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS + absl::endian absl::random_internal_randen_slow gtest_main ) diff --git a/absl/random/internal/BUILD.bazel b/absl/random/internal/BUILD.bazel index 2c1a5f4a..4e778aee 100644 --- a/absl/random/internal/BUILD.bazel +++ b/absl/random/internal/BUILD.bazel @@ -124,7 +124,10 @@ cc_library( ], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, - deps = ["//absl/base:config"], + deps = [ + "//absl/base:config", + "//absl/base:endian", + ], ) cc_library( @@ -242,6 +245,7 @@ cc_library( deps = [ ":iostream_state_saver", ":randen", + "//absl/base:endian", "//absl/meta:type_traits", ], ) @@ -606,6 +610,7 @@ cc_test( deps = [ ":platform", ":randen_slow", + "//absl/base:endian", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/random/internal/explicit_seed_seq.h b/absl/random/internal/explicit_seed_seq.h index 6a743eaf..e3aa31a1 100644 --- a/absl/random/internal/explicit_seed_seq.h +++ b/absl/random/internal/explicit_seed_seq.h @@ -23,6 +23,7 @@ #include #include "absl/base/config.h" +#include "absl/base/internal/endian.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -73,7 +74,7 @@ class ExplicitSeedSeq { template void generate(OutIterator begin, OutIterator end) { for (size_t index = 0; begin != end; begin++) { - *begin = state_.empty() ? 0 : state_[index++]; + *begin = state_.empty() ? 0 : little_endian::FromHost32(state_[index++]); if (index >= state_.size()) { index = 0; } diff --git a/absl/random/internal/randen_engine.h b/absl/random/internal/randen_engine.h index 6b337313..92bb8905 100644 --- a/absl/random/internal/randen_engine.h +++ b/absl/random/internal/randen_engine.h @@ -23,6 +23,7 @@ #include #include +#include "absl/base/internal/endian.h" #include "absl/meta/type_traits.h" #include "absl/random/internal/iostream_state_saver.h" #include "absl/random/internal/randen.h" @@ -76,7 +77,7 @@ class alignas(16) randen_engine { impl_.Generate(state_); } - return state_[next_++]; + return little_endian::ToHost(state_[next_++]); } template @@ -181,7 +182,8 @@ class alignas(16) randen_engine { // In the case that `elem` is `uint8_t`, it must be cast to something // larger so that it prints as an integer rather than a character. For // simplicity, apply the cast all circumstances. - os << static_cast(elem) << os.fill(); + os << static_cast(little_endian::FromHost(elem)) + << os.fill(); } os << engine.next_; return os; @@ -200,7 +202,7 @@ class alignas(16) randen_engine { // necessary to read a wider type and then cast it to uint8_t. numeric_type value; is >> value; - elem = static_cast(value); + elem = little_endian::ToHost(static_cast(value)); } is >> next; if (is.fail()) { diff --git a/absl/random/internal/randen_slow_test.cc b/absl/random/internal/randen_slow_test.cc index 4a535837..4861ffa4 100644 --- a/absl/random/internal/randen_slow_test.cc +++ b/absl/random/internal/randen_slow_test.cc @@ -17,6 +17,7 @@ #include #include "gtest/gtest.h" +#include "absl/base/internal/endian.h" #include "absl/random/internal/randen_traits.h" namespace { @@ -56,7 +57,7 @@ TEST(RandenSlowTest, Default) { uint64_t* id = d.state; for (const auto& elem : kGolden) { - EXPECT_EQ(elem, *id++); + EXPECT_EQ(absl::little_endian::FromHost64(elem), *id++); } } diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 320226d2..fa9cb913 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -25,7 +25,7 @@ // // Because a Cord consists of these chunks, data can be added to or removed from // a Cord during its lifetime. Chunks may also be shared between Cords. Unlike a -// `std::string`, a Cord can therefore accomodate data that changes over its +// `std::string`, a Cord can therefore accommodate data that changes over its // lifetime, though it's not quite "mutable"; it can change only in the // attachment, detachment, or rearrangement of chunks of its constituent data. // diff --git a/absl/strings/internal/str_format/convert_test.cc b/absl/strings/internal/str_format/convert_test.cc index 375db0a0..926283cf 100644 --- a/absl/strings/internal/str_format/convert_test.cc +++ b/absl/strings/internal/str_format/convert_test.cc @@ -554,7 +554,8 @@ TEST_F(FormatConvertTest, Uint128) { } template -void TestWithMultipleFormatsHelper(const std::vector &floats) { +void TestWithMultipleFormatsHelper(const std::vector &floats, + const std::set &skip_verify) { const NativePrintfTraits &native_traits = VerifyNativeImplementation(); // Reserve the space to ensure we don't allocate memory in the output itself. std::string str_format_result; @@ -602,7 +603,16 @@ void TestWithMultipleFormatsHelper(const std::vector &floats) { AppendPack(&str_format_result, format, absl::MakeSpan(args)); } - if (string_printf_result != str_format_result) { +#ifdef _MSC_VER + // MSVC has a different rounding policy than us so we can't test our + // implementation against the native one there. + continue; +#elif defined(__APPLE__) + // Apple formats NaN differently (+nan) vs. (nan) + if (std::isnan(d)) continue; +#endif + if (string_printf_result != str_format_result && + skip_verify.find(d) == skip_verify.end()) { // We use ASSERT_EQ here because failures are usually correlated and a // bug would print way too many failed expectations causing the test // to time out. @@ -616,12 +626,6 @@ void TestWithMultipleFormatsHelper(const std::vector &floats) { } TEST_F(FormatConvertTest, Float) { -#ifdef _MSC_VER - // MSVC has a different rounding policy than us so we can't test our - // implementation against the native one there. - return; -#endif // _MSC_VER - std::vector floats = {0.0f, -0.0f, .9999999f, @@ -635,7 +639,8 @@ TEST_F(FormatConvertTest, Float) { std::numeric_limits::epsilon(), std::numeric_limits::epsilon() + 1.0f, std::numeric_limits::infinity(), - -std::numeric_limits::infinity()}; + -std::numeric_limits::infinity(), + std::nanf("")}; // Some regression tests. floats.push_back(0.999999989f); @@ -664,21 +669,14 @@ TEST_F(FormatConvertTest, Float) { std::sort(floats.begin(), floats.end()); floats.erase(std::unique(floats.begin(), floats.end()), floats.end()); -#ifndef __APPLE__ - // Apple formats NaN differently (+nan) vs. (nan) - floats.push_back(std::nan("")); -#endif - - TestWithMultipleFormatsHelper(floats); + TestWithMultipleFormatsHelper(floats, {}); } TEST_F(FormatConvertTest, Double) { -#ifdef _MSC_VER - // MSVC has a different rounding policy than us so we can't test our - // implementation against the native one there. - return; -#endif // _MSC_VER - + // For values that we know won't match the standard library implementation we + // skip verification, but still run the algorithm to catch asserts/sanitizer + // bugs. + std::set skip_verify; std::vector doubles = {0.0, -0.0, .99999999999999, @@ -692,7 +690,8 @@ TEST_F(FormatConvertTest, Double) { std::numeric_limits::epsilon(), std::numeric_limits::epsilon() + 1, std::numeric_limits::infinity(), - -std::numeric_limits::infinity()}; + -std::numeric_limits::infinity(), + std::nan("")}; // Some regression tests. doubles.push_back(0.99999999999999989); @@ -722,33 +721,29 @@ TEST_F(FormatConvertTest, Double) { "5084551339423045832369032229481658085593321233482747978262041447231" "68738177180919299881250404026184124858368.000000"; - if (!gcc_bug_22142) { - for (int exp = -300; exp <= 300; ++exp) { - const double all_ones_mantissa = 0x1fffffffffffff; - doubles.push_back(std::ldexp(all_ones_mantissa, exp)); + for (int exp = -300; exp <= 300; ++exp) { + const double all_ones_mantissa = 0x1fffffffffffff; + doubles.push_back(std::ldexp(all_ones_mantissa, exp)); + if (gcc_bug_22142) { + skip_verify.insert(doubles.back()); } } if (gcc_bug_22142) { - for (auto &d : doubles) { - using L = std::numeric_limits; - double d2 = std::abs(d); - if (d2 == L::max() || d2 == L::min() || d2 == L::denorm_min()) { - d = 0; - } - } + using L = std::numeric_limits; + skip_verify.insert(L::max()); + skip_verify.insert(L::min()); // NOLINT + skip_verify.insert(L::denorm_min()); + skip_verify.insert(-L::max()); + skip_verify.insert(-L::min()); // NOLINT + skip_verify.insert(-L::denorm_min()); } // Remove duplicates to speed up the logic below. std::sort(doubles.begin(), doubles.end()); doubles.erase(std::unique(doubles.begin(), doubles.end()), doubles.end()); -#ifndef __APPLE__ - // Apple formats NaN differently (+nan) vs. (nan) - doubles.push_back(std::nan("")); -#endif - - TestWithMultipleFormatsHelper(doubles); + TestWithMultipleFormatsHelper(doubles, skip_verify); } TEST_F(FormatConvertTest, DoubleRound) { @@ -1069,11 +1064,6 @@ TEST_F(FormatConvertTest, ExtremeWidthPrecision) { } TEST_F(FormatConvertTest, LongDouble) { -#ifdef _MSC_VER - // MSVC has a different rounding policy than us so we can't test our - // implementation against the native one there. - return; -#endif // _MSC_VER const NativePrintfTraits &native_traits = VerifyNativeImplementation(); const char *const kFormats[] = {"%", "%.3", "%8.5", "%9", "%.5000", "%.60", "%+", "% ", "%-10"}; @@ -1134,10 +1124,18 @@ TEST_F(FormatConvertTest, LongDouble) { for (auto d : doubles) { FormatArgImpl arg(d); UntypedFormatSpecImpl format(fmt_str); + std::string result = FormatPack(format, {&arg, 1}); + +#ifdef _MSC_VER + // MSVC has a different rounding policy than us so we can't test our + // implementation against the native one there. + continue; +#endif // _MSC_VER + // We use ASSERT_EQ here because failures are usually correlated and a // bug would print way too many failed expectations causing the test to // time out. - ASSERT_EQ(StrPrint(fmt_str.c_str(), d), FormatPack(format, {&arg, 1})) + ASSERT_EQ(StrPrint(fmt_str.c_str(), d), result) << fmt_str << " " << StrPrint("%.18Lg", d) << " " << StrPrint("%La", d) << " " << StrPrint("%.1080Lf", d); } diff --git a/absl/strings/internal/str_format/float_conversion.cc b/absl/strings/internal/str_format/float_conversion.cc index 2aa41aa7..2b1fd8cb 100644 --- a/absl/strings/internal/str_format/float_conversion.cc +++ b/absl/strings/internal/str_format/float_conversion.cc @@ -112,12 +112,22 @@ inline uint64_t DivideBy10WithCarry(uint64_t *v, uint64_t carry) { return next_carry % divisor; } +constexpr bool IsDoubleDouble() { + // This is the `double-double` representation of `long double`. + // We do not handle it natively. Fallback to snprintf. + return std::numeric_limits::digits == + 2 * std::numeric_limits::digits; +} + +using MaxFloatType = + typename std::conditional::type; + // Generates the decimal representation for an integer of the form `v * 2^exp`, // where `v` and `exp` are both positive integers. // It generates the digits from the left (ie the most significant digit first) // to allow for direct printing into the sink. // -// Requires `0 <= exp` and `exp <= numeric_limits::max_exponent`. +// Requires `0 <= exp` and `exp <= numeric_limits::max_exponent`. class BinaryToDecimal { static constexpr int ChunksNeeded(int exp) { // We will left shift a uint128 by `exp` bits, so we need `128+exp` total @@ -132,10 +142,10 @@ class BinaryToDecimal { static void RunConversion(uint128 v, int exp, absl::FunctionRef f) { assert(exp > 0); - assert(exp <= std::numeric_limits::max_exponent); + assert(exp <= std::numeric_limits::max_exponent); static_assert( static_cast(StackArray::kMaxCapacity) >= - ChunksNeeded(std::numeric_limits::max_exponent), + ChunksNeeded(std::numeric_limits::max_exponent), ""); StackArray::RunWithCapacity( @@ -232,14 +242,14 @@ class BinaryToDecimal { // Converts a value of the form `x * 2^-exp` into a sequence of decimal digits. // Requires `-exp < 0` and -// `-exp >= limits::min_exponent - limits::digits`. +// `-exp >= limits::min_exponent - limits::digits`. class FractionalDigitGenerator { public: // Run the conversion for `v * 2^exp` and call `f(generator)`. // This function will allocate enough stack space to perform the conversion. static void RunConversion( uint128 v, int exp, absl::FunctionRef f) { - using Limits = std::numeric_limits; + using Limits = std::numeric_limits; assert(-exp < 0); assert(-exp >= Limits::min_exponent - 128); static_assert(StackArray::kMaxCapacity >= @@ -871,10 +881,10 @@ void FormatA(const HexFloatTypeParams float_traits, Int mantissa, int exp, // This buffer holds the "0x1.ab1de3" portion of "0x1.ab1de3pe+2". Compute the // size with long double which is the largest of the floats. constexpr size_t kBufSizeForHexFloatRepr = - 2 // 0x - + std::numeric_limits::digits / 4 // number of hex digits - + 1 // round up - + 1; // "." (dot) + 2 // 0x + + std::numeric_limits::digits / 4 // number of hex digits + + 1 // round up + + 1; // "." (dot) char digits_buffer[kBufSizeForHexFloatRepr]; char *digits_iter = digits_buffer; const char *const digits = @@ -1393,10 +1403,7 @@ bool FloatToSink(const Float v, const FormatConversionSpecImpl &conv, bool ConvertFloatImpl(long double v, const FormatConversionSpecImpl &conv, FormatSinkImpl *sink) { - if (std::numeric_limits::digits == - 2 * std::numeric_limits::digits) { - // This is the `double-double` representation of `long double`. - // We do not handle it natively. Fallback to snprintf. + if (IsDoubleDouble()) { return FallbackToSnprintf(v, conv, sink); } diff --git a/absl/synchronization/mutex.h b/absl/synchronization/mutex.h index 4dd51fec..8c6d573d 100644 --- a/absl/synchronization/mutex.h +++ b/absl/synchronization/mutex.h @@ -147,7 +147,7 @@ class ABSL_LOCKABLE Mutex { // // Example usage: // namespace foo { - // ABSL_CONST_INIT Mutex mu(absl::kConstInit); + // ABSL_CONST_INIT absl::Mutex mu(absl::kConstInit); // } explicit constexpr Mutex(absl::ConstInitType); -- cgit v1.2.3