diff options
205 files changed, 11706 insertions, 4090 deletions
diff --git a/.github/ISSUE_TEMPLATE/00-bug_report.md b/.github/ISSUE_TEMPLATE/00-bug_report.md deleted file mode 100644 index 1edf3de0..00000000 --- a/.github/ISSUE_TEMPLATE/00-bug_report.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: 'bug' -assignees: '' ---- - -**Describe the bug** - -Include a clear and concise description of what the problem is, including what -you expected to happen, and what actually happened. - -**Steps to reproduce the bug** - -It's important that we are able to reproduce the problem that you are -experiencing. Please provide all code and relevant steps to reproduce the -problem, including your `BUILD`/`CMakeLists.txt` file and build commands. Links -to a GitHub branch or [godbolt.org](https://godbolt.org/) that demonstrate the -problem are also helpful. - -**What version of Abseil are you using?** - -**What operating system and version are you using** - -If you are using a Linux distribution please include the name and version of the -distribution as well. - -**What compiler and version are you using?** - -Please include the output of `gcc -v` or `clang -v`, or the equivalent for your -compiler. - -**What build system are you using?** - -Please include the output of `bazel --version` or `cmake --version`, or the -equivalent for your build system. - -**Additional context** - -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/00-bug_report.yml b/.github/ISSUE_TEMPLATE/00-bug_report.yml new file mode 100644 index 00000000..32cebe15 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/00-bug_report.yml @@ -0,0 +1,53 @@ +name: Bug Report +description: Let us know that something does not work as expected. +title: "[Bug]: Please title this bug report" +body: + - type: textarea + id: what-happened + attributes: + label: Describe the issue + description: What happened, and what did you expect to happen? + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to reproduce the problem + description: It is important that we are able to reproduce the problem that you are experiencing. Please provide all code and relevant steps to reproduce the problem, including your `BUILD`/`CMakeLists.txt` file and build commands. Links to a GitHub branch or [godbolt.org](https://godbolt.org/) that demonstrate the problem are also helpful. + validations: + required: true + - type: textarea + id: version + attributes: + label: What version of Abseil are you using? + description: Please include the output of `git rev-parse HEAD` or the name of the LTS release that you are using. + validations: + required: true + - type: textarea + id: os + attributes: + label: What operating system and version are you using? + description: If you are using a Linux distribution please include the name and version of the distribution as well. + validations: + required: true + - type: textarea + id: compiler + attributes: + label: What compiler and version are you using? + description: Please include the output of `gcc -v` or `clang -v`, or the equivalent for your compiler. + validations: + required: true + - type: textarea + id: buildsystem + attributes: + label: What build system are you using? + description: Please include the output of `bazel --version` or `cmake --version`, or the equivalent for your build system. + validations: + required: true + - type: textarea + id: additional + attributes: + label: Additional context + description: Add any other context about the problem here. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/90-question.md b/.github/ISSUE_TEMPLATE/90-question.md deleted file mode 100644 index 84cf3491..00000000 --- a/.github/ISSUE_TEMPLATE/90-question.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -name: Question -about: Have a question? Ask us anything! :-) -title: '' -labels: 'question' -assignees: '' ---- diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 0086358d..c690fd9a 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1 +1,5 @@ -blank_issues_enabled: true +blank_issues_enabled: false +contact_links: + - name: Question + url: https://github.com/abseil/abseil-cpp/discussions + about: Have a question? Ask us anything! :-) diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index 508939ae..0f884fbe 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake @@ -91,6 +91,26 @@ set(ABSL_INTERNAL_DLL_FILES "container/internal/tracked.h" "container/node_hash_map.h" "container/node_hash_set.h" + "crc/crc32c.cc" + "crc/crc32c.h" + "crc/internal/cpu_detect.cc" + "crc/internal/cpu_detect.h" + "crc/internal/crc32c.h" + "crc/internal/crc32c_inline.h" + "crc/internal/crc32_x86_arm_combined_simd.h" + "crc/internal/crc.cc" + "crc/internal/crc.h" + "crc/internal/crc_cord_state.cc" + "crc/internal/crc_cord_state.h" + "crc/internal/crc_internal.h" + "crc/internal/crc_x86_arm_combined.cc" + "crc/internal/crc_memcpy_fallback.cc" + "crc/internal/crc_memcpy.h" + "crc/internal/crc_memcpy_x86_64.cc" + "crc/internal/crc_non_temporal_memcpy.cc" + "crc/internal/crc_x86_arm_combined.cc" + "crc/internal/non_temporal_arm_intrinsics.h" + "crc/internal/non_temporal_memcpy.h" "debugging/failure_signal_handler.cc" "debugging/failure_signal_handler.h" "debugging/leak_check.h" @@ -243,6 +263,8 @@ set(ABSL_INTERNAL_DLL_FILES "strings/internal/stl_type_traits.h" "strings/internal/string_constant.h" "strings/internal/stringify_sink.h" + "strings/internal/stringify_sink.cc" + "strings/internal/has_absl_stringify.h" "strings/match.cc" "strings/match.h" "strings/numbers.cc" @@ -275,6 +297,7 @@ set(ABSL_INTERNAL_DLL_FILES "strings/internal/str_format/bind.cc" "strings/internal/str_format/bind.h" "strings/internal/str_format/checker.h" + "strings/internal/str_format/constexpr_parser.h" "strings/internal/str_format/extension.cc" "strings/internal/str_format/extension.h" "strings/internal/str_format/float_conversion.cc" @@ -384,6 +407,9 @@ set(ABSL_INTERNAL_DLL_TARGETS "cord" "core_headers" "counting_allocator" + "crc_cpu_detect" + "crc_internal" + "crc32c" "debugging" "debugging_internal" "demangle_internal" @@ -416,6 +442,8 @@ set(ABSL_INTERNAL_DLL_TARGETS "node_hash_map" "node_hash_set" "node_slot_policy" + "non_temporal_arm_intrinsics" + "non_temporal_memcpy" "numeric" "optional" "periodic_sampler" @@ -475,6 +503,35 @@ set(ABSL_INTERNAL_DLL_TARGETS "variant" ) +function(_absl_target_compile_features_if_available TARGET TYPE FEATURE) + if(FEATURE IN_LIST CMAKE_CXX_COMPILE_FEATURES) + target_compile_features(${TARGET} ${TYPE} ${FEATURE}) + else() + message(WARNING "Feature ${FEATURE} is unknown for the CXX compiler") + endif() +endfunction() + +include(CheckCXXSourceCompiles) + +check_cxx_source_compiles( + [==[ +#ifdef _MSC_VER +# if _MSVC_LANG < 201700L +# error "The compiler defaults or is configured for C++ < 17" +# endif +#elif __cplusplus < 201700L +# error "The compiler defaults or is configured for C++ < 17" +#endif +int main() { return 0; } +]==] + ABSL_INTERNAL_AT_LEAST_CXX17) + +if(ABSL_INTERNAL_AT_LEAST_CXX17) + set(ABSL_INTERNAL_CXX_STD_FEATURE cxx_std_17) +else() + set(ABSL_INTERNAL_CXX_STD_FEATURE cxx_std_14) +endif() + function(absl_internal_dll_contains) cmake_parse_arguments(ABSL_INTERNAL_DLL "" @@ -546,14 +603,14 @@ function(absl_make_dll) ) foreach(cflag ${ABSL_CC_LIB_COPTS}) - if(${cflag} MATCHES "^(-Wno|/wd)") - # These flags are needed to suppress warnings that might fire in our headers. - set(PC_CFLAGS "${PC_CFLAGS} ${cflag}") - elseif(${cflag} MATCHES "^(-W|/w[1234eo])") - # Don't impose our warnings on others. - else() - set(PC_CFLAGS "${PC_CFLAGS} ${cflag}") - endif() + if(${cflag} MATCHES "^(-Wno|/wd)") + # These flags are needed to suppress warnings that might fire in our headers. + set(PC_CFLAGS "${PC_CFLAGS} ${cflag}") + elseif(${cflag} MATCHES "^(-W|/w[1234eo])") + # Don't impose our warnings on others. + else() + set(PC_CFLAGS "${PC_CFLAGS} ${cflag}") + endif() endforeach() string(REPLACE ";" " " PC_LINKOPTS "${ABSL_CC_LIB_LINKOPTS}") @@ -570,7 +627,7 @@ Version: ${absl_VERSION}\n\ Libs: -L\${libdir} ${PC_LINKOPTS} $<$<NOT:$<BOOL:${ABSL_CC_LIB_IS_INTERFACE}>>:-labseil_dll>\n\ Cflags: -I\${includedir}${PC_CFLAGS}\n") INSTALL(FILES "${CMAKE_BINARY_DIR}/lib/pkgconfig/abseil_dll.pc" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") target_compile_definitions( abseil_dll @@ -581,6 +638,25 @@ Cflags: -I\${includedir}${PC_CFLAGS}\n") ${ABSL_CC_LIB_DEFINES} ABSL_CONSUME_DLL ) + + if(ABSL_PROPAGATE_CXX_STD) + # Abseil libraries require C++14 as the current minimum standard. When + # compiled with C++17 (either because it is the compiler's default or + # explicitly requested), then Abseil requires C++17. + _absl_target_compile_features_if_available(${_NAME} PUBLIC ${ABSL_INTERNAL_CXX_STD_FEATURE}) + else() + # Note: This is legacy (before CMake 3.8) behavior. Setting the + # target-level CXX_STANDARD property to ABSL_CXX_STANDARD (which is + # initialized by CMAKE_CXX_STANDARD) should have no real effect, since + # that is the default value anyway. + # + # CXX_STANDARD_REQUIRED does guard against the top-level CMake project + # not having enabled CMAKE_CXX_STANDARD_REQUIRED (which prevents + # "decaying" to an older standard if the requested one isn't available). + set_property(TARGET ${_NAME} PROPERTY CXX_STANDARD ${ABSL_CXX_STANDARD}) + set_property(TARGET ${_NAME} PROPERTY CXX_STANDARD_REQUIRED ON) + endif() + install(TARGETS abseil_dll EXPORT ${PROJECT_NAME}Targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} diff --git a/CMake/AbseilHelpers.cmake b/CMake/AbseilHelpers.cmake index 5c348a83..e3569e99 100644 --- a/CMake/AbseilHelpers.cmake +++ b/CMake/AbseilHelpers.cmake @@ -32,35 +32,6 @@ else() set(ABSL_INTERNAL_INCLUDE_WARNING_GUARD "") endif() -function(_absl_target_compile_features_if_available TARGET TYPE FEATURE) - if(FEATURE IN_LIST CMAKE_CXX_COMPILE_FEATURES) - target_compile_features(${TARGET} ${TYPE} ${FEATURE}) - else() - message(WARNING "Feature ${FEATURE} is unknown for the CXX compiler") - endif() -endfunction() - -include(CheckCXXSourceCompiles) - -check_cxx_source_compiles( - [==[ -#ifdef _MSC_VER -# if _MSVC_LANG < 201700L -# error "The compiler defaults or is configured for C++ < 17" -# endif -#elif __cplusplus < 201700L -# error "The compiler defaults or is configured for C++ < 17" -#endif -int main() { return 0; } -]==] - ABSL_INTERNAL_AT_LEAST_CXX17) - -if(ABSL_INTERNAL_AT_LEAST_CXX17) - set(ABSL_INTERNAL_CXX_STD_FEATURE cxx_std_17) -else() - set(ABSL_INTERNAL_CXX_STD_FEATURE cxx_std_14) -endif() - # absl_cc_library() # # CMake function to imitate Bazel's cc_library rule. @@ -313,9 +284,9 @@ Cflags: -I\${includedir}${PC_CFLAGS}\n") endif() if(ABSL_PROPAGATE_CXX_STD) - # Abseil libraries require C++14 as the current minimum standard. - # Top-level application CMake projects should ensure a consistent C++ - # standard for all compiled sources by setting CMAKE_CXX_STANDARD. + # Abseil libraries require C++14 as the current minimum standard. When + # compiled with C++17 (either because it is the compiler's default or + # explicitly requested), then Abseil requires C++17. _absl_target_compile_features_if_available(${_NAME} PUBLIC ${ABSL_INTERNAL_CXX_STD_FEATURE}) else() # Note: This is legacy (before CMake 3.8) behavior. Setting the @@ -80,6 +80,8 @@ Abseil contains the following C++ library components: * [`container`](absl/container/) <br /> The `container` library contains additional STL-style containers, including Abseil's unordered "Swiss table" containers. +* [`crc`](absl/crc/) The `crc` library contains code for + computing error-detecting cyclic redundancy checks on data. * [`debugging`](absl/debugging/) <br /> The `debugging` library contains code useful for enabling leak checks, and stacktrace and symbolization utilities. @@ -39,10 +39,10 @@ http_archive( # Google benchmark. http_archive( - name = "com_github_google_benchmark", # 2021-09-20T09:19:51Z - sha256 = "62e2f2e6d8a744d67e4bbc212fcfd06647080de4253c97ad5c6749e09faf2cb0", - strip_prefix = "benchmark-0baacde3618ca617da95375e0af13ce1baadea47", - urls = ["https://github.com/google/benchmark/archive/0baacde3618ca617da95375e0af13ce1baadea47.zip"], + name = "com_github_google_benchmark", # 2022-12-10T23:42:44Z + sha256 = "a62735f4f4bebc069225aa725dc68eb6836dcdbf550424b5f18e2673e7d40f2c", + strip_prefix = "benchmark-da652a748675b679947710117329e9f77f374f2d", + urls = ["https://github.com/google/benchmark/archive/da652a748675b679947710117329e9f77f374f2d.zip"], ) # Bazel Skylib. diff --git a/absl/CMakeLists.txt b/absl/CMakeLists.txt index 925be19b..19a91c62 100644 --- a/absl/CMakeLists.txt +++ b/absl/CMakeLists.txt @@ -18,6 +18,7 @@ add_subdirectory(base) add_subdirectory(algorithm) add_subdirectory(cleanup) add_subdirectory(container) +add_subdirectory(crc) add_subdirectory(debugging) add_subdirectory(flags) add_subdirectory(functional) diff --git a/absl/algorithm/container.h b/absl/algorithm/container.h index 26b19529..1f57dabf 100644 --- a/absl/algorithm/container.h +++ b/absl/algorithm/container.h @@ -1655,7 +1655,7 @@ bool c_prev_permutation(C& c, LessThan&& comp) { // c_iota() // -// Container-based version of the <algorithm> `std::iota()` function +// Container-based version of the <numeric> `std::iota()` function // to compute successive values of `value`, as if incremented with `++value` // after each element is written. and write them to the container. template <typename Sequence, typename T> @@ -1666,7 +1666,7 @@ void c_iota(Sequence& sequence, T&& value) { } // c_accumulate() // -// Container-based version of the <algorithm> `std::accumulate()` function +// Container-based version of the <numeric> `std::accumulate()` function // to accumulate the element values of a container to `init` and return that // accumulation by value. // @@ -1693,7 +1693,7 @@ decay_t<T> c_accumulate(const Sequence& sequence, T&& init, // c_inner_product() // -// Container-based version of the <algorithm> `std::inner_product()` function +// Container-based version of the <numeric> `std::inner_product()` function // to compute the cumulative inner product of container element pairs. // // Note: Due to a language technicality this function has return type @@ -1724,7 +1724,7 @@ decay_t<T> c_inner_product(const Sequence1& factors1, const Sequence2& factors2, // c_adjacent_difference() // -// Container-based version of the <algorithm> `std::adjacent_difference()` +// Container-based version of the <numeric> `std::adjacent_difference()` // function to compute the difference between each element and the one preceding // it and write it to an iterator. template <typename InputSequence, typename OutputIt> @@ -1747,7 +1747,7 @@ OutputIt c_adjacent_difference(const InputSequence& input, // c_partial_sum() // -// Container-based version of the <algorithm> `std::partial_sum()` function +// Container-based version of the <numeric> `std::partial_sum()` function // to compute the partial sum of the elements in a sequence and write them // to an iterator. The partial sum is the sum of all element values so far in // the sequence. diff --git a/absl/base/policy_checks.h b/absl/base/policy_checks.h index d13073c8..2626fb6a 100644 --- a/absl/base/policy_checks.h +++ b/absl/base/policy_checks.h @@ -44,10 +44,10 @@ // Toolchain Check // ----------------------------------------------------------------------------- -// We support MSVC++ 14.0 update 2 and later. +// We support Visual Studio 2017 (MSVC++ 15.0) and later. // This minimum will go up. -#if defined(_MSC_FULL_VER) && _MSC_FULL_VER < 190023918 && !defined(__clang__) -#error "This package requires Visual Studio 2015 Update 2 or higher." +#if defined(_MSC_VER) && _MSC_VER < 1910 && !defined(__clang__) +#error "This package requires Visual Studio 2017 (MSVC++ 15.0) or higher." #endif // We support gcc 5 and later. diff --git a/absl/base/throw_delegate_test.cc b/absl/base/throw_delegate_test.cc index 5ba4ce55..e74362b7 100644 --- a/absl/base/throw_delegate_test.cc +++ b/absl/base/throw_delegate_test.cc @@ -78,29 +78,97 @@ void ExpectThrowNoWhat(void (*f)()) { #endif } -TEST(ThrowHelper, Test) { - // Not using EXPECT_THROW because we want to check the .what() message too. +TEST(ThrowDelegate, ThrowStdLogicErrorChar) { ExpectThrowChar<std::logic_error>(ThrowStdLogicError); +} + +TEST(ThrowDelegate, ThrowStdInvalidArgumentChar) { ExpectThrowChar<std::invalid_argument>(ThrowStdInvalidArgument); +} + +TEST(ThrowDelegate, ThrowStdDomainErrorChar) { ExpectThrowChar<std::domain_error>(ThrowStdDomainError); +} + +TEST(ThrowDelegate, ThrowStdLengthErrorChar) { ExpectThrowChar<std::length_error>(ThrowStdLengthError); +} + +TEST(ThrowDelegate, ThrowStdOutOfRangeChar) { ExpectThrowChar<std::out_of_range>(ThrowStdOutOfRange); +} + +TEST(ThrowDelegate, ThrowStdRuntimeErrorChar) { ExpectThrowChar<std::runtime_error>(ThrowStdRuntimeError); +} + +TEST(ThrowDelegate, ThrowStdRangeErrorChar) { ExpectThrowChar<std::range_error>(ThrowStdRangeError); +} + +TEST(ThrowDelegate, ThrowStdOverflowErrorChar) { ExpectThrowChar<std::overflow_error>(ThrowStdOverflowError); +} + +TEST(ThrowDelegate, ThrowStdUnderflowErrorChar) { ExpectThrowChar<std::underflow_error>(ThrowStdUnderflowError); +} +TEST(ThrowDelegate, ThrowStdLogicErrorString) { ExpectThrowString<std::logic_error>(ThrowStdLogicError); +} + +TEST(ThrowDelegate, ThrowStdInvalidArgumentString) { ExpectThrowString<std::invalid_argument>(ThrowStdInvalidArgument); +} + +TEST(ThrowDelegate, ThrowStdDomainErrorString) { ExpectThrowString<std::domain_error>(ThrowStdDomainError); +} + +TEST(ThrowDelegate, ThrowStdLengthErrorString) { ExpectThrowString<std::length_error>(ThrowStdLengthError); +} + +TEST(ThrowDelegate, ThrowStdOutOfRangeString) { ExpectThrowString<std::out_of_range>(ThrowStdOutOfRange); +} + +TEST(ThrowDelegate, ThrowStdRuntimeErrorString) { ExpectThrowString<std::runtime_error>(ThrowStdRuntimeError); +} + +TEST(ThrowDelegate, ThrowStdRangeErrorString) { ExpectThrowString<std::range_error>(ThrowStdRangeError); +} + +TEST(ThrowDelegate, ThrowStdOverflowErrorString) { ExpectThrowString<std::overflow_error>(ThrowStdOverflowError); +} + +TEST(ThrowDelegate, ThrowStdUnderflowErrorString) { ExpectThrowString<std::underflow_error>(ThrowStdUnderflowError); +} + +TEST(ThrowDelegate, ThrowStdBadFunctionCallNoWhat) { +#ifdef ABSL_HAVE_EXCEPTIONS + try { + ThrowStdBadFunctionCall(); + FAIL() << "Didn't throw"; + } catch (const std::bad_function_call&) { + } +#ifdef _LIBCPP_VERSION + catch (const std::exception&) { + // https://reviews.llvm.org/D92397 causes issues with the vtable for + // std::bad_function_call when using libc++ as a shared library. + } +#endif +#else + EXPECT_DEATH_IF_SUPPORTED(ThrowStdBadFunctionCall(), ""); +#endif +} - ExpectThrowNoWhat<std::bad_function_call>(ThrowStdBadFunctionCall); +TEST(ThrowDelegate, ThrowStdBadAllocNoWhat) { ExpectThrowNoWhat<std::bad_alloc>(ThrowStdBadAlloc); } diff --git a/absl/container/btree_map.h b/absl/container/btree_map.h index 479db8b7..cd3ee2b4 100644 --- a/absl/container/btree_map.h +++ b/absl/container/btree_map.h @@ -42,10 +42,10 @@ // Importantly, insertions and deletions may invalidate outstanding iterators, // pointers, and references to elements. Such invalidations are typically only // an issue if insertion and deletion operations are interleaved with the use of -// more than one iterator, pointer, or reference simultaneously. For this -// reason, `insert()` and `erase()` return a valid iterator at the current -// position. Another important difference is that key-types must be -// copy-constructible. +// more than one iterator, pointer, or reference simultaneously. For this +// reason, `insert()`, `erase()`, and `extract_and_get_next()` return a valid +// iterator at the current position. Another important difference is that +// key-types must be copy-constructible. // // Another API difference is that btree iterators can be subtracted, and this // is faster than using std::distance. @@ -325,7 +325,8 @@ class btree_map // btree_map::extract() // // Extracts the indicated element, erasing it in the process, and returns it - // as a C++17-compatible node handle. Overloads are listed below. + // as a C++17-compatible node handle. Any references, pointers, or iterators + // are invalidated. Overloads are listed below. // // node_type extract(const_iterator position): // @@ -350,6 +351,21 @@ class btree_map // It does NOT refer to the data layout of the underlying btree. using Base::extract; + // btree_map::extract_and_get_next() + // + // Extracts the indicated element, erasing it in the process, and returns it + // as a C++17-compatible node handle along with an iterator to the next + // element. + // + // extract_and_get_next_return_type extract_and_get_next( + // const_iterator position): + // + // Extracts the element at the indicated position, returns a struct + // containing a member named `node`: a node handle owning that extracted + // data and a member named `next`: an iterator pointing to the next element + // in the btree. + using Base::extract_and_get_next; + // btree_map::merge() // // Extracts elements from a given `source` btree_map into this @@ -701,6 +717,21 @@ class btree_multimap // It does NOT refer to the data layout of the underlying btree. using Base::extract; + // btree_multimap::extract_and_get_next() + // + // Extracts the indicated element, erasing it in the process, and returns it + // as a C++17-compatible node handle along with an iterator to the next + // element. + // + // extract_and_get_next_return_type extract_and_get_next( + // const_iterator position): + // + // Extracts the element at the indicated position, returns a struct + // containing a member named `node`: a node handle owning that extracted + // data and a member named `next`: an iterator pointing to the next element + // in the btree. + using Base::extract_and_get_next; + // btree_multimap::merge() // // Extracts all elements from a given `source` btree_multimap into this diff --git a/absl/container/btree_set.h b/absl/container/btree_set.h index bbff65d1..51dc42b7 100644 --- a/absl/container/btree_set.h +++ b/absl/container/btree_set.h @@ -43,8 +43,8 @@ // pointers, and references to elements. Such invalidations are typically only // an issue if insertion and deletion operations are interleaved with the use of // more than one iterator, pointer, or reference simultaneously. For this -// reason, `insert()` and `erase()` return a valid iterator at the current -// position. +// reason, `insert()`, `erase()`, and `extract_and_get_next()` return a valid +// iterator at the current position. // // Another API difference is that btree iterators can be subtracted, and this // is faster than using std::distance. @@ -272,7 +272,8 @@ class btree_set // btree_set::extract() // // Extracts the indicated element, erasing it in the process, and returns it - // as a C++17-compatible node handle. Overloads are listed below. + // as a C++17-compatible node handle. Any references, pointers, or iterators + // are invalidated. Overloads are listed below. // // node_type extract(const_iterator position): // @@ -292,6 +293,21 @@ class btree_set // It does NOT refer to the data layout of the underlying btree. using Base::extract; + // btree_set::extract_and_get_next() + // + // Extracts the indicated element, erasing it in the process, and returns it + // as a C++17-compatible node handle along with an iterator to the next + // element. + // + // extract_and_get_next_return_type extract_and_get_next( + // const_iterator position): + // + // Extracts the element at the indicated position, returns a struct + // containing a member named `node`: a node handle owning that extracted + // data and a member named `next`: an iterator pointing to the next element + // in the btree. + using Base::extract_and_get_next; + // btree_set::merge() // // Extracts elements from a given `source` btree_set into this @@ -614,6 +630,21 @@ class btree_multiset // It does NOT refer to the data layout of the underlying btree. using Base::extract; + // btree_multiset::extract_and_get_next() + // + // Extracts the indicated element, erasing it in the process, and returns it + // as a C++17-compatible node handle along with an iterator to the next + // element. + // + // extract_and_get_next_return_type extract_and_get_next( + // const_iterator position): + // + // Extracts the element at the indicated position, returns a struct + // containing a member named `node`: a node handle owning that extracted + // data and a member named `next`: an iterator pointing to the next element + // in the btree. + using Base::extract_and_get_next; + // btree_multiset::merge() // // Extracts all elements from a given `source` btree_multiset into this diff --git a/absl/container/btree_test.cc b/absl/container/btree_test.cc index 6a5351fe..cc763b29 100644 --- a/absl/container/btree_test.cc +++ b/absl/container/btree_test.cc @@ -74,6 +74,16 @@ void CheckPairEquals(const std::pair<T, U> &x, const std::pair<V, W> &y) { CheckPairEquals(x.first, y.first); CheckPairEquals(x.second, y.second); } + +bool IsAssertEnabled() { + // Use an assert with side-effects to figure out if they are actually enabled. + bool assert_enabled = false; + assert([&]() { // NOLINT + assert_enabled = true; + return true; + }()); + return assert_enabled; +} } // namespace // The base class for a sorted associative container checker. TreeType is the @@ -1651,10 +1661,9 @@ TEST(Btree, BtreeMultisetEmplace) { auto iter = s.emplace(value_to_insert); ASSERT_NE(iter, s.end()); EXPECT_EQ(*iter, value_to_insert); - auto iter2 = s.emplace(value_to_insert); - EXPECT_NE(iter2, iter); - ASSERT_NE(iter2, s.end()); - EXPECT_EQ(*iter2, value_to_insert); + iter = s.emplace(value_to_insert); + ASSERT_NE(iter, s.end()); + EXPECT_EQ(*iter, value_to_insert); auto result = s.equal_range(value_to_insert); EXPECT_EQ(std::distance(result.first, result.second), 2); } @@ -1665,44 +1674,45 @@ TEST(Btree, BtreeMultisetEmplaceHint) { auto iter = s.emplace(value_to_insert); ASSERT_NE(iter, s.end()); EXPECT_EQ(*iter, value_to_insert); - auto emplace_iter = s.emplace_hint(iter, value_to_insert); - EXPECT_NE(emplace_iter, iter); - ASSERT_NE(emplace_iter, s.end()); - EXPECT_EQ(*emplace_iter, value_to_insert); + iter = s.emplace_hint(iter, value_to_insert); + // The new element should be before the previously inserted one. + EXPECT_EQ(iter, s.lower_bound(value_to_insert)); + ASSERT_NE(iter, s.end()); + EXPECT_EQ(*iter, value_to_insert); } TEST(Btree, BtreeMultimapEmplace) { const int key_to_insert = 123456; const char value0[] = "a"; - absl::btree_multimap<int, std::string> s; - auto iter = s.emplace(key_to_insert, value0); - ASSERT_NE(iter, s.end()); + absl::btree_multimap<int, std::string> m; + auto iter = m.emplace(key_to_insert, value0); + ASSERT_NE(iter, m.end()); EXPECT_EQ(iter->first, key_to_insert); EXPECT_EQ(iter->second, value0); const char value1[] = "b"; - auto iter2 = s.emplace(key_to_insert, value1); - EXPECT_NE(iter2, iter); - ASSERT_NE(iter2, s.end()); - EXPECT_EQ(iter2->first, key_to_insert); - EXPECT_EQ(iter2->second, value1); - auto result = s.equal_range(key_to_insert); + iter = m.emplace(key_to_insert, value1); + ASSERT_NE(iter, m.end()); + EXPECT_EQ(iter->first, key_to_insert); + EXPECT_EQ(iter->second, value1); + auto result = m.equal_range(key_to_insert); EXPECT_EQ(std::distance(result.first, result.second), 2); } TEST(Btree, BtreeMultimapEmplaceHint) { const int key_to_insert = 123456; const char value0[] = "a"; - absl::btree_multimap<int, std::string> s; - auto iter = s.emplace(key_to_insert, value0); - ASSERT_NE(iter, s.end()); + absl::btree_multimap<int, std::string> m; + auto iter = m.emplace(key_to_insert, value0); + ASSERT_NE(iter, m.end()); EXPECT_EQ(iter->first, key_to_insert); EXPECT_EQ(iter->second, value0); const char value1[] = "b"; - auto emplace_iter = s.emplace_hint(iter, key_to_insert, value1); - EXPECT_NE(emplace_iter, iter); - ASSERT_NE(emplace_iter, s.end()); - EXPECT_EQ(emplace_iter->first, key_to_insert); - EXPECT_EQ(emplace_iter->second, value1); + iter = m.emplace_hint(iter, key_to_insert, value1); + // The new element should be before the previously inserted one. + EXPECT_EQ(iter, m.lower_bound(key_to_insert)); + ASSERT_NE(iter, m.end()); + EXPECT_EQ(iter->first, key_to_insert); + EXPECT_EQ(iter->second, value1); } TEST(Btree, ConstIteratorAccessors) { @@ -2113,6 +2123,79 @@ TEST(Btree, ExtractMultiMapEquivalentKeys) { } } +TEST(Btree, ExtractAndGetNextSet) { + absl::btree_set<int> src = {1, 2, 3, 4, 5}; + auto it = src.find(3); + auto extracted_and_next = src.extract_and_get_next(it); + EXPECT_THAT(src, ElementsAre(1, 2, 4, 5)); + EXPECT_EQ(extracted_and_next.node.value(), 3); + EXPECT_EQ(*extracted_and_next.next, 4); +} + +TEST(Btree, ExtractAndGetNextMultiSet) { + absl::btree_multiset<int> src = {1, 2, 3, 4, 5}; + auto it = src.find(3); + auto extracted_and_next = src.extract_and_get_next(it); + EXPECT_THAT(src, ElementsAre(1, 2, 4, 5)); + EXPECT_EQ(extracted_and_next.node.value(), 3); + EXPECT_EQ(*extracted_and_next.next, 4); +} + +TEST(Btree, ExtractAndGetNextMap) { + absl::btree_map<int, int> src = {{1, 2}, {3, 4}, {5, 6}}; + auto it = src.find(3); + auto extracted_and_next = src.extract_and_get_next(it); + EXPECT_THAT(src, ElementsAre(Pair(1, 2), Pair(5, 6))); + EXPECT_EQ(extracted_and_next.node.key(), 3); + EXPECT_EQ(extracted_and_next.node.mapped(), 4); + EXPECT_THAT(*extracted_and_next.next, Pair(5, 6)); +} + +TEST(Btree, ExtractAndGetNextMultiMap) { + absl::btree_multimap<int, int> src = {{1, 2}, {3, 4}, {5, 6}}; + auto it = src.find(3); + auto extracted_and_next = src.extract_and_get_next(it); + EXPECT_THAT(src, ElementsAre(Pair(1, 2), Pair(5, 6))); + EXPECT_EQ(extracted_and_next.node.key(), 3); + EXPECT_EQ(extracted_and_next.node.mapped(), 4); + EXPECT_THAT(*extracted_and_next.next, Pair(5, 6)); +} + +TEST(Btree, ExtractAndGetNextEndIter) { + absl::btree_set<int> src = {1, 2, 3, 4, 5}; + auto it = src.find(5); + auto extracted_and_next = src.extract_and_get_next(it); + EXPECT_THAT(src, ElementsAre(1, 2, 3, 4)); + EXPECT_EQ(extracted_and_next.node.value(), 5); + EXPECT_EQ(extracted_and_next.next, src.end()); +} + +TEST(Btree, ExtractDoesntCauseExtraMoves) { +#ifdef _MSC_VER + GTEST_SKIP() << "This test fails on MSVC."; +#endif + + using Set = absl::btree_set<MovableOnlyInstance>; + std::array<std::function<void(Set &)>, 3> extracters = { + [](Set &s) { auto node = s.extract(s.begin()); }, + [](Set &s) { auto ret = s.extract_and_get_next(s.begin()); }, + [](Set &s) { auto node = s.extract(MovableOnlyInstance(0)); }}; + + InstanceTracker tracker; + for (int i = 0; i < 3; ++i) { + Set s; + s.insert(MovableOnlyInstance(0)); + tracker.ResetCopiesMovesSwaps(); + + extracters[i](s); + // We expect to see exactly 1 move: from the original slot into the + // extracted node. + EXPECT_EQ(tracker.copies(), 0) << i; + EXPECT_EQ(tracker.moves(), 1) << i; + EXPECT_EQ(tracker.swaps(), 0) << i; + } +} + // For multisets, insert with hint also affects correctness because we need to // insert immediately before the hint if possible. struct InsertMultiHintData { @@ -3005,8 +3088,9 @@ TEST(Btree, ConstructImplicitlyWithUnadaptedComparator) { absl::btree_set<MultiKey, MultiKeyComp> set = {{}, MultiKeyComp{}}; } -#ifndef NDEBUG TEST(Btree, InvalidComparatorsCaught) { + if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled."; + { struct ZeroAlwaysLessCmp { bool operator()(int lhs, int rhs) const { @@ -3054,7 +3138,6 @@ TEST(Btree, InvalidComparatorsCaught) { EXPECT_DEATH(set.insert({0, 1, 2}), "lhs_comp_rhs < 0 -> rhs_comp_lhs > 0"); } } -#endif #ifndef _MSC_VER // This test crashes on MSVC. @@ -3084,6 +3167,14 @@ TEST(Btree, InvalidIteratorUse) { set.erase(1); EXPECT_DEATH(*it, "invalidated iterator"); } + { + absl::btree_set<int> set; + for (int i = 0; i < 10; ++i) set.insert(i); + auto it = set.insert(20).first; + set.insert(30); + EXPECT_DEATH(void(it == set.begin()), "invalidated iterator"); + EXPECT_DEATH(void(set.begin() == it), "invalidated iterator"); + } } #endif @@ -3340,6 +3431,39 @@ TEST(Btree, IteratorSubtraction) { } } +TEST(Btree, DereferencingEndIterator) { + if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled."; + + absl::btree_set<int> set; + for (int i = 0; i < 1000; ++i) set.insert(i); + EXPECT_DEATH(*set.end(), R"regex(Dereferencing end\(\) iterator)regex"); +} + +TEST(Btree, InvalidIteratorComparison) { + if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled."; + + absl::btree_set<int> set1, set2; + for (int i = 0; i < 1000; ++i) { + set1.insert(i); + set2.insert(i); + } + + constexpr const char *kValueInitDeathMessage = + "Comparing default-constructed iterator with .*non-default-constructed " + "iterator"; + typename absl::btree_set<int>::iterator iter1, iter2; + EXPECT_EQ(iter1, iter2); + EXPECT_DEATH(void(set1.begin() == iter1), kValueInitDeathMessage); + EXPECT_DEATH(void(iter1 == set1.begin()), kValueInitDeathMessage); + + constexpr const char *kDifferentContainerDeathMessage = + "Comparing iterators from different containers"; + iter1 = set1.begin(); + iter2 = set2.begin(); + EXPECT_DEATH(void(iter1 == iter2), kDifferentContainerDeathMessage); + EXPECT_DEATH(void(iter2 == iter1), kDifferentContainerDeathMessage); +} + } // namespace } // namespace container_internal ABSL_NAMESPACE_END diff --git a/absl/container/flat_hash_map_test.cc b/absl/container/flat_hash_map_test.cc index 263951f1..03171f6d 100644 --- a/absl/container/flat_hash_map_test.cc +++ b/absl/container/flat_hash_map_test.cc @@ -311,6 +311,14 @@ TEST(FlatHashMap, Reserve) { } } +TEST(FlatHashMap, RecursiveTypeCompiles) { + struct RecursiveType { + flat_hash_map<int, RecursiveType> m; + }; + RecursiveType t; + t.m[0] = RecursiveType{}; +} + } // namespace } // namespace container_internal ABSL_NAMESPACE_END diff --git a/absl/container/inlined_vector.h b/absl/container/inlined_vector.h index 10b1896b..7058f375 100644 --- a/absl/container/inlined_vector.h +++ b/absl/container/inlined_vector.h @@ -97,14 +97,11 @@ class InlinedVector { using DisableIfAtLeastForwardIterator = absl::enable_if_t< !inlined_vector_internal::IsAtLeastForwardIterator<Iterator>::value, int>; - struct MemcpyPolicy {}; - struct ElementwiseAssignPolicy {}; - struct ElementwiseConstructPolicy {}; - - using MoveAssignmentPolicy = absl::conditional_t< - IsMemcpyOk<A>::value, MemcpyPolicy, - absl::conditional_t<IsMoveAssignOk<A>::value, ElementwiseAssignPolicy, - ElementwiseConstructPolicy>>; + using MemcpyPolicy = typename Storage::MemcpyPolicy; + using ElementwiseAssignPolicy = typename Storage::ElementwiseAssignPolicy; + using ElementwiseConstructPolicy = + typename Storage::ElementwiseConstructPolicy; + using MoveAssignmentPolicy = typename Storage::MoveAssignmentPolicy; public: using allocator_type = A; @@ -664,10 +661,22 @@ class InlinedVector { ABSL_HARDENING_ASSERT(pos <= end()); value_type dealias(std::forward<Args>(args)...); + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102329#c2 + // It appears that GCC thinks that since `pos` is a const pointer and may + // point to uninitialized memory at this point, a warning should be + // issued. But `pos` is actually only used to compute an array index to + // write to. +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif return storage_.Insert(pos, IteratorValueAdapter<A, MoveIterator<A>>( MoveIterator<A>(std::addressof(dealias))), 1); +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic pop +#endif } // `InlinedVector::emplace_back(...)` diff --git a/absl/container/inlined_vector_test.cc b/absl/container/inlined_vector_test.cc index 65ddbab6..898b40db 100644 --- a/absl/container/inlined_vector_test.cc +++ b/absl/container/inlined_vector_test.cc @@ -1208,6 +1208,8 @@ TYPED_TEST_P(InstanceTest, CountConstructorsDestructorsOnMoveAssignment) { } TEST(CountElemAssign, SimpleTypeWithInlineBacking) { + const size_t inlined_capacity = absl::InlinedVector<int, 2>().capacity(); + for (size_t original_size = 0; original_size <= 5; ++original_size) { SCOPED_TRACE(original_size); // Original contents are [12345, 12345, ...] @@ -1217,9 +1219,9 @@ TEST(CountElemAssign, SimpleTypeWithInlineBacking) { original_contents.end()); v.assign(2, 123); EXPECT_THAT(v, AllOf(SizeIs(2u), ElementsAre(123, 123))); - if (original_size <= 2) { + if (original_size <= inlined_capacity) { // If the original had inline backing, it should stay inline. - EXPECT_EQ(2u, v.capacity()); + EXPECT_EQ(v.capacity(), inlined_capacity); } } } @@ -1360,6 +1362,8 @@ TEST(RangedConstructor, ElementsAreConstructed) { } TEST(RangedAssign, SimpleType) { + const size_t inlined_capacity = absl::InlinedVector<int, 3>().capacity(); + // Test for all combinations of original sizes (empty and non-empty inline, // and out of line) and target sizes. for (size_t original_size = 0; original_size <= 5; ++original_size) { @@ -1367,13 +1371,13 @@ TEST(RangedAssign, SimpleType) { // Original contents are [12345, 12345, ...] std::vector<int> original_contents(original_size, 12345); - for (int target_size = 0; target_size <= 5; ++target_size) { + for (size_t target_size = 0; target_size <= 5; ++target_size) { SCOPED_TRACE(target_size); // New contents are [3, 4, ...] std::vector<int> new_contents; - for (int i = 0; i < target_size; ++i) { - new_contents.push_back(i + 3); + for (size_t i = 0; i < target_size; ++i) { + new_contents.push_back(static_cast<int>(i + 3)); } absl::InlinedVector<int, 3> v(original_contents.begin(), @@ -1382,9 +1386,10 @@ TEST(RangedAssign, SimpleType) { EXPECT_EQ(new_contents.size(), v.size()); EXPECT_LE(new_contents.size(), v.capacity()); - if (target_size <= 3 && original_size <= 3) { + if (target_size <= inlined_capacity && + original_size <= inlined_capacity) { // Storage should stay inline when target size is small. - EXPECT_EQ(3u, v.capacity()); + EXPECT_EQ(v.capacity(), inlined_capacity); } EXPECT_THAT(v, ElementsAreArray(new_contents)); } @@ -1470,9 +1475,12 @@ TEST(InitializerListConstructor, DisparateTypesInList) { } TEST(InitializerListConstructor, ComplexTypeWithInlineBacking) { - EXPECT_THAT((absl::InlinedVector<CopyableMovableInstance, 1>{ - CopyableMovableInstance(0)}), - AllOf(SizeIs(1u), CapacityIs(1u), ElementsAre(ValueIs(0)))); + const size_t inlined_capacity = + absl::InlinedVector<CopyableMovableInstance, 1>().capacity(); + EXPECT_THAT( + (absl::InlinedVector<CopyableMovableInstance, 1>{ + CopyableMovableInstance(0)}), + AllOf(SizeIs(1u), CapacityIs(inlined_capacity), ElementsAre(ValueIs(0)))); } TEST(InitializerListConstructor, ComplexTypeWithReallocationRequired) { @@ -1841,98 +1849,212 @@ MATCHER(HasValue, "") { return ::testing::get<0>(arg).value() == ::testing::get<1>(arg); } -TEST(MoveAssignment, NonAssignable) { +TEST(NonAssignableMoveAssignmentTest, AllocatedToInline) { using X = MoveConstructibleOnlyInstance; - { - InstanceTracker tracker; - absl::InlinedVector<X, 2> inlined; - inlined.emplace_back(1); - absl::InlinedVector<X, 2> allocated; - allocated.emplace_back(1); - allocated.emplace_back(2); - allocated.emplace_back(3); - tracker.ResetCopiesMovesSwaps(); + InstanceTracker tracker; + absl::InlinedVector<X, 2> inlined; + inlined.emplace_back(1); + absl::InlinedVector<X, 2> allocated; + allocated.emplace_back(1); + allocated.emplace_back(2); + allocated.emplace_back(3); + tracker.ResetCopiesMovesSwaps(); - inlined = std::move(allocated); - // passed ownership of the allocated storage - EXPECT_EQ(tracker.moves(), 0); - EXPECT_EQ(tracker.live_instances(), 3); + inlined = std::move(allocated); + // passed ownership of the allocated storage + EXPECT_EQ(tracker.moves(), 0); + EXPECT_EQ(tracker.live_instances(), 3); - EXPECT_THAT(inlined, Pointwise(HasValue(), {1, 2, 3})); - } + EXPECT_THAT(inlined, Pointwise(HasValue(), {1, 2, 3})); +} - { - InstanceTracker tracker; - absl::InlinedVector<X, 2> inlined; - inlined.emplace_back(1); - absl::InlinedVector<X, 2> allocated; - allocated.emplace_back(1); - allocated.emplace_back(2); - allocated.emplace_back(3); - tracker.ResetCopiesMovesSwaps(); +TEST(NonAssignableMoveAssignmentTest, InlineToAllocated) { + using X = MoveConstructibleOnlyInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> inlined; + inlined.emplace_back(1); + absl::InlinedVector<X, 2> allocated; + allocated.emplace_back(1); + allocated.emplace_back(2); + allocated.emplace_back(3); + tracker.ResetCopiesMovesSwaps(); - allocated = std::move(inlined); - // Moved elements - EXPECT_EQ(tracker.moves(), 1); - EXPECT_EQ(tracker.live_instances(), 1); + allocated = std::move(inlined); + // Moved elements + EXPECT_EQ(tracker.moves(), 1); + EXPECT_EQ(tracker.live_instances(), 1); - EXPECT_THAT(allocated, Pointwise(HasValue(), {1})); - } + EXPECT_THAT(allocated, Pointwise(HasValue(), {1})); +} - { - InstanceTracker tracker; - absl::InlinedVector<X, 2> inlined_a; - inlined_a.emplace_back(1); - absl::InlinedVector<X, 2> inlined_b; - inlined_b.emplace_back(1); - tracker.ResetCopiesMovesSwaps(); +TEST(NonAssignableMoveAssignmentTest, InlineToInline) { + using X = MoveConstructibleOnlyInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> inlined_a; + inlined_a.emplace_back(1); + absl::InlinedVector<X, 2> inlined_b; + inlined_b.emplace_back(1); + tracker.ResetCopiesMovesSwaps(); - inlined_a = std::move(inlined_b); - // Moved elements - EXPECT_EQ(tracker.moves(), 1); - EXPECT_EQ(tracker.live_instances(), 1); + inlined_a = std::move(inlined_b); + // Moved elements + EXPECT_EQ(tracker.moves(), 1); + EXPECT_EQ(tracker.live_instances(), 1); - EXPECT_THAT(inlined_a, Pointwise(HasValue(), {1})); - } + EXPECT_THAT(inlined_a, Pointwise(HasValue(), {1})); +} - { - InstanceTracker tracker; - absl::InlinedVector<X, 2> allocated_a; - allocated_a.emplace_back(1); - allocated_a.emplace_back(2); - allocated_a.emplace_back(3); - absl::InlinedVector<X, 2> allocated_b; - allocated_b.emplace_back(4); - allocated_b.emplace_back(5); - allocated_b.emplace_back(6); - allocated_b.emplace_back(7); - tracker.ResetCopiesMovesSwaps(); +TEST(NonAssignableMoveAssignmentTest, AllocatedToAllocated) { + using X = MoveConstructibleOnlyInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> allocated_a; + allocated_a.emplace_back(1); + allocated_a.emplace_back(2); + allocated_a.emplace_back(3); + absl::InlinedVector<X, 2> allocated_b; + allocated_b.emplace_back(4); + allocated_b.emplace_back(5); + allocated_b.emplace_back(6); + allocated_b.emplace_back(7); + tracker.ResetCopiesMovesSwaps(); - allocated_a = std::move(allocated_b); - // passed ownership of the allocated storage - EXPECT_EQ(tracker.moves(), 0); - EXPECT_EQ(tracker.live_instances(), 4); + allocated_a = std::move(allocated_b); + // passed ownership of the allocated storage + EXPECT_EQ(tracker.moves(), 0); + EXPECT_EQ(tracker.live_instances(), 4); - EXPECT_THAT(allocated_a, Pointwise(HasValue(), {4, 5, 6, 7})); - } + EXPECT_THAT(allocated_a, Pointwise(HasValue(), {4, 5, 6, 7})); +} - { - InstanceTracker tracker; - absl::InlinedVector<X, 2> v; - v.emplace_back(1); - v.emplace_back(2); - v.emplace_back(3); +TEST(NonAssignableMoveAssignmentTest, AssignThis) { + using X = MoveConstructibleOnlyInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> v; + v.emplace_back(1); + v.emplace_back(2); + v.emplace_back(3); - tracker.ResetCopiesMovesSwaps(); + tracker.ResetCopiesMovesSwaps(); - // Obfuscated in order to pass -Wself-move. - v = std::move(*std::addressof(v)); - // nothing happens - EXPECT_EQ(tracker.moves(), 0); - EXPECT_EQ(tracker.live_instances(), 3); + // Obfuscated in order to pass -Wself-move. + v = std::move(*std::addressof(v)); + // nothing happens + EXPECT_EQ(tracker.moves(), 0); + EXPECT_EQ(tracker.live_instances(), 3); - EXPECT_THAT(v, Pointwise(HasValue(), {1, 2, 3})); - } + EXPECT_THAT(v, Pointwise(HasValue(), {1, 2, 3})); +} + +class NonSwappableInstance : public absl::test_internal::BaseCountedInstance { + public: + explicit NonSwappableInstance(int x) : BaseCountedInstance(x) {} + NonSwappableInstance(const NonSwappableInstance& other) = default; + NonSwappableInstance& operator=(const NonSwappableInstance& other) = default; + NonSwappableInstance(NonSwappableInstance&& other) = default; + NonSwappableInstance& operator=(NonSwappableInstance&& other) = default; +}; + +void swap(NonSwappableInstance&, NonSwappableInstance&) = delete; + +TEST(NonSwappableSwapTest, InlineAndAllocatedTransferStorageAndMove) { + using X = NonSwappableInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> inlined; + inlined.emplace_back(1); + absl::InlinedVector<X, 2> allocated; + allocated.emplace_back(1); + allocated.emplace_back(2); + allocated.emplace_back(3); + tracker.ResetCopiesMovesSwaps(); + + inlined.swap(allocated); + EXPECT_EQ(tracker.moves(), 1); + EXPECT_EQ(tracker.live_instances(), 4); + + EXPECT_THAT(inlined, Pointwise(HasValue(), {1, 2, 3})); +} + +TEST(NonSwappableSwapTest, InlineAndInlineMoveIndividualElements) { + using X = NonSwappableInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> inlined_a; + inlined_a.emplace_back(1); + absl::InlinedVector<X, 2> inlined_b; + inlined_b.emplace_back(2); + tracker.ResetCopiesMovesSwaps(); + + inlined_a.swap(inlined_b); + EXPECT_EQ(tracker.moves(), 3); + EXPECT_EQ(tracker.live_instances(), 2); + + EXPECT_THAT(inlined_a, Pointwise(HasValue(), {2})); + EXPECT_THAT(inlined_b, Pointwise(HasValue(), {1})); +} + +TEST(NonSwappableSwapTest, AllocatedAndAllocatedOnlyTransferStorage) { + using X = NonSwappableInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> allocated_a; + allocated_a.emplace_back(1); + allocated_a.emplace_back(2); + allocated_a.emplace_back(3); + absl::InlinedVector<X, 2> allocated_b; + allocated_b.emplace_back(4); + allocated_b.emplace_back(5); + allocated_b.emplace_back(6); + allocated_b.emplace_back(7); + tracker.ResetCopiesMovesSwaps(); + + allocated_a.swap(allocated_b); + EXPECT_EQ(tracker.moves(), 0); + EXPECT_EQ(tracker.live_instances(), 7); + + EXPECT_THAT(allocated_a, Pointwise(HasValue(), {4, 5, 6, 7})); + EXPECT_THAT(allocated_b, Pointwise(HasValue(), {1, 2, 3})); +} + +TEST(NonSwappableSwapTest, SwapThis) { + using X = NonSwappableInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> v; + v.emplace_back(1); + v.emplace_back(2); + v.emplace_back(3); + + tracker.ResetCopiesMovesSwaps(); + + v.swap(v); + EXPECT_EQ(tracker.moves(), 0); + EXPECT_EQ(tracker.live_instances(), 3); + + EXPECT_THAT(v, Pointwise(HasValue(), {1, 2, 3})); +} + +template <size_t N> +using CharVec = absl::InlinedVector<char, N>; + +// Warning: This struct "simulates" the type `InlinedVector::Storage::Allocated` +// to make reasonable expectations for inlined storage capacity optimization. If +// implementation changes `Allocated`, then `MySpan` and tests that use it need +// to be updated accordingly. +template <typename T> +struct MySpan { + T* data; + size_t size; +}; + +TEST(StorageTest, InlinedCapacityAutoIncrease) { + // The requested capacity is auto increased to `sizeof(MySpan<char>)`. + EXPECT_GT(CharVec<1>().capacity(), 1); + EXPECT_EQ(CharVec<1>().capacity(), sizeof(MySpan<char>)); + EXPECT_EQ(CharVec<1>().capacity(), CharVec<2>().capacity()); + EXPECT_EQ(sizeof(CharVec<1>), sizeof(CharVec<2>)); + + // The requested capacity is auto increased to + // `sizeof(MySpan<int>) / sizeof(int)`. + EXPECT_GT((absl::InlinedVector<int, 1>().capacity()), 1); + EXPECT_EQ((absl::InlinedVector<int, 1>().capacity()), + sizeof(MySpan<int>) / sizeof(int)); } } // anonymous namespace diff --git a/absl/container/internal/btree.h b/absl/container/internal/btree.h index 5000d1c3..d734676a 100644 --- a/absl/container/internal/btree.h +++ b/absl/container/internal/btree.h @@ -1017,8 +1017,61 @@ class btree_node { friend struct btree_access; }; +template <typename Node> +bool AreNodesFromSameContainer(const Node *node_a, const Node *node_b) { + // If either node is null, then give up on checking whether they're from the + // same container. (If exactly one is null, then we'll trigger the + // default-constructed assert in Equals.) + if (node_a == nullptr || node_b == nullptr) return true; + while (!node_a->is_root()) node_a = node_a->parent(); + while (!node_b->is_root()) node_b = node_b->parent(); + return node_a == node_b; +} + +class btree_iterator_generation_info_enabled { + public: + explicit btree_iterator_generation_info_enabled(uint32_t g) + : generation_(g) {} + + // Updates the generation. For use internally right before we return an + // iterator to the user. + template <typename Node> + void update_generation(const Node *node) { + if (node != nullptr) generation_ = node->generation(); + } + uint32_t generation() const { return generation_; } + + template <typename Node> + void assert_valid_generation(const Node *node) const { + if (node != nullptr && node->generation() != generation_) { + ABSL_INTERNAL_LOG( + FATAL, + "Attempting to use an invalidated iterator. The corresponding b-tree " + "container has been mutated since this iterator was constructed."); + } + } + + private: + // Used to check that the iterator hasn't been invalidated. + uint32_t generation_; +}; + +class btree_iterator_generation_info_disabled { + public: + explicit btree_iterator_generation_info_disabled(uint32_t) {} + void update_generation(const void *) {} + uint32_t generation() const { return 0; } + void assert_valid_generation(const void *) const {} +}; + +#ifdef ABSL_BTREE_ENABLE_GENERATIONS +using btree_iterator_generation_info = btree_iterator_generation_info_enabled; +#else +using btree_iterator_generation_info = btree_iterator_generation_info_disabled; +#endif + template <typename Node, typename Reference, typename Pointer> -class btree_iterator { +class btree_iterator : private btree_iterator_generation_info { using field_type = typename Node::field_type; using key_type = typename Node::key_type; using size_type = typename Node::size_type; @@ -1049,13 +1102,11 @@ class btree_iterator { btree_iterator() : btree_iterator(nullptr, -1) {} explicit btree_iterator(Node *n) : btree_iterator(n, n->start()) {} - btree_iterator(Node *n, int p) : node_(n), position_(p) { -#ifdef ABSL_BTREE_ENABLE_GENERATIONS - // Use `~uint32_t{}` as a sentinel value for iterator generations so it - // doesn't match the initial value for the actual generation. - generation_ = n != nullptr ? n->generation() : ~uint32_t{}; -#endif - } + btree_iterator(Node *n, int p) + : btree_iterator_generation_info(n != nullptr ? n->generation() + : ~uint32_t{}), + node_(n), + position_(p) {} // NOTE: this SFINAE allows for implicit conversions from iterator to // const_iterator, but it specifically avoids hiding the copy constructor so @@ -1066,23 +1117,21 @@ class btree_iterator { std::is_same<btree_iterator, const_iterator>::value, int> = 0> btree_iterator(const btree_iterator<N, R, P> other) // NOLINT - : node_(other.node_), position_(other.position_) { -#ifdef ABSL_BTREE_ENABLE_GENERATIONS - generation_ = other.generation_; -#endif - } + : btree_iterator_generation_info(other), + node_(other.node_), + position_(other.position_) {} bool operator==(const iterator &other) const { - return node_ == other.node_ && position_ == other.position_; + return Equals(other); } bool operator==(const const_iterator &other) const { - return node_ == other.node_ && position_ == other.position_; + return Equals(other); } bool operator!=(const iterator &other) const { - return node_ != other.node_ || position_ != other.position_; + return !Equals(other); } bool operator!=(const const_iterator &other) const { - return node_ != other.node_ || position_ != other.position_; + return !Equals(other); } // Returns n such that n calls to ++other yields *this. @@ -1098,9 +1147,12 @@ class btree_iterator { // Accessors for the key/value the iterator is pointing at. reference operator*() const { ABSL_HARDENING_ASSERT(node_ != nullptr); - ABSL_HARDENING_ASSERT(node_->start() <= position_); - ABSL_HARDENING_ASSERT(node_->finish() > position_); - assert_valid_generation(); + assert_valid_generation(node_); + ABSL_HARDENING_ASSERT(position_ >= node_->start()); + if (position_ >= node_->finish()) { + ABSL_HARDENING_ASSERT(!IsEndIterator() && "Dereferencing end() iterator"); + ABSL_HARDENING_ASSERT(position_ < node_->finish()); + } return node_->value(static_cast<field_type>(position_)); } pointer operator->() const { return &operator*(); } @@ -1151,11 +1203,33 @@ class btree_iterator { std::is_same<btree_iterator, iterator>::value, int> = 0> explicit btree_iterator(const btree_iterator<N, R, P> other) - : node_(const_cast<node_type *>(other.node_)), - position_(other.position_) { -#ifdef ABSL_BTREE_ENABLE_GENERATIONS - generation_ = other.generation_; -#endif + : btree_iterator_generation_info(other.generation()), + node_(const_cast<node_type *>(other.node_)), + position_(other.position_) {} + + bool Equals(const const_iterator other) const { + ABSL_HARDENING_ASSERT(((node_ == nullptr && other.node_ == nullptr) || + (node_ != nullptr && other.node_ != nullptr)) && + "Comparing default-constructed iterator with " + "non-default-constructed iterator."); + // Note: we use assert instead of ABSL_HARDENING_ASSERT here because this + // changes the complexity of Equals from O(1) to O(log(N) + log(M)) where + // N/M are sizes of the containers containing node_/other.node_. + assert(AreNodesFromSameContainer(node_, other.node_) && + "Comparing iterators from different containers."); + assert_valid_generation(node_); + other.assert_valid_generation(other.node_); + return node_ == other.node_ && position_ == other.position_; + } + + bool IsEndIterator() const { + if (position_ != node_->finish()) return false; + node_type *node = node_; + while (!node->is_root()) { + if (node->position() != node->parent()->finish()) return false; + node = node->parent(); + } + return true; } // Returns n such that n calls to ++other yields *this. @@ -1165,7 +1239,7 @@ class btree_iterator { // Increment/decrement the iterator. void increment() { - assert_valid_generation(); + assert_valid_generation(node_); if (node_->is_leaf() && ++position_ < node_->finish()) { return; } @@ -1174,7 +1248,7 @@ class btree_iterator { void increment_slow(); void decrement() { - assert_valid_generation(); + assert_valid_generation(node_); if (node_->is_leaf() && --position_ >= node_->start()) { return; } @@ -1182,14 +1256,6 @@ class btree_iterator { } void decrement_slow(); - // Updates the generation. For use internally right before we return an - // iterator to the user. - void update_generation() { -#ifdef ABSL_BTREE_ENABLE_GENERATIONS - if (node_ != nullptr) generation_ = node_->generation(); -#endif - } - const key_type &key() const { return node_->key(static_cast<size_type>(position_)); } @@ -1197,15 +1263,8 @@ class btree_iterator { return node_->slot(static_cast<size_type>(position_)); } - void assert_valid_generation() const { -#ifdef ABSL_BTREE_ENABLE_GENERATIONS - if (node_ != nullptr && node_->generation() != generation_) { - ABSL_INTERNAL_LOG( - FATAL, - "Attempting to use an invalidated iterator. The corresponding b-tree " - "container has been mutated since this iterator was constructed."); - } -#endif + void update_generation() { + btree_iterator_generation_info::update_generation(node_); } // The node in the tree the iterator is pointing at. @@ -1214,10 +1273,6 @@ class btree_iterator { // 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_; -#ifdef ABSL_BTREE_ENABLE_GENERATIONS - // Used to check that the iterator hasn't been invalidated. - uint32_t generation_; -#endif }; template <typename Params> diff --git a/absl/container/internal/btree_container.h b/absl/container/internal/btree_container.h index 3e259861..2bff11db 100644 --- a/absl/container/internal/btree_container.h +++ b/absl/container/internal/btree_container.h @@ -65,6 +65,11 @@ class btree_container { using const_reverse_iterator = typename Tree::const_reverse_iterator; using node_type = typename Tree::node_handle_type; + struct extract_and_get_next_return_type { + node_type node; + iterator next; + }; + // Constructors/assignments. btree_container() : tree_(key_compare(), allocator_type()) {} explicit btree_container(const key_compare &comp, @@ -165,6 +170,15 @@ class btree_container { } // Extract routines. + extract_and_get_next_return_type extract_and_get_next( + const_iterator position) { + // Use Construct instead of Transfer because the rebalancing code will + // destroy the slot later. + // Note: we rely on erase() taking place after Construct(). + return {CommonAccess::Construct<node_type>(get_allocator(), + iterator(position).slot()), + erase(position)}; + } node_type extract(iterator position) { // Use Construct instead of Transfer because the rebalancing code will // destroy the slot later. diff --git a/absl/container/internal/common_policy_traits.h b/absl/container/internal/common_policy_traits.h index 0fd4866e..b42c9a48 100644 --- a/absl/container/internal/common_policy_traits.h +++ b/absl/container/internal/common_policy_traits.h @@ -63,7 +63,7 @@ struct common_policy_traits { // UNINITIALIZED template <class Alloc> static void transfer(Alloc* alloc, slot_type* new_slot, slot_type* old_slot) { - transfer_impl(alloc, new_slot, old_slot, 0); + transfer_impl(alloc, new_slot, old_slot, Rank0{}); } // PRECONDITION: `slot` is INITIALIZED @@ -80,29 +80,46 @@ struct common_policy_traits { return P::element(slot); } + static constexpr bool transfer_uses_memcpy() { + return std::is_same<decltype(transfer_impl<std::allocator<char>>( + nullptr, nullptr, nullptr, Rank0{})), + std::true_type>::value; + } + private: + // To rank the overloads below for overload resoltion. Rank0 is preferred. + struct Rank2 {}; + struct Rank1 : Rank2 {}; + struct Rank0 : Rank1 {}; + // Use auto -> decltype as an enabler. template <class Alloc, class P = Policy> static auto transfer_impl(Alloc* alloc, slot_type* new_slot, - slot_type* old_slot, int) + slot_type* old_slot, Rank0) -> decltype((void)P::transfer(alloc, new_slot, old_slot)) { P::transfer(alloc, new_slot, old_slot); } - template <class Alloc> - static void transfer_impl(Alloc* alloc, slot_type* new_slot, - slot_type* old_slot, char) { #if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606 - if (absl::is_trivially_relocatable<value_type>()) { - // TODO(b/247130232,b/251814870): remove casts after fixing warnings. - std::memcpy(static_cast<void*>( - std::launder(const_cast<std::remove_const_t<value_type>*>( - &element(new_slot)))), - static_cast<const void*>(&element(old_slot)), - sizeof(value_type)); - return; - } + // This overload returns true_type for the trait below. + // The conditional_t is to make the enabler type dependent. + template <class Alloc, + typename = std::enable_if_t<absl::is_trivially_relocatable< + std::conditional_t<false, Alloc, value_type>>::value>> + static std::true_type transfer_impl(Alloc*, slot_type* new_slot, + slot_type* old_slot, Rank1) { + // TODO(b/247130232): remove casts after fixing warnings. + // TODO(b/251814870): remove casts after fixing warnings. + std::memcpy( + static_cast<void*>(std::launder( + const_cast<std::remove_const_t<value_type>*>(&element(new_slot)))), + static_cast<const void*>(&element(old_slot)), sizeof(value_type)); + return {}; + } #endif + template <class Alloc> + static void transfer_impl(Alloc* alloc, slot_type* new_slot, + slot_type* old_slot, Rank2) { construct(alloc, new_slot, std::move(element(old_slot))); destroy(alloc, old_slot); } diff --git a/absl/container/internal/hashtablez_sampler.cc b/absl/container/internal/hashtablez_sampler.cc index 5b8cf341..6b6d3491 100644 --- a/absl/container/internal/hashtablez_sampler.cc +++ b/absl/container/internal/hashtablez_sampler.cc @@ -14,6 +14,7 @@ #include "absl/container/internal/hashtablez_sampler.h" +#include <algorithm> #include <atomic> #include <cassert> #include <cmath> @@ -158,6 +159,43 @@ void UnsampleSlow(HashtablezInfo* info) { GlobalHashtablezSampler().Unregister(info); } +void RecordRehashSlow(HashtablezInfo* info, size_t total_probe_length) { +#ifdef ABSL_INTERNAL_HAVE_SSE2 + total_probe_length /= 16; +#else + total_probe_length /= 8; +#endif + info->total_probe_length.store(total_probe_length, std::memory_order_relaxed); + info->num_erases.store(0, std::memory_order_relaxed); + // There is only one concurrent writer, so `load` then `store` is sufficient + // instead of using `fetch_add`. + info->num_rehashes.store( + 1 + info->num_rehashes.load(std::memory_order_relaxed), + std::memory_order_relaxed); +} + +void RecordReservationSlow(HashtablezInfo* info, size_t target_capacity) { + info->max_reserve.store( + (std::max)(info->max_reserve.load(std::memory_order_relaxed), + target_capacity), + std::memory_order_relaxed); +} + +void RecordClearedReservationSlow(HashtablezInfo* info) { + info->max_reserve.store(0, std::memory_order_relaxed); +} + +void RecordStorageChangedSlow(HashtablezInfo* info, size_t size, + size_t capacity) { + info->size.store(size, std::memory_order_relaxed); + info->capacity.store(capacity, std::memory_order_relaxed); + if (size == 0) { + // This is a clear, reset the total/num_erases too. + info->total_probe_length.store(0, std::memory_order_relaxed); + info->num_erases.store(0, std::memory_order_relaxed); + } +} + void RecordInsertSlow(HashtablezInfo* info, size_t hash, size_t distance_from_desired) { // SwissTables probe in groups of 16, so scale this to count items probes and @@ -180,6 +218,14 @@ void RecordInsertSlow(HashtablezInfo* info, size_t hash, info->size.fetch_add(1, std::memory_order_relaxed); } +void RecordEraseSlow(HashtablezInfo* info) { + info->size.fetch_sub(1, std::memory_order_relaxed); + // There is only one concurrent writer, so `load` then `store` is sufficient + // instead of using `fetch_add`. + info->num_erases.store(1 + info->num_erases.load(std::memory_order_relaxed), + std::memory_order_relaxed); +} + void SetHashtablezConfigListener(HashtablezConfigListener l) { g_hashtablez_config_listener.store(l, std::memory_order_release); } diff --git a/absl/container/internal/hashtablez_sampler.h b/absl/container/internal/hashtablez_sampler.h index a89518bb..d8fd8f34 100644 --- a/absl/container/internal/hashtablez_sampler.h +++ b/absl/container/internal/hashtablez_sampler.h @@ -95,55 +95,19 @@ struct HashtablezInfo : public profiling_internal::Sample<HashtablezInfo> { size_t inline_element_size; // How big is the slot? }; -inline void RecordRehashSlow(HashtablezInfo* info, size_t total_probe_length) { -#ifdef ABSL_INTERNAL_HAVE_SSE2 - total_probe_length /= 16; -#else - total_probe_length /= 8; -#endif - info->total_probe_length.store(total_probe_length, std::memory_order_relaxed); - info->num_erases.store(0, std::memory_order_relaxed); - // There is only one concurrent writer, so `load` then `store` is sufficient - // instead of using `fetch_add`. - info->num_rehashes.store( - 1 + info->num_rehashes.load(std::memory_order_relaxed), - std::memory_order_relaxed); -} +void RecordRehashSlow(HashtablezInfo* info, size_t total_probe_length); -inline void RecordReservationSlow(HashtablezInfo* info, - size_t target_capacity) { - info->max_reserve.store( - (std::max)(info->max_reserve.load(std::memory_order_relaxed), - target_capacity), - std::memory_order_relaxed); -} +void RecordReservationSlow(HashtablezInfo* info, size_t target_capacity); -inline void RecordClearedReservationSlow(HashtablezInfo* info) { - info->max_reserve.store(0, std::memory_order_relaxed); -} +void RecordClearedReservationSlow(HashtablezInfo* info); -inline void RecordStorageChangedSlow(HashtablezInfo* info, size_t size, - size_t capacity) { - info->size.store(size, std::memory_order_relaxed); - info->capacity.store(capacity, std::memory_order_relaxed); - if (size == 0) { - // This is a clear, reset the total/num_erases too. - info->total_probe_length.store(0, std::memory_order_relaxed); - info->num_erases.store(0, std::memory_order_relaxed); - } -} +void RecordStorageChangedSlow(HashtablezInfo* info, size_t size, + size_t capacity); void RecordInsertSlow(HashtablezInfo* info, size_t hash, size_t distance_from_desired); -inline void RecordEraseSlow(HashtablezInfo* info) { - info->size.fetch_sub(1, std::memory_order_relaxed); - // There is only one concurrent writer, so `load` then `store` is sufficient - // instead of using `fetch_add`. - info->num_erases.store( - 1 + info->num_erases.load(std::memory_order_relaxed), - std::memory_order_relaxed); -} +void RecordEraseSlow(HashtablezInfo* info); struct SamplingState { int64_t next_sample; @@ -165,7 +129,10 @@ class HashtablezInfoHandle { public: explicit HashtablezInfoHandle() : info_(nullptr) {} explicit HashtablezInfoHandle(HashtablezInfo* info) : info_(info) {} - ~HashtablezInfoHandle() { + + // We do not have a destructor. Caller is responsible for calling Unregister + // before destroying the handle. + void Unregister() { if (ABSL_PREDICT_TRUE(info_ == nullptr)) return; UnsampleSlow(info_); } @@ -230,6 +197,7 @@ class HashtablezInfoHandle { explicit HashtablezInfoHandle() = default; explicit HashtablezInfoHandle(std::nullptr_t) {} + inline void Unregister() {} inline void RecordStorageChanged(size_t /*size*/, size_t /*capacity*/) {} inline void RecordRehash(size_t /*total_probe_length*/) {} inline void RecordReservation(size_t /*target_capacity*/) {} diff --git a/absl/container/internal/inlined_vector.h b/absl/container/internal/inlined_vector.h index f623494c..0398f530 100644 --- a/absl/container/internal/inlined_vector.h +++ b/absl/container/internal/inlined_vector.h @@ -85,6 +85,8 @@ using IsMemcpyOk = template <typename A> using IsMoveAssignOk = std::is_move_assignable<ValueType<A>>; +template <typename A> +using IsSwapOk = absl::type_traits_internal::IsSwappable<ValueType<A>>; template <typename T> struct TypeIdentity { @@ -123,8 +125,8 @@ struct DestroyAdapter<A, /* IsTriviallyDestructible */ true> { template <typename A> struct Allocation { - Pointer<A> data; - SizeType<A> capacity; + Pointer<A> data = nullptr; + SizeType<A> capacity = 0; }; template <typename A, @@ -300,6 +302,20 @@ class ConstructionTransaction { template <typename T, size_t N, typename A> class Storage { public: + struct MemcpyPolicy {}; + struct ElementwiseAssignPolicy {}; + struct ElementwiseSwapPolicy {}; + struct ElementwiseConstructPolicy {}; + + using MoveAssignmentPolicy = absl::conditional_t< + IsMemcpyOk<A>::value, MemcpyPolicy, + absl::conditional_t<IsMoveAssignOk<A>::value, ElementwiseAssignPolicy, + ElementwiseConstructPolicy>>; + using SwapPolicy = absl::conditional_t< + IsMemcpyOk<A>::value, MemcpyPolicy, + absl::conditional_t<IsSwapOk<A>::value, ElementwiseSwapPolicy, + ElementwiseConstructPolicy>>; + static SizeType<A> NextCapacity(SizeType<A> current_capacity) { return current_capacity * 2; } @@ -363,7 +379,9 @@ class Storage { return data_.allocated.allocated_capacity; } - SizeType<A> GetInlinedCapacity() const { return static_cast<SizeType<A>>(N); } + SizeType<A> GetInlinedCapacity() const { + return static_cast<SizeType<A>>(kOptimalInlinedSize); + } StorageView<A> MakeStorageView() { return GetIsAllocated() ? StorageView<A>{GetAllocatedData(), GetSize(), @@ -467,8 +485,15 @@ class Storage { SizeType<A> allocated_capacity; }; + // `kOptimalInlinedSize` is an automatically adjusted inlined capacity of the + // `InlinedVector`. Sometimes, it is possible to increase the capacity (from + // the user requested `N`) without increasing the size of the `InlinedVector`. + static constexpr size_t kOptimalInlinedSize = + (std::max)(N, sizeof(Allocated) / sizeof(ValueType<A>)); + struct Inlined { - alignas(ValueType<A>) char inlined_data[sizeof(ValueType<A>[N])]; + alignas(ValueType<A>) char inlined_data[sizeof( + ValueType<A>[kOptimalInlinedSize])]; }; union Data { @@ -476,6 +501,13 @@ class Storage { Inlined inlined; }; + void SwapN(ElementwiseSwapPolicy, Storage* other, SizeType<A> n); + void SwapN(ElementwiseConstructPolicy, Storage* other, SizeType<A> n); + + void SwapInlinedElements(MemcpyPolicy, Storage* other); + template <typename NotMemcpyPolicy> + void SwapInlinedElements(NotMemcpyPolicy, Storage* other); + template <typename... Args> ABSL_ATTRIBUTE_NOINLINE Reference<A> EmplaceBackSlow(Args&&... args); @@ -889,26 +921,7 @@ auto Storage<T, N, A>::Swap(Storage* other_storage_ptr) -> void { if (GetIsAllocated() && other_storage_ptr->GetIsAllocated()) { swap(data_.allocated, other_storage_ptr->data_.allocated); } else if (!GetIsAllocated() && !other_storage_ptr->GetIsAllocated()) { - Storage* small_ptr = this; - Storage* large_ptr = other_storage_ptr; - if (small_ptr->GetSize() > large_ptr->GetSize()) swap(small_ptr, large_ptr); - - for (SizeType<A> i = 0; i < small_ptr->GetSize(); ++i) { - swap(small_ptr->GetInlinedData()[i], large_ptr->GetInlinedData()[i]); - } - - IteratorValueAdapter<A, MoveIterator<A>> move_values( - MoveIterator<A>(large_ptr->GetInlinedData() + small_ptr->GetSize())); - - ConstructElements<A>(large_ptr->GetAllocator(), - small_ptr->GetInlinedData() + small_ptr->GetSize(), - move_values, - large_ptr->GetSize() - small_ptr->GetSize()); - - DestroyAdapter<A>::DestroyElements( - large_ptr->GetAllocator(), - large_ptr->GetInlinedData() + small_ptr->GetSize(), - large_ptr->GetSize() - small_ptr->GetSize()); + SwapInlinedElements(SwapPolicy{}, other_storage_ptr); } else { Storage* allocated_ptr = this; Storage* inlined_ptr = other_storage_ptr; @@ -944,6 +957,68 @@ auto Storage<T, N, A>::Swap(Storage* other_storage_ptr) -> void { swap(GetAllocator(), other_storage_ptr->GetAllocator()); } +template <typename T, size_t N, typename A> +void Storage<T, N, A>::SwapN(ElementwiseSwapPolicy, Storage* other, + SizeType<A> n) { + std::swap_ranges(GetInlinedData(), GetInlinedData() + n, + other->GetInlinedData()); +} + +template <typename T, size_t N, typename A> +void Storage<T, N, A>::SwapN(ElementwiseConstructPolicy, Storage* other, + SizeType<A> n) { + Pointer<A> a = GetInlinedData(); + Pointer<A> b = other->GetInlinedData(); + // see note on allocators in `SwapInlinedElements`. + A& allocator_a = GetAllocator(); + A& allocator_b = other->GetAllocator(); + for (SizeType<A> i = 0; i < n; ++i, ++a, ++b) { + ValueType<A> tmp(std::move(*a)); + + AllocatorTraits<A>::destroy(allocator_a, a); + AllocatorTraits<A>::construct(allocator_b, a, std::move(*b)); + + AllocatorTraits<A>::destroy(allocator_b, b); + AllocatorTraits<A>::construct(allocator_a, b, std::move(tmp)); + } +} + +template <typename T, size_t N, typename A> +void Storage<T, N, A>::SwapInlinedElements(MemcpyPolicy, Storage* other) { + Data tmp = data_; + data_ = other->data_; + other->data_ = tmp; +} + +template <typename T, size_t N, typename A> +template <typename NotMemcpyPolicy> +void Storage<T, N, A>::SwapInlinedElements(NotMemcpyPolicy policy, + Storage* other) { + // Note: `destroy` needs to use pre-swap allocator while `construct` - + // post-swap allocator. Allocators will be swaped later on outside of + // `SwapInlinedElements`. + Storage* small_ptr = this; + Storage* large_ptr = other; + if (small_ptr->GetSize() > large_ptr->GetSize()) { + std::swap(small_ptr, large_ptr); + } + + auto small_size = small_ptr->GetSize(); + auto diff = large_ptr->GetSize() - small_size; + SwapN(policy, other, small_size); + + IteratorValueAdapter<A, MoveIterator<A>> move_values( + MoveIterator<A>(large_ptr->GetInlinedData() + small_size)); + + ConstructElements<A>(large_ptr->GetAllocator(), + small_ptr->GetInlinedData() + small_size, move_values, + diff); + + DestroyAdapter<A>::DestroyElements(large_ptr->GetAllocator(), + large_ptr->GetInlinedData() + small_size, + diff); +} + // End ignore "array-bounds" #if !defined(__clang__) && defined(__GNUC__) #pragma GCC diagnostic pop diff --git a/absl/container/internal/raw_hash_set.cc b/absl/container/internal/raw_hash_set.cc index c63a2e02..79220836 100644 --- a/absl/container/internal/raw_hash_set.cc +++ b/absl/container/internal/raw_hash_set.cc @@ -16,6 +16,7 @@ #include <atomic> #include <cstddef> +#include <cstring> #include "absl/base/config.h" @@ -63,8 +64,155 @@ void ConvertDeletedToEmptyAndFullToDeleted(ctrl_t* ctrl, size_t capacity) { std::memcpy(ctrl + capacity + 1, ctrl, NumClonedBytes()); ctrl[capacity] = ctrl_t::kSentinel; } -// Extern template instantiotion for inline function. -template FindInfo find_first_non_full(const ctrl_t*, size_t, size_t); +// Extern template instantiation for inline function. +template FindInfo find_first_non_full(const CommonFields&, size_t); + +FindInfo find_first_non_full_outofline(const CommonFields& common, + size_t hash) { + return find_first_non_full(common, hash); +} + +// Return address of the ith slot in slots where each slot occupies slot_size. +static inline void* SlotAddress(void* slot_array, size_t slot, + size_t slot_size) { + return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot_array) + + (slot * slot_size)); +} + +// Return the address of the slot just after slot assuming each slot +// has the specified size. +static inline void* NextSlot(void* slot, size_t slot_size) { + return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot) + slot_size); +} + +// Return the address of the slot just before slot assuming each slot +// has the specified size. +static inline void* PrevSlot(void* slot, size_t slot_size) { + return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot) - slot_size); +} + +void DropDeletesWithoutResize(CommonFields& common, + const PolicyFunctions& policy, void* tmp_space) { + void* set = &common; + void* slot_array = common.slots_; + const size_t capacity = common.capacity_; + assert(IsValidCapacity(capacity)); + assert(!is_small(capacity)); + // Algorithm: + // - mark all DELETED slots as EMPTY + // - mark all FULL slots as DELETED + // - for each slot marked as DELETED + // hash = Hash(element) + // target = find_first_non_full(hash) + // if target is in the same group + // mark slot as FULL + // else if target is EMPTY + // transfer element to target + // mark slot as EMPTY + // mark target as FULL + // else if target is DELETED + // swap current element with target element + // mark target as FULL + // repeat procedure for current slot with moved from element (target) + ctrl_t* ctrl = common.control_; + ConvertDeletedToEmptyAndFullToDeleted(ctrl, capacity); + auto hasher = policy.hash_slot; + auto transfer = policy.transfer; + const size_t slot_size = policy.slot_size; + + size_t total_probe_length = 0; + void* slot_ptr = SlotAddress(slot_array, 0, slot_size); + for (size_t i = 0; i != capacity; + ++i, slot_ptr = NextSlot(slot_ptr, slot_size)) { + assert(slot_ptr == SlotAddress(slot_array, i, slot_size)); + if (!IsDeleted(ctrl[i])) continue; + const size_t hash = (*hasher)(set, slot_ptr); + const FindInfo target = find_first_non_full(common, hash); + const size_t new_i = target.offset; + total_probe_length += target.probe_length; + + // Verify if the old and new i fall within the same group wrt the hash. + // If they do, we don't need to move the object as it falls already in the + // best probe we can. + const size_t probe_offset = probe(common, hash).offset(); + const auto probe_index = [probe_offset, capacity](size_t pos) { + return ((pos - probe_offset) & capacity) / Group::kWidth; + }; + + // Element doesn't move. + if (ABSL_PREDICT_TRUE(probe_index(new_i) == probe_index(i))) { + SetCtrl(common, i, H2(hash), slot_size); + continue; + } + + void* new_slot_ptr = SlotAddress(slot_array, new_i, slot_size); + if (IsEmpty(ctrl[new_i])) { + // Transfer element to the empty spot. + // SetCtrl poisons/unpoisons the slots so we have to call it at the + // right time. + SetCtrl(common, new_i, H2(hash), slot_size); + (*transfer)(set, new_slot_ptr, slot_ptr); + SetCtrl(common, i, ctrl_t::kEmpty, slot_size); + } else { + assert(IsDeleted(ctrl[new_i])); + SetCtrl(common, new_i, H2(hash), slot_size); + // Until we are done rehashing, DELETED marks previously FULL slots. + + // Swap i and new_i elements. + (*transfer)(set, tmp_space, new_slot_ptr); + (*transfer)(set, new_slot_ptr, slot_ptr); + (*transfer)(set, slot_ptr, tmp_space); + + // repeat the processing of the ith slot + --i; + slot_ptr = PrevSlot(slot_ptr, slot_size); + } + } + ResetGrowthLeft(common); + common.infoz().RecordRehash(total_probe_length); +} + +void EraseMetaOnly(CommonFields& c, ctrl_t* it, size_t slot_size) { + assert(IsFull(*it) && "erasing a dangling iterator"); + --c.size_; + const auto index = static_cast<size_t>(it - c.control_); + const size_t index_before = (index - Group::kWidth) & c.capacity_; + const auto empty_after = Group(it).MaskEmpty(); + const auto empty_before = Group(c.control_ + index_before).MaskEmpty(); + + // We count how many consecutive non empties we have to the right and to the + // left of `it`. If the sum is >= kWidth then there is at least one probe + // window that might have seen a full group. + bool was_never_full = empty_before && empty_after && + static_cast<size_t>(empty_after.TrailingZeros()) + + empty_before.LeadingZeros() < + Group::kWidth; + + SetCtrl(c, index, was_never_full ? ctrl_t::kEmpty : ctrl_t::kDeleted, + slot_size); + c.growth_left_ += (was_never_full ? 1 : 0); + c.infoz().RecordErase(); +} + +void ClearBackingArray(CommonFields& c, const PolicyFunctions& policy, + bool reuse) { + if (reuse) { + c.size_ = 0; + ResetCtrl(c, policy.slot_size); + c.infoz().RecordStorageChanged(0, c.capacity_); + } else { + void* set = &c; + (*policy.dealloc)(set, policy, c.control_, c.slots_, c.capacity_); + c.control_ = EmptyGroup(); + c.slots_ = nullptr; + c.size_ = 0; + c.capacity_ = 0; + c.growth_left_ = 0; + c.infoz().RecordClearedReservation(); + assert(c.size_ == 0); + c.infoz().RecordStorageChanged(0, 0); + } +} } // namespace container_internal ABSL_NAMESPACE_END diff --git a/absl/container/internal/raw_hash_set.h b/absl/container/internal/raw_hash_set.h index 93de2221..8a33106f 100644 --- a/absl/container/internal/raw_hash_set.h +++ b/absl/container/internal/raw_hash_set.h @@ -717,6 +717,66 @@ using Group = GroupAArch64Impl; using Group = GroupPortableImpl; #endif +// CommonFields hold the fields in raw_hash_set that do not depend +// on template parameters. This allows us to conveniently pass all +// of this state to helper functions as a single argument. +// +// We make HashtablezInfoHandle a base class to take advantage of +// the empty base-class optimization when sampling is turned off. +class CommonFields : public HashtablezInfoHandle { + public: + CommonFields() = default; + + // Not copyable + CommonFields(const CommonFields&) = delete; + CommonFields& operator=(const CommonFields&) = delete; + + // Movable + CommonFields(CommonFields&& that) + : HashtablezInfoHandle( + std::move(static_cast<HashtablezInfoHandle&&>(that))), + // Explicitly copying fields into "this" and then resetting "that" + // fields generates less code then calling absl::exchange per field. + control_(that.control_), + slots_(that.slots_), + size_(that.size_), + capacity_(that.capacity_), + growth_left_(that.growth_left_) { + that.control_ = EmptyGroup(); + that.slots_ = nullptr; + that.size_ = 0; + that.capacity_ = 0; + that.growth_left_ = 0; + } + CommonFields& operator=(CommonFields&&) = default; + + HashtablezInfoHandle& infoz() { return *this; } + const HashtablezInfoHandle& infoz() const { return *this; } + + // TODO(b/259599413): Investigate removing some of these fields: + // - control/slots can be derived from each other + // - size can be moved into the slot array + + // The control bytes (and, also, a pointer to the base of the backing array). + // + // This contains `capacity + 1 + NumClonedBytes()` entries, even + // when the table is empty (hence EmptyGroup). + ctrl_t* control_ = EmptyGroup(); + + // The beginning of the slots, located at `SlotOffset()` bytes after + // `control`. May be null for empty tables. + void* slots_ = nullptr; + + // The number of filled slots. + size_t size_ = 0; + + // The total number of available slots. + size_t capacity_ = 0; + + // The number of slots we can still fill without needing to rehash. + size_t growth_left_ = 0; +}; + // Returns he number of "cloned control bytes". // // This is the number of control bytes that are present both at the beginning @@ -797,15 +857,54 @@ size_t SelectBucketCountForIterRange(InputIter first, InputIter last, return 0; } -#define ABSL_INTERNAL_ASSERT_IS_FULL(ctrl, msg) \ - ABSL_HARDENING_ASSERT((ctrl != nullptr && IsFull(*ctrl)) && msg) +#define ABSL_INTERNAL_ASSERT_IS_FULL(ctrl, operation) \ + do { \ + ABSL_HARDENING_ASSERT( \ + (ctrl != nullptr) && operation \ + " called on invalid iterator. The iterator might be an end() " \ + "iterator or may have been default constructed."); \ + ABSL_HARDENING_ASSERT( \ + (IsFull(*ctrl)) && operation \ + " called on invalid iterator. The element might have been erased or " \ + "the table might have rehashed."); \ + } while (0) + +// Note that for comparisons, null/end iterators are valid. +inline void AssertIsValidForComparison(const ctrl_t* ctrl) { + ABSL_HARDENING_ASSERT((ctrl == nullptr || IsFull(*ctrl)) && + "Invalid iterator comparison. The element might have " + "been erased or the table might have rehashed."); +} + +// If the two iterators come from the same container, then their pointers will +// interleave such that ctrl_a <= ctrl_b < slot_a <= slot_b or vice/versa. +// Note: we take slots by reference so that it's not UB if they're uninitialized +// as long as we don't read them (when ctrl is null). +inline bool AreItersFromSameContainer(const ctrl_t* ctrl_a, + const ctrl_t* ctrl_b, + const void* const& slot_a, + const void* const& slot_b) { + // If either control byte is null, then we can't tell. + if (ctrl_a == nullptr || ctrl_b == nullptr) return true; + const void* low_slot = slot_a; + const void* hi_slot = slot_b; + if (ctrl_a > ctrl_b) { + std::swap(ctrl_a, ctrl_b); + std::swap(low_slot, hi_slot); + } + return ctrl_b < low_slot && low_slot <= hi_slot; +} -inline void AssertIsValid(ctrl_t* ctrl) { +// Asserts that two iterators come from the same container. +// Note: we take slots by reference so that it's not UB if they're uninitialized +// as long as we don't read them (when ctrl is null). +inline void AssertSameContainer(const ctrl_t* ctrl_a, const ctrl_t* ctrl_b, + const void* const& slot_a, + const void* const& slot_b) { ABSL_HARDENING_ASSERT( - (ctrl == nullptr || IsFull(*ctrl)) && - "Invalid operation on iterator. The element might have " - "been erased, the table might have rehashed, or this may " - "be an end() iterator."); + AreItersFromSameContainer(ctrl_a, ctrl_b, slot_a, slot_b) && + "Invalid iterator comparison. The iterators may be from different " + "containers or the container might have rehashed."); } struct FindInfo { @@ -827,9 +926,10 @@ struct FindInfo { // `ShouldInsertBackwards()` for small tables. inline bool is_small(size_t capacity) { return capacity < Group::kWidth - 1; } -// Begins a probing operation on `ctrl`, using `hash`. -inline probe_seq<Group::kWidth> probe(const ctrl_t* ctrl, size_t hash, - size_t capacity) { +// Begins a probing operation on `common.control`, using `hash`. +inline probe_seq<Group::kWidth> probe(const CommonFields& common, size_t hash) { + const ctrl_t* ctrl = common.control_; + const size_t capacity = common.capacity_; return probe_seq<Group::kWidth>(H1(hash, ctrl), capacity); } @@ -841,9 +941,9 @@ inline probe_seq<Group::kWidth> probe(const ctrl_t* ctrl, size_t hash, // NOTE: this function must work with tables having both empty and deleted // slots in the same group. Such tables appear during `erase()`. template <typename = void> -inline FindInfo find_first_non_full(const ctrl_t* ctrl, size_t hash, - size_t capacity) { - auto seq = probe(ctrl, hash, capacity); +inline FindInfo find_first_non_full(const CommonFields& common, size_t hash) { + auto seq = probe(common, hash); + const ctrl_t* ctrl = common.control_; while (true) { Group g{ctrl + seq.offset()}; auto mask = g.MaskEmptyOrDeleted(); @@ -853,55 +953,67 @@ inline FindInfo find_first_non_full(const ctrl_t* ctrl, size_t hash, // In debug build we will randomly insert in either the front or back of // the group. // TODO(kfm,sbenza): revisit after we do unconditional mixing - if (!is_small(capacity) && ShouldInsertBackwards(hash, ctrl)) { + if (!is_small(common.capacity_) && ShouldInsertBackwards(hash, ctrl)) { return {seq.offset(mask.HighestBitSet()), seq.index()}; } #endif return {seq.offset(mask.LowestBitSet()), seq.index()}; } seq.next(); - assert(seq.index() <= capacity && "full table!"); + assert(seq.index() <= common.capacity_ && "full table!"); } } // Extern template for inline function keep possibility of inlining. // When compiler decided to not inline, no symbols will be added to the // corresponding translation unit. -extern template FindInfo find_first_non_full(const ctrl_t*, size_t, size_t); +extern template FindInfo find_first_non_full(const CommonFields&, size_t); + +// Non-inlined version of find_first_non_full for use in less +// performance critical routines. +FindInfo find_first_non_full_outofline(const CommonFields&, size_t); + +inline void ResetGrowthLeft(CommonFields& common) { + common.growth_left_ = CapacityToGrowth(common.capacity_) - common.size_; +} // Sets `ctrl` to `{kEmpty, kSentinel, ..., kEmpty}`, marking the entire // array as marked as empty. -inline void ResetCtrl(size_t capacity, ctrl_t* ctrl, const void* slot, - size_t slot_size) { +inline void ResetCtrl(CommonFields& common, size_t slot_size) { + const size_t capacity = common.capacity_; + ctrl_t* ctrl = common.control_; std::memset(ctrl, static_cast<int8_t>(ctrl_t::kEmpty), capacity + 1 + NumClonedBytes()); ctrl[capacity] = ctrl_t::kSentinel; - SanitizerPoisonMemoryRegion(slot, slot_size * capacity); + SanitizerPoisonMemoryRegion(common.slots_, slot_size * capacity); + ResetGrowthLeft(common); } // Sets `ctrl[i]` to `h`. // // Unlike setting it directly, this function will perform bounds checks and // mirror the value to the cloned tail if necessary. -inline void SetCtrl(size_t i, ctrl_t h, size_t capacity, ctrl_t* ctrl, - const void* slot, size_t slot_size) { +inline void SetCtrl(const CommonFields& common, size_t i, ctrl_t h, + size_t slot_size) { + const size_t capacity = common.capacity_; assert(i < capacity); - auto* slot_i = static_cast<const char*>(slot) + i * slot_size; + auto* slot_i = static_cast<const char*>(common.slots_) + i * slot_size; if (IsFull(h)) { SanitizerUnpoisonMemoryRegion(slot_i, slot_size); } else { SanitizerPoisonMemoryRegion(slot_i, slot_size); } + ctrl_t* ctrl = common.control_; ctrl[i] = h; ctrl[((i - NumClonedBytes()) & capacity) + (NumClonedBytes() & capacity)] = h; } // Overload for setting to an occupied `h2_t` rather than a special `ctrl_t`. -inline void SetCtrl(size_t i, h2_t h, size_t capacity, ctrl_t* ctrl, - const void* slot, size_t slot_size) { - SetCtrl(i, static_cast<ctrl_t>(h), capacity, ctrl, slot, slot_size); +inline void SetCtrl(const CommonFields& common, size_t i, h2_t h, + size_t slot_size) { + SetCtrl(common, i, static_cast<ctrl_t>(h), slot_size); } // Given the capacity of a table, computes the offset (from the start of the @@ -918,6 +1030,87 @@ inline size_t AllocSize(size_t capacity, size_t slot_size, size_t slot_align) { return SlotOffset(capacity, slot_align) + capacity * slot_size; } +template <typename Alloc, size_t SizeOfSlot, size_t AlignOfSlot> +ABSL_ATTRIBUTE_NOINLINE void InitializeSlots(CommonFields& c, Alloc alloc) { + assert(c.capacity_); + // Folks with custom allocators often make unwarranted assumptions about the + // behavior of their classes vis-a-vis trivial destructability and what + // calls they will or won't make. Avoid sampling for people with custom + // allocators to get us out of this mess. This is not a hard guarantee but + // a workaround while we plan the exact guarantee we want to provide. + const size_t sample_size = + (std::is_same<Alloc, std::allocator<char>>::value && c.slots_ == nullptr) + ? SizeOfSlot + : 0; + + const size_t cap = c.capacity_; + char* mem = static_cast<char*>( + Allocate<AlignOfSlot>(&alloc, AllocSize(cap, SizeOfSlot, AlignOfSlot))); + c.control_ = reinterpret_cast<ctrl_t*>(mem); + c.slots_ = mem + SlotOffset(cap, AlignOfSlot); + ResetCtrl(c, SizeOfSlot); + if (sample_size) { + c.infoz() = Sample(sample_size); + } + c.infoz().RecordStorageChanged(c.size_, cap); +} + +// PolicyFunctions bundles together some information for a particular +// raw_hash_set<T, ...> instantiation. This information is passed to +// type-erased functions that want to do small amounts of type-specific +// work. +struct PolicyFunctions { + size_t slot_size; + + // Return the hash of the pointed-to slot. + size_t (*hash_slot)(void* set, void* slot); + + // Transfer the contents of src_slot to dst_slot. + void (*transfer)(void* set, void* dst_slot, void* src_slot); + + // Deallocate the specified backing store which is sized for n slots. + void (*dealloc)(void* set, const PolicyFunctions& policy, ctrl_t* ctrl, + void* slot_array, size_t n); +}; + +// ClearBackingArray clears the backing array, either modifying it in place, +// or creating a new one based on the value of "reuse". +// REQUIRES: c.capacity > 0 +void ClearBackingArray(CommonFields& c, const PolicyFunctions& policy, + bool reuse); + +// Type-erased version of raw_hash_set::erase_meta_only. +void EraseMetaOnly(CommonFields& c, ctrl_t* it, size_t slot_size); + +// Function to place in PolicyFunctions::dealloc for raw_hash_sets +// that are using std::allocator. This allows us to share the same +// function body for raw_hash_set instantiations that have the +// same slot alignment. +template <size_t AlignOfSlot> +ABSL_ATTRIBUTE_NOINLINE void DeallocateStandard(void*, + const PolicyFunctions& policy, + ctrl_t* ctrl, void* slot_array, + size_t n) { + // Unpoison before returning the memory to the allocator. + SanitizerUnpoisonMemoryRegion(slot_array, policy.slot_size * n); + + std::allocator<char> alloc; + Deallocate<AlignOfSlot>(&alloc, ctrl, + AllocSize(n, policy.slot_size, AlignOfSlot)); +} + +// For trivially relocatable types we use memcpy directly. This allows us to +// share the same function body for raw_hash_set instantiations that have the +// same slot size as long as they are relocatable. +template <size_t SizeOfSlot> +ABSL_ATTRIBUTE_NOINLINE void TransferRelocatable(void*, void* dst, void* src) { + memcpy(dst, src, SizeOfSlot); +} + +// Type-erased version of raw_hash_set::drop_deletes_without_resize. +void DropDeletesWithoutResize(CommonFields& common, + const PolicyFunctions& policy, void* tmp_space); + // A SwissTable. // // Policy: a policy defines how to perform different operations on @@ -1034,22 +1227,19 @@ class raw_hash_set { // PRECONDITION: not an end() iterator. reference operator*() const { - ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, - "operator*() called on invalid iterator."); + ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, "operator*()"); return PolicyTraits::element(slot_); } // PRECONDITION: not an end() iterator. pointer operator->() const { - ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, - "operator-> called on invalid iterator."); + ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, "operator->"); return &operator*(); } // PRECONDITION: not an end() iterator. iterator& operator++() { - ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, - "operator++ called on invalid iterator."); + ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, "operator++"); ++ctrl_; ++slot_; skip_empty_or_deleted(); @@ -1063,8 +1253,9 @@ class raw_hash_set { } friend bool operator==(const iterator& a, const iterator& b) { - AssertIsValid(a.ctrl_); - AssertIsValid(b.ctrl_); + AssertSameContainer(a.ctrl_, b.ctrl_, a.slot_, b.slot_); + AssertIsValidForComparison(a.ctrl_); + AssertIsValidForComparison(b.ctrl_); return a.ctrl_ == b.ctrl_; } friend bool operator!=(const iterator& a, const iterator& b) { @@ -1081,7 +1272,7 @@ class raw_hash_set { // Fixes up `ctrl_` to point to a full by advancing it and `slot_` until // they reach one. // - // If a sentinel is reached, we null both of them out instead. + // If a sentinel is reached, we null `ctrl_` out instead. void skip_empty_or_deleted() { while (IsEmptyOrDeleted(*ctrl_)) { uint32_t shift = Group{ctrl_}.CountLeadingEmptyOrDeleted(); @@ -1109,9 +1300,9 @@ class raw_hash_set { using pointer = typename raw_hash_set::const_pointer; using difference_type = typename raw_hash_set::difference_type; - const_iterator() {} + const_iterator() = default; // Implicit construction from iterator. - const_iterator(iterator i) : inner_(std::move(i)) {} + const_iterator(iterator i) : inner_(std::move(i)) {} // NOLINT reference operator*() const { return *inner_; } pointer operator->() const { return inner_.operator->(); } @@ -1139,19 +1330,20 @@ class raw_hash_set { using node_type = node_handle<Policy, hash_policy_traits<Policy>, Alloc>; using insert_return_type = InsertReturnType<iterator, node_type>; + // Note: can't use `= default` due to non-default noexcept (causes + // problems for some compilers). NOLINTNEXTLINE raw_hash_set() noexcept( std::is_nothrow_default_constructible<hasher>::value&& std::is_nothrow_default_constructible<key_equal>::value&& std::is_nothrow_default_constructible<allocator_type>::value) {} - explicit raw_hash_set(size_t bucket_count, - const hasher& hash = hasher(), - const key_equal& eq = key_equal(), - const allocator_type& alloc = allocator_type()) - : ctrl_(EmptyGroup()), - settings_(0u, HashtablezInfoHandle(), hash, eq, alloc) { + ABSL_ATTRIBUTE_NOINLINE explicit raw_hash_set( + size_t bucket_count, const hasher& hash = hasher(), + const key_equal& eq = key_equal(), + const allocator_type& alloc = allocator_type()) + : settings_(CommonFields{}, hash, eq, alloc) { if (bucket_count) { - capacity_ = NormalizeCapacity(bucket_count); + common().capacity_ = NormalizeCapacity(bucket_count); initialize_slots(); } } @@ -1258,47 +1450,29 @@ class raw_hash_set { // than a full `insert`. for (const auto& v : that) { const size_t hash = PolicyTraits::apply(HashElement{hash_ref()}, v); - auto target = find_first_non_full(ctrl_, hash, capacity_); - SetCtrl(target.offset, H2(hash), capacity_, ctrl_, slots_, - sizeof(slot_type)); + auto target = find_first_non_full_outofline(common(), hash); + SetCtrl(common(), target.offset, H2(hash), sizeof(slot_type)); emplace_at(target.offset, v); infoz().RecordInsert(hash, target.probe_length); } - size_ = that.size(); + common().size_ = that.size(); growth_left() -= that.size(); } - raw_hash_set(raw_hash_set&& that) noexcept( + ABSL_ATTRIBUTE_NOINLINE raw_hash_set(raw_hash_set&& that) noexcept( std::is_nothrow_copy_constructible<hasher>::value&& std::is_nothrow_copy_constructible<key_equal>::value&& std::is_nothrow_copy_constructible<allocator_type>::value) - : ctrl_(absl::exchange(that.ctrl_, EmptyGroup())), - slots_(absl::exchange(that.slots_, nullptr)), - size_(absl::exchange(that.size_, size_t{0})), - capacity_(absl::exchange(that.capacity_, size_t{0})), - // Hash, equality and allocator are copied instead of moved because - // `that` must be left valid. If Hash is std::function<Key>, moving it - // would create a nullptr functor that cannot be called. - settings_(absl::exchange(that.growth_left(), size_t{0}), - absl::exchange(that.infoz(), HashtablezInfoHandle()), - that.hash_ref(), - that.eq_ref(), - that.alloc_ref()) {} + : // Hash, equality and allocator are copied instead of moved because + // `that` must be left valid. If Hash is std::function<Key>, moving it + // would create a nullptr functor that cannot be called. + settings_(absl::exchange(that.common(), CommonFields{}), + that.hash_ref(), that.eq_ref(), that.alloc_ref()) {} raw_hash_set(raw_hash_set&& that, const allocator_type& a) - : ctrl_(EmptyGroup()), - slots_(nullptr), - size_(0), - capacity_(0), - settings_(0, HashtablezInfoHandle(), that.hash_ref(), that.eq_ref(), - a) { + : settings_(CommonFields{}, that.hash_ref(), that.eq_ref(), a) { if (a == that.alloc_ref()) { - std::swap(ctrl_, that.ctrl_); - std::swap(slots_, that.slots_); - std::swap(size_, that.size_); - std::swap(capacity_, that.capacity_); - std::swap(growth_left(), that.growth_left()); - std::swap(infoz(), that.infoz()); + std::swap(common(), that.common()); } else { reserve(that.size()); // Note: this will copy elements of dense_set and unordered_set instead of @@ -1322,12 +1496,25 @@ class raw_hash_set { std::is_nothrow_move_assignable<key_equal>::value) { // TODO(sbenza): We should only use the operations from the noexcept clause // to make sure we actually adhere to that contract. + // NOLINTNEXTLINE: not returning *this for performance. return move_assign( std::move(that), typename AllocTraits::propagate_on_container_move_assignment()); } - ~raw_hash_set() { destroy_slots(); } + ~raw_hash_set() { + const size_t cap = capacity(); + if (!cap) return; + destroy_slots(); + + // Unpoison before returning the memory to the allocator. + SanitizerUnpoisonMemoryRegion(slot_array(), sizeof(slot_type) * cap); + Deallocate<alignof(slot_type)>( + &alloc_ref(), control(), + AllocSize(cap, sizeof(slot_type), alignof(slot_type))); + + infoz().Unregister(); + } iterator begin() { auto it = iterator_at(0); @@ -1344,8 +1531,8 @@ class raw_hash_set { const_iterator cend() const { return end(); } bool empty() const { return !size(); } - size_t size() const { return size_; } - size_t capacity() const { return capacity_; } + size_t size() const { return common().size_; } + size_t capacity() const { return common().capacity_; } size_t max_size() const { return (std::numeric_limits<size_t>::max)(); } ABSL_ATTRIBUTE_REINITIALIZES void clear() { @@ -1356,22 +1543,25 @@ class raw_hash_set { // compared to destruction of the elements of the container. So we pick the // largest bucket_count() threshold for which iteration is still fast and // past that we simply deallocate the array. - if (capacity_ > 127) { + const size_t cap = capacity(); + if (cap == 0) { + // Already guaranteed to be empty; so nothing to do. + } else { destroy_slots(); + ClearBackingArray(common(), GetPolicyFunctions(), + /*reuse=*/cap < 128); + } + } - infoz().RecordClearedReservation(); - } else if (capacity_) { - for (size_t i = 0; i != capacity_; ++i) { - if (IsFull(ctrl_[i])) { - PolicyTraits::destroy(&alloc_ref(), slots_ + i); - } + inline void destroy_slots() { + const size_t cap = capacity(); + const ctrl_t* ctrl = control(); + slot_type* slot = slot_array(); + for (size_t i = 0; i != cap; ++i) { + if (IsFull(ctrl[i])) { + PolicyTraits::destroy(&alloc_ref(), slot + i); } - size_ = 0; - ResetCtrl(capacity_, ctrl_, slots_, sizeof(slot_type)); - reset_growth_left(); } - assert(empty()); - infoz().RecordStorageChanged(0, capacity_); } // This overload kicks in when the argument is an rvalue of insertable and @@ -1559,7 +1749,7 @@ class raw_hash_set { iterator lazy_emplace(const key_arg<K>& key, F&& f) { auto res = find_or_prepare_insert(key); if (res.second) { - slot_type* slot = slots_ + res.first; + slot_type* slot = slot_array() + res.first; std::forward<F>(f)(constructor(&alloc_ref(), &slot)); assert(!slot); } @@ -1601,8 +1791,7 @@ class raw_hash_set { // This overload is necessary because otherwise erase<K>(const K&) would be // a better match if non-const iterator is passed as an argument. void erase(iterator it) { - ABSL_INTERNAL_ASSERT_IS_FULL(it.ctrl_, - "erase() called on invalid iterator."); + ABSL_INTERNAL_ASSERT_IS_FULL(it.ctrl_, "erase()"); PolicyTraits::destroy(&alloc_ref(), it.slot_); erase_meta_only(it); } @@ -1636,8 +1825,7 @@ class raw_hash_set { } node_type extract(const_iterator position) { - ABSL_INTERNAL_ASSERT_IS_FULL(position.inner_.ctrl_, - "extract() called on invalid iterator."); + ABSL_INTERNAL_ASSERT_IS_FULL(position.inner_.ctrl_, "extract()"); auto node = CommonAccess::Transfer<node_type>(alloc_ref(), position.inner_.slot_); erase_meta_only(position); @@ -1657,24 +1845,18 @@ class raw_hash_set { IsNoThrowSwappable<allocator_type>( typename AllocTraits::propagate_on_container_swap{})) { using std::swap; - swap(ctrl_, that.ctrl_); - swap(slots_, that.slots_); - swap(size_, that.size_); - swap(capacity_, that.capacity_); - swap(growth_left(), that.growth_left()); + swap(common(), that.common()); swap(hash_ref(), that.hash_ref()); swap(eq_ref(), that.eq_ref()); - swap(infoz(), that.infoz()); SwapAlloc(alloc_ref(), that.alloc_ref(), typename AllocTraits::propagate_on_container_swap{}); } void rehash(size_t n) { - if (n == 0 && capacity_ == 0) return; - if (n == 0 && size_ == 0) { - destroy_slots(); - infoz().RecordStorageChanged(0, 0); - infoz().RecordClearedReservation(); + if (n == 0 && capacity() == 0) return; + if (n == 0 && size() == 0) { + ClearBackingArray(common(), GetPolicyFunctions(), + /*reuse=*/false); return; } @@ -1682,7 +1864,7 @@ class raw_hash_set { // power-of-2-minus-1, so bitor is good enough. auto m = NormalizeCapacity(n | GrowthToLowerboundCapacity(size())); // n == 0 unconditionally rehashes as per the standard. - if (n == 0 || m > capacity_) { + if (n == 0 || m > capacity()) { resize(m); // This is after resize, to ensure that we have completed the allocation @@ -1727,9 +1909,9 @@ class raw_hash_set { // Avoid probing if we won't be able to prefetch the addresses received. #ifdef ABSL_INTERNAL_HAVE_PREFETCH prefetch_heap_block(); - auto seq = probe(ctrl_, hash_ref()(key), capacity_); - base_internal::PrefetchT0(ctrl_ + seq.offset()); - base_internal::PrefetchT0(slots_ + seq.offset()); + auto seq = probe(common(), hash_ref()(key)); + base_internal::PrefetchT0(control() + seq.offset()); + base_internal::PrefetchT0(slot_array() + seq.offset()); #endif // ABSL_INTERNAL_HAVE_PREFETCH } @@ -1742,18 +1924,20 @@ class raw_hash_set { // called heterogeneous key support. template <class K = key_type> iterator find(const key_arg<K>& key, size_t hash) { - auto seq = probe(ctrl_, hash, capacity_); + auto seq = probe(common(), hash); + slot_type* slot_ptr = slot_array(); + const ctrl_t* ctrl = control(); while (true) { - Group g{ctrl_ + seq.offset()}; + Group g{ctrl + seq.offset()}; for (uint32_t i : g.Match(H2(hash))) { if (ABSL_PREDICT_TRUE(PolicyTraits::apply( EqualElement<K>{key, eq_ref()}, - PolicyTraits::element(slots_ + seq.offset(i))))) + PolicyTraits::element(slot_ptr + seq.offset(i))))) return iterator_at(seq.offset(i)); } if (ABSL_PREDICT_TRUE(g.MaskEmpty())) return end(); seq.next(); - assert(seq.index() <= capacity_ && "full table!"); + assert(seq.index() <= capacity() && "full table!"); } } template <class K = key_type> @@ -1791,9 +1975,9 @@ class raw_hash_set { return {it, it}; } - size_t bucket_count() const { return capacity_; } + size_t bucket_count() const { return capacity(); } float load_factor() const { - return capacity_ ? static_cast<double>(size()) / capacity_ : 0.0; + return capacity() ? static_cast<double>(size()) / capacity() : 0.0; } float max_load_factor() const { return 1.0f; } void max_load_factor(float) { @@ -1880,7 +2064,8 @@ class raw_hash_set { std::pair<iterator, bool> operator()(const K& key, Args&&...) && { auto res = s.find_or_prepare_insert(key); if (res.second) { - PolicyTraits::transfer(&s.alloc_ref(), s.slots_ + res.first, &slot); + PolicyTraits::transfer(&s.alloc_ref(), s.slot_array() + res.first, + &slot); } else if (do_destroy) { PolicyTraits::destroy(&s.alloc_ref(), &slot); } @@ -1896,102 +2081,43 @@ class raw_hash_set { // This merely updates the pertinent control byte. This can be used in // conjunction with Policy::transfer to move the object to another place. void erase_meta_only(const_iterator it) { - assert(IsFull(*it.inner_.ctrl_) && "erasing a dangling iterator"); - --size_; - const size_t index = static_cast<size_t>(it.inner_.ctrl_ - ctrl_); - const size_t index_before = (index - Group::kWidth) & capacity_; - const auto empty_after = Group(it.inner_.ctrl_).MaskEmpty(); - const auto empty_before = Group(ctrl_ + index_before).MaskEmpty(); - - // We count how many consecutive non empties we have to the right and to the - // left of `it`. If the sum is >= kWidth then there is at least one probe - // window that might have seen a full group. - bool was_never_full = - empty_before && empty_after && - static_cast<size_t>(empty_after.TrailingZeros() + - empty_before.LeadingZeros()) < Group::kWidth; - - SetCtrl(index, was_never_full ? ctrl_t::kEmpty : ctrl_t::kDeleted, - capacity_, ctrl_, slots_, sizeof(slot_type)); - growth_left() += was_never_full; - infoz().RecordErase(); + EraseMetaOnly(common(), it.inner_.ctrl_, sizeof(slot_type)); } // Allocates a backing array for `self` and initializes its control bytes. - // This reads `capacity_` and updates all other fields based on the result of + // This reads `capacity` and updates all other fields based on the result of // the allocation. // - // This does not free the currently held array; `capacity_` must be nonzero. - void initialize_slots() { - assert(capacity_); - // Folks with custom allocators often make unwarranted assumptions about the - // behavior of their classes vis-a-vis trivial destructability and what - // calls they will or wont make. Avoid sampling for people with custom - // allocators to get us out of this mess. This is not a hard guarantee but - // a workaround while we plan the exact guarantee we want to provide. - // + // This does not free the currently held array; `capacity` must be nonzero. + inline void initialize_slots() { // People are often sloppy with the exact type of their allocator (sometimes // it has an extra const or is missing the pair, but rebinds made it work - // anyway). To avoid the ambiguity, we work off SlotAlloc which we have - // bound more carefully. - if (std::is_same<SlotAlloc, std::allocator<slot_type>>::value && - slots_ == nullptr) { - infoz() = Sample(sizeof(slot_type)); - } - - char* mem = static_cast<char*>(Allocate<alignof(slot_type)>( - &alloc_ref(), - AllocSize(capacity_, sizeof(slot_type), alignof(slot_type)))); - ctrl_ = reinterpret_cast<ctrl_t*>(mem); - slots_ = reinterpret_cast<slot_type*>( - mem + SlotOffset(capacity_, alignof(slot_type))); - ResetCtrl(capacity_, ctrl_, slots_, sizeof(slot_type)); - reset_growth_left(); - infoz().RecordStorageChanged(size_, capacity_); - } - - // Destroys all slots in the backing array, frees the backing array, and - // clears all top-level book-keeping data. - // - // This essentially implements `map = raw_hash_set();`. - void destroy_slots() { - if (!capacity_) return; - for (size_t i = 0; i != capacity_; ++i) { - if (IsFull(ctrl_[i])) { - PolicyTraits::destroy(&alloc_ref(), slots_ + i); - } - } - - // Unpoison before returning the memory to the allocator. - SanitizerUnpoisonMemoryRegion(slots_, sizeof(slot_type) * capacity_); - Deallocate<alignof(slot_type)>( - &alloc_ref(), ctrl_, - AllocSize(capacity_, sizeof(slot_type), alignof(slot_type))); - ctrl_ = EmptyGroup(); - slots_ = nullptr; - size_ = 0; - capacity_ = 0; - growth_left() = 0; + // anyway). + using CharAlloc = + typename absl::allocator_traits<Alloc>::template rebind_alloc<char>; + InitializeSlots<CharAlloc, sizeof(slot_type), alignof(slot_type)>( + common(), CharAlloc(alloc_ref())); } - void resize(size_t new_capacity) { + ABSL_ATTRIBUTE_NOINLINE void resize(size_t new_capacity) { assert(IsValidCapacity(new_capacity)); - auto* old_ctrl = ctrl_; - auto* old_slots = slots_; - const size_t old_capacity = capacity_; - capacity_ = new_capacity; + auto* old_ctrl = control(); + auto* old_slots = slot_array(); + const size_t old_capacity = common().capacity_; + common().capacity_ = new_capacity; initialize_slots(); + auto* new_slots = slot_array(); size_t total_probe_length = 0; for (size_t i = 0; i != old_capacity; ++i) { if (IsFull(old_ctrl[i])) { size_t hash = PolicyTraits::apply(HashElement{hash_ref()}, PolicyTraits::element(old_slots + i)); - auto target = find_first_non_full(ctrl_, hash, capacity_); + auto target = find_first_non_full(common(), hash); size_t new_i = target.offset; total_probe_length += target.probe_length; - SetCtrl(new_i, H2(hash), capacity_, ctrl_, slots_, sizeof(slot_type)); - PolicyTraits::transfer(&alloc_ref(), slots_ + new_i, old_slots + i); + SetCtrl(common(), new_i, H2(hash), sizeof(slot_type)); + PolicyTraits::transfer(&alloc_ref(), new_slots + new_i, old_slots + i); } } if (old_capacity) { @@ -2007,70 +2133,10 @@ class raw_hash_set { // Prunes control bytes to remove as many tombstones as possible. // // See the comment on `rehash_and_grow_if_necessary()`. - void drop_deletes_without_resize() ABSL_ATTRIBUTE_NOINLINE { - assert(IsValidCapacity(capacity_)); - assert(!is_small(capacity_)); - // Algorithm: - // - mark all DELETED slots as EMPTY - // - mark all FULL slots as DELETED - // - for each slot marked as DELETED - // hash = Hash(element) - // target = find_first_non_full(hash) - // if target is in the same group - // mark slot as FULL - // else if target is EMPTY - // transfer element to target - // mark slot as EMPTY - // mark target as FULL - // else if target is DELETED - // swap current element with target element - // mark target as FULL - // repeat procedure for current slot with moved from element (target) - ConvertDeletedToEmptyAndFullToDeleted(ctrl_, capacity_); - alignas(slot_type) unsigned char raw[sizeof(slot_type)]; - size_t total_probe_length = 0; - slot_type* slot = reinterpret_cast<slot_type*>(&raw); - for (size_t i = 0; i != capacity_; ++i) { - if (!IsDeleted(ctrl_[i])) continue; - const size_t hash = PolicyTraits::apply( - HashElement{hash_ref()}, PolicyTraits::element(slots_ + i)); - const FindInfo target = find_first_non_full(ctrl_, hash, capacity_); - const size_t new_i = target.offset; - total_probe_length += target.probe_length; - - // Verify if the old and new i fall within the same group wrt the hash. - // If they do, we don't need to move the object as it falls already in the - // best probe we can. - const size_t probe_offset = probe(ctrl_, hash, capacity_).offset(); - const auto probe_index = [probe_offset, this](size_t pos) { - return ((pos - probe_offset) & capacity_) / Group::kWidth; - }; - - // Element doesn't move. - if (ABSL_PREDICT_TRUE(probe_index(new_i) == probe_index(i))) { - SetCtrl(i, H2(hash), capacity_, ctrl_, slots_, sizeof(slot_type)); - continue; - } - if (IsEmpty(ctrl_[new_i])) { - // Transfer element to the empty spot. - // SetCtrl poisons/unpoisons the slots so we have to call it at the - // right time. - SetCtrl(new_i, H2(hash), capacity_, ctrl_, slots_, sizeof(slot_type)); - PolicyTraits::transfer(&alloc_ref(), slots_ + new_i, slots_ + i); - SetCtrl(i, ctrl_t::kEmpty, capacity_, ctrl_, slots_, sizeof(slot_type)); - } else { - assert(IsDeleted(ctrl_[new_i])); - SetCtrl(new_i, H2(hash), capacity_, ctrl_, slots_, sizeof(slot_type)); - // Until we are done rehashing, DELETED marks previously FULL slots. - // Swap i and new_i elements. - PolicyTraits::transfer(&alloc_ref(), slot, slots_ + i); - PolicyTraits::transfer(&alloc_ref(), slots_ + i, slots_ + new_i); - PolicyTraits::transfer(&alloc_ref(), slots_ + new_i, slot); - --i; // repeat - } - } - reset_growth_left(); - infoz().RecordRehash(total_probe_length); + inline void drop_deletes_without_resize() { + // Stack-allocate space for swapping elements. + alignas(slot_type) unsigned char tmp[sizeof(slot_type)]; + DropDeletesWithoutResize(common(), GetPolicyFunctions(), tmp); } // Called whenever the table *might* need to conditionally grow. @@ -2079,14 +2145,13 @@ class raw_hash_set { // growth is unnecessary, because vacating tombstones is beneficial for // performance in the long-run. void rehash_and_grow_if_necessary() { - if (capacity_ == 0) { - resize(1); - } else if (capacity_ > Group::kWidth && - // Do these calcuations in 64-bit to avoid overflow. - size() * uint64_t{32} <= capacity_ * uint64_t{25}) { + const size_t cap = capacity(); + if (cap > Group::kWidth && + // Do these calcuations in 64-bit to avoid overflow. + size() * uint64_t{32} <= cap* uint64_t{25}) { // Squash DELETED without growing if there is enough capacity. // - // Rehash in place if the current size is <= 25/32 of capacity_. + // Rehash in place if the current size is <= 25/32 of capacity. // Rationale for such a high factor: 1) drop_deletes_without_resize() is // faster than resize, and 2) it takes quite a bit of work to add // tombstones. In the worst case, seems to take approximately 4 @@ -2104,8 +2169,8 @@ class raw_hash_set { // // Here is output of an experiment using the BM_CacheInSteadyState // benchmark running the old case (where we rehash-in-place only if we can - // reclaim at least 7/16*capacity_) vs. this code (which rehashes in place - // if we can recover 3/32*capacity_). + // reclaim at least 7/16*capacity) vs. this code (which rehashes in place + // if we can recover 3/32*capacity). // // Note that although in the worst-case number of rehashes jumped up from // 15 to 190, but the number of operations per second is almost the same. @@ -2128,23 +2193,24 @@ class raw_hash_set { drop_deletes_without_resize(); } else { // Otherwise grow the container. - resize(capacity_ * 2 + 1); + resize(cap * 2 + 1); } } bool has_element(const value_type& elem) const { size_t hash = PolicyTraits::apply(HashElement{hash_ref()}, elem); - auto seq = probe(ctrl_, hash, capacity_); + auto seq = probe(common(), hash); + const ctrl_t* ctrl = control(); while (true) { - Group g{ctrl_ + seq.offset()}; + Group g{ctrl + seq.offset()}; for (uint32_t i : g.Match(H2(hash))) { - if (ABSL_PREDICT_TRUE(PolicyTraits::element(slots_ + seq.offset(i)) == - elem)) + if (ABSL_PREDICT_TRUE( + PolicyTraits::element(slot_array() + seq.offset(i)) == elem)) return true; } if (ABSL_PREDICT_TRUE(g.MaskEmpty())) return false; seq.next(); - assert(seq.index() <= capacity_ && "full table!"); + assert(seq.index() <= capacity() && "full table!"); } return false; } @@ -2169,18 +2235,19 @@ class raw_hash_set { std::pair<size_t, bool> find_or_prepare_insert(const K& key) { prefetch_heap_block(); auto hash = hash_ref()(key); - auto seq = probe(ctrl_, hash, capacity_); + auto seq = probe(common(), hash); + const ctrl_t* ctrl = control(); while (true) { - Group g{ctrl_ + seq.offset()}; + Group g{ctrl + seq.offset()}; for (uint32_t i : g.Match(H2(hash))) { if (ABSL_PREDICT_TRUE(PolicyTraits::apply( EqualElement<K>{key, eq_ref()}, - PolicyTraits::element(slots_ + seq.offset(i))))) + PolicyTraits::element(slot_array() + seq.offset(i))))) return {seq.offset(i), false}; } if (ABSL_PREDICT_TRUE(g.MaskEmpty())) break; seq.next(); - assert(seq.index() <= capacity_ && "full table!"); + assert(seq.index() <= capacity() && "full table!"); } return {prepare_insert(hash), true}; } @@ -2190,16 +2257,15 @@ class raw_hash_set { // // REQUIRES: At least one non-full slot available. size_t prepare_insert(size_t hash) ABSL_ATTRIBUTE_NOINLINE { - auto target = find_first_non_full(ctrl_, hash, capacity_); + auto target = find_first_non_full(common(), hash); if (ABSL_PREDICT_FALSE(growth_left() == 0 && - !IsDeleted(ctrl_[target.offset]))) { + !IsDeleted(control()[target.offset]))) { rehash_and_grow_if_necessary(); - target = find_first_non_full(ctrl_, hash, capacity_); + target = find_first_non_full(common(), hash); } - ++size_; - growth_left() -= IsEmpty(ctrl_[target.offset]); - SetCtrl(target.offset, H2(hash), capacity_, ctrl_, slots_, - sizeof(slot_type)); + ++common().size_; + growth_left() -= IsEmpty(control()[target.offset]); + SetCtrl(common(), target.offset, H2(hash), sizeof(slot_type)); infoz().RecordInsert(hash, target.probe_length); return target.offset; } @@ -2214,7 +2280,7 @@ class raw_hash_set { // POSTCONDITION: *m.iterator_at(i) == value_type(forward<Args>(args)...). template <class... Args> void emplace_at(size_t i, Args&&... args) { - PolicyTraits::construct(&alloc_ref(), slots_ + i, + PolicyTraits::construct(&alloc_ref(), slot_array() + i, std::forward<Args>(args)...); assert(PolicyTraits::apply(FindElement{*this}, *iterator_at(i)) == @@ -2222,16 +2288,14 @@ class raw_hash_set { "constructed value does not match the lookup key"); } - iterator iterator_at(size_t i) { return {ctrl_ + i, slots_ + i}; } - const_iterator iterator_at(size_t i) const { return {ctrl_ + i, slots_ + i}; } + iterator iterator_at(size_t i) { return {control() + i, slot_array() + i}; } + const_iterator iterator_at(size_t i) const { + return {control() + i, slot_array() + i}; + } private: friend struct RawHashSetTestOnlyAccess; - void reset_growth_left() { - growth_left() = CapacityToGrowth(capacity()) - size_; - } - // The number of slots we can still fill without needing to rehash. // // This is stored separately due to tombstones: we do not include tombstones @@ -2242,49 +2306,76 @@ class raw_hash_set { // side-effect. // // See `CapacityToGrowth()`. - size_t& growth_left() { return settings_.template get<0>(); } + size_t& growth_left() { return common().growth_left_; } // Prefetch the heap-allocated memory region to resolve potential TLB misses. // This is intended to overlap with execution of calculating the hash for a // key. - void prefetch_heap_block() const { - base_internal::PrefetchT2(ctrl_); - } + void prefetch_heap_block() const { base_internal::PrefetchT2(control()); } + + CommonFields& common() { return settings_.template get<0>(); } + const CommonFields& common() const { return settings_.template get<0>(); } - HashtablezInfoHandle& infoz() { return settings_.template get<1>(); } + ctrl_t* control() const { return common().control_; } + slot_type* slot_array() const { + return static_cast<slot_type*>(common().slots_); + } + HashtablezInfoHandle& infoz() { return common().infoz(); } - hasher& hash_ref() { return settings_.template get<2>(); } - const hasher& hash_ref() const { return settings_.template get<2>(); } - key_equal& eq_ref() { return settings_.template get<3>(); } - const key_equal& eq_ref() const { return settings_.template get<3>(); } - allocator_type& alloc_ref() { return settings_.template get<4>(); } + hasher& hash_ref() { return settings_.template get<1>(); } + const hasher& hash_ref() const { return settings_.template get<1>(); } + key_equal& eq_ref() { return settings_.template get<2>(); } + const key_equal& eq_ref() const { return settings_.template get<2>(); } + allocator_type& alloc_ref() { return settings_.template get<3>(); } const allocator_type& alloc_ref() const { - return settings_.template get<4>(); + return settings_.template get<3>(); } - // TODO(alkis): Investigate removing some of these fields: - // - ctrl/slots can be derived from each other - // - size can be moved into the slot array + // Make type-specific functions for this type's PolicyFunctions struct. + static size_t hash_slot_fn(void* set, void* slot) { + auto* h = static_cast<raw_hash_set*>(set); + return PolicyTraits::apply( + HashElement{h->hash_ref()}, + PolicyTraits::element(static_cast<slot_type*>(slot))); + } + static void transfer_slot_fn(void* set, void* dst, void* src) { + auto* h = static_cast<raw_hash_set*>(set); + PolicyTraits::transfer(&h->alloc_ref(), static_cast<slot_type*>(dst), + static_cast<slot_type*>(src)); + } + // Note: dealloc_fn will only be used if we have a non-standard allocator. + static void dealloc_fn(void* set, const PolicyFunctions&, ctrl_t* ctrl, + void* slot_mem, size_t n) { + auto* h = static_cast<raw_hash_set*>(set); - // The control bytes (and, also, a pointer to the base of the backing array). - // - // This contains `capacity_ + 1 + NumClonedBytes()` entries, even - // when the table is empty (hence EmptyGroup). - ctrl_t* ctrl_ = EmptyGroup(); - // The beginning of the slots, located at `SlotOffset()` bytes after - // `ctrl_`. May be null for empty tables. - slot_type* slots_ = nullptr; + // Unpoison before returning the memory to the allocator. + SanitizerUnpoisonMemoryRegion(slot_mem, sizeof(slot_type) * n); - // The number of filled slots. - size_t size_ = 0; + Deallocate<alignof(slot_type)>( + &h->alloc_ref(), ctrl, + AllocSize(n, sizeof(slot_type), alignof(slot_type))); + } + + static const PolicyFunctions& GetPolicyFunctions() { + static constexpr PolicyFunctions value = { + sizeof(slot_type), + &raw_hash_set::hash_slot_fn, + PolicyTraits::transfer_uses_memcpy() + ? TransferRelocatable<sizeof(slot_type)> + : &raw_hash_set::transfer_slot_fn, + (std::is_same<SlotAlloc, std::allocator<slot_type>>::value + ? &DeallocateStandard<alignof(slot_type)> + : &raw_hash_set::dealloc_fn), + }; + return value; + } - // The total number of available slots. - size_t capacity_ = 0; - absl::container_internal::CompressedTuple<size_t /* growth_left */, - HashtablezInfoHandle, hasher, - key_equal, allocator_type> - settings_{0u, HashtablezInfoHandle{}, hasher{}, key_equal{}, - allocator_type{}}; + // Bundle together CommonFields plus other objects which might be empty. + // CompressedTuple will ensure that sizeof is not affected by any of the empty + // fields that occur after CommonFields. + absl::container_internal::CompressedTuple<CommonFields, hasher, key_equal, + allocator_type> + settings_{CommonFields{}, hasher{}, key_equal{}, allocator_type{}}; }; // Erases all elements that satisfy the predicate `pred` from the container `c`. @@ -2312,14 +2403,15 @@ struct HashtableDebugAccess<Set, absl::void_t<typename Set::raw_hash_set>> { const typename Set::key_type& key) { size_t num_probes = 0; size_t hash = set.hash_ref()(key); - auto seq = probe(set.ctrl_, hash, set.capacity_); + auto seq = probe(set.common(), hash); + const ctrl_t* ctrl = set.control(); while (true) { - container_internal::Group g{set.ctrl_ + seq.offset()}; + container_internal::Group g{ctrl + seq.offset()}; for (uint32_t i : g.Match(container_internal::H2(hash))) { if (Traits::apply( typename Set::template EqualElement<typename Set::key_type>{ key, set.eq_ref()}, - Traits::element(set.slots_ + seq.offset(i)))) + Traits::element(set.slot_array() + seq.offset(i)))) return num_probes; ++num_probes; } @@ -2330,7 +2422,7 @@ struct HashtableDebugAccess<Set, absl::void_t<typename Set::raw_hash_set>> { } static size_t AllocatedByteSize(const Set& c) { - size_t capacity = c.capacity_; + size_t capacity = c.capacity(); if (capacity == 0) return 0; size_t m = AllocSize(capacity, sizeof(Slot), alignof(Slot)); @@ -2338,9 +2430,10 @@ struct HashtableDebugAccess<Set, absl::void_t<typename Set::raw_hash_set>> { if (per_slot != ~size_t{}) { m += per_slot * c.size(); } else { + const ctrl_t* ctrl = c.control(); for (size_t i = 0; i != capacity; ++i) { - if (container_internal::IsFull(c.ctrl_[i])) { - m += Traits::space_used(c.slots_ + i); + if (container_internal::IsFull(ctrl[i])) { + m += Traits::space_used(c.slot_array() + i); } } } diff --git a/absl/container/internal/raw_hash_set_benchmark.cc b/absl/container/internal/raw_hash_set_benchmark.cc index e17ba9b4..15deddcf 100644 --- a/absl/container/internal/raw_hash_set_benchmark.cc +++ b/absl/container/internal/raw_hash_set_benchmark.cc @@ -477,6 +477,24 @@ void BM_DropDeletes(benchmark::State& state) { } BENCHMARK(BM_DropDeletes); +void BM_Resize(benchmark::State& state) { + // For now just measure a small cheap hash table since we + // are mostly interested in the overhead of type-erasure + // in resize(). + constexpr int kElements = 64; + const int kCapacity = kElements * 2; + + IntTable table; + for (int i = 0; i < kElements; i++) { + table.insert(i); + } + for (auto unused : state) { + table.rehash(0); + table.rehash(kCapacity); + } +} +BENCHMARK(BM_Resize); + } // namespace } // namespace container_internal ABSL_NAMESPACE_END diff --git a/absl/container/internal/raw_hash_set_test.cc b/absl/container/internal/raw_hash_set_test.cc index eec9da43..eb0757b2 100644 --- a/absl/container/internal/raw_hash_set_test.cc +++ b/absl/container/internal/raw_hash_set_test.cc @@ -56,8 +56,8 @@ namespace container_internal { struct RawHashSetTestOnlyAccess { template <typename C> - static auto GetSlots(const C& c) -> decltype(c.slots_) { - return c.slots_; + static auto GetSlots(const C& c) -> decltype(c.slot_array()) { + return c.slot_array(); } }; @@ -399,7 +399,7 @@ struct StringEq : std::equal_to<absl::string_view> { struct StringTable : raw_hash_set<StringPolicy, StringHash, StringEq, std::allocator<int>> { using Base = typename StringTable::raw_hash_set; - StringTable() {} + StringTable() = default; using Base::Base; }; @@ -419,7 +419,7 @@ struct Uint8Table template <typename T> struct CustomAlloc : std::allocator<T> { - CustomAlloc() {} + CustomAlloc() = default; template <typename U> explicit CustomAlloc(const CustomAlloc<U>& /*other*/) {} @@ -446,7 +446,7 @@ struct BadFastHash { struct BadTable : raw_hash_set<IntPolicy, BadFastHash, std::equal_to<int>, std::allocator<int>> { using Base = typename BadTable::raw_hash_set; - BadTable() {} + BadTable() = default; using Base::Base; }; @@ -455,12 +455,12 @@ TEST(Table, EmptyFunctorOptimization) { static_assert(std::is_empty<std::allocator<int>>::value, ""); struct MockTable { + void* infoz; void* ctrl; void* slots; size_t size; size_t capacity; size_t growth_left; - void* infoz; }; struct MockTableInfozDisabled { void* ctrl; @@ -1003,7 +1003,7 @@ TEST(Table, ClearBug) { // We are checking that original and second are close enough to each other // that they are probably still in the same group. This is not strictly // guaranteed. - EXPECT_LT(std::abs(original - second), + EXPECT_LT(static_cast<size_t>(std::abs(original - second)), capacity * sizeof(IntTable::value_type)); } @@ -1080,19 +1080,6 @@ struct ProbeStats { // Ratios total_probe_length/size for every tested table. std::vector<double> single_table_ratios; - friend ProbeStats operator+(const ProbeStats& a, const ProbeStats& b) { - ProbeStats res = a; - res.all_probes_histogram.resize(std::max(res.all_probes_histogram.size(), - b.all_probes_histogram.size())); - std::transform(b.all_probes_histogram.begin(), b.all_probes_histogram.end(), - res.all_probes_histogram.begin(), - res.all_probes_histogram.begin(), std::plus<size_t>()); - res.single_table_ratios.insert(res.single_table_ratios.end(), - b.single_table_ratios.begin(), - b.single_table_ratios.end()); - return res; - } - // Average ratio total_probe_length/size over tables. double AvgRatio() const { return std::accumulate(single_table_ratios.begin(), @@ -1555,7 +1542,7 @@ TEST(Table, CopyConstructWithAlloc) { struct ExplicitAllocIntTable : raw_hash_set<IntPolicy, container_internal::hash_default_hash<int64_t>, std::equal_to<int64_t>, Alloc<int64_t>> { - ExplicitAllocIntTable() {} + ExplicitAllocIntTable() = default; }; TEST(Table, AllocWithExplicitCtor) { @@ -1943,7 +1930,7 @@ TEST(Nodes, ExtractInsert) { EXPECT_FALSE(res.inserted); EXPECT_THAT(*res.position, Pair(k0, "")); EXPECT_TRUE(res.node); - EXPECT_FALSE(node); + EXPECT_FALSE(node); // NOLINT(bugprone-use-after-move) } TEST(Nodes, HintInsert) { @@ -1953,7 +1940,7 @@ TEST(Nodes, HintInsert) { auto it = t.insert(t.begin(), std::move(node)); EXPECT_THAT(t, UnorderedElementsAre(1, 2, 3)); EXPECT_EQ(*it, 1); - EXPECT_FALSE(node); + EXPECT_FALSE(node); // NOLINT(bugprone-use-after-move) node = t.extract(2); EXPECT_THAT(t, UnorderedElementsAre(1, 3)); @@ -1963,7 +1950,7 @@ TEST(Nodes, HintInsert) { it = t.insert(t.begin(), std::move(node)); EXPECT_EQ(*it, 2); // The node was not emptied by the insert call. - EXPECT_TRUE(node); + EXPECT_TRUE(node); // NOLINT(bugprone-use-after-move) } IntTable MakeSimpleTable(size_t size) { @@ -2036,20 +2023,75 @@ TEST(Table, UnstablePointers) { EXPECT_NE(old_ptr, addr(0)); } -// Confirm that we assert if we try to erase() end(). -TEST(TableDeathTest, EraseOfEndAsserts) { +bool IsAssertEnabled() { // Use an assert with side-effects to figure out if they are actually enabled. bool assert_enabled = false; assert([&]() { // NOLINT assert_enabled = true; return true; }()); - if (!assert_enabled) return; + return assert_enabled; +} + +TEST(TableDeathTest, InvalidIteratorAsserts) { + if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled."; IntTable t; // Extra simple "regexp" as regexp support is highly varied across platforms. - constexpr char kDeathMsg[] = "erase.. called on invalid iterator"; - EXPECT_DEATH_IF_SUPPORTED(t.erase(t.end()), kDeathMsg); + EXPECT_DEATH_IF_SUPPORTED( + t.erase(t.end()), + "erase.* called on invalid iterator. The iterator might be an " + "end.*iterator or may have been default constructed."); + typename IntTable::iterator iter; + EXPECT_DEATH_IF_SUPPORTED( + ++iter, + "operator.* called on invalid iterator. The iterator might be an " + "end.*iterator or may have been default constructed."); + t.insert(0); + iter = t.begin(); + t.erase(iter); + EXPECT_DEATH_IF_SUPPORTED( + ++iter, + "operator.* called on invalid iterator. The element might have been " + "erased or .*the table might have rehashed."); +} + +TEST(TableDeathTest, IteratorInvalidAssertsEqualityOperator) { + if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled."; + + IntTable t; + t.insert(1); + t.insert(2); + t.insert(3); + auto iter1 = t.begin(); + auto iter2 = std::next(iter1); + ASSERT_NE(iter1, t.end()); + ASSERT_NE(iter2, t.end()); + t.erase(iter1); + // Extra simple "regexp" as regexp support is highly varied across platforms. + const char* const kErasedDeathMessage = + "Invalid iterator comparison. The element might have .*been erased or " + "the table might have rehashed."; + EXPECT_DEATH_IF_SUPPORTED(void(iter1 == iter2), kErasedDeathMessage); + EXPECT_DEATH_IF_SUPPORTED(void(iter2 != iter1), kErasedDeathMessage); + t.erase(iter2); + EXPECT_DEATH_IF_SUPPORTED(void(iter1 == iter2), kErasedDeathMessage); + + IntTable t1, t2; + t1.insert(0); + t2.insert(0); + iter1 = t1.begin(); + iter2 = t2.begin(); + const char* const kContainerDiffDeathMessage = + "Invalid iterator comparison. The iterators may be from different " + ".*containers or the container might have rehashed."; + EXPECT_DEATH_IF_SUPPORTED(void(iter1 == iter2), kContainerDiffDeathMessage); + EXPECT_DEATH_IF_SUPPORTED(void(iter2 == iter1), kContainerDiffDeathMessage); + + for (int i = 0; i < 10; ++i) t1.insert(i); + // There should have been a rehash in t1. + EXPECT_DEATH_IF_SUPPORTED(void(iter1 == t1.begin()), + kContainerDiffDeathMessage); } #if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE) diff --git a/absl/container/node_hash_map_test.cc b/absl/container/node_hash_map_test.cc index e941a836..9bcf470c 100644 --- a/absl/container/node_hash_map_test.cc +++ b/absl/container/node_hash_map_test.cc @@ -272,6 +272,14 @@ TEST(NodeHashMap, NodeHandleMutableKeyAccess) { } #endif +TEST(NodeHashMap, RecursiveTypeCompiles) { + struct RecursiveType { + node_hash_map<int, RecursiveType> m; + }; + RecursiveType t; + t.m[0] = RecursiveType{}; +} + } // namespace } // namespace container_internal ABSL_NAMESPACE_END diff --git a/absl/copts/AbseilConfigureCopts.cmake b/absl/copts/AbseilConfigureCopts.cmake index 73435e99..f728c0e5 100644 --- a/absl/copts/AbseilConfigureCopts.cmake +++ b/absl/copts/AbseilConfigureCopts.cmake @@ -67,7 +67,6 @@ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm.*|aarch64") message(WARNING "Value of CMAKE_SIZEOF_VOID_P (${CMAKE_SIZEOF_VOID_P}) is not supported.") endif() else() - message(WARNING "Value of CMAKE_SYSTEM_PROCESSOR (${CMAKE_SYSTEM_PROCESSOR}) is unknown and cannot be used to set ABSL_RANDOM_RANDEN_COPTS") set(ABSL_RANDOM_RANDEN_COPTS "") endif() diff --git a/absl/copts/GENERATED_AbseilCopts.cmake b/absl/copts/GENERATED_AbseilCopts.cmake index 38b57eec..ba70ef9b 100644 --- a/absl/copts/GENERATED_AbseilCopts.cmake +++ b/absl/copts/GENERATED_AbseilCopts.cmake @@ -13,25 +13,21 @@ list(APPEND ABSL_CLANG_CL_FLAGS ) list(APPEND ABSL_CLANG_CL_TEST_FLAGS - "-Wno-c99-extensions" "-Wno-deprecated-declarations" "-Wno-implicit-int-conversion" - "-Wno-missing-noreturn" "-Wno-missing-prototypes" "-Wno-missing-variable-declarations" - "-Wno-null-conversion" "-Wno-shadow" - "-Wno-shift-sign-overflow" "-Wno-shorten-64-to-32" "-Wno-sign-compare" "-Wno-sign-conversion" + "-Wno-unreachable-code-loop-increment" "-Wno-unused-function" "-Wno-unused-member-function" "-Wno-unused-parameter" "-Wno-unused-private-field" "-Wno-unused-template" "-Wno-used-but-marked-unused" - "-Wno-zero-as-null-pointer-constant" "-Wno-gnu-zero-variadic-macro-arguments" ) @@ -54,7 +50,6 @@ list(APPEND ABSL_GCC_FLAGS ) list(APPEND ABSL_GCC_TEST_FLAGS - "-Wno-conversion-null" "-Wno-deprecated-declarations" "-Wno-missing-declarations" "-Wno-sign-compare" @@ -100,25 +95,21 @@ list(APPEND ABSL_LLVM_FLAGS ) list(APPEND ABSL_LLVM_TEST_FLAGS - "-Wno-c99-extensions" "-Wno-deprecated-declarations" "-Wno-implicit-int-conversion" - "-Wno-missing-noreturn" "-Wno-missing-prototypes" "-Wno-missing-variable-declarations" - "-Wno-null-conversion" "-Wno-shadow" - "-Wno-shift-sign-overflow" "-Wno-shorten-64-to-32" "-Wno-sign-compare" "-Wno-sign-conversion" + "-Wno-unreachable-code-loop-increment" "-Wno-unused-function" "-Wno-unused-member-function" "-Wno-unused-parameter" "-Wno-unused-private-field" "-Wno-unused-template" "-Wno-used-but-marked-unused" - "-Wno-zero-as-null-pointer-constant" "-Wno-gnu-zero-variadic-macro-arguments" ) diff --git a/absl/copts/GENERATED_copts.bzl b/absl/copts/GENERATED_copts.bzl index fb0788a7..62aab656 100644 --- a/absl/copts/GENERATED_copts.bzl +++ b/absl/copts/GENERATED_copts.bzl @@ -14,25 +14,21 @@ ABSL_CLANG_CL_FLAGS = [ ] ABSL_CLANG_CL_TEST_FLAGS = [ - "-Wno-c99-extensions", "-Wno-deprecated-declarations", "-Wno-implicit-int-conversion", - "-Wno-missing-noreturn", "-Wno-missing-prototypes", "-Wno-missing-variable-declarations", - "-Wno-null-conversion", "-Wno-shadow", - "-Wno-shift-sign-overflow", "-Wno-shorten-64-to-32", "-Wno-sign-compare", "-Wno-sign-conversion", + "-Wno-unreachable-code-loop-increment", "-Wno-unused-function", "-Wno-unused-member-function", "-Wno-unused-parameter", "-Wno-unused-private-field", "-Wno-unused-template", "-Wno-used-but-marked-unused", - "-Wno-zero-as-null-pointer-constant", "-Wno-gnu-zero-variadic-macro-arguments", ] @@ -55,7 +51,6 @@ ABSL_GCC_FLAGS = [ ] ABSL_GCC_TEST_FLAGS = [ - "-Wno-conversion-null", "-Wno-deprecated-declarations", "-Wno-missing-declarations", "-Wno-sign-compare", @@ -101,25 +96,21 @@ ABSL_LLVM_FLAGS = [ ] ABSL_LLVM_TEST_FLAGS = [ - "-Wno-c99-extensions", "-Wno-deprecated-declarations", "-Wno-implicit-int-conversion", - "-Wno-missing-noreturn", "-Wno-missing-prototypes", "-Wno-missing-variable-declarations", - "-Wno-null-conversion", "-Wno-shadow", - "-Wno-shift-sign-overflow", "-Wno-shorten-64-to-32", "-Wno-sign-compare", "-Wno-sign-conversion", + "-Wno-unreachable-code-loop-increment", "-Wno-unused-function", "-Wno-unused-member-function", "-Wno-unused-parameter", "-Wno-unused-private-field", "-Wno-unused-template", "-Wno-used-but-marked-unused", - "-Wno-zero-as-null-pointer-constant", "-Wno-gnu-zero-variadic-macro-arguments", ] diff --git a/absl/copts/copts.py b/absl/copts/copts.py index 8281d989..732af9ea 100644 --- a/absl/copts/copts.py +++ b/absl/copts/copts.py @@ -17,25 +17,21 @@ MSVC_BIG_WARNING_FLAGS = [ ] LLVM_TEST_DISABLE_WARNINGS_FLAGS = [ - "-Wno-c99-extensions", "-Wno-deprecated-declarations", "-Wno-implicit-int-conversion", - "-Wno-missing-noreturn", "-Wno-missing-prototypes", "-Wno-missing-variable-declarations", - "-Wno-null-conversion", "-Wno-shadow", - "-Wno-shift-sign-overflow", "-Wno-shorten-64-to-32", "-Wno-sign-compare", "-Wno-sign-conversion", + "-Wno-unreachable-code-loop-increment", "-Wno-unused-function", "-Wno-unused-member-function", "-Wno-unused-parameter", "-Wno-unused-private-field", "-Wno-unused-template", "-Wno-used-but-marked-unused", - "-Wno-zero-as-null-pointer-constant", # gtest depends on this GNU extension being offered. "-Wno-gnu-zero-variadic-macro-arguments", ] @@ -71,7 +67,6 @@ COPT_VARS = { "-DNOMINMAX", ], "ABSL_GCC_TEST_FLAGS": [ - "-Wno-conversion-null", "-Wno-deprecated-declarations", "-Wno-missing-declarations", "-Wno-sign-compare", diff --git a/absl/crc/BUILD.bazel b/absl/crc/BUILD.bazel new file mode 100644 index 00000000..1c58f46c --- /dev/null +++ b/absl/crc/BUILD.bazel @@ -0,0 +1,210 @@ +# Copyright 2022 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( + "//absl:copts/configure_copts.bzl", + "ABSL_DEFAULT_COPTS", + "ABSL_DEFAULT_LINKOPTS", + "ABSL_TEST_COPTS", +) + +package(default_visibility = ["//visibility:private"]) + +licenses(["notice"]) + +cc_library( + name = "cpu_detect", + srcs = [ + "internal/cpu_detect.cc", + ], + hdrs = ["internal/cpu_detect.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:private"], + deps = [ + "//absl/base", + "//absl/base:config", + ], +) + +cc_library( + name = "crc_internal", + srcs = [ + "internal/crc.cc", + "internal/crc_internal.h", + "internal/crc_x86_arm_combined.cc", + ], + hdrs = [ + "internal/crc.h", + "internal/crc32_x86_arm_combined_simd.h", + ], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:private"], + deps = [ + ":cpu_detect", + "//absl/base", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:dynamic_annotations", + "//absl/base:endian", + "//absl/base:prefetch", + "//absl/base:raw_logging_internal", + "//absl/memory", + "//absl/numeric:bits", + ], +) + +cc_library( + name = "crc32c", + srcs = [ + "crc32c.cc", + "internal/crc32c_inline.h", + "internal/crc_memcpy_fallback.cc", + "internal/crc_memcpy_x86_64.cc", + "internal/crc_non_temporal_memcpy.cc", + ], + hdrs = [ + "crc32c.h", + "internal/crc32c.h", + "internal/crc_memcpy.h", + ], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:public"], + deps = [ + ":cpu_detect", + ":crc_internal", + ":non_temporal_memcpy", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:dynamic_annotations", + "//absl/base:endian", + "//absl/base:prefetch", + "//absl/strings", + ], +) + +cc_test( + name = "crc32c_test", + srcs = ["crc32c_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:private"], + deps = [ + ":crc32c", + "//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "non_temporal_arm_intrinsics", + hdrs = ["internal/non_temporal_arm_intrinsics.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:private"], + deps = [ + "//absl/base:config", + ], +) + +cc_library( + name = "non_temporal_memcpy", + hdrs = ["internal/non_temporal_memcpy.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:private"], + deps = [ + ":non_temporal_arm_intrinsics", + "//absl/base:config", + "//absl/base:core_headers", + ], +) + +cc_test( + name = "crc_memcpy_test", + size = "large", + srcs = ["internal/crc_memcpy_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + shard_count = 3, + visibility = ["//visibility:private"], + deps = [ + ":crc32c", + "//absl/memory", + "//absl/random", + "//absl/random:distributions", + "//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "non_temporal_memcpy_test", + srcs = ["internal/non_temporal_memcpy_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:private"], + deps = [ + ":non_temporal_memcpy", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "crc_cord_state", + srcs = ["internal/crc_cord_state.cc"], + hdrs = ["internal/crc_cord_state.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//absl/strings:__pkg__"], + deps = [ + ":crc32c", + "//absl/base:config", + "//absl/numeric:bits", + "//absl/strings", + ], +) + +cc_test( + name = "crc_cord_state_test", + srcs = ["internal/crc_cord_state_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:private"], + deps = [ + ":crc32c", + ":crc_cord_state", + "@com_google_googletest//:gtest_main", + ], +) + +cc_binary( + name = "crc32c_benchmark", + testonly = 1, + srcs = ["crc32c_benchmark.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = [ + "benchmark", + ], + visibility = ["//visibility:private"], + deps = [ + ":crc32c", + "//absl/memory", + "//absl/strings", + "@com_github_google_benchmark//:benchmark_main", + ], +) diff --git a/absl/crc/CMakeLists.txt b/absl/crc/CMakeLists.txt new file mode 100644 index 00000000..72ea2094 --- /dev/null +++ b/absl/crc/CMakeLists.txt @@ -0,0 +1,176 @@ +# Copyright 2022 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. + +# Internal-only target, do not depend on directly. +absl_cc_library( + NAME + crc_cpu_detect + HDRS + "internal/cpu_detect.h" + SRCS + "internal/cpu_detect.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::base + absl::config +) + +# Internal-only target, do not depend on directly. +absl_cc_library( + NAME + crc_internal + HDRS + "internal/crc.h" + "internal/crc32_x86_arm_combined_simd.h" + SRCS + "internal/crc.cc" + "internal/crc_internal.h" + "internal/crc_x86_arm_combined.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::crc_cpu_detect + absl::base + absl::config + absl::core_headers + absl::dynamic_annotations + absl::endian + absl::prefetch + absl::raw_logging_internal + absl::memory + absl::bits +) + +absl_cc_library( + NAME + crc32c + HDRS + "crc32c.h" + "internal/crc32c.h" + "internal/crc_memcpy.h" + SRCS + "crc32c.cc" + "internal/crc32c_inline.h" + "internal/crc_memcpy_fallback.cc" + "internal/crc_memcpy_x86_64.cc" + "internal/crc_non_temporal_memcpy.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::crc_cpu_detect + absl::crc_internal + absl::non_temporal_memcpy + absl::config + absl::core_headers + absl::dynamic_annotations + absl::endian + absl::prefetch + absl::strings +) + +absl_cc_test( + NAME + crc32c_test + SRCS + "crc32c_test.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::crc32c + absl::strings + GTest::gtest_main +) + +# Internal-only target, do not depend on directly. +absl_cc_library( + NAME + non_temporal_arm_intrinsics + HDRS + "internal/non_temporal_arm_intrinsics.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::config +) + +# Internal-only target, do not depend on directly. +absl_cc_library( + NAME + non_temporal_memcpy + HDRS + "internal/non_temporal_memcpy.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::non_temporal_arm_intrinsics + absl::config + absl::core_headers +) + +absl_cc_test( + NAME + crc_memcpy_test + SRCS + "internal/crc_memcpy_test.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::crc32c + absl::memory + absl::random_random + absl::random_distributions + absl::strings + GTest::gtest_main +) + +absl_cc_test( + NAME + non_temporal_memcpy_test + SRCS + "internal/non_temporal_memcpy_test.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::non_temporal_memcpy + GTest::gtest_main +) + +absl_cc_library( + NAME + crc_cord_state + HDRS + "internal/crc_cord_state.h" + SRCS + "internal/crc_cord_state.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::crc32c + absl::config + absl::strings +) + +absl_cc_test( + NAME + crc_cord_state_test + SRCS + "internal/crc_cord_state_test.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::crc_cord_state + absl::crc32c + GTest::gtest_main +) diff --git a/absl/crc/crc32c.cc b/absl/crc/crc32c.cc new file mode 100644 index 00000000..169826f5 --- /dev/null +++ b/absl/crc/crc32c.cc @@ -0,0 +1,101 @@ +// Copyright 2022 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/crc/crc32c.h" + +#include <cstdint> + +#include "absl/crc/internal/crc.h" +#include "absl/crc/internal/crc32c.h" +#include "absl/crc/internal/crc_memcpy.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +namespace { + +const crc_internal::CRC* CrcEngine() { + static const crc_internal::CRC* engine = crc_internal::CRC::Crc32c(); + return engine; +} + +constexpr uint32_t kCRC32Xor = 0xffffffffU; + +} // namespace + +namespace crc_internal { + +crc32c_t UnextendCrc32cByZeroes(crc32c_t initial_crc, size_t length) { + uint32_t crc = static_cast<uint32_t>(initial_crc) ^ kCRC32Xor; + CrcEngine()->UnextendByZeroes(&crc, length); + return static_cast<crc32c_t>(crc ^ kCRC32Xor); +} + +// Called by `absl::ExtendCrc32c()` on strings with size > 64 or when hardware +// CRC32C support is missing. +crc32c_t ExtendCrc32cInternal(crc32c_t initial_crc, + absl::string_view buf_to_add) { + uint32_t crc = static_cast<uint32_t>(initial_crc) ^ kCRC32Xor; + CrcEngine()->Extend(&crc, buf_to_add.data(), buf_to_add.size()); + return static_cast<crc32c_t>(crc ^ kCRC32Xor); +} + +} // namespace crc_internal + +crc32c_t ComputeCrc32c(absl::string_view buf) { + return ExtendCrc32c(crc32c_t{0}, buf); +} + +crc32c_t ExtendCrc32cByZeroes(crc32c_t initial_crc, size_t length) { + uint32_t crc = static_cast<uint32_t>(initial_crc) ^ kCRC32Xor; + CrcEngine()->ExtendByZeroes(&crc, length); + return static_cast<crc32c_t>(crc ^ kCRC32Xor); +} + +crc32c_t ConcatCrc32c(crc32c_t lhs_crc, crc32c_t rhs_crc, size_t rhs_len) { + uint32_t result = static_cast<uint32_t>(lhs_crc); + CrcEngine()->ExtendByZeroes(&result, rhs_len); + return crc32c_t{result ^ static_cast<uint32_t>(rhs_crc)}; +} + +crc32c_t RemoveCrc32cPrefix(crc32c_t crc_a, crc32c_t crc_ab, size_t length_b) { + return ConcatCrc32c(crc_a, crc_ab, length_b); +} + +crc32c_t MemcpyCrc32c(void* dest, const void* src, size_t count, + crc32c_t initial_crc) { + return static_cast<crc32c_t>( + crc_internal::Crc32CAndCopy(dest, src, count, initial_crc, false)); +} + +// Remove a Suffix of given size from a buffer +// +// Given a CRC32C of an existing buffer, `full_string_crc`; the CRC32C of a +// suffix of that buffer to remove, `suffix_crc`; and suffix buffer's length, +// `suffix_len` return the CRC32C of the buffer with suffix removed +// +// This operation has a runtime cost of O(log(`suffix_len`)) +crc32c_t RemoveCrc32cSuffix(crc32c_t full_string_crc, crc32c_t suffix_crc, + size_t suffix_len) { + crc32c_t crc_with_suffix_zeroed = crc32c_t{ + static_cast<uint32_t>(suffix_crc) ^ + static_cast<uint32_t>(full_string_crc) ^ + static_cast<uint32_t>(ExtendCrc32cByZeroes(crc32c_t{0}, suffix_len))}; + return crc_internal::UnextendCrc32cByZeroes( + crc_with_suffix_zeroed, suffix_len); +} + +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/crc/crc32c.h b/absl/crc/crc32c.h new file mode 100644 index 00000000..ba09e52a --- /dev/null +++ b/absl/crc/crc32c.h @@ -0,0 +1,183 @@ +// Copyright 2022 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: crc32c.h +// ----------------------------------------------------------------------------- +// +// This header file defines the API for computing CRC32C values as checksums +// for arbitrary sequences of bytes provided as a string buffer. +// +// The API includes the basic functions for computing such CRC32C values and +// some utility functions for performing more efficient mathematical +// computations using an existing checksum. +#ifndef ABSL_CRC_CRC32C_H_ +#define ABSL_CRC_CRC32C_H_ + +#include <cstdint> +#include <ostream> + +#include "absl/crc/internal/crc32c_inline.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +//----------------------------------------------------------------------------- +// crc32c_t +//----------------------------------------------------------------------------- + +// `crc32c_t` defines a strongly-typed integer for holding a CRC32C value. +// +// Some operators are intentionally omitted. Only equality operators are defined +// so that `crc32c_t` can be directly compared. Methods for putting `crc32c_t` +// directly into a set are omitted because this is bug-prone due to checksum +// collisions. Use an explicit conversion to the `uint32_t` space for operations +// that treat `crc32c_t` as an integer. +class crc32c_t final { + public: + crc32c_t() = default; + constexpr explicit crc32c_t(uint32_t crc) : crc_(crc) {} + + crc32c_t(const crc32c_t&) = default; + crc32c_t& operator=(const crc32c_t&) = default; + + explicit operator uint32_t() const { return crc_; } + + friend bool operator==(crc32c_t lhs, crc32c_t rhs) { + return static_cast<uint32_t>(lhs) == static_cast<uint32_t>(rhs); + } + + friend bool operator!=(crc32c_t lhs, crc32c_t rhs) { return !(lhs == rhs); } + + private: + uint32_t crc_; +}; + +namespace crc_internal { +// Non-inline code path for `absl::ExtendCrc32c()`. Do not call directly. +// Call `absl::ExtendCrc32c()` (defined below) instead. +crc32c_t ExtendCrc32cInternal(crc32c_t initial_crc, + absl::string_view buf_to_add); +} // namespace crc_internal + +// ----------------------------------------------------------------------------- +// CRC32C Computation Functions +// ----------------------------------------------------------------------------- + +// ComputeCrc32c() +// +// Returns the CRC32C value of the provided string. +crc32c_t ComputeCrc32c(absl::string_view buf); + +// ExtendCrc32c() +// +// Computes a CRC32C value from an `initial_crc` CRC32C value including the +// `buf_to_add` bytes of an additional buffer. Using this function is more +// efficient than computing a CRC32C value for the combined buffer from +// scratch. +// +// Note: `ExtendCrc32c` with an initial_crc of 0 is equivalent to +// `ComputeCrc32c`. +// +// This operation has a runtime cost of O(`buf_to_add.size()`) +inline crc32c_t ExtendCrc32c(crc32c_t initial_crc, + absl::string_view buf_to_add) { + // Approximately 75% of calls have size <= 64. + if (buf_to_add.size() <= 64) { + uint32_t crc = static_cast<uint32_t>(initial_crc); + if (crc_internal::ExtendCrc32cInline(&crc, buf_to_add.data(), + buf_to_add.size())) { + return crc32c_t{crc}; + } + } + return crc_internal::ExtendCrc32cInternal(initial_crc, buf_to_add); +} + +// ExtendCrc32cByZeroes() +// +// Computes a CRC32C value for a buffer with an `initial_crc` CRC32C value, +// where `length` bytes with a value of 0 are appended to the buffer. Using this +// function is more efficient than computing a CRC32C value for the combined +// buffer from scratch. +// +// This operation has a runtime cost of O(log(`length`)) +crc32c_t ExtendCrc32cByZeroes(crc32c_t initial_crc, size_t length); + +// MemcpyCrc32c() +// +// Copies `src` to `dest` using `memcpy()` semantics, returning the CRC32C +// value of the copied buffer. +// +// Using `MemcpyCrc32c()` is potentially faster than performing the `memcpy()` +// and `ComputeCrc32c()` operations separately. +crc32c_t MemcpyCrc32c(void* dest, const void* src, size_t count, + crc32c_t initial_crc = crc32c_t{0}); + +// ----------------------------------------------------------------------------- +// CRC32C Arithmetic Functions +// ----------------------------------------------------------------------------- + +// The following functions perform arithmetic on CRC32C values, which are +// generally more efficient than recalculating any given result's CRC32C value. + +// ConcatCrc32c() +// +// Calculates the CRC32C value of two buffers with known CRC32C values +// concatenated together. +// +// Given a buffer with CRC32C value `crc1` and a buffer with +// CRC32C value `crc2` and length, `crc2_length`, returns the CRC32C value of +// the concatenation of these two buffers. +// +// This operation has a runtime cost of O(log(`crc2_length`)). +crc32c_t ConcatCrc32c(crc32c_t crc1, crc32c_t crc2, size_t crc2_length); + +// RemoveCrc32cPrefix() +// +// Calculates the CRC32C value of an existing buffer with a series of bytes +// (the prefix) removed from the beginning of that buffer. +// +// Given the CRC32C value of an existing buffer, `full_string_crc`; The CRC32C +// value of a prefix of that buffer, `prefix_crc`; and the length of the buffer +// with the prefix removed, `remaining_string_length` , return the CRC32C +// value of the buffer with the prefix removed. +// +// This operation has a runtime cost of O(log(`remaining_string_length`)). +crc32c_t RemoveCrc32cPrefix(crc32c_t prefix_crc, crc32c_t full_string_crc, + size_t remaining_string_length); +// RemoveCrc32cSuffix() +// +// Calculates the CRC32C value of an existing buffer with a series of bytes +// (the suffix) removed from the end of that buffer. +// +// Given a CRC32C value of an existing buffer `full_string_crc`, the CRC32C +// value of the suffix to remove `suffix_crc`, and the length of that suffix +// `suffix_len`, returns the CRC32C value of the buffer with suffix removed. +// +// This operation has a runtime cost of O(log(`suffix_len`)) +crc32c_t RemoveCrc32cSuffix(crc32c_t full_string_crc, crc32c_t suffix_crc, + size_t suffix_length); + +// operator<< +// +// Streams the CRC32C value `crc` to the stream `os`. +inline std::ostream& operator<<(std::ostream& os, crc32c_t crc) { + return os << static_cast<uint32_t>(crc); +} + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CRC_CRC32C_H_ diff --git a/absl/crc/crc32c_benchmark.cc b/absl/crc/crc32c_benchmark.cc new file mode 100644 index 00000000..3b46ef36 --- /dev/null +++ b/absl/crc/crc32c_benchmark.cc @@ -0,0 +1,183 @@ +// Copyright 2022 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 <string> + +#include "absl/crc/crc32c.h" +#include "absl/crc/internal/crc32c.h" +#include "absl/memory/memory.h" +#include "absl/strings/string_view.h" +#include "benchmark/benchmark.h" + +namespace { + +std::string TestString(size_t len) { + std::string result; + result.reserve(len); + for (size_t i = 0; i < len; ++i) { + result.push_back(static_cast<char>(i % 256)); + } + return result; +} + +void BM_Calculate(benchmark::State& state) { + int len = state.range(0); + std::string data = TestString(len); + for (auto s : state) { + benchmark::DoNotOptimize(data); + absl::crc32c_t crc = absl::ComputeCrc32c(data); + benchmark::DoNotOptimize(crc); + } +} +BENCHMARK(BM_Calculate)->Arg(0)->Arg(1)->Arg(100)->Arg(10000)->Arg(500000); + +void BM_Extend(benchmark::State& state) { + int len = state.range(0); + std::string extension = TestString(len); + absl::crc32c_t base = absl::crc32c_t{0xC99465AA}; // CRC32C of "Hello World" + for (auto s : state) { + benchmark::DoNotOptimize(base); + benchmark::DoNotOptimize(extension); + absl::crc32c_t crc = absl::ExtendCrc32c(base, extension); + benchmark::DoNotOptimize(crc); + } +} +BENCHMARK(BM_Extend)->Arg(0)->Arg(1)->Arg(100)->Arg(10000)->Arg(500000)->Arg( + 100 * 1000 * 1000); + +// Make working set >> CPU cache size to benchmark prefetches better +void BM_ExtendCacheMiss(benchmark::State& state) { + int len = state.range(0); + constexpr int total = 300 * 1000 * 1000; + std::string extension = TestString(total); + absl::crc32c_t base = absl::crc32c_t{0xC99465AA}; // CRC32C of "Hello World" + for (auto s : state) { + for (int i = 0; i < total; i += len * 2) { + benchmark::DoNotOptimize(base); + benchmark::DoNotOptimize(extension); + absl::crc32c_t crc = + absl::ExtendCrc32c(base, absl::string_view(&extension[i], len)); + benchmark::DoNotOptimize(crc); + } + } + state.SetBytesProcessed(static_cast<int64_t>(state.iterations()) * total / 2); +} +BENCHMARK(BM_ExtendCacheMiss)->Arg(10)->Arg(100)->Arg(1000)->Arg(100000); + +void BM_ExtendByZeroes(benchmark::State& state) { + absl::crc32c_t base = absl::crc32c_t{0xC99465AA}; // CRC32C of "Hello World" + int num_zeroes = state.range(0); + for (auto s : state) { + benchmark::DoNotOptimize(base); + absl::crc32c_t crc = absl::ExtendCrc32cByZeroes(base, num_zeroes); + benchmark::DoNotOptimize(crc); + } +} +BENCHMARK(BM_ExtendByZeroes) + ->RangeMultiplier(10) + ->Range(1, 1000000) + ->RangeMultiplier(32) + ->Range(1, 1 << 20); + +void BM_UnextendByZeroes(benchmark::State& state) { + absl::crc32c_t base = absl::crc32c_t{0xdeadbeef}; + int num_zeroes = state.range(0); + for (auto s : state) { + benchmark::DoNotOptimize(base); + absl::crc32c_t crc = + absl::crc_internal::UnextendCrc32cByZeroes(base, num_zeroes); + benchmark::DoNotOptimize(crc); + } +} +BENCHMARK(BM_UnextendByZeroes) + ->RangeMultiplier(10) + ->Range(1, 1000000) + ->RangeMultiplier(32) + ->Range(1, 1 << 20); + +void BM_Concat(benchmark::State& state) { + int string_b_len = state.range(0); + std::string string_b = TestString(string_b_len); + + // CRC32C of "Hello World" + absl::crc32c_t crc_a = absl::crc32c_t{0xC99465AA}; + absl::crc32c_t crc_b = absl::ComputeCrc32c(string_b); + + for (auto s : state) { + benchmark::DoNotOptimize(crc_a); + benchmark::DoNotOptimize(crc_b); + benchmark::DoNotOptimize(string_b_len); + absl::crc32c_t crc_ab = absl::ConcatCrc32c(crc_a, crc_b, string_b_len); + benchmark::DoNotOptimize(crc_ab); + } +} +BENCHMARK(BM_Concat) + ->RangeMultiplier(10) + ->Range(1, 1000000) + ->RangeMultiplier(32) + ->Range(1, 1 << 20); + +void BM_Memcpy(benchmark::State& state) { + int string_len = state.range(0); + + std::string source = TestString(string_len); + auto dest = absl::make_unique<char[]>(string_len); + + for (auto s : state) { + benchmark::DoNotOptimize(source); + absl::crc32c_t crc = + absl::MemcpyCrc32c(dest.get(), source.data(), source.size()); + benchmark::DoNotOptimize(crc); + benchmark::DoNotOptimize(dest); + benchmark::DoNotOptimize(dest.get()); + benchmark::DoNotOptimize(dest[0]); + } + + state.SetBytesProcessed(static_cast<int64_t>(state.iterations()) * + state.range(0)); +} +BENCHMARK(BM_Memcpy)->Arg(0)->Arg(1)->Arg(100)->Arg(10000)->Arg(500000); + +void BM_RemoveSuffix(benchmark::State& state) { + int full_string_len = state.range(0); + int suffix_len = state.range(1); + + std::string full_string = TestString(full_string_len); + std::string suffix = full_string.substr( + full_string_len - suffix_len, full_string_len); + + absl::crc32c_t full_string_crc = absl::ComputeCrc32c(full_string); + absl::crc32c_t suffix_crc = absl::ComputeCrc32c(suffix); + + for (auto s : state) { + benchmark::DoNotOptimize(full_string_crc); + benchmark::DoNotOptimize(suffix_crc); + benchmark::DoNotOptimize(suffix_len); + absl::crc32c_t crc = absl::RemoveCrc32cSuffix(full_string_crc, suffix_crc, + suffix_len); + benchmark::DoNotOptimize(crc); + } +} +BENCHMARK(BM_RemoveSuffix) + ->ArgPair(1, 1) + ->ArgPair(100, 10) + ->ArgPair(100, 100) + ->ArgPair(10000, 1) + ->ArgPair(10000, 100) + ->ArgPair(10000, 10000) + ->ArgPair(500000, 1) + ->ArgPair(500000, 100) + ->ArgPair(500000, 10000) + ->ArgPair(500000, 500000); +} // namespace diff --git a/absl/crc/crc32c_test.cc b/absl/crc/crc32c_test.cc new file mode 100644 index 00000000..72d422a1 --- /dev/null +++ b/absl/crc/crc32c_test.cc @@ -0,0 +1,194 @@ +// Copyright 2022 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/crc/crc32c.h" + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <cstring> +#include <string> + +#include "gtest/gtest.h" +#include "absl/crc/internal/crc32c.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" + +namespace { + +TEST(CRC32C, RFC3720) { + // Test the results of the vectors from + // https://www.rfc-editor.org/rfc/rfc3720#appendix-B.4 + char data[32]; + + // 32 bytes of ones. + memset(data, 0, sizeof(data)); + EXPECT_EQ(absl::ComputeCrc32c(absl::string_view(data, sizeof(data))), + absl::crc32c_t{0x8a9136aa}); + + // 32 bytes of ones. + memset(data, 0xff, sizeof(data)); + EXPECT_EQ(absl::ComputeCrc32c(absl::string_view(data, sizeof(data))), + absl::crc32c_t{0x62a8ab43}); + + // 32 incrementing bytes. + for (int i = 0; i < 32; ++i) data[i] = static_cast<char>(i); + EXPECT_EQ(absl::ComputeCrc32c(absl::string_view(data, sizeof(data))), + absl::crc32c_t{0x46dd794e}); + + // 32 decrementing bytes. + for (int i = 0; i < 32; ++i) data[i] = static_cast<char>(31 - i); + EXPECT_EQ(absl::ComputeCrc32c(absl::string_view(data, sizeof(data))), + absl::crc32c_t{0x113fdb5c}); + + // An iSCSI - SCSI Read (10) Command PDU. + constexpr uint8_t cmd[48] = { + 0x01, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x28, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + EXPECT_EQ(absl::ComputeCrc32c(absl::string_view( + reinterpret_cast<const char*>(cmd), sizeof(cmd))), + absl::crc32c_t{0xd9963a56}); +} + +std::string TestString(size_t len) { + std::string result; + result.reserve(len); + for (size_t i = 0; i < len; ++i) { + result.push_back(static_cast<char>(i % 256)); + } + return result; +} + +TEST(CRC32C, Compute) { + EXPECT_EQ(absl::ComputeCrc32c(""), absl::crc32c_t{0}); + EXPECT_EQ(absl::ComputeCrc32c("hello world"), absl::crc32c_t{0xc99465aa}); +} + +TEST(CRC32C, Extend) { + uint32_t base = 0xC99465AA; // CRC32C of "Hello World" + std::string extension = "Extension String"; + + EXPECT_EQ( + absl::ExtendCrc32c(absl::crc32c_t{base}, extension), + absl::crc32c_t{0xD2F65090}); // CRC32C of "Hello WorldExtension String" +} + +TEST(CRC32C, ExtendByZeroes) { + std::string base = "hello world"; + absl::crc32c_t base_crc = absl::crc32c_t{0xc99465aa}; + + constexpr size_t kExtendByValues[] = {100, 10000, 100000}; + for (const size_t extend_by : kExtendByValues) { + SCOPED_TRACE(extend_by); + absl::crc32c_t crc2 = absl::ExtendCrc32cByZeroes(base_crc, extend_by); + EXPECT_EQ(crc2, absl::ComputeCrc32c(base + std::string(extend_by, '\0'))); + } +} + +TEST(CRC32C, UnextendByZeroes) { + constexpr size_t kExtendByValues[] = {2, 200, 20000, 200000, 20000000}; + constexpr size_t kUnextendByValues[] = {0, 100, 10000, 100000, 10000000}; + + for (auto seed_crc : {absl::crc32c_t{0}, absl::crc32c_t{0xc99465aa}}) { + SCOPED_TRACE(seed_crc); + for (const size_t size_1 : kExtendByValues) { + for (const size_t size_2 : kUnextendByValues) { + size_t extend_size = std::max(size_1, size_2); + size_t unextend_size = std::min(size_1, size_2); + SCOPED_TRACE(extend_size); + SCOPED_TRACE(unextend_size); + + // Extending by A zeroes an unextending by B<A zeros should be identical + // to extending by A-B zeroes. + absl::crc32c_t crc1 = seed_crc; + crc1 = absl::ExtendCrc32cByZeroes(crc1, extend_size); + crc1 = absl::crc_internal::UnextendCrc32cByZeroes(crc1, unextend_size); + + absl::crc32c_t crc2 = seed_crc; + crc2 = absl::ExtendCrc32cByZeroes(crc2, extend_size - unextend_size); + + EXPECT_EQ(crc1, crc2); + } + } + } + + constexpr size_t kSizes[] = {0, 1, 100, 10000}; + for (const size_t size : kSizes) { + SCOPED_TRACE(size); + std::string string_before = TestString(size); + std::string string_after = string_before + std::string(size, '\0'); + + absl::crc32c_t crc_before = absl::ComputeCrc32c(string_before); + absl::crc32c_t crc_after = absl::ComputeCrc32c(string_after); + + EXPECT_EQ(crc_before, + absl::crc_internal::UnextendCrc32cByZeroes(crc_after, size)); + } +} + +TEST(CRC32C, Concat) { + std::string hello = "Hello, "; + std::string world = "world!"; + std::string hello_world = absl::StrCat(hello, world); + + absl::crc32c_t crc_a = absl::ComputeCrc32c(hello); + absl::crc32c_t crc_b = absl::ComputeCrc32c(world); + absl::crc32c_t crc_ab = absl::ComputeCrc32c(hello_world); + + EXPECT_EQ(absl::ConcatCrc32c(crc_a, crc_b, world.size()), crc_ab); +} + +TEST(CRC32C, Memcpy) { + constexpr size_t kBytesSize[] = {0, 1, 20, 500, 100000}; + for (size_t bytes : kBytesSize) { + SCOPED_TRACE(bytes); + std::string sample_string = TestString(bytes); + std::string target_buffer = std::string(bytes, '\0'); + + absl::crc32c_t memcpy_crc = + absl::MemcpyCrc32c(&(target_buffer[0]), sample_string.data(), bytes); + absl::crc32c_t compute_crc = absl::ComputeCrc32c(sample_string); + + EXPECT_EQ(memcpy_crc, compute_crc); + EXPECT_EQ(sample_string, target_buffer); + } +} + +TEST(CRC32C, RemovePrefix) { + std::string hello = "Hello, "; + std::string world = "world!"; + std::string hello_world = absl::StrCat(hello, world); + + absl::crc32c_t crc_a = absl::ComputeCrc32c(hello); + absl::crc32c_t crc_b = absl::ComputeCrc32c(world); + absl::crc32c_t crc_ab = absl::ComputeCrc32c(hello_world); + + EXPECT_EQ(absl::RemoveCrc32cPrefix(crc_a, crc_ab, world.size()), crc_b); +} + +TEST(CRC32C, RemoveSuffix) { + std::string hello = "Hello, "; + std::string world = "world!"; + std::string hello_world = absl::StrCat(hello, world); + + absl::crc32c_t crc_a = absl::ComputeCrc32c(hello); + absl::crc32c_t crc_b = absl::ComputeCrc32c(world); + absl::crc32c_t crc_ab = absl::ComputeCrc32c(hello_world); + + EXPECT_EQ(absl::RemoveCrc32cSuffix(crc_ab, crc_b, world.size()), crc_a); +} +} // namespace diff --git a/absl/crc/internal/cpu_detect.cc b/absl/crc/internal/cpu_detect.cc new file mode 100644 index 00000000..d61b7018 --- /dev/null +++ b/absl/crc/internal/cpu_detect.cc @@ -0,0 +1,256 @@ +// Copyright 2022 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/crc/internal/cpu_detect.h" + +#include <cstdint> +#include <string> + +#include "absl/base/config.h" + +#if defined(__aarch64__) && defined(__linux__) +#include <asm/hwcap.h> +#include <sys/auxv.h> +#endif + +#if defined(_WIN32) || defined(_WIN64) +#include <intrin.h> +#endif + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +#if defined(__x86_64__) || defined(_M_X64) + +namespace { + +#if !defined(_WIN32) && !defined(_WIN64) +// MSVC defines this function for us. +// https://learn.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex +static void __cpuid(int cpu_info[4], int info_type) { + __asm__ volatile("cpuid \n\t" + : "=a"(cpu_info[0]), "=b"(cpu_info[1]), "=c"(cpu_info[2]), + "=d"(cpu_info[3]) + : "a"(info_type), "c"(0)); +} +#endif // !defined(_WIN32) && !defined(_WIN64) + +enum class Vendor { + kUnknown, + kIntel, + kAmd, +}; + +Vendor GetVendor() { + // Get the vendor string (issue CPUID with eax = 0). + int cpu_info[4]; + __cpuid(cpu_info, 0); + + std::string vendor; + vendor.append(reinterpret_cast<char*>(&cpu_info[1]), 4); + vendor.append(reinterpret_cast<char*>(&cpu_info[3]), 4); + vendor.append(reinterpret_cast<char*>(&cpu_info[2]), 4); + if (vendor == "GenuineIntel") { + return Vendor::kIntel; + } else if (vendor == "AuthenticAMD") { + return Vendor::kAmd; + } else { + return Vendor::kUnknown; + } +} + +CpuType GetIntelCpuType() { + // To get general information and extended features we send eax = 1 and + // ecx = 0 to cpuid. The response is returned in eax, ebx, ecx and edx. + // (See Intel 64 and IA-32 Architectures Software Developer's Manual + // Volume 2A: Instruction Set Reference, A-M CPUID). + // https://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-2a-manual.html + // https://learn.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex + int cpu_info[4]; + __cpuid(cpu_info, 1); + + // Response in eax bits as follows: + // 0-3 (stepping id) + // 4-7 (model number), + // 8-11 (family code), + // 12-13 (processor type), + // 16-19 (extended model) + // 20-27 (extended family) + + int family = (cpu_info[0] >> 8) & 0x0f; + int model_num = (cpu_info[0] >> 4) & 0x0f; + int ext_family = (cpu_info[0] >> 20) & 0xff; + int ext_model_num = (cpu_info[0] >> 16) & 0x0f; + + int brand_id = cpu_info[1] & 0xff; + + // Process the extended family and model info if necessary + if (family == 0x0f) { + family += ext_family; + } + + if (family == 0x0f || family == 0x6) { + model_num += (ext_model_num << 4); + } + + switch (brand_id) { + case 0: // no brand ID, so parse CPU family/model + switch (family) { + case 6: // Most PentiumIII processors are in this category + switch (model_num) { + case 0x2c: // Westmere: Gulftown + return CpuType::kIntelWestmere; + case 0x2d: // Sandybridge + return CpuType::kIntelSandybridge; + case 0x3e: // Ivybridge + return CpuType::kIntelIvybridge; + case 0x3c: // Haswell (client) + case 0x3f: // Haswell + return CpuType::kIntelHaswell; + case 0x4f: // Broadwell + case 0x56: // BroadwellDE + return CpuType::kIntelBroadwell; + case 0x55: // Skylake Xeon + if ((cpu_info[0] & 0x0f) < 5) { // stepping < 5 is skylake + return CpuType::kIntelSkylakeXeon; + } else { // stepping >= 5 is cascadelake + return CpuType::kIntelCascadelakeXeon; + } + case 0x5e: // Skylake (client) + return CpuType::kIntelSkylake; + default: + return CpuType::kUnknown; + } + default: + return CpuType::kUnknown; + } + default: + return CpuType::kUnknown; + } +} + +CpuType GetAmdCpuType() { + // To get general information and extended features we send eax = 1 and + // ecx = 0 to cpuid. The response is returned in eax, ebx, ecx and edx. + // (See Intel 64 and IA-32 Architectures Software Developer's Manual + // Volume 2A: Instruction Set Reference, A-M CPUID). + // https://learn.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex + int cpu_info[4]; + __cpuid(cpu_info, 1); + + // Response in eax bits as follows: + // 0-3 (stepping id) + // 4-7 (model number), + // 8-11 (family code), + // 12-13 (processor type), + // 16-19 (extended model) + // 20-27 (extended family) + + int family = (cpu_info[0] >> 8) & 0x0f; + int model_num = (cpu_info[0] >> 4) & 0x0f; + int ext_family = (cpu_info[0] >> 20) & 0xff; + int ext_model_num = (cpu_info[0] >> 16) & 0x0f; + + if (family == 0x0f) { + family += ext_family; + model_num += (ext_model_num << 4); + } + + switch (family) { + case 0x17: + switch (model_num) { + case 0x0: // Stepping Ax + case 0x1: // Stepping Bx + return CpuType::kAmdNaples; + case 0x30: // Stepping Ax + case 0x31: // Stepping Bx + return CpuType::kAmdRome; + default: + return CpuType::kUnknown; + } + break; + case 0x19: + switch (model_num) { + case 0x1: // Stepping B0 + return CpuType::kAmdMilan; + default: + return CpuType::kUnknown; + } + break; + default: + return CpuType::kUnknown; + } +} + +} // namespace + +CpuType GetCpuType() { + switch (GetVendor()) { + case Vendor::kIntel: + return GetIntelCpuType(); + case Vendor::kAmd: + return GetAmdCpuType(); + default: + return CpuType::kUnknown; + } +} + +bool SupportsArmCRC32PMULL() { return false; } + +#elif defined(__aarch64__) && defined(__linux__) + +#ifndef HWCAP_CPUID +#define HWCAP_CPUID (1 << 11) +#endif + +#define ABSL_INTERNAL_AARCH64_ID_REG_READ(id, val) \ + asm("mrs %0, " #id : "=r"(val)) + +CpuType GetCpuType() { + // MIDR_EL1 is not visible to EL0, however the access will be emulated by + // linux if AT_HWCAP has HWCAP_CPUID set. + // + // This method will be unreliable on heterogeneous computing systems (ex: + // big.LITTLE) since the value of MIDR_EL1 will change based on the calling + // thread. + uint64_t hwcaps = getauxval(AT_HWCAP); + if (hwcaps & HWCAP_CPUID) { + uint64_t midr = 0; + ABSL_INTERNAL_AARCH64_ID_REG_READ(MIDR_EL1, midr); + uint32_t implementer = (midr >> 24) & 0xff; + uint32_t part_number = (midr >> 4) & 0xfff; + if (implementer == 0x41 && part_number == 0xd0c) { + return CpuType::kArmNeoverseN1; + } + } + return CpuType::kUnknown; +} + +bool SupportsArmCRC32PMULL() { + uint64_t hwcaps = getauxval(AT_HWCAP); + return (hwcaps & HWCAP_CRC32) && (hwcaps & HWCAP_PMULL); +} + +#else + +CpuType GetCpuType() { return CpuType::kUnknown; } + +bool SupportsArmCRC32PMULL() { return false; } + +#endif + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/crc/internal/cpu_detect.h b/absl/crc/internal/cpu_detect.h new file mode 100644 index 00000000..6054f696 --- /dev/null +++ b/absl/crc/internal/cpu_detect.h @@ -0,0 +1,57 @@ +// Copyright 2022 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_CRC_INTERNAL_CPU_DETECT_H_ +#define ABSL_CRC_INTERNAL_CPU_DETECT_H_ + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +// Enumeration of architectures that we have special-case tuning parameters for. +// This set may change over time. +enum class CpuType { + kUnknown, + kIntelHaswell, + kAmdRome, + kAmdNaples, + kAmdMilan, + kIntelCascadelakeXeon, + kIntelSkylakeXeon, + kIntelBroadwell, + kIntelSkylake, + kIntelIvybridge, + kIntelSandybridge, + kIntelWestmere, + kArmNeoverseN1, +}; + +// Returns the type of host CPU this code is running on. Returns kUnknown if +// the host CPU is of unknown type, or if detection otherwise fails. +CpuType GetCpuType(); + +// Returns whether the host CPU supports the CPU features needed for our +// accelerated implementations. The CpuTypes enumerated above apart from +// kUnknown support the required features. On unknown CPUs, we can use +// this to see if it's safe to use hardware acceleration, though without any +// tuning. +bool SupportsArmCRC32PMULL(); + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CRC_INTERNAL_CPU_DETECT_H_ diff --git a/absl/crc/internal/crc.cc b/absl/crc/internal/crc.cc new file mode 100644 index 00000000..bb8936e3 --- /dev/null +++ b/absl/crc/internal/crc.cc @@ -0,0 +1,468 @@ +// Copyright 2022 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 CRCs (aka Rabin Fingerprints). +// Treats the input as a polynomial with coefficients in Z(2), +// and finds the remainder when divided by an irreducible polynomial +// of the appropriate length. +// It handles all CRC sizes from 8 to 128 bits. +// It's somewhat complicated by having separate implementations optimized for +// CRC's <=32 bits, <= 64 bits, and <= 128 bits. +// The input string is prefixed with a "1" bit, and has "degree" "0" bits +// appended to it before the remainder is found. This ensures that +// short strings are scrambled somewhat and that strings consisting +// of all nulls have a non-zero CRC. +// +// Uses the "interleaved word-by-word" method from +// "Everything we know about CRC but afraid to forget" by Andrew Kadatch +// and Bob Jenkins, +// http://crcutil.googlecode.com/files/crc-doc.1.0.pdf +// +// The idea is to compute kStride CRCs simultaneously, allowing the +// processor to more effectively use multiple execution units. Each of +// the CRCs is calculated on one word of data followed by kStride - 1 +// words of zeroes; the CRC starting points are staggered by one word. +// Assuming a stride of 4 with data words "ABCDABCDABCD", the first +// CRC is over A000A000A, the second over 0B000B000B, and so on. +// The CRC of the whole data is then calculated by properly aligning the +// CRCs by appending zeroes until the data lengths agree then XORing +// the CRCs. + +#include "absl/crc/internal/crc.h" + +#include <cstdint> + +#include "absl/base/internal/endian.h" +#include "absl/base/internal/prefetch.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/crc/internal/crc_internal.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +namespace { + +// Constants +#if defined(__i386__) || defined(__x86_64__) +constexpr bool kNeedAlignedLoads = false; +#else +constexpr bool kNeedAlignedLoads = true; +#endif + +// We express the number of zeroes as a number in base ZEROES_BASE. By +// pre-computing the zero extensions for all possible components of such an +// expression (numbers in a form a*ZEROES_BASE**b), we can calculate the +// resulting extension by multiplying the extensions for individual components +// using log_{ZEROES_BASE}(num_zeroes) polynomial multiplications. The tables of +// zero extensions contain (ZEROES_BASE - 1) * (log_{ZEROES_BASE}(64)) entries. +constexpr int ZEROES_BASE_LG = 4; // log_2(ZEROES_BASE) +constexpr int ZEROES_BASE = (1 << ZEROES_BASE_LG); // must be a power of 2 + +constexpr uint32_t kCrc32cPoly = 0x82f63b78; + +uint32_t ReverseBits(uint32_t bits) { + bits = (bits & 0xaaaaaaaau) >> 1 | (bits & 0x55555555u) << 1; + bits = (bits & 0xccccccccu) >> 2 | (bits & 0x33333333u) << 2; + bits = (bits & 0xf0f0f0f0u) >> 4 | (bits & 0x0f0f0f0fu) << 4; + return absl::gbswap_32(bits); +} + +// Polynomial long multiplication mod the polynomial of degree 32. +void PolyMultiply(uint32_t* val, uint32_t m, uint32_t poly) { + uint32_t l = *val; + uint32_t result = 0; + auto onebit = uint32_t{0x80000000u}; + for (uint32_t one = onebit; one != 0; one >>= 1) { + if ((l & one) != 0) { + result ^= m; + } + if (m & 1) { + m = (m >> 1) ^ poly; + } else { + m >>= 1; + } + } + *val = result; +} +} // namespace + +void CRCImpl::FillWordTable(uint32_t poly, uint32_t last, int word_size, + Uint32By256* t) { + for (int j = 0; j != word_size; j++) { // for each byte of extension.... + t[j][0] = 0; // a zero has no effect + for (int i = 128; i != 0; i >>= 1) { // fill in entries for powers of 2 + if (j == 0 && i == 128) { + t[j][i] = last; // top bit in last byte is given + } else { + // each successive power of two is derived from the previous + // one, either in this table, or the last table + uint32_t pred; + if (i == 128) { + pred = t[j - 1][1]; + } else { + pred = t[j][i << 1]; + } + // Advance the CRC by one bit (multiply by X, and take remainder + // through one step of polynomial long division) + if (pred & 1) { + t[j][i] = (pred >> 1) ^ poly; + } else { + t[j][i] = pred >> 1; + } + } + } + // CRCs have the property that CRC(a xor b) == CRC(a) xor CRC(b) + // so we can make all the tables for non-powers of two by + // xoring previously created entries. + for (int i = 2; i != 256; i <<= 1) { + for (int k = i + 1; k != (i << 1); k++) { + t[j][k] = t[j][i] ^ t[j][k - i]; + } + } + } +} + +int CRCImpl::FillZeroesTable(uint32_t poly, Uint32By256* t) { + uint32_t inc = 1; + inc <<= 31; + + // Extend by one zero bit. We know degree > 1 so (inc & 1) == 0. + inc >>= 1; + + // Now extend by 2, 4, and 8 bits, so now `inc` is extended by one zero byte. + for (int i = 0; i < 3; ++i) { + PolyMultiply(&inc, inc, poly); + } + + int j = 0; + for (uint64_t inc_len = 1; inc_len != 0; inc_len <<= ZEROES_BASE_LG) { + // Every entry in the table adds an additional inc_len zeroes. + uint32_t v = inc; + for (int a = 1; a != ZEROES_BASE; a++) { + t[0][j] = v; + PolyMultiply(&v, inc, poly); + j++; + } + inc = v; + } + ABSL_RAW_CHECK(j <= 256, ""); + return j; +} + +// Internal version of the "constructor". +CRCImpl* CRCImpl::NewInternal() { + // Find an accelearated implementation first. + CRCImpl* result = TryNewCRC32AcceleratedX86ARMCombined(); + + // Fall back to generic implementions if no acceleration is available. + if (result == nullptr) { + result = new CRC32(); + } + + result->InitTables(); + + return result; +} + +// The CRC of the empty string is always the CRC polynomial itself. +void CRCImpl::Empty(uint32_t* crc) const { *crc = kCrc32cPoly; } + +// The 32-bit implementation + +void CRC32::InitTables() { + // Compute the table for extending a CRC by one byte. + Uint32By256* t = new Uint32By256[4]; + FillWordTable(kCrc32cPoly, kCrc32cPoly, 1, t); + for (int i = 0; i != 256; i++) { + this->table0_[i] = t[0][i]; + } + + // Construct a table for updating the CRC by 4 bytes data followed by + // 12 bytes of zeroes. + // + // Note: the data word size could be larger than the CRC size; it might + // be slightly faster to use a 64-bit data word, but doing so doubles the + // table size. + uint32_t last = kCrc32cPoly; + const size_t size = 12; + for (size_t i = 0; i < size; ++i) { + last = (last >> 8) ^ this->table0_[last & 0xff]; + } + FillWordTable(kCrc32cPoly, last, 4, t); + for (size_t b = 0; b < 4; ++b) { + for (int i = 0; i < 256; ++i) { + this->table_[b][i] = t[b][i]; + } + } + + int j = FillZeroesTable(kCrc32cPoly, t); + ABSL_RAW_CHECK(j <= static_cast<int>(ABSL_ARRAYSIZE(this->zeroes_)), ""); + for (int i = 0; i < j; i++) { + this->zeroes_[i] = t[0][i]; + } + + delete[] t; + + // Build up tables for _reversing_ the operation of doing CRC operations on + // zero bytes. + + // In C++, extending `crc` by a single zero bit is done by the following: + // (A) bool low_bit_set = (crc & 1); + // crc >>= 1; + // if (low_bit_set) crc ^= kCrc32cPoly; + // + // In particular note that the high bit of `crc` after this operation will be + // set if and only if the low bit of `crc` was set before it. This means that + // no information is lost, and the operation can be reversed, as follows: + // (B) bool high_bit_set = (crc & 0x80000000u); + // if (high_bit_set) crc ^= kCrc32cPoly; + // crc <<= 1; + // if (high_bit_set) crc ^= 1; + // + // Or, equivalently: + // (C) bool high_bit_set = (crc & 0x80000000u); + // crc <<= 1; + // if (high_bit_set) crc ^= ((kCrc32cPoly << 1) ^ 1); + // + // The last observation is, if we store our checksums in variable `rcrc`, + // with order of the bits reversed, the inverse operation becomes: + // (D) bool low_bit_set = (rcrc & 1); + // rcrc >>= 1; + // if (low_bit_set) rcrc ^= ReverseBits((kCrc32cPoly << 1) ^ 1) + // + // This is the same algorithm (A) that we started with, only with a different + // polynomial bit pattern. This means that by building up our tables with + // this alternate polynomial, we can apply the CRC algorithms to a + // bit-reversed CRC checksum to perform inverse zero-extension. + + const uint32_t kCrc32cUnextendPoly = + ReverseBits(static_cast<uint32_t>((kCrc32cPoly << 1) ^ 1)); + FillWordTable(kCrc32cUnextendPoly, kCrc32cUnextendPoly, 1, &reverse_table0_); + + j = FillZeroesTable(kCrc32cUnextendPoly, &reverse_zeroes_); + ABSL_RAW_CHECK(j <= static_cast<int>(ABSL_ARRAYSIZE(this->reverse_zeroes_)), + ""); +} + +void CRC32::Extend(uint32_t* crc, const void* bytes, size_t length) const { + const uint8_t* p = static_cast<const uint8_t*>(bytes); + const uint8_t* e = p + length; + uint32_t l = *crc; + + auto step_one_byte = [this, &p, &l] () { + int c = (l & 0xff) ^ *p++; + l = this->table0_[c] ^ (l >> 8); + }; + + if (kNeedAlignedLoads) { + // point x at first 4-byte aligned byte in string. this might be past the + // end of the string. + const uint8_t* x = RoundUp<4>(p); + if (x <= e) { + // Process bytes until finished or p is 4-byte aligned + while (p != x) { + step_one_byte(); + } + } + } + + const size_t kSwathSize = 16; + if (static_cast<size_t>(e - p) >= kSwathSize) { + // Load one swath of data into the operating buffers. + uint32_t buf0 = absl::little_endian::Load32(p) ^ l; + uint32_t buf1 = absl::little_endian::Load32(p + 4); + uint32_t buf2 = absl::little_endian::Load32(p + 8); + uint32_t buf3 = absl::little_endian::Load32(p + 12); + p += kSwathSize; + + // Increment a CRC value by a "swath"; this combines the four bytes + // starting at `ptr` and twelve zero bytes, so that four CRCs can be + // built incrementally and combined at the end. + const auto step_swath = [this](uint32_t crc_in, const std::uint8_t* ptr) { + return absl::little_endian::Load32(ptr) ^ + this->table_[3][crc_in & 0xff] ^ + this->table_[2][(crc_in >> 8) & 0xff] ^ + this->table_[1][(crc_in >> 16) & 0xff] ^ + this->table_[0][crc_in >> 24]; + }; + + // Run one CRC calculation step over all swaths in one 16-byte stride + const auto step_stride = [&]() { + buf0 = step_swath(buf0, p); + buf1 = step_swath(buf1, p + 4); + buf2 = step_swath(buf2, p + 8); + buf3 = step_swath(buf3, p + 12); + p += 16; + }; + + // Process kStride interleaved swaths through the data in parallel. + while ((e - p) > kPrefetchHorizon) { + base_internal::PrefetchNta( + reinterpret_cast<const void*>(p + kPrefetchHorizon)); + // Process 64 bytes at a time + step_stride(); + step_stride(); + step_stride(); + step_stride(); + } + while (static_cast<size_t>(e - p) >= kSwathSize) { + step_stride(); + } + + // Now advance one word at a time as far as possible. This isn't worth + // doing if we have word-advance tables. + while (static_cast<size_t>(e - p) >= 4) { + buf0 = step_swath(buf0, p); + uint32_t tmp = buf0; + buf0 = buf1; + buf1 = buf2; + buf2 = buf3; + buf3 = tmp; + p += 4; + } + + // Combine the results from the different swaths. This is just a CRC + // on the data values in the bufX words. + auto combine_one_word = [this](uint32_t crc_in, uint32_t w) { + w ^= crc_in; + for (size_t i = 0; i < 4; ++i) { + w = (w >> 8) ^ this->table0_[w & 0xff]; + } + return w; + }; + + l = combine_one_word(0, buf0); + l = combine_one_word(l, buf1); + l = combine_one_word(l, buf2); + l = combine_one_word(l, buf3); + } + + // Process the last few bytes + while (p != e) { + step_one_byte(); + } + + *crc = l; +} + +void CRC32::ExtendByZeroesImpl(uint32_t* crc, size_t length, + const uint32_t zeroes_table[256], + const uint32_t poly_table[256]) const { + if (length != 0) { + uint32_t l = *crc; + // For each ZEROES_BASE_LG bits in length + // (after the low-order bits have been removed) + // we lookup the appropriate polynomial in the zeroes_ array + // and do a polynomial long multiplication (mod the CRC polynomial) + // to extend the CRC by the appropriate number of bits. + for (int i = 0; length != 0; + i += ZEROES_BASE - 1, length >>= ZEROES_BASE_LG) { + int c = length & (ZEROES_BASE - 1); // pick next ZEROES_BASE_LG bits + if (c != 0) { // if they are not zero, + // multiply by entry in table + // Build a table to aid in multiplying 2 bits at a time. + // It takes too long to build tables for more bits. + uint64_t m = zeroes_table[c + i - 1]; + m <<= 1; + uint64_t m2 = m << 1; + uint64_t mtab[4] = {0, m, m2, m2 ^ m}; + + // Do the multiply one byte at a time. + uint64_t result = 0; + for (int x = 0; x < 32; x += 8) { + // The carry-less multiply. + result ^= mtab[l & 3] ^ (mtab[(l >> 2) & 3] << 2) ^ + (mtab[(l >> 4) & 3] << 4) ^ (mtab[(l >> 6) & 3] << 6); + l >>= 8; + + // Reduce modulo the polynomial + result = (result >> 8) ^ poly_table[result & 0xff]; + } + l = static_cast<uint32_t>(result); + } + } + *crc = l; + } +} + +void CRC32::ExtendByZeroes(uint32_t* crc, size_t length) const { + return CRC32::ExtendByZeroesImpl(crc, length, zeroes_, table0_); +} + +void CRC32::UnextendByZeroes(uint32_t* crc, size_t length) const { + // See the comment in CRC32::InitTables() for an explanation of the algorithm + // below. + *crc = ReverseBits(*crc); + ExtendByZeroesImpl(crc, length, reverse_zeroes_, reverse_table0_); + *crc = ReverseBits(*crc); +} + +void CRC32::Scramble(uint32_t* crc) const { + // Rotate by near half the word size plus 1. See the scramble comment in + // crc_internal.h for an explanation. + constexpr int scramble_rotate = (32 / 2) + 1; + *crc = RotateRight<uint32_t>(static_cast<unsigned int>(*crc + kScrambleLo), + 32, scramble_rotate) & + MaskOfLength<uint32_t>(32); +} + +void CRC32::Unscramble(uint32_t* crc) const { + constexpr int scramble_rotate = (32 / 2) + 1; + uint64_t rotated = RotateRight<uint32_t>(static_cast<unsigned int>(*crc), 32, + 32 - scramble_rotate); + *crc = (rotated - kScrambleLo) & MaskOfLength<uint32_t>(32); +} + +// Constructor and destructor for base class CRC. +CRC::~CRC() {} +CRC::CRC() {} + +// The "constructor" for a CRC32C with a standard polynomial. +CRC* CRC::Crc32c() { + static CRC* singleton = CRCImpl::NewInternal(); + return singleton; +} + +// This Concat implementation works for arbitrary polynomials. +void CRC::Concat(uint32_t* px, uint32_t y, size_t ylen) { + // https://en.wikipedia.org/wiki/Mathematics_of_cyclic_redundancy_checks + // The CRC of a message M is the remainder of polynomial divison modulo G, + // where the coefficient arithmetic is performed modulo 2 (so +/- are XOR): + // R(x) = M(x) x**n (mod G) + // (n is the degree of G) + // In practice, we use an initial value A and a bitmask B to get + // R = (A ^ B)x**|M| ^ Mx**n ^ B (mod G) + // If M is the concatenation of two strings S and T, and Z is the string of + // len(T) 0s, then the remainder CRC(ST) can be expressed as: + // R = (A ^ B)x**|ST| ^ STx**n ^ B + // = (A ^ B)x**|SZ| ^ SZx**n ^ B ^ Tx**n + // = CRC(SZ) ^ Tx**n + // CRC(Z) = (A ^ B)x**|T| ^ B + // CRC(T) = (A ^ B)x**|T| ^ Tx**n ^ B + // So R = CRC(SZ) ^ CRC(Z) ^ CRC(T) + // + // And further, since CRC(SZ) = Extend(CRC(S), Z), + // CRC(SZ) ^ CRC(Z) = Extend(CRC(S) ^ CRC(''), Z). + uint32_t z; + uint32_t t; + Empty(&z); + t = *px ^ z; + ExtendByZeroes(&t, ylen); + *px = t ^ y; +} + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/crc/internal/crc.h b/absl/crc/internal/crc.h new file mode 100644 index 00000000..72515b06 --- /dev/null +++ b/absl/crc/internal/crc.h @@ -0,0 +1,91 @@ +// Copyright 2022 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_CRC_INTERNAL_CRC_H_ +#define ABSL_CRC_INTERNAL_CRC_H_ + +#include <cstdint> + +#include "absl/base/config.h" + +// This class implements CRCs (aka Rabin Fingerprints). +// Treats the input as a polynomial with coefficients in Z(2), +// and finds the remainder when divided by an primitive polynomial +// of the appropriate length. + +// A polynomial is represented by the bit pattern formed by its coefficients, +// but with the highest order bit not stored. +// The highest degree coefficient is stored in the lowest numbered bit +// in the lowest addressed byte. Thus, in what follows, the highest degree +// coefficient that is stored is in the low order bit of "lo" or "*lo". + +// Hardware acceleration is used when available. + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +class CRC { + public: + virtual ~CRC(); + + // Place the CRC of the empty string in "*crc" + virtual void Empty(uint32_t* crc) const = 0; + + // If "*crc" is the CRC of bytestring A, place the CRC of + // the bytestring formed from the concatenation of A and the "length" + // bytes at "bytes" into "*crc". + virtual void Extend(uint32_t* crc, const void* bytes, + size_t length) const = 0; + + // Equivalent to Extend(crc, bytes, length) where "bytes" + // points to an array of "length" zero bytes. + virtual void ExtendByZeroes(uint32_t* crc, size_t length) const = 0; + + // Inverse opration of ExtendByZeroes. If `crc` is the CRC value of a string + // ending in `length` zero bytes, this returns a CRC value of that string + // with those zero bytes removed. + virtual void UnextendByZeroes(uint32_t* crc, size_t length) const = 0; + + // If *px is the CRC (as defined by *crc) of some string X, + // and y is the CRC of some string Y that is ylen bytes long, set + // *px to the CRC of the concatenation of X followed by Y. + virtual void Concat(uint32_t* px, uint32_t y, size_t ylen); + + // Apply a non-linear transformation to "*crc" so that + // it is safe to CRC the result with the same polynomial without + // any reduction of error-detection ability in the outer CRC. + // Unscramble() performs the inverse transformation. + // It is strongly recommended that CRCs be scrambled before storage or + // transmission, and unscrambled at the other end before futher manipulation. + virtual void Scramble(uint32_t* crc) const = 0; + virtual void Unscramble(uint32_t* crc) const = 0; + + // Crc32c() returns the singleton implementation of CRC for the CRC32C + // polynomial. Returns a handle that MUST NOT be destroyed with delete. + static CRC* Crc32c(); + + protected: + CRC(); // Clients may not call constructor; use Crc32c() instead. + + private: + CRC(const CRC&) = delete; + CRC& operator=(const CRC&) = delete; +}; + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CRC_INTERNAL_CRC_H_ diff --git a/absl/crc/internal/crc32_x86_arm_combined_simd.h b/absl/crc/internal/crc32_x86_arm_combined_simd.h new file mode 100644 index 00000000..f23cd75e --- /dev/null +++ b/absl/crc/internal/crc32_x86_arm_combined_simd.h @@ -0,0 +1,267 @@ +// Copyright 2022 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_CRC_INTERNAL_CRC32_X86_ARM_COMBINED_SIMD_H_ +#define ABSL_CRC_INTERNAL_CRC32_X86_ARM_COMBINED_SIMD_H_ + +#include <cstdint> + +#include "absl/base/config.h" + +// ------------------------------------------------------------------------- +// Many x86 and ARM machines have CRC acceleration hardware. +// We can do a faster version of Extend() on such machines. +// We define a translation layer for both x86 and ARM for the ease of use and +// most performance gains. + +// We need CRC (part of SSE 4.2) and PCLMULQDQ instructions. +#if defined(__SSE4_2__) && defined(__PCLMUL__) + +#include <x86intrin.h> +#define ABSL_CRC_INTERNAL_HAVE_X86_SIMD + +#elif defined(_MSC_VER) && defined(__AVX__) + +// MSVC AVX (/arch:AVX) implies SSE 4.2 and PCLMULQDQ. +#include <intrin.h> +#define ABSL_CRC_INTERNAL_HAVE_X86_SIMD + +#elif defined(__aarch64__) && defined(__LITTLE_ENDIAN__) && \ + defined(__ARM_FEATURE_CRC32) && defined(__ARM_NEON) && \ + defined(__ARM_FEATURE_CRYPTO) + +#include <arm_acle.h> +#include <arm_neon.h> +#define ABSL_CRC_INTERNAL_HAVE_ARM_SIMD + +#endif + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +#if defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD) || \ + defined(ABSL_CRC_INTERNAL_HAVE_X86_SIMD) + +#if defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD) +using V128 = uint64x2_t; +#else +using V128 = __m128i; +#endif + +// Starting with the initial value in |crc|, accumulates a CRC32 value for +// unsigned integers of different sizes. +uint32_t CRC32_u8(uint32_t crc, uint8_t v); + +uint32_t CRC32_u16(uint32_t crc, uint16_t v); + +uint32_t CRC32_u32(uint32_t crc, uint32_t v); + +uint32_t CRC32_u64(uint32_t crc, uint64_t v); + +// Loads 128 bits of integer data. |src| must be 16-byte aligned. +V128 V128_Load(const V128* src); + +// Load 128 bits of integer data. |src| does not need to be aligned. +V128 V128_LoadU(const V128* src); + +// Polynomially multiplies the high 64 bits of |l| and |r|. +V128 V128_PMulHi(const V128 l, const V128 r); + +// Polynomially multiplies the low 64 bits of |l| and |r|. +V128 V128_PMulLow(const V128 l, const V128 r); + +// Polynomially multiplies the low 64 bits of |r| and high 64 bits of |l|. +V128 V128_PMul01(const V128 l, const V128 r); + +// Polynomially multiplies the low 64 bits of |l| and high 64 bits of |r|. +V128 V128_PMul10(const V128 l, const V128 r); + +// Produces a XOR operation of |l| and |r|. +V128 V128_Xor(const V128 l, const V128 r); + +// Produces an AND operation of |l| and |r|. +V128 V128_And(const V128 l, const V128 r); + +// Sets two 64 bit integers to one 128 bit vector. The order is reverse. +// dst[63:0] := |r| +// dst[127:64] := |l| +V128 V128_From2x64(const uint64_t l, const uint64_t r); + +// Shift |l| right by |imm| bytes while shifting in zeros. +template <int imm> +V128 V128_ShiftRight(const V128 l); + +// Extracts a 32-bit integer from |l|, selected with |imm|. +template <int imm> +int V128_Extract32(const V128 l); + +// Extracts the low 64 bits from V128. +int64_t V128_Low64(const V128 l); + +// Left-shifts packed 64-bit integers in l by r. +V128 V128_ShiftLeft64(const V128 l, const V128 r); + +#endif + +#if defined(ABSL_CRC_INTERNAL_HAVE_X86_SIMD) + +inline uint32_t CRC32_u8(uint32_t crc, uint8_t v) { + return _mm_crc32_u8(crc, v); +} + +inline uint32_t CRC32_u16(uint32_t crc, uint16_t v) { + return _mm_crc32_u16(crc, v); +} + +inline uint32_t CRC32_u32(uint32_t crc, uint32_t v) { + return _mm_crc32_u32(crc, v); +} + +inline uint32_t CRC32_u64(uint32_t crc, uint64_t v) { + return static_cast<uint32_t>(_mm_crc32_u64(crc, v)); +} + +inline V128 V128_Load(const V128* src) { return _mm_load_si128(src); } + +inline V128 V128_LoadU(const V128* src) { return _mm_loadu_si128(src); } + +inline V128 V128_PMulHi(const V128 l, const V128 r) { + return _mm_clmulepi64_si128(l, r, 0x11); +} + +inline V128 V128_PMulLow(const V128 l, const V128 r) { + return _mm_clmulepi64_si128(l, r, 0x00); +} + +inline V128 V128_PMul01(const V128 l, const V128 r) { + return _mm_clmulepi64_si128(l, r, 0x01); +} + +inline V128 V128_PMul10(const V128 l, const V128 r) { + return _mm_clmulepi64_si128(l, r, 0x10); +} + +inline V128 V128_Xor(const V128 l, const V128 r) { return _mm_xor_si128(l, r); } + +inline V128 V128_And(const V128 l, const V128 r) { return _mm_and_si128(l, r); } + +inline V128 V128_From2x64(const uint64_t l, const uint64_t r) { + return _mm_set_epi64x(static_cast<int64_t>(l), static_cast<int64_t>(r)); +} + +template <int imm> +inline V128 V128_ShiftRight(const V128 l) { + return _mm_srli_si128(l, imm); +} + +template <int imm> +inline int V128_Extract32(const V128 l) { + return _mm_extract_epi32(l, imm); +} + +inline int64_t V128_Low64(const V128 l) { return _mm_cvtsi128_si64(l); } + +inline V128 V128_ShiftLeft64(const V128 l, const V128 r) { + return _mm_sll_epi64(l, r); +} + +#elif defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD) + +inline uint32_t CRC32_u8(uint32_t crc, uint8_t v) { return __crc32cb(crc, v); } + +inline uint32_t CRC32_u16(uint32_t crc, uint16_t v) { + return __crc32ch(crc, v); +} + +inline uint32_t CRC32_u32(uint32_t crc, uint32_t v) { + return __crc32cw(crc, v); +} + +inline uint32_t CRC32_u64(uint32_t crc, uint64_t v) { + return __crc32cd(crc, v); +} + +inline V128 V128_Load(const V128* src) { + return vld1q_u64(reinterpret_cast<const uint64_t*>(src)); +} + +inline V128 V128_LoadU(const V128* src) { + return vld1q_u64(reinterpret_cast<const uint64_t*>(src)); +} + +// Using inline assembly as clang does not generate the pmull2 instruction and +// performance drops by 15-20%. +// TODO(b/193678732): Investigate why the compiler decides not to generate +// such instructions and why it becomes so much worse. +inline V128 V128_PMulHi(const V128 l, const V128 r) { + uint64x2_t res; + __asm__ __volatile__("pmull2 %0.1q, %1.2d, %2.2d \n\t" + : "=w"(res) + : "w"(l), "w"(r)); + return res; +} + +inline V128 V128_PMulLow(const V128 l, const V128 r) { + return reinterpret_cast<V128>(vmull_p64( + reinterpret_cast<poly64_t>(vget_low_p64(vreinterpretq_p64_u64(l))), + reinterpret_cast<poly64_t>(vget_low_p64(vreinterpretq_p64_u64(r))))); +} + +inline V128 V128_PMul01(const V128 l, const V128 r) { + return reinterpret_cast<V128>(vmull_p64( + reinterpret_cast<poly64_t>(vget_high_p64(vreinterpretq_p64_u64(l))), + reinterpret_cast<poly64_t>(vget_low_p64(vreinterpretq_p64_u64(r))))); +} + +inline V128 V128_PMul10(const V128 l, const V128 r) { + return reinterpret_cast<V128>(vmull_p64( + reinterpret_cast<poly64_t>(vget_low_p64(vreinterpretq_p64_u64(l))), + reinterpret_cast<poly64_t>(vget_high_p64(vreinterpretq_p64_u64(r))))); +} + +inline V128 V128_Xor(const V128 l, const V128 r) { return veorq_u64(l, r); } + +inline V128 V128_And(const V128 l, const V128 r) { return vandq_u64(l, r); } + +inline V128 V128_From2x64(const uint64_t l, const uint64_t r) { + return vcombine_u64(vcreate_u64(r), vcreate_u64(l)); +} + +template <int imm> +inline V128 V128_ShiftRight(const V128 l) { + return vreinterpretq_u64_s8( + vextq_s8(vreinterpretq_s8_u64(l), vdupq_n_s8(0), imm)); +} + +template <int imm> +inline int V128_Extract32(const V128 l) { + return vgetq_lane_s32(vreinterpretq_s32_u64(l), imm); +} + +inline int64_t V128_Low64(const V128 l) { + return vgetq_lane_s64(vreinterpretq_s64_u64(l), 0); +} + +inline V128 V128_ShiftLeft64(const V128 l, const V128 r) { + return vshlq_u64(l, vreinterpretq_s64_u64(r)); +} + +#endif + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CRC_INTERNAL_CRC32_X86_ARM_COMBINED_SIMD_H_ diff --git a/absl/crc/internal/crc32c.h b/absl/crc/internal/crc32c.h new file mode 100644 index 00000000..34027c55 --- /dev/null +++ b/absl/crc/internal/crc32c.h @@ -0,0 +1,39 @@ +// Copyright 2022 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_CRC_INTERNAL_CRC32C_H_ +#define ABSL_CRC_INTERNAL_CRC32C_H_ + +#include "absl/base/config.h" +#include "absl/crc/crc32c.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +// Modifies a CRC32 value by removing `length` bytes with a value of 0 from +// the end of the string. +// +// This is the inverse operation of ExtendCrc32cByZeroes(). +// +// This operation has a runtime cost of O(log(`length`)) +// +// Internal implementation detail, exposed for testing only. +crc32c_t UnextendCrc32cByZeroes(crc32c_t initial_crc, size_t length); + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CRC_INTERNAL_CRC32C_H_ diff --git a/absl/crc/internal/crc32c_inline.h b/absl/crc/internal/crc32c_inline.h new file mode 100644 index 00000000..6236c10b --- /dev/null +++ b/absl/crc/internal/crc32c_inline.h @@ -0,0 +1,72 @@ +// Copyright 2022 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_CRC_INTERNAL_CRC32C_INLINE_H_ +#define ABSL_CRC_INTERNAL_CRC32C_INLINE_H_ + +#include <cstdint> + +#include "absl/base/config.h" +#include "absl/base/internal/endian.h" +#include "absl/crc/internal/crc32_x86_arm_combined_simd.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +// CRC32C implementation optimized for small inputs. +// Either computes crc and return true, or if there is +// no hardware support does nothing and returns false. +inline bool ExtendCrc32cInline(uint32_t* crc, const char* p, size_t n) { +#if defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD) || \ + defined(ABSL_CRC_INTERNAL_HAVE_X86_SIMD) + constexpr uint32_t kCrc32Xor = 0xffffffffU; + *crc ^= kCrc32Xor; + if (n & 1) { + *crc = CRC32_u8(*crc, static_cast<uint8_t>(*p)); + n--; + p++; + } + if (n & 2) { + *crc = CRC32_u16(*crc, absl::little_endian::Load16(p)); + n -= 2; + p += 2; + } + if (n & 4) { + *crc = CRC32_u32(*crc, absl::little_endian::Load32(p)); + n -= 4; + p += 4; + } + while (n) { + *crc = CRC32_u64(*crc, absl::little_endian::Load64(p)); + n -= 8; + p += 8; + } + *crc ^= kCrc32Xor; + return true; +#else + // No hardware support, signal the need to fallback. + static_cast<void>(crc); + static_cast<void>(p); + static_cast<void>(n); + return false; +#endif // defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD) || + // defined(ABSL_CRC_INTERNAL_HAVE_X86_SIMD) +} + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CRC_INTERNAL_CRC32C_INLINE_H_ diff --git a/absl/crc/internal/crc_cord_state.cc b/absl/crc/internal/crc_cord_state.cc new file mode 100644 index 00000000..d0be0ddd --- /dev/null +++ b/absl/crc/internal/crc_cord_state.cc @@ -0,0 +1,130 @@ +// Copyright 2022 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/crc/internal/crc_cord_state.h" + +#include <cassert> + +#include "absl/base/config.h" +#include "absl/numeric/bits.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +CrcCordState::RefcountedRep* CrcCordState::RefSharedEmptyRep() { + static CrcCordState::RefcountedRep* empty = new CrcCordState::RefcountedRep; + + assert(empty->count.load(std::memory_order_relaxed) >= 1); + assert(empty->rep.removed_prefix.length == 0); + assert(empty->rep.prefix_crc.empty()); + + Ref(empty); + return empty; +} + +CrcCordState::CrcCordState() : refcounted_rep_(new RefcountedRep) {} + +CrcCordState::CrcCordState(const CrcCordState& other) + : refcounted_rep_(other.refcounted_rep_) { + Ref(refcounted_rep_); +} + +CrcCordState::CrcCordState(CrcCordState&& other) + : refcounted_rep_(other.refcounted_rep_) { + // Make `other` valid for use after move. + other.refcounted_rep_ = RefSharedEmptyRep(); +} + +CrcCordState& CrcCordState::operator=(const CrcCordState& other) { + if (this != &other) { + Unref(refcounted_rep_); + refcounted_rep_ = other.refcounted_rep_; + Ref(refcounted_rep_); + } + return *this; +} + +CrcCordState& CrcCordState::operator=(CrcCordState&& other) { + if (this != &other) { + Unref(refcounted_rep_); + refcounted_rep_ = other.refcounted_rep_; + // Make `other` valid for use after move. + other.refcounted_rep_ = RefSharedEmptyRep(); + } + return *this; +} + +CrcCordState::~CrcCordState() { + Unref(refcounted_rep_); +} + +crc32c_t CrcCordState::Checksum() const { + if (rep().prefix_crc.empty()) { + return absl::crc32c_t{0}; + } + if (IsNormalized()) { + return rep().prefix_crc.back().crc; + } + return absl::RemoveCrc32cPrefix( + rep().removed_prefix.crc, rep().prefix_crc.back().crc, + rep().prefix_crc.back().length - rep().removed_prefix.length); +} + +CrcCordState::PrefixCrc CrcCordState::NormalizedPrefixCrcAtNthChunk( + size_t n) const { + assert(n < NumChunks()); + if (IsNormalized()) { + return rep().prefix_crc[n]; + } + size_t length = rep().prefix_crc[n].length - rep().removed_prefix.length; + return PrefixCrc(length, + absl::RemoveCrc32cPrefix(rep().removed_prefix.crc, + rep().prefix_crc[n].crc, length)); +} + +void CrcCordState::Normalize() { + if (IsNormalized() || rep().prefix_crc.empty()) { + return; + } + + Rep* r = mutable_rep(); + for (auto& prefix_crc : r->prefix_crc) { + size_t remaining = prefix_crc.length - r->removed_prefix.length; + prefix_crc.crc = absl::RemoveCrc32cPrefix(r->removed_prefix.crc, + prefix_crc.crc, remaining); + prefix_crc.length = remaining; + } + r->removed_prefix = PrefixCrc(); +} + +void CrcCordState::Poison() { + Rep* rep = mutable_rep(); + if (NumChunks() > 0) { + for (auto& prefix_crc : rep->prefix_crc) { + // This is basically CRC32::Scramble(). + uint32_t crc = static_cast<uint32_t>(prefix_crc.crc); + crc += 0x2e76e41b; + crc = absl::rotr(crc, 17); + prefix_crc.crc = crc32c_t{crc}; + } + } else { + // Add a fake corrupt chunk. + rep->prefix_crc.push_back(PrefixCrc(0, crc32c_t{1})); + } +} + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/crc/internal/crc_cord_state.h b/absl/crc/internal/crc_cord_state.h new file mode 100644 index 00000000..d305424c --- /dev/null +++ b/absl/crc/internal/crc_cord_state.h @@ -0,0 +1,159 @@ +// Copyright 2022 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_CRC_INTERNAL_CRC_CORD_STATE_H_ +#define ABSL_CRC_INTERNAL_CRC_CORD_STATE_H_ + +#include <atomic> +#include <cstddef> +#include <deque> + +#include "absl/base/config.h" +#include "absl/crc/crc32c.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +// CrcCordState is a copy-on-write class that holds the chunked CRC32C data +// that allows CrcCord to perform efficient substring operations. CrcCordState +// is used as a member variable in CrcCord. When a CrcCord is converted to a +// Cord, the CrcCordState is shallow-copied into the root node of the Cord. If +// the converted Cord is modified outside of CrcCord, the CrcCordState is +// discarded from the Cord. If the Cord is converted back to a CrcCord, and the +// Cord is still carrying the CrcCordState in its root node, the CrcCord can +// re-use the CrcCordState, making the construction of the CrcCord cheap. +// +// CrcCordState does not try to encapsulate the CRC32C state (CrcCord requires +// knowledge of how CrcCordState represents the CRC32C state). It does +// encapsulate the copy-on-write nature of the state. +class CrcCordState { + public: + // Constructors. + CrcCordState(); + CrcCordState(const CrcCordState&); + CrcCordState(CrcCordState&&); + + // Destructor. Atomically unreferences the data. + ~CrcCordState(); + + // Copy and move operators. + CrcCordState& operator=(const CrcCordState&); + CrcCordState& operator=(CrcCordState&&); + + // A (length, crc) pair. + struct PrefixCrc { + PrefixCrc() = default; + PrefixCrc(size_t length_arg, absl::crc32c_t crc_arg) + : length(length_arg), crc(crc_arg) {} + + size_t length = 0; + + // TODO(absl-team): Memory stomping often zeros out memory. If this struct + // gets overwritten, we could end up with {0, 0}, which is the correct CRC + // for a string of length 0. Consider storing a scrambled value and + // unscrambling it before verifying it. + absl::crc32c_t crc = absl::crc32c_t{0}; + }; + + // The representation of the chunked CRC32C data. + struct Rep { + // `removed_prefix` is the crc and length of any prefix that has been + // removed from the Cord (for example, by calling + // `CrcCord::RemovePrefix()`). To get the checkum of any prefix of the cord, + // this value must be subtracted from `prefix_crc`. See `Checksum()` for an + // example. + // + // CrcCordState is said to be "normalized" if removed_prefix.length == 0. + PrefixCrc removed_prefix; + + // A deque of (length, crc) pairs, representing length and crc of a prefix + // of the Cord, before removed_prefix has been subtracted. The lengths of + // the prefixes are stored in increasing order. If the Cord is not empty, + // the last value in deque is the contains the CRC32C of the entire Cord + // when removed_prefix is subtracted from it. + std::deque<PrefixCrc> prefix_crc; + }; + + // Returns a reference to the representation of the chunked CRC32C data. + const Rep& rep() const { return refcounted_rep_->rep; } + + // Returns a mutable reference to the representation of the chunked CRC32C + // data. Calling this function will copy the data if another instance also + // holds a reference to the data, so it is important to call rep() instead if + // the data may not be mutated. + Rep* mutable_rep() { + if (refcounted_rep_->count.load(std::memory_order_acquire) != 1) { + RefcountedRep* copy = new RefcountedRep; + copy->rep = refcounted_rep_->rep; + Unref(refcounted_rep_); + refcounted_rep_ = copy; + } + return &refcounted_rep_->rep; + } + + // Returns the CRC32C of the entire Cord. + absl::crc32c_t Checksum() const; + + // Returns true if the chunked CRC32C cached is normalized. + bool IsNormalized() const { return rep().removed_prefix.length == 0; } + + // Normalizes the chunked CRC32C checksum cache by substracting any removed + // prefix from the chunks. + void Normalize(); + + // Returns the number of cached chunks. + size_t NumChunks() const { return rep().prefix_crc.size(); } + + // Helper that returns the (length, crc) of the `n`-th cached chunked. + PrefixCrc NormalizedPrefixCrcAtNthChunk(size_t n) const; + + // Poisons all chunks to so that Checksum() will likely be incorrect with high + // probability. + void Poison(); + + private: + struct RefcountedRep { + std::atomic<int32_t> count{1}; + Rep rep; + }; + + // Adds a reference to the shared global empty `RefcountedRep`, and returns a + // pointer to the `RefcountedRep`. This is an optimization to avoid unneeded + // allocations when the allocation is unlikely to ever be used. The returned + // pointer can be `Unref()`ed when it is no longer needed. Since the returned + // instance will always have a reference counter greater than 1, attempts to + // modify it (by calling `mutable_rep()`) will create a new unshared copy. + static RefcountedRep* RefSharedEmptyRep(); + + static void Ref(RefcountedRep* r) { + assert(r != nullptr); + r->count.fetch_add(1, std::memory_order_relaxed); + } + + static void Unref(RefcountedRep* r) { + assert(r != nullptr); + if (r->count.fetch_sub(1, std::memory_order_acq_rel) == 1) { + delete r; + } + } + + RefcountedRep* refcounted_rep_; +}; + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CRC_INTERNAL_CRC_CORD_STATE_H_ diff --git a/absl/crc/internal/crc_cord_state_test.cc b/absl/crc/internal/crc_cord_state_test.cc new file mode 100644 index 00000000..e2c8e3cd --- /dev/null +++ b/absl/crc/internal/crc_cord_state_test.cc @@ -0,0 +1,124 @@ +// Copyright 2022 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/crc/internal/crc_cord_state.h" + +#include <algorithm> +#include <cstdint> +#include <string> +#include <utility> + +#include "gtest/gtest.h" +#include "absl/crc/crc32c.h" + +namespace { + +TEST(CrcCordState, Default) { + absl::crc_internal::CrcCordState state; + EXPECT_TRUE(state.IsNormalized()); + EXPECT_EQ(state.Checksum(), absl::crc32c_t{0}); + state.Normalize(); + EXPECT_EQ(state.Checksum(), absl::crc32c_t{0}); +} + +TEST(CrcCordState, Normalize) { + absl::crc_internal::CrcCordState state; + auto* rep = state.mutable_rep(); + rep->prefix_crc.push_back( + absl::crc_internal::CrcCordState::PrefixCrc(1000, absl::crc32c_t{1000})); + rep->prefix_crc.push_back( + absl::crc_internal::CrcCordState::PrefixCrc(2000, absl::crc32c_t{2000})); + rep->removed_prefix = + absl::crc_internal::CrcCordState::PrefixCrc(500, absl::crc32c_t{500}); + + // The removed_prefix means state is not normalized. + EXPECT_FALSE(state.IsNormalized()); + + absl::crc32c_t crc = state.Checksum(); + state.Normalize(); + EXPECT_TRUE(state.IsNormalized()); + + // The checksum should not change as a result of calling Normalize(). + EXPECT_EQ(state.Checksum(), crc); + EXPECT_EQ(rep->removed_prefix.length, 0); +} + +TEST(CrcCordState, Copy) { + absl::crc_internal::CrcCordState state; + auto* rep = state.mutable_rep(); + rep->prefix_crc.push_back( + absl::crc_internal::CrcCordState::PrefixCrc(1000, absl::crc32c_t{1000})); + + absl::crc_internal::CrcCordState copy = state; + + EXPECT_EQ(state.Checksum(), absl::crc32c_t{1000}); + EXPECT_EQ(copy.Checksum(), absl::crc32c_t{1000}); +} + +TEST(CrcCordState, UnsharedSelfCopy) { + absl::crc_internal::CrcCordState state; + auto* rep = state.mutable_rep(); + rep->prefix_crc.push_back( + absl::crc_internal::CrcCordState::PrefixCrc(1000, absl::crc32c_t{1000})); + + const absl::crc_internal::CrcCordState& ref = state; + state = ref; + + EXPECT_EQ(state.Checksum(), absl::crc32c_t{1000}); +} + +TEST(CrcCordState, Move) { + absl::crc_internal::CrcCordState state; + auto* rep = state.mutable_rep(); + rep->prefix_crc.push_back( + absl::crc_internal::CrcCordState::PrefixCrc(1000, absl::crc32c_t{1000})); + + absl::crc_internal::CrcCordState moved = std::move(state); + EXPECT_EQ(moved.Checksum(), absl::crc32c_t{1000}); +} + +TEST(CrcCordState, UnsharedSelfMove) { + absl::crc_internal::CrcCordState state; + auto* rep = state.mutable_rep(); + rep->prefix_crc.push_back( + absl::crc_internal::CrcCordState::PrefixCrc(1000, absl::crc32c_t{1000})); + + absl::crc_internal::CrcCordState& ref = state; + state = std::move(ref); + + EXPECT_EQ(state.Checksum(), absl::crc32c_t{1000}); +} + +TEST(CrcCordState, PoisonDefault) { + absl::crc_internal::CrcCordState state; + state.Poison(); + EXPECT_NE(state.Checksum(), absl::crc32c_t{0}); +} + +TEST(CrcCordState, PoisonData) { + absl::crc_internal::CrcCordState state; + auto* rep = state.mutable_rep(); + rep->prefix_crc.push_back( + absl::crc_internal::CrcCordState::PrefixCrc(1000, absl::crc32c_t{1000})); + rep->prefix_crc.push_back( + absl::crc_internal::CrcCordState::PrefixCrc(2000, absl::crc32c_t{2000})); + rep->removed_prefix = + absl::crc_internal::CrcCordState::PrefixCrc(500, absl::crc32c_t{500}); + + absl::crc32c_t crc = state.Checksum(); + state.Poison(); + EXPECT_NE(state.Checksum(), crc); +} + +} // namespace diff --git a/absl/crc/internal/crc_internal.h b/absl/crc/internal/crc_internal.h new file mode 100644 index 00000000..0611b383 --- /dev/null +++ b/absl/crc/internal/crc_internal.h @@ -0,0 +1,179 @@ +// Copyright 2022 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_CRC_INTERNAL_CRC_INTERNAL_H_ +#define ABSL_CRC_INTERNAL_CRC_INTERNAL_H_ + +#include <cstdint> +#include <memory> +#include <vector> + +#include "absl/base/internal/raw_logging.h" +#include "absl/crc/internal/crc.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +namespace crc_internal { + +// Prefetch constants used in some Extend() implementations +constexpr int kPrefetchHorizon = ABSL_CACHELINE_SIZE * 4; // Prefetch this far +// Shorter prefetch distance for smaller buffers +constexpr int kPrefetchHorizonMedium = ABSL_CACHELINE_SIZE * 1; +static_assert(kPrefetchHorizon >= 64, "CRCPrefetchHorizon less than loop len"); + +// We require the Scramble() function: +// - to be reversible (Unscramble() must exist) +// - to be non-linear in the polynomial's Galois field (so the CRC of a +// scrambled CRC is not linearly affected by the scrambled CRC, even if +// using the same polynomial) +// - not to be its own inverse. Preferably, if X=Scramble^N(X) and N!=0, then +// N is large. +// - to be fast. +// - not to change once defined. +// We introduce non-linearity in two ways: +// Addition of a constant. +// - The carries introduce non-linearity; we use bits of an irrational +// (phi) to make it unlikely that we introduce no carries. +// Rotate by a constant number of bits. +// - We use floor(degree/2)+1, which does not divide the degree, and +// splits the bits nearly evenly, which makes it less likely the +// halves will be the same or one will be all zeroes. +// We do both things to improve the chances of non-linearity in the face of +// bit patterns with low numbers of bits set, while still being fast. +// Below is the constant that we add. The bits are the first 128 bits of the +// fractional part of phi, with a 1 ored into the bottom bit to maximize the +// cycle length of repeated adds. +constexpr uint64_t kScrambleHi = (static_cast<uint64_t>(0x4f1bbcdcU) << 32) | + static_cast<uint64_t>(0xbfa53e0aU); +constexpr uint64_t kScrambleLo = (static_cast<uint64_t>(0xf9ce6030U) << 32) | + static_cast<uint64_t>(0x2e76e41bU); + +class CRCImpl : public CRC { // Implemention of the abstract class CRC + public: + using Uint32By256 = uint32_t[256]; + + CRCImpl() {} + ~CRCImpl() override = default; + + // The internal version of CRC::New(). + static CRCImpl* NewInternal(); + + void Empty(uint32_t* crc) const override; + + // Fill in a table for updating a CRC by one word of 'word_size' bytes + // [last_lo, last_hi] contains the answer if the last bit in the word + // is set. + static void FillWordTable(uint32_t poly, uint32_t last, int word_size, + Uint32By256* t); + + // Build the table for extending by zeroes, returning the number of entries. + // For a in {1, 2, ..., ZEROES_BASE-1}, b in {0, 1, 2, 3, ...}, + // entry j=a-1+(ZEROES_BASE-1)*b + // contains a polynomial Pi such that multiplying + // a CRC by Pi mod P, where P is the CRC polynomial, is equivalent to + // appending a*2**(ZEROES_BASE_LG*b) zero bytes to the original string. + static int FillZeroesTable(uint32_t poly, Uint32By256* t); + + virtual void InitTables() = 0; + + private: + CRCImpl(const CRCImpl&) = delete; + CRCImpl& operator=(const CRCImpl&) = delete; +}; + +// This is the 32-bit implementation. It handles all sizes from 8 to 32. +class CRC32 : public CRCImpl { + public: + CRC32() {} + ~CRC32() override {} + + void Extend(uint32_t* crc, const void* bytes, size_t length) const override; + void ExtendByZeroes(uint32_t* crc, size_t length) const override; + void Scramble(uint32_t* crc) const override; + void Unscramble(uint32_t* crc) const override; + void UnextendByZeroes(uint32_t* crc, size_t length) const override; + + void InitTables() override; + + private: + // Common implementation guts for ExtendByZeroes and UnextendByZeroes(). + // + // zeroes_table is a table as returned by FillZeroesTable(), containing + // polynomials representing CRCs of strings-of-zeros of various lenghts, + // and which can be combined by polynomial multiplication. poly_table is + // a table of CRC byte extension values. These tables are determined by + // the generator polynomial. + // + // These will be set to reverse_zeroes_ and reverse_table0_ for Unextend, and + // CRC32::zeroes_ and CRC32::table0_ for Extend. + void ExtendByZeroesImpl(uint32_t* crc, size_t length, + const uint32_t zeroes_table[256], + const uint32_t poly_table[256]) const; + + uint32_t table0_[256]; // table of byte extensions + uint32_t zeroes_[256]; // table of zero extensions + + // table of 4-byte extensions shifted by 12 bytes of zeroes + uint32_t table_[4][256]; + + // Reverse lookup tables, using the alternate polynomial used by + // UnextendByZeroes(). + uint32_t reverse_table0_[256]; // table of reverse byte extensions + uint32_t reverse_zeroes_[256]; // table of reverse zero extensions + + CRC32(const CRC32&) = delete; + CRC32& operator=(const CRC32&) = delete; +}; + +// Helpers + +// Return a bit mask containing len 1-bits. +// Requires 0 < len <= sizeof(T) +template <typename T> +T MaskOfLength(int len) { + // shift 2 by len-1 rather than 1 by len because shifts of wordsize + // are undefined. + return (T(2) << (len - 1)) - 1; +} + +// Rotate low-order "width" bits of "in" right by "r" bits, +// setting other bits in word to arbitrary values. +template <typename T> +T RotateRight(T in, int width, int r) { + return (in << (width - r)) | ((in >> r) & MaskOfLength<T>(width - r)); +} + +// RoundUp<N>(p) returns the lowest address >= p aligned to an N-byte +// boundary. Requires that N is a power of 2. +template <int alignment> +const uint8_t* RoundUp(const uint8_t* p) { + static_assert((alignment & (alignment - 1)) == 0, "alignment is not 2^n"); + constexpr uintptr_t mask = alignment - 1; + const uintptr_t as_uintptr = reinterpret_cast<uintptr_t>(p); + return reinterpret_cast<const uint8_t*>((as_uintptr + mask) & ~mask); +} + +// Return a newly created CRC32AcceleratedX86ARMCombined if we can use Intel's +// or ARM's CRC acceleration for a given polynomial. Return nullptr otherwise. +CRCImpl* TryNewCRC32AcceleratedX86ARMCombined(); + +// Return all possible hardware accelerated implementations. For testing only. +std::vector<std::unique_ptr<CRCImpl>> NewCRC32AcceleratedX86ARMCombinedAll(); + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CRC_INTERNAL_CRC_INTERNAL_H_ diff --git a/absl/crc/internal/crc_memcpy.h b/absl/crc/internal/crc_memcpy.h new file mode 100644 index 00000000..ae9cccad --- /dev/null +++ b/absl/crc/internal/crc_memcpy.h @@ -0,0 +1,112 @@ +// Copyright 2022 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_CRC_INTERNAL_CRC_MEMCPY_H_ +#define ABSL_CRC_INTERNAL_CRC_MEMCPY_H_ + +#include <cstddef> +#include <memory> + +#include "absl/base/config.h" +#include "absl/crc/crc32c.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +class CrcMemcpyEngine { + public: + virtual ~CrcMemcpyEngine() = default; + + virtual crc32c_t Compute(void* __restrict dst, const void* __restrict src, + std::size_t length, crc32c_t initial_crc) const = 0; + + protected: + CrcMemcpyEngine() = default; +}; + +class CrcMemcpy { + public: + static crc32c_t CrcAndCopy(void* __restrict dst, const void* __restrict src, + std::size_t length, + crc32c_t initial_crc = crc32c_t{0}, + bool non_temporal = false) { + static const ArchSpecificEngines engines = GetArchSpecificEngines(); + auto* engine = non_temporal ? engines.non_temporal : engines.temporal; + return engine->Compute(dst, src, length, initial_crc); + } + + // For testing only: get an architecture-specific engine for tests. + static std::unique_ptr<CrcMemcpyEngine> GetTestEngine(int vector, + int integer); + + private: + struct ArchSpecificEngines { + CrcMemcpyEngine* temporal; + CrcMemcpyEngine* non_temporal; + }; + + static ArchSpecificEngines GetArchSpecificEngines(); +}; + +// Fallback CRC-memcpy engine. +class FallbackCrcMemcpyEngine : public CrcMemcpyEngine { + public: + FallbackCrcMemcpyEngine() = default; + FallbackCrcMemcpyEngine(const FallbackCrcMemcpyEngine&) = delete; + FallbackCrcMemcpyEngine operator=(const FallbackCrcMemcpyEngine&) = delete; + + crc32c_t Compute(void* __restrict dst, const void* __restrict src, + std::size_t length, crc32c_t initial_crc) const override; +}; + +// CRC Non-Temporal-Memcpy engine. +class CrcNonTemporalMemcpyEngine : public CrcMemcpyEngine { + public: + CrcNonTemporalMemcpyEngine() = default; + CrcNonTemporalMemcpyEngine(const CrcNonTemporalMemcpyEngine&) = delete; + CrcNonTemporalMemcpyEngine operator=(const CrcNonTemporalMemcpyEngine&) = + delete; + + crc32c_t Compute(void* __restrict dst, const void* __restrict src, + std::size_t length, crc32c_t initial_crc) const override; +}; + +// CRC Non-Temporal-Memcpy AVX engine. +class CrcNonTemporalMemcpyAVXEngine : public CrcMemcpyEngine { + public: + CrcNonTemporalMemcpyAVXEngine() = default; + CrcNonTemporalMemcpyAVXEngine(const CrcNonTemporalMemcpyAVXEngine&) = delete; + CrcNonTemporalMemcpyAVXEngine operator=( + const CrcNonTemporalMemcpyAVXEngine&) = delete; + + crc32c_t Compute(void* __restrict dst, const void* __restrict src, + std::size_t length, crc32c_t initial_crc) const override; +}; + +// Copy source to destination and return the CRC32C of the data copied. If an +// accelerated version is available, use the accelerated version, otherwise use +// the generic fallback version. +inline crc32c_t Crc32CAndCopy(void* __restrict dst, const void* __restrict src, + std::size_t length, + crc32c_t initial_crc = crc32c_t{0}, + bool non_temporal = false) { + return CrcMemcpy::CrcAndCopy(dst, src, length, initial_crc, non_temporal); +} + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CRC_INTERNAL_CRC_MEMCPY_H_ diff --git a/absl/crc/internal/crc_memcpy_fallback.cc b/absl/crc/internal/crc_memcpy_fallback.cc new file mode 100644 index 00000000..4579c164 --- /dev/null +++ b/absl/crc/internal/crc_memcpy_fallback.cc @@ -0,0 +1,75 @@ +// Copyright 2022 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 <cstdint> +#include <memory> + +#include "absl/base/config.h" +#include "absl/crc/crc32c.h" +#include "absl/crc/internal/crc_memcpy.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +absl::crc32c_t FallbackCrcMemcpyEngine::Compute(void* __restrict dst, + const void* __restrict src, + std::size_t length, + crc32c_t initial_crc) const { + constexpr size_t kBlockSize = 8192; + absl::crc32c_t crc = initial_crc; + + const char* src_bytes = reinterpret_cast<const char*>(src); + char* dst_bytes = reinterpret_cast<char*>(dst); + + // Copy + CRC loop - run 8k chunks until we are out of full chunks. CRC + // then copy was found to be slightly more efficient in our test cases. + std::size_t offset = 0; + for (; offset + kBlockSize < length; offset += kBlockSize) { + crc = absl::ExtendCrc32c(crc, + absl::string_view(src_bytes + offset, kBlockSize)); + memcpy(dst_bytes + offset, src_bytes + offset, kBlockSize); + } + + // Save some work if length is 0. + if (offset < length) { + std::size_t final_copy_size = length - offset; + crc = absl::ExtendCrc32c( + crc, absl::string_view(src_bytes + offset, final_copy_size)); + memcpy(dst_bytes + offset, src_bytes + offset, final_copy_size); + } + + return crc; +} + +// Compile the following only if we don't have +#ifndef __SSE4_2__ + +CrcMemcpy::ArchSpecificEngines CrcMemcpy::GetArchSpecificEngines() { + CrcMemcpy::ArchSpecificEngines engines; + engines.temporal = new FallbackCrcMemcpyEngine(); + engines.non_temporal = new FallbackCrcMemcpyEngine(); + return engines; +} + +std::unique_ptr<CrcMemcpyEngine> CrcMemcpy::GetTestEngine(int /*vector*/, + int /*integer*/) { + return std::make_unique<FallbackCrcMemcpyEngine>(); +} + +#endif + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/crc/internal/crc_memcpy_test.cc b/absl/crc/internal/crc_memcpy_test.cc new file mode 100644 index 00000000..bbdcd205 --- /dev/null +++ b/absl/crc/internal/crc_memcpy_test.cc @@ -0,0 +1,169 @@ +// Copyright 2022 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/crc/internal/crc_memcpy.h" + +#include <cstddef> +#include <cstdint> +#include <cstring> +#include <limits> +#include <memory> +#include <string> +#include <utility> + +#include "gtest/gtest.h" +#include "absl/crc/crc32c.h" +#include "absl/memory/memory.h" +#include "absl/random/distributions.h" +#include "absl/random/random.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" + +namespace { + +enum CrcEngine { + X86 = 0, + NONTEMPORAL = 1, + FALLBACK = 2, +}; + +// Correctness tests: +// - Every source/destination byte alignment 0-15, every size 0-511 bytes +// - Arbitrarily aligned source, large size +template <size_t max_size> +class CrcMemcpyTest : public testing::Test { + protected: + CrcMemcpyTest() { + source_ = std::make_unique<char[]>(kSize); + destination_ = std::make_unique<char[]>(kSize); + } + static constexpr size_t kAlignment = 16; + static constexpr size_t kMaxCopySize = max_size; + static constexpr size_t kSize = kAlignment + kMaxCopySize; + std::unique_ptr<char[]> source_; + std::unique_ptr<char[]> destination_; + + absl::BitGen gen_; +}; + +// Small test is slightly larger 4096 bytes to allow coverage of the "large" +// copy function. The minimum size to exercise all code paths in that function +// would be around 256 consecutive tests (getting every possible tail value +// and 0-2 small copy loops after the main block), so testing from 4096-4500 +// will cover all of those code paths multiple times. +typedef CrcMemcpyTest<4500> CrcSmallTest; +typedef CrcMemcpyTest<(1 << 24)> CrcLargeTest; +// Parametrize the small test so that it can be done with all configurations. +template <typename ParamsT> +class x86ParamTestTemplate : public CrcSmallTest, + public ::testing::WithParamInterface<ParamsT> { + protected: + x86ParamTestTemplate() { + if (GetParam().crc_engine_selector == FALLBACK) { + engine_ = std::make_unique<absl::crc_internal::FallbackCrcMemcpyEngine>(); + } else if (GetParam().crc_engine_selector == NONTEMPORAL) { + engine_ = + std::make_unique<absl::crc_internal::CrcNonTemporalMemcpyEngine>(); + } else { + engine_ = absl::crc_internal::CrcMemcpy::GetTestEngine( + GetParam().vector_lanes, GetParam().integer_lanes); + } + } + + // Convenience method. + ParamsT GetParam() const { + return ::testing::WithParamInterface<ParamsT>::GetParam(); + } + + std::unique_ptr<absl::crc_internal::CrcMemcpyEngine> engine_; +}; +struct TestParams { + CrcEngine crc_engine_selector = X86; + int vector_lanes = 0; + int integer_lanes = 0; +}; +using x86ParamTest = x86ParamTestTemplate<TestParams>; +// SmallCorrectness is designed to exercise every possible set of code paths +// in the memcpy code, not including the loop. +TEST_P(x86ParamTest, SmallCorrectnessCheckSourceAlignment) { + constexpr size_t kTestSizes[] = {0, 100, 255, 512, 1024, 4000, kMaxCopySize}; + + for (size_t source_alignment = 0; source_alignment < kAlignment; + source_alignment++) { + for (auto size : kTestSizes) { + char* base_data = static_cast<char*>(source_.get()) + source_alignment; + for (size_t i = 0; i < size; i++) { + *(base_data + i) = + static_cast<char>(absl::Uniform<unsigned char>(gen_)); + } + absl::crc32c_t initial_crc = + absl::crc32c_t{absl::Uniform<uint32_t>(gen_)}; + absl::crc32c_t experiment_crc = + engine_->Compute(destination_.get(), source_.get() + source_alignment, + size, initial_crc); + // Check the memory region to make sure it is the same + int mem_comparison = + memcmp(destination_.get(), source_.get() + source_alignment, size); + SCOPED_TRACE(absl::StrCat("Error in memcpy of size: ", size, + " with source alignment: ", source_alignment)); + ASSERT_EQ(mem_comparison, 0); + absl::crc32c_t baseline_crc = absl::ExtendCrc32c( + initial_crc, + absl::string_view( + static_cast<char*>(source_.get()) + source_alignment, size)); + ASSERT_EQ(baseline_crc, experiment_crc); + } + } +} + +TEST_P(x86ParamTest, SmallCorrectnessCheckDestAlignment) { + constexpr size_t kTestSizes[] = {0, 100, 255, 512, 1024, 4000, kMaxCopySize}; + + for (size_t dest_alignment = 0; dest_alignment < kAlignment; + dest_alignment++) { + for (auto size : kTestSizes) { + char* base_data = static_cast<char*>(source_.get()); + for (size_t i = 0; i < size; i++) { + *(base_data + i) = + static_cast<char>(absl::Uniform<unsigned char>(gen_)); + } + absl::crc32c_t initial_crc = + absl::crc32c_t{absl::Uniform<uint32_t>(gen_)}; + absl::crc32c_t experiment_crc = + engine_->Compute(destination_.get() + dest_alignment, source_.get(), + size, initial_crc); + // Check the memory region to make sure it is the same + int mem_comparison = + memcmp(destination_.get() + dest_alignment, source_.get(), size); + SCOPED_TRACE(absl::StrCat("Error in memcpy of size: ", size, + " with dest alignment: ", dest_alignment)); + ASSERT_EQ(mem_comparison, 0); + absl::crc32c_t baseline_crc = absl::ExtendCrc32c( + initial_crc, + absl::string_view(static_cast<char*>(source_.get()), size)); + ASSERT_EQ(baseline_crc, experiment_crc); + } + } +} + +INSTANTIATE_TEST_SUITE_P(x86ParamTest, x86ParamTest, + ::testing::Values( + // Tests for configurations that may occur in prod. + TestParams{X86, 3, 0}, TestParams{X86, 1, 2}, + // Fallback test. + TestParams{FALLBACK, 0, 0}, + // Non Temporal + TestParams{NONTEMPORAL, 0, 0})); + +} // namespace diff --git a/absl/crc/internal/crc_memcpy_x86_64.cc b/absl/crc/internal/crc_memcpy_x86_64.cc new file mode 100644 index 00000000..a148fe17 --- /dev/null +++ b/absl/crc/internal/crc_memcpy_x86_64.cc @@ -0,0 +1,434 @@ +// Copyright 2022 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. + +// Simultaneous memcopy and CRC-32C for x86-64. Uses integer registers because +// XMM registers do not support the CRC instruction (yet). While copying, +// compute the running CRC of the data being copied. +// +// It is assumed that any CPU running this code has SSE4.2 instructions +// available (for CRC32C). This file will do nothing if that is not true. +// +// The CRC instruction has a 3-byte latency, and we are stressing the ALU ports +// here (unlike a traditional memcopy, which has almost no ALU use), so we will +// need to copy in such a way that the CRC unit is used efficiently. We have two +// regimes in this code: +// 1. For operations of size < kCrcSmallSize, do the CRC then the memcpy +// 2. For operations of size > kCrcSmallSize: +// a) compute an initial CRC + copy on a small amount of data to align the +// destination pointer on a 16-byte boundary. +// b) Split the data into 3 main regions and a tail (smaller than 48 bytes) +// c) Do the copy and CRC of the 3 main regions, interleaving (start with +// full cache line copies for each region, then move to single 16 byte +// pieces per region). +// d) Combine the CRCs with CRC32C::Concat. +// e) Copy the tail and extend the CRC with the CRC of the tail. +// This method is not ideal for op sizes between ~1k and ~8k because CRC::Concat +// takes a significant amount of time. A medium-sized approach could be added +// using 3 CRCs over fixed-size blocks where the zero-extensions required for +// CRC32C::Concat can be precomputed. + +#ifdef __SSE4_2__ +#include <immintrin.h> +#endif + +#ifdef _MSC_VER +#include <intrin.h> +#endif + +#include <array> +#include <cstddef> +#include <cstdint> +#include <type_traits> + +#include "absl/base/dynamic_annotations.h" +#include "absl/base/internal/prefetch.h" +#include "absl/base/optimization.h" +#include "absl/crc/crc32c.h" +#include "absl/crc/internal/cpu_detect.h" +#include "absl/crc/internal/crc_memcpy.h" +#include "absl/strings/string_view.h" + +#if defined(__SSE4_2__) || (defined(_MSC_VER) && defined(__AVX__)) + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +namespace { + +inline crc32c_t ShortCrcCopy(char* dst, const char* src, std::size_t length, + crc32c_t crc) { + // Small copy: just go 1 byte at a time: being nice to the branch predictor + // is more important here than anything else + uint32_t crc_uint32 = static_cast<uint32_t>(crc); + for (std::size_t i = 0; i < length; i++) { + uint8_t data = *reinterpret_cast<const uint8_t*>(src); + crc_uint32 = _mm_crc32_u8(crc_uint32, data); + *reinterpret_cast<uint8_t*>(dst) = data; + ++src; + ++dst; + } + return crc32c_t{crc_uint32}; +} + +constexpr size_t kIntLoadsPerVec = sizeof(__m128i) / sizeof(uint64_t); + +// Common function for copying the tails of multiple large regions. +template <size_t vec_regions, size_t int_regions> +inline void LargeTailCopy(crc32c_t* crcs, char** dst, const char** src, + size_t region_size, size_t copy_rounds) { + std::array<__m128i, vec_regions> data; + std::array<uint64_t, kIntLoadsPerVec * int_regions> int_data; + + while (copy_rounds > 0) { + for (size_t i = 0; i < vec_regions; i++) { + size_t region = i; + + auto* vsrc = + reinterpret_cast<const __m128i*>(*src + region_size * region); + auto* vdst = reinterpret_cast<__m128i*>(*dst + region_size * region); + + // Load the blocks, unaligned + data[i] = _mm_loadu_si128(vsrc); + + // Store the blocks, aligned + _mm_store_si128(vdst, data[i]); + + // Compute the running CRC + crcs[region] = crc32c_t{static_cast<uint32_t>( + _mm_crc32_u64(static_cast<uint32_t>(crcs[region]), + static_cast<uint64_t>(_mm_extract_epi64(data[i], 0))))}; + crcs[region] = crc32c_t{static_cast<uint32_t>( + _mm_crc32_u64(static_cast<uint32_t>(crcs[region]), + static_cast<uint64_t>(_mm_extract_epi64(data[i], 1))))}; + } + + for (size_t i = 0; i < int_regions; i++) { + size_t region = vec_regions + i; + + auto* usrc = + reinterpret_cast<const uint64_t*>(*src + region_size * region); + auto* udst = reinterpret_cast<uint64_t*>(*dst + region_size * region); + + for (size_t j = 0; j < kIntLoadsPerVec; j++) { + size_t data_index = i * kIntLoadsPerVec + j; + + int_data[data_index] = *(usrc + j); + crcs[region] = crc32c_t{static_cast<uint32_t>(_mm_crc32_u64( + static_cast<uint32_t>(crcs[region]), int_data[data_index]))}; + + *(udst + j) = int_data[data_index]; + } + } + + // Increment pointers + *src += sizeof(__m128i); + *dst += sizeof(__m128i); + --copy_rounds; + } +} + +} // namespace + +template <size_t vec_regions, size_t int_regions> +class AcceleratedCrcMemcpyEngine : public CrcMemcpyEngine { + public: + AcceleratedCrcMemcpyEngine() = default; + AcceleratedCrcMemcpyEngine(const AcceleratedCrcMemcpyEngine&) = delete; + AcceleratedCrcMemcpyEngine operator=(const AcceleratedCrcMemcpyEngine&) = + delete; + + crc32c_t Compute(void* __restrict dst, const void* __restrict src, + std::size_t length, crc32c_t initial_crc) const override; +}; + +template <size_t vec_regions, size_t int_regions> +crc32c_t AcceleratedCrcMemcpyEngine<vec_regions, int_regions>::Compute( + void* __restrict dst, const void* __restrict src, std::size_t length, + crc32c_t initial_crc) const { + constexpr std::size_t kRegions = vec_regions + int_regions; + constexpr uint32_t kCrcDataXor = uint32_t{0xffffffff}; + constexpr std::size_t kBlockSize = sizeof(__m128i); + constexpr std::size_t kCopyRoundSize = kRegions * kBlockSize; + + // Number of blocks per cacheline. + constexpr std::size_t kBlocksPerCacheLine = ABSL_CACHELINE_SIZE / kBlockSize; + + char* dst_bytes = static_cast<char*>(dst); + const char* src_bytes = static_cast<const char*>(src); + + // Make sure that one prefetch per big block is enough to cover the whole + // dataset, and we don't prefetch too much. + static_assert(ABSL_CACHELINE_SIZE % kBlockSize == 0, + "Cache lines are not divided evenly into blocks, may have " + "unintended behavior!"); + + // Experimentally-determined boundary between a small and large copy. + // Below this number, spin-up and concatenation of CRCs takes enough time that + // it kills the throughput gains of using 3 regions and wide vectors. + constexpr size_t kCrcSmallSize = 256; + + // Experimentally-determined prefetch distance. Main loop copies will + // prefeth data 2 cache lines ahead. + constexpr std::size_t kPrefetchAhead = 2 * ABSL_CACHELINE_SIZE; + + // Small-size CRC-memcpy : just do CRC + memcpy + if (length < kCrcSmallSize) { + crc32c_t crc = + ExtendCrc32c(initial_crc, absl::string_view(src_bytes, length)); + memcpy(dst, src, length); + return crc; + } + + // Start work on the CRC: undo the XOR from the previous calculation or set up + // the initial value of the CRC. + // initial_crc ^= kCrcDataXor; + initial_crc = crc32c_t{static_cast<uint32_t>(initial_crc) ^ kCrcDataXor}; + + // Do an initial alignment copy, so we can use aligned store instructions to + // the destination pointer. We align the destination pointer because the + // penalty for an unaligned load is small compared to the penalty of an + // unaligned store on modern CPUs. + std::size_t bytes_from_last_aligned = + reinterpret_cast<uintptr_t>(dst) & (kBlockSize - 1); + if (bytes_from_last_aligned != 0) { + std::size_t bytes_for_alignment = kBlockSize - bytes_from_last_aligned; + + // Do the short-sized copy and CRC. + initial_crc = + ShortCrcCopy(dst_bytes, src_bytes, bytes_for_alignment, initial_crc); + src_bytes += bytes_for_alignment; + dst_bytes += bytes_for_alignment; + length -= bytes_for_alignment; + } + + // We are going to do the copy and CRC in kRegions regions to make sure that + // we can saturate the CRC unit. The CRCs will be combined at the end of the + // run. Copying will use the SSE registers, and we will extract words from + // the SSE registers to add to the CRC. Initially, we run the loop one full + // cache line per region at a time, in order to insert prefetches. + + // Initialize CRCs for kRegions regions. + crc32c_t crcs[kRegions]; + crcs[0] = initial_crc; + for (size_t i = 1; i < kRegions; i++) { + crcs[i] = crc32c_t{kCrcDataXor}; + } + + // Find the number of rounds to copy and the region size. Also compute the + // tail size here. + size_t copy_rounds = length / kCopyRoundSize; + + // Find the size of each region and the size of the tail. + const std::size_t region_size = copy_rounds * kBlockSize; + const std::size_t tail_size = length - (kRegions * region_size); + + // Holding registers for data in each region. + std::array<__m128i, vec_regions> vec_data; + std::array<uint64_t, int_regions * kIntLoadsPerVec> int_data; + + // Main loop. + while (copy_rounds > kBlocksPerCacheLine) { + // Prefetch kPrefetchAhead bytes ahead of each pointer. + for (size_t i = 0; i < kRegions; i++) { + absl::base_internal::PrefetchT0(src_bytes + kPrefetchAhead + + region_size * i); + absl::base_internal::PrefetchT0(dst_bytes + kPrefetchAhead + + region_size * i); + } + + // Load and store data, computing CRC on the way. + for (size_t i = 0; i < kBlocksPerCacheLine; i++) { + // Copy and CRC the data for the CRC regions. + for (size_t j = 0; j < vec_regions; j++) { + // Cycle which regions get vector load/store and integer load/store, to + // engage prefetching logic around vector load/stores and save issue + // slots by using the integer registers. + size_t region = (j + i) % kRegions; + + auto* vsrc = + reinterpret_cast<const __m128i*>(src_bytes + region_size * region); + auto* vdst = + reinterpret_cast<__m128i*>(dst_bytes + region_size * region); + + // Load and CRC data. + vec_data[j] = _mm_loadu_si128(vsrc + i); + crcs[region] = crc32c_t{static_cast<uint32_t>(_mm_crc32_u64( + static_cast<uint32_t>(crcs[region]), + static_cast<uint64_t>(_mm_extract_epi64(vec_data[j], 0))))}; + crcs[region] = crc32c_t{static_cast<uint32_t>(_mm_crc32_u64( + static_cast<uint32_t>(crcs[region]), + static_cast<uint64_t>(_mm_extract_epi64(vec_data[j], 1))))}; + + // Store the data. + _mm_store_si128(vdst + i, vec_data[j]); + } + + // Preload the partial CRCs for the CLMUL subregions. + for (size_t j = 0; j < int_regions; j++) { + // Cycle which regions get vector load/store and integer load/store, to + // engage prefetching logic around vector load/stores and save issue + // slots by using the integer registers. + size_t region = (j + vec_regions + i) % kRegions; + + auto* usrc = + reinterpret_cast<const uint64_t*>(src_bytes + region_size * region); + auto* udst = + reinterpret_cast<uint64_t*>(dst_bytes + region_size * region); + + for (size_t k = 0; k < kIntLoadsPerVec; k++) { + size_t data_index = j * kIntLoadsPerVec + k; + + // Load and CRC the data. + int_data[data_index] = *(usrc + i * kIntLoadsPerVec + k); + crcs[region] = crc32c_t{static_cast<uint32_t>(_mm_crc32_u64( + static_cast<uint32_t>(crcs[region]), int_data[data_index]))}; + + // Store the data. + *(udst + i * kIntLoadsPerVec + k) = int_data[data_index]; + } + } + } + + // Increment pointers + src_bytes += kBlockSize * kBlocksPerCacheLine; + dst_bytes += kBlockSize * kBlocksPerCacheLine; + copy_rounds -= kBlocksPerCacheLine; + } + + // Copy and CRC the tails of each region. + LargeTailCopy<vec_regions, int_regions>(crcs, &dst_bytes, &src_bytes, + region_size, copy_rounds); + + // Move the source and destination pointers to the end of the region + src_bytes += region_size * (kRegions - 1); + dst_bytes += region_size * (kRegions - 1); + + // Finalize the first CRCs: XOR the internal CRCs by the XOR mask to undo the + // XOR done before doing block copy + CRCs. + for (size_t i = 0; i + 1 < kRegions; i++) { + crcs[i] = crc32c_t{static_cast<uint32_t>(crcs[i]) ^ kCrcDataXor}; + } + + // Build a CRC of the first kRegions - 1 regions. + crc32c_t full_crc = crcs[0]; + for (size_t i = 1; i + 1 < kRegions; i++) { + full_crc = ConcatCrc32c(full_crc, crcs[i], region_size); + } + + // Copy and CRC the tail through the XMM registers. + std::size_t tail_blocks = tail_size / kBlockSize; + LargeTailCopy<0, 1>(&crcs[kRegions - 1], &dst_bytes, &src_bytes, 0, + tail_blocks); + + // Final tail copy for under 16 bytes. + crcs[kRegions - 1] = + ShortCrcCopy(dst_bytes, src_bytes, tail_size - tail_blocks * kBlockSize, + crcs[kRegions - 1]); + + // Finalize and concatenate the final CRC, then return. + crcs[kRegions - 1] = + crc32c_t{static_cast<uint32_t>(crcs[kRegions - 1]) ^ kCrcDataXor}; + return ConcatCrc32c(full_crc, crcs[kRegions - 1], region_size + tail_size); +} + +CrcMemcpy::ArchSpecificEngines CrcMemcpy::GetArchSpecificEngines() { +#ifdef UNDEFINED_BEHAVIOR_SANITIZER + // UBSAN does not play nicely with unaligned loads (which we use a lot). + // Get the underlying architecture. + CpuType cpu_type = GetCpuType(); + switch (cpu_type) { + case CpuType::kUnknown: + case CpuType::kAmdRome: + case CpuType::kAmdNaples: + case CpuType::kIntelCascadelakeXeon: + case CpuType::kIntelSkylakeXeon: + case CpuType::kIntelSkylake: + case CpuType::kIntelBroadwell: + case CpuType::kIntelHaswell: + case CpuType::kIntelIvybridge: + return { + .temporal = new FallbackCrcMemcpyEngine(), + .non_temporal = new CrcNonTemporalMemcpyAVXEngine(), + }; + // INTEL_SANDYBRIDGE performs better with SSE than AVX. + case CpuType::kIntelSandybridge: + return { + .temporal = new FallbackCrcMemcpyEngine(), + .non_temporal = new CrcNonTemporalMemcpyEngine(), + }; + default: + return {.temporal = new FallbackCrcMemcpyEngine(), + .non_temporal = new FallbackCrcMemcpyEngine()}; + } +#else + // Get the underlying architecture. + CpuType cpu_type = GetCpuType(); + switch (cpu_type) { + // On Zen 2, PEXTRQ uses 2 micro-ops, including one on the vector store port + // which data movement from the vector registers to the integer registers + // (where CRC32C happens) to crowd the same units as vector stores. As a + // result, using that path exclusively causes bottlenecking on this port. + // We can avoid this bottleneck by using the integer side of the CPU for + // most operations rather than the vector side. We keep a vector region to + // engage some of the prefetching logic in the cache hierarchy which seems + // to give vector instructions special treatment. These prefetch units see + // strided access to each region, and do the right thing. + case CpuType::kAmdRome: + case CpuType::kAmdNaples: + return { + .temporal = new AcceleratedCrcMemcpyEngine<1, 2>(), + .non_temporal = new CrcNonTemporalMemcpyAVXEngine(), + }; + // PCLMULQDQ is slow and we don't have wide enough issue width to take + // advantage of it. For an unknown architecture, don't risk using CLMULs. + case CpuType::kIntelCascadelakeXeon: + case CpuType::kIntelSkylakeXeon: + case CpuType::kIntelSkylake: + case CpuType::kIntelBroadwell: + case CpuType::kIntelHaswell: + case CpuType::kIntelIvybridge: + return { + .temporal = new AcceleratedCrcMemcpyEngine<3, 0>(), + .non_temporal = new CrcNonTemporalMemcpyAVXEngine(), + }; + // INTEL_SANDYBRIDGE performs better with SSE than AVX. + case CpuType::kIntelSandybridge: + return { + .temporal = new AcceleratedCrcMemcpyEngine<3, 0>(), + .non_temporal = new CrcNonTemporalMemcpyEngine(), + }; + default: + return {.temporal = new FallbackCrcMemcpyEngine(), + .non_temporal = new FallbackCrcMemcpyEngine()}; + } +#endif // UNDEFINED_BEHAVIOR_SANITIZER +} + +// For testing, allow the user to specify which engine they want. +std::unique_ptr<CrcMemcpyEngine> CrcMemcpy::GetTestEngine(int vector, + int integer) { + if (vector == 3 && integer == 0) { + return std::make_unique<AcceleratedCrcMemcpyEngine<3, 0>>(); + } else if (vector == 1 && integer == 2) { + return std::make_unique<AcceleratedCrcMemcpyEngine<1, 2>>(); + } + return nullptr; +} + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // defined(__SSE4_2__) || (defined(_MSC_VER) && defined(__AVX__)) diff --git a/absl/crc/internal/crc_non_temporal_memcpy.cc b/absl/crc/internal/crc_non_temporal_memcpy.cc new file mode 100644 index 00000000..adc867f6 --- /dev/null +++ b/absl/crc/internal/crc_non_temporal_memcpy.cc @@ -0,0 +1,93 @@ +// Copyright 2022 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 <cstdint> + +#include "absl/base/config.h" +#include "absl/crc/crc32c.h" +#include "absl/crc/internal/crc_memcpy.h" +#include "absl/crc/internal/non_temporal_memcpy.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +crc32c_t CrcNonTemporalMemcpyEngine::Compute(void* __restrict dst, + const void* __restrict src, + std::size_t length, + crc32c_t initial_crc) const { + constexpr size_t kBlockSize = 8192; + crc32c_t crc = initial_crc; + + const char* src_bytes = reinterpret_cast<const char*>(src); + char* dst_bytes = reinterpret_cast<char*>(dst); + + // Copy + CRC loop - run 8k chunks until we are out of full chunks. + std::size_t offset = 0; + for (; offset + kBlockSize < length; offset += kBlockSize) { + crc = absl::ExtendCrc32c(crc, + absl::string_view(src_bytes + offset, kBlockSize)); + non_temporal_store_memcpy(dst_bytes + offset, src_bytes + offset, + kBlockSize); + } + + // Save some work if length is 0. + if (offset < length) { + std::size_t final_copy_size = length - offset; + crc = ExtendCrc32c(crc, + absl::string_view(src_bytes + offset, final_copy_size)); + + non_temporal_store_memcpy(dst_bytes + offset, src_bytes + offset, + final_copy_size); + } + + return crc; +} + +crc32c_t CrcNonTemporalMemcpyAVXEngine::Compute(void* __restrict dst, + const void* __restrict src, + std::size_t length, + crc32c_t initial_crc) const { + constexpr size_t kBlockSize = 8192; + crc32c_t crc = initial_crc; + + const char* src_bytes = reinterpret_cast<const char*>(src); + char* dst_bytes = reinterpret_cast<char*>(dst); + + // Copy + CRC loop - run 8k chunks until we are out of full chunks. + std::size_t offset = 0; + for (; offset + kBlockSize < length; offset += kBlockSize) { + crc = ExtendCrc32c(crc, absl::string_view(src_bytes + offset, kBlockSize)); + + non_temporal_store_memcpy_avx(dst_bytes + offset, src_bytes + offset, + kBlockSize); + } + + // Save some work if length is 0. + if (offset < length) { + std::size_t final_copy_size = length - offset; + crc = ExtendCrc32c(crc, + absl::string_view(src_bytes + offset, final_copy_size)); + + non_temporal_store_memcpy_avx(dst_bytes + offset, src_bytes + offset, + final_copy_size); + } + + return crc; +} + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/crc/internal/crc_x86_arm_combined.cc b/absl/crc/internal/crc_x86_arm_combined.cc new file mode 100644 index 00000000..d71191e3 --- /dev/null +++ b/absl/crc/internal/crc_x86_arm_combined.cc @@ -0,0 +1,725 @@ +// Copyright 2022 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. + +// Hardware accelerated CRC32 computation on Intel and ARM architecture. + +#include <cstddef> +#include <cstdint> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/dynamic_annotations.h" +#include "absl/base/internal/endian.h" +#include "absl/base/internal/prefetch.h" +#include "absl/crc/internal/cpu_detect.h" +#include "absl/crc/internal/crc.h" +#include "absl/crc/internal/crc32_x86_arm_combined_simd.h" +#include "absl/crc/internal/crc_internal.h" +#include "absl/memory/memory.h" +#include "absl/numeric/bits.h" + +#if defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD) || \ + defined(ABSL_CRC_INTERNAL_HAVE_X86_SIMD) +#define ABSL_INTERNAL_CAN_USE_SIMD_CRC32C +#endif + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +#if defined(ABSL_INTERNAL_CAN_USE_SIMD_CRC32C) + +// Implementation details not exported outside of file +namespace { + +// Some machines have CRC acceleration hardware. +// We can do a faster version of Extend() on such machines. +class CRC32AcceleratedX86ARMCombined : public CRC32 { + public: + CRC32AcceleratedX86ARMCombined() {} + ~CRC32AcceleratedX86ARMCombined() override {} + void ExtendByZeroes(uint32_t* crc, size_t length) const override; + uint32_t ComputeZeroConstant(size_t length) const; + + private: + CRC32AcceleratedX86ARMCombined(const CRC32AcceleratedX86ARMCombined&) = + delete; + CRC32AcceleratedX86ARMCombined& operator=( + const CRC32AcceleratedX86ARMCombined&) = delete; +}; + +// Constants for switching between algorithms. +// Chosen by comparing speed at different powers of 2. +constexpr size_t kSmallCutoff = 256; +constexpr size_t kMediumCutoff = 2048; + +#define ABSL_INTERNAL_STEP1(crc) \ + do { \ + crc = CRC32_u8(static_cast<uint32_t>(crc), *p++); \ + } while (0) +#define ABSL_INTERNAL_STEP2(crc) \ + do { \ + crc = \ + CRC32_u16(static_cast<uint32_t>(crc), absl::little_endian::Load16(p)); \ + p += 2; \ + } while (0) +#define ABSL_INTERNAL_STEP4(crc) \ + do { \ + crc = \ + CRC32_u32(static_cast<uint32_t>(crc), absl::little_endian::Load32(p)); \ + p += 4; \ + } while (0) +#define ABSL_INTERNAL_STEP8(crc, data) \ + do { \ + crc = CRC32_u64(static_cast<uint32_t>(crc), \ + absl::little_endian::Load64(data)); \ + data += 8; \ + } while (0) +#define ABSL_INTERNAL_STEP8BY2(crc0, crc1, p0, p1) \ + do { \ + ABSL_INTERNAL_STEP8(crc0, p0); \ + ABSL_INTERNAL_STEP8(crc1, p1); \ + } while (0) +#define ABSL_INTERNAL_STEP8BY3(crc0, crc1, crc2, p0, p1, p2) \ + do { \ + ABSL_INTERNAL_STEP8(crc0, p0); \ + ABSL_INTERNAL_STEP8(crc1, p1); \ + ABSL_INTERNAL_STEP8(crc2, p2); \ + } while (0) + +namespace { + +uint32_t multiply(uint32_t a, uint32_t b) { + V128 shifts = V128_From2x64(0, 1); + V128 power = V128_From2x64(0, a); + V128 crc = V128_From2x64(0, b); + V128 res = V128_PMulLow(power, crc); + + // Combine crc values + res = V128_ShiftLeft64(res, shifts); + return static_cast<uint32_t>(V128_Extract32<1>(res)) ^ + CRC32_u32(0, static_cast<uint32_t>(V128_Low64(res))); +} + +// Powers of crc32c polynomial, for faster ExtendByZeros. +// Verified against folly: +// folly/hash/detail/Crc32CombineDetail.cpp +constexpr uint32_t kCRC32CPowers[] = { + 0x82f63b78, 0x6ea2d55c, 0x18b8ea18, 0x510ac59a, 0xb82be955, 0xb8fdb1e7, + 0x88e56f72, 0x74c360a4, 0xe4172b16, 0x0d65762a, 0x35d73a62, 0x28461564, + 0xbf455269, 0xe2ea32dc, 0xfe7740e6, 0xf946610b, 0x3c204f8f, 0x538586e3, + 0x59726915, 0x734d5309, 0xbc1ac763, 0x7d0722cc, 0xd289cabe, 0xe94ca9bc, + 0x05b74f3f, 0xa51e1f42, 0x40000000, 0x20000000, 0x08000000, 0x00800000, + 0x00008000, 0x82f63b78, 0x6ea2d55c, 0x18b8ea18, 0x510ac59a, 0xb82be955, + 0xb8fdb1e7, 0x88e56f72, 0x74c360a4, 0xe4172b16, 0x0d65762a, 0x35d73a62, + 0x28461564, 0xbf455269, 0xe2ea32dc, 0xfe7740e6, 0xf946610b, 0x3c204f8f, + 0x538586e3, 0x59726915, 0x734d5309, 0xbc1ac763, 0x7d0722cc, 0xd289cabe, + 0xe94ca9bc, 0x05b74f3f, 0xa51e1f42, 0x40000000, 0x20000000, 0x08000000, + 0x00800000, 0x00008000, +}; + +} // namespace + +// Compute a magic constant, so that multiplying by it is the same as +// extending crc by length zeros. +uint32_t CRC32AcceleratedX86ARMCombined::ComputeZeroConstant( + size_t length) const { + // Lowest 2 bits are handled separately in ExtendByZeroes + length >>= 2; + + int index = absl::countr_zero(length); + uint32_t prev = kCRC32CPowers[index]; + length &= length - 1; + + while (length) { + // For each bit of length, extend by 2**n zeros. + index = absl::countr_zero(length); + prev = multiply(prev, kCRC32CPowers[index]); + length &= length - 1; + } + return prev; +} + +void CRC32AcceleratedX86ARMCombined::ExtendByZeroes(uint32_t* crc, + size_t length) const { + uint32_t val = *crc; + // Don't bother with multiplication for small length. + switch (length & 3) { + case 0: + break; + case 1: + val = CRC32_u8(val, 0); + break; + case 2: + val = CRC32_u16(val, 0); + break; + case 3: + val = CRC32_u8(val, 0); + val = CRC32_u16(val, 0); + break; + } + if (length > 3) { + val = multiply(val, ComputeZeroConstant(length)); + } + *crc = val; +} + +// Taken from Intel paper "Fast CRC Computation for iSCSI Polynomial Using CRC32 +// Instruction" +// https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/crc-iscsi-polynomial-crc32-instruction-paper.pdf +// We only need every 4th value, because we unroll loop by 4. +constexpr uint64_t kClmulConstants[] = { + 0x09e4addf8, 0x0ba4fc28e, 0x00d3b6092, 0x09e4addf8, 0x0ab7aff2a, + 0x102f9b8a2, 0x0b9e02b86, 0x00d3b6092, 0x1bf2e8b8a, 0x18266e456, + 0x0d270f1a2, 0x0ab7aff2a, 0x11eef4f8e, 0x083348832, 0x0dd7e3b0c, + 0x0b9e02b86, 0x0271d9844, 0x1b331e26a, 0x06b749fb2, 0x1bf2e8b8a, + 0x0e6fc4e6a, 0x0ce7f39f4, 0x0d7a4825c, 0x0d270f1a2, 0x026f6a60a, + 0x12ed0daac, 0x068bce87a, 0x11eef4f8e, 0x1329d9f7e, 0x0b3e32c28, + 0x0170076fa, 0x0dd7e3b0c, 0x1fae1cc66, 0x010746f3c, 0x086d8e4d2, + 0x0271d9844, 0x0b3af077a, 0x093a5f730, 0x1d88abd4a, 0x06b749fb2, + 0x0c9c8b782, 0x0cec3662e, 0x1ddffc5d4, 0x0e6fc4e6a, 0x168763fa6, + 0x0b0cd4768, 0x19b1afbc4, 0x0d7a4825c, 0x123888b7a, 0x00167d312, + 0x133d7a042, 0x026f6a60a, 0x000bcf5f6, 0x19d34af3a, 0x1af900c24, + 0x068bce87a, 0x06d390dec, 0x16cba8aca, 0x1f16a3418, 0x1329d9f7e, + 0x19fb2a8b0, 0x02178513a, 0x1a0f717c4, 0x0170076fa, +}; + +enum class CutoffStrategy { + // Use 3 CRC streams to fold into 1. + Fold3, + // Unroll CRC instructions for 64 bytes. + Unroll64CRC, +}; + +// Base class for CRC32AcceleratedX86ARMCombinedMultipleStreams containing the +// methods and data that don't need the template arguments. +class CRC32AcceleratedX86ARMCombinedMultipleStreamsBase + : public CRC32AcceleratedX86ARMCombined { + protected: + // Update partialCRC with crc of 64 byte block. Calling FinalizePclmulStream + // would produce a single crc checksum, but it is expensive. PCLMULQDQ has a + // high latency, so we run 4 128-bit partial checksums that can be reduced to + // a single value by FinalizePclmulStream later. Computing crc for arbitrary + // polynomialas with PCLMULQDQ is described in Intel paper "Fast CRC + // Computation for Generic Polynomials Using PCLMULQDQ Instruction" + // https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf + // We are applying it to CRC32C polynomial. + ABSL_ATTRIBUTE_ALWAYS_INLINE void Process64BytesPclmul( + const uint8_t* p, V128* partialCRC) const { + V128 loopMultiplicands = V128_Load(reinterpret_cast<const V128*>(k1k2)); + + V128 partialCRC1 = partialCRC[0]; + V128 partialCRC2 = partialCRC[1]; + V128 partialCRC3 = partialCRC[2]; + V128 partialCRC4 = partialCRC[3]; + + V128 tmp1 = V128_PMulHi(partialCRC1, loopMultiplicands); + V128 tmp2 = V128_PMulHi(partialCRC2, loopMultiplicands); + V128 tmp3 = V128_PMulHi(partialCRC3, loopMultiplicands); + V128 tmp4 = V128_PMulHi(partialCRC4, loopMultiplicands); + V128 data1 = V128_LoadU(reinterpret_cast<const V128*>(p + 16 * 0)); + V128 data2 = V128_LoadU(reinterpret_cast<const V128*>(p + 16 * 1)); + V128 data3 = V128_LoadU(reinterpret_cast<const V128*>(p + 16 * 2)); + V128 data4 = V128_LoadU(reinterpret_cast<const V128*>(p + 16 * 3)); + partialCRC1 = V128_PMulLow(partialCRC1, loopMultiplicands); + partialCRC2 = V128_PMulLow(partialCRC2, loopMultiplicands); + partialCRC3 = V128_PMulLow(partialCRC3, loopMultiplicands); + partialCRC4 = V128_PMulLow(partialCRC4, loopMultiplicands); + partialCRC1 = V128_Xor(tmp1, partialCRC1); + partialCRC2 = V128_Xor(tmp2, partialCRC2); + partialCRC3 = V128_Xor(tmp3, partialCRC3); + partialCRC4 = V128_Xor(tmp4, partialCRC4); + partialCRC1 = V128_Xor(partialCRC1, data1); + partialCRC2 = V128_Xor(partialCRC2, data2); + partialCRC3 = V128_Xor(partialCRC3, data3); + partialCRC4 = V128_Xor(partialCRC4, data4); + partialCRC[0] = partialCRC1; + partialCRC[1] = partialCRC2; + partialCRC[2] = partialCRC3; + partialCRC[3] = partialCRC4; + } + + // Reduce partialCRC produced by Process64BytesPclmul into a single value, + // that represents crc checksum of all the processed bytes. + ABSL_ATTRIBUTE_ALWAYS_INLINE uint64_t + FinalizePclmulStream(V128* partialCRC) const { + V128 partialCRC1 = partialCRC[0]; + V128 partialCRC2 = partialCRC[1]; + V128 partialCRC3 = partialCRC[2]; + V128 partialCRC4 = partialCRC[3]; + + // Combine 4 vectors of partial crc into a single vector. + V128 reductionMultiplicands = + V128_Load(reinterpret_cast<const V128*>(k5k6)); + + V128 low = V128_PMulLow(reductionMultiplicands, partialCRC1); + V128 high = V128_PMulHi(reductionMultiplicands, partialCRC1); + + partialCRC1 = V128_Xor(low, high); + partialCRC1 = V128_Xor(partialCRC1, partialCRC2); + + low = V128_PMulLow(reductionMultiplicands, partialCRC3); + high = V128_PMulHi(reductionMultiplicands, partialCRC3); + + partialCRC3 = V128_Xor(low, high); + partialCRC3 = V128_Xor(partialCRC3, partialCRC4); + + reductionMultiplicands = V128_Load(reinterpret_cast<const V128*>(k3k4)); + + low = V128_PMulLow(reductionMultiplicands, partialCRC1); + high = V128_PMulHi(reductionMultiplicands, partialCRC1); + V128 fullCRC = V128_Xor(low, high); + fullCRC = V128_Xor(fullCRC, partialCRC3); + + // Reduce fullCRC into scalar value. + reductionMultiplicands = V128_Load(reinterpret_cast<const V128*>(k5k6)); + + V128 mask = V128_Load(reinterpret_cast<const V128*>(kMask)); + + V128 tmp = V128_PMul01(reductionMultiplicands, fullCRC); + fullCRC = V128_ShiftRight<8>(fullCRC); + fullCRC = V128_Xor(fullCRC, tmp); + + reductionMultiplicands = V128_Load(reinterpret_cast<const V128*>(k7k0)); + + tmp = V128_ShiftRight<4>(fullCRC); + fullCRC = V128_And(fullCRC, mask); + fullCRC = V128_PMulLow(reductionMultiplicands, fullCRC); + fullCRC = V128_Xor(tmp, fullCRC); + + reductionMultiplicands = V128_Load(reinterpret_cast<const V128*>(kPoly)); + + tmp = V128_And(fullCRC, mask); + tmp = V128_PMul01(reductionMultiplicands, tmp); + tmp = V128_And(tmp, mask); + tmp = V128_PMulLow(reductionMultiplicands, tmp); + + fullCRC = V128_Xor(tmp, fullCRC); + + return static_cast<uint64_t>(V128_Extract32<1>(fullCRC)); + } + + // Update crc with 64 bytes of data from p. + ABSL_ATTRIBUTE_ALWAYS_INLINE uint64_t Process64BytesCRC(const uint8_t* p, + uint64_t crc) const { + for (int i = 0; i < 8; i++) { + crc = + CRC32_u64(static_cast<uint32_t>(crc), absl::little_endian::Load64(p)); + p += 8; + } + return crc; + } + + // Generated by crc32c_x86_test --crc32c_generate_constants=true + // and verified against constants in linux kernel for S390: + // https://github.com/torvalds/linux/blob/master/arch/s390/crypto/crc32le-vx.S + alignas(16) static constexpr uint64_t k1k2[2] = {0x0740eef02, 0x09e4addf8}; + alignas(16) static constexpr uint64_t k3k4[2] = {0x1384aa63a, 0x0ba4fc28e}; + alignas(16) static constexpr uint64_t k5k6[2] = {0x0f20c0dfe, 0x14cd00bd6}; + alignas(16) static constexpr uint64_t k7k0[2] = {0x0dd45aab8, 0x000000000}; + alignas(16) static constexpr uint64_t kPoly[2] = {0x105ec76f0, 0x0dea713f1}; + alignas(16) static constexpr uint32_t kMask[4] = {~0u, 0u, ~0u, 0u}; + + // Medium runs of bytes are broken into groups of kGroupsSmall blocks of same + // size. Each group is CRCed in parallel then combined at the end of the + // block. + static constexpr size_t kGroupsSmall = 3; + // For large runs we use up to kMaxStreams blocks computed with CRC + // instruction, and up to kMaxStreams blocks computed with PCLMULQDQ, which + // are combined in the end. + static constexpr size_t kMaxStreams = 3; +}; + +#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL +alignas(16) constexpr uint64_t + CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::k1k2[2]; +alignas(16) constexpr uint64_t + CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::k3k4[2]; +alignas(16) constexpr uint64_t + CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::k5k6[2]; +alignas(16) constexpr uint64_t + CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::k7k0[2]; +alignas(16) constexpr uint64_t + CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::kPoly[2]; +alignas(16) constexpr uint32_t + CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::kMask[4]; +constexpr size_t + CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::kGroupsSmall; +constexpr size_t CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::kMaxStreams; +#endif // ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL + +template <size_t num_crc_streams, size_t num_pclmul_streams, + CutoffStrategy strategy> +class CRC32AcceleratedX86ARMCombinedMultipleStreams + : public CRC32AcceleratedX86ARMCombinedMultipleStreamsBase { + ABSL_ATTRIBUTE_HOT + void Extend(uint32_t* crc, const void* bytes, size_t length) const override { + static_assert(num_crc_streams >= 1 && num_crc_streams <= kMaxStreams, + "Invalid number of crc streams"); + static_assert(num_pclmul_streams >= 0 && num_pclmul_streams <= kMaxStreams, + "Invalid number of pclmul streams"); + const uint8_t* p = static_cast<const uint8_t*>(bytes); + const uint8_t* e = p + length; + uint32_t l = *crc; + uint64_t l64; + + // We have dedicated instruction for 1,2,4 and 8 bytes. + if (length & 8) { + ABSL_INTERNAL_STEP8(l, p); + length &= ~size_t{8}; + } + if (length & 4) { + ABSL_INTERNAL_STEP4(l); + length &= ~size_t{4}; + } + if (length & 2) { + ABSL_INTERNAL_STEP2(l); + length &= ~size_t{2}; + } + if (length & 1) { + ABSL_INTERNAL_STEP1(l); + length &= ~size_t{1}; + } + if (length == 0) { + *crc = l; + return; + } + // length is now multiple of 16. + + // For small blocks just run simple loop, because cost of combining multiple + // streams is significant. + if (strategy != CutoffStrategy::Unroll64CRC) { + if (length < kSmallCutoff) { + while (length >= 16) { + ABSL_INTERNAL_STEP8(l, p); + ABSL_INTERNAL_STEP8(l, p); + length -= 16; + } + *crc = l; + return; + } + } + + // For medium blocks we run 3 crc streams and combine them as described in + // Intel paper above. Running 4th stream doesn't help, because crc + // instruction has latency 3 and throughput 1. + if (length < kMediumCutoff) { + l64 = l; + if (strategy == CutoffStrategy::Fold3) { + uint64_t l641 = 0; + uint64_t l642 = 0; + const size_t blockSize = 32; + size_t bs = static_cast<size_t>(e - p) / kGroupsSmall / blockSize; + const uint8_t* p1 = p + bs * blockSize; + const uint8_t* p2 = p1 + bs * blockSize; + + for (size_t i = 0; i + 1 < bs; ++i) { + ABSL_INTERNAL_STEP8BY3(l64, l641, l642, p, p1, p2); + ABSL_INTERNAL_STEP8BY3(l64, l641, l642, p, p1, p2); + ABSL_INTERNAL_STEP8BY3(l64, l641, l642, p, p1, p2); + ABSL_INTERNAL_STEP8BY3(l64, l641, l642, p, p1, p2); + base_internal::PrefetchT0( + reinterpret_cast<const char*>(p + kPrefetchHorizonMedium)); + base_internal::PrefetchT0( + reinterpret_cast<const char*>(p1 + kPrefetchHorizonMedium)); + base_internal::PrefetchT0( + reinterpret_cast<const char*>(p2 + kPrefetchHorizonMedium)); + } + // Don't run crc on last 8 bytes. + ABSL_INTERNAL_STEP8BY3(l64, l641, l642, p, p1, p2); + ABSL_INTERNAL_STEP8BY3(l64, l641, l642, p, p1, p2); + ABSL_INTERNAL_STEP8BY3(l64, l641, l642, p, p1, p2); + ABSL_INTERNAL_STEP8BY2(l64, l641, p, p1); + + V128 magic = *(reinterpret_cast<const V128*>(kClmulConstants) + bs - 1); + + V128 tmp = V128_From2x64(0, l64); + + V128 res1 = V128_PMulLow(tmp, magic); + + tmp = V128_From2x64(0, l641); + + V128 res2 = V128_PMul10(tmp, magic); + V128 x = V128_Xor(res1, res2); + l64 = static_cast<uint64_t>(V128_Low64(x)) ^ + absl::little_endian::Load64(p2); + l64 = CRC32_u64(static_cast<uint32_t>(l642), l64); + + p = p2 + 8; + } else if (strategy == CutoffStrategy::Unroll64CRC) { + while ((e - p) >= 64) { + l64 = Process64BytesCRC(p, l64); + p += 64; + } + } + } else { + // There is a lot of data, we can ignore combine costs and run all + // requested streams (num_crc_streams + num_pclmul_streams), + // using prefetch. CRC and PCLMULQDQ use different cpu execution units, + // so on some cpus it makes sense to execute both of them for different + // streams. + + // Point x at first 8-byte aligned byte in string. + const uint8_t* x = RoundUp<8>(p); + // Process bytes until p is 8-byte aligned, if that isn't past the end. + while (p != x) { + ABSL_INTERNAL_STEP1(l); + } + + size_t bs = static_cast<size_t>(e - p) / + (num_crc_streams + num_pclmul_streams) / 64; + const uint8_t* crc_streams[kMaxStreams]; + const uint8_t* pclmul_streams[kMaxStreams]; + // We are guaranteed to have at least one crc stream. + crc_streams[0] = p; + for (size_t i = 1; i < num_crc_streams; i++) { + crc_streams[i] = crc_streams[i - 1] + bs * 64; + } + pclmul_streams[0] = crc_streams[num_crc_streams - 1] + bs * 64; + for (size_t i = 1; i < num_pclmul_streams; i++) { + pclmul_streams[i] = pclmul_streams[i - 1] + bs * 64; + } + + // Per stream crc sums. + uint64_t l64_crc[kMaxStreams] = {l}; + uint64_t l64_pclmul[kMaxStreams] = {0}; + + // Peel first iteration, because PCLMULQDQ stream, needs setup. + for (size_t i = 0; i < num_crc_streams; i++) { + l64_crc[i] = Process64BytesCRC(crc_streams[i], l64_crc[i]); + crc_streams[i] += 16 * 4; + } + + V128 partialCRC[kMaxStreams][4]; + for (size_t i = 0; i < num_pclmul_streams; i++) { + partialCRC[i][0] = V128_LoadU( + reinterpret_cast<const V128*>(pclmul_streams[i] + 16 * 0)); + partialCRC[i][1] = V128_LoadU( + reinterpret_cast<const V128*>(pclmul_streams[i] + 16 * 1)); + partialCRC[i][2] = V128_LoadU( + reinterpret_cast<const V128*>(pclmul_streams[i] + 16 * 2)); + partialCRC[i][3] = V128_LoadU( + reinterpret_cast<const V128*>(pclmul_streams[i] + 16 * 3)); + pclmul_streams[i] += 16 * 4; + } + + for (size_t i = 1; i < bs; i++) { + // Prefetch data for next itterations. + for (size_t j = 0; j < num_crc_streams; j++) { + base_internal::PrefetchT0( + reinterpret_cast<const char*>(crc_streams[j] + kPrefetchHorizon)); + } + for (size_t j = 0; j < num_pclmul_streams; j++) { + base_internal::PrefetchT0(reinterpret_cast<const char*>( + pclmul_streams[j] + kPrefetchHorizon)); + } + + // We process each stream in 64 byte blocks. This can be written as + // for (int i = 0; i < num_pclmul_streams; i++) { + // Process64BytesPclmul(pclmul_streams[i], partialCRC[i]); + // pclmul_streams[i] += 16 * 4; + // } + // for (int i = 0; i < num_crc_streams; i++) { + // l64_crc[i] = Process64BytesCRC(crc_streams[i], l64_crc[i]); + // crc_streams[i] += 16*4; + // } + // But unrolling and interleaving PCLMULQDQ and CRC blocks manually + // gives ~2% performance boost. + l64_crc[0] = Process64BytesCRC(crc_streams[0], l64_crc[0]); + crc_streams[0] += 16 * 4; + if (num_pclmul_streams > 0) { + Process64BytesPclmul(pclmul_streams[0], partialCRC[0]); + pclmul_streams[0] += 16 * 4; + } + if (num_crc_streams > 1) { + l64_crc[1] = Process64BytesCRC(crc_streams[1], l64_crc[1]); + crc_streams[1] += 16 * 4; + } + if (num_pclmul_streams > 1) { + Process64BytesPclmul(pclmul_streams[1], partialCRC[1]); + pclmul_streams[1] += 16 * 4; + } + if (num_crc_streams > 2) { + l64_crc[2] = Process64BytesCRC(crc_streams[2], l64_crc[2]); + crc_streams[2] += 16 * 4; + } + if (num_pclmul_streams > 2) { + Process64BytesPclmul(pclmul_streams[2], partialCRC[2]); + pclmul_streams[2] += 16 * 4; + } + } + + // PCLMULQDQ based streams require special final step; + // CRC based don't. + for (size_t i = 0; i < num_pclmul_streams; i++) { + l64_pclmul[i] = FinalizePclmulStream(partialCRC[i]); + } + + // Combine all streams into single result. + uint32_t magic = ComputeZeroConstant(bs * 64); + l64 = l64_crc[0]; + for (size_t i = 1; i < num_crc_streams; i++) { + l64 = multiply(static_cast<uint32_t>(l64), magic); + l64 ^= l64_crc[i]; + } + for (size_t i = 0; i < num_pclmul_streams; i++) { + l64 = multiply(static_cast<uint32_t>(l64), magic); + l64 ^= l64_pclmul[i]; + } + + // Update p. + if (num_pclmul_streams > 0) { + p = pclmul_streams[num_pclmul_streams - 1]; + } else { + p = crc_streams[num_crc_streams - 1]; + } + } + l = static_cast<uint32_t>(l64); + + while ((e - p) >= 16) { + ABSL_INTERNAL_STEP8(l, p); + ABSL_INTERNAL_STEP8(l, p); + } + // Process the last few bytes + while (p != e) { + ABSL_INTERNAL_STEP1(l); + } + +#undef ABSL_INTERNAL_STEP8BY3 +#undef ABSL_INTERNAL_STEP8BY2 +#undef ABSL_INTERNAL_STEP8 +#undef ABSL_INTERNAL_STEP4 +#undef ABSL_INTERNAL_STEP2 +#undef ABSL_INTERNAL_STEP1 + + *crc = l; + } +}; + +} // namespace + +// Intel processors with SSE4.2 have an instruction for one particular +// 32-bit CRC polynomial: crc32c +CRCImpl* TryNewCRC32AcceleratedX86ARMCombined() { + CpuType type = GetCpuType(); + switch (type) { + case CpuType::kIntelHaswell: + case CpuType::kAmdRome: + case CpuType::kAmdNaples: + case CpuType::kAmdMilan: + return new CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 1, CutoffStrategy::Fold3>(); + // PCLMULQDQ is fast, use combined PCLMULQDQ + CRC implementation. + case CpuType::kIntelCascadelakeXeon: + case CpuType::kIntelSkylakeXeon: + case CpuType::kIntelBroadwell: + case CpuType::kIntelSkylake: + return new CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 2, CutoffStrategy::Fold3>(); + // PCLMULQDQ is slow, don't use it. + case CpuType::kIntelIvybridge: + case CpuType::kIntelSandybridge: + case CpuType::kIntelWestmere: + return new CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 0, CutoffStrategy::Fold3>(); + case CpuType::kArmNeoverseN1: + return new CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 1, CutoffStrategy::Unroll64CRC>(); +#if defined(__aarch64__) + default: + // Not all ARM processors support the needed instructions, so check here + // before trying to use an accelerated implementation. + if (SupportsArmCRC32PMULL()) { + return new CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 1, CutoffStrategy::Unroll64CRC>(); + } else { + return nullptr; + } +#else + default: + // Something else, play it safe and assume slow PCLMULQDQ. + return new CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 0, CutoffStrategy::Fold3>(); +#endif + } +} + +std::vector<std::unique_ptr<CRCImpl>> NewCRC32AcceleratedX86ARMCombinedAll() { + auto ret = std::vector<std::unique_ptr<CRCImpl>>(); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 0, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 1, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 2, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 3, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 2, 0, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 2, 1, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 2, 2, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 2, 3, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 0, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 1, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 2, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 3, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 0, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 1, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 2, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 3, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 2, 0, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 2, 1, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 2, 2, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 2, 3, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 0, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 1, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 2, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 3, CutoffStrategy::Unroll64CRC>>()); + + return ret; +} + +#else // !ABSL_INTERNAL_CAN_USE_SIMD_CRC32C + +std::vector<std::unique_ptr<CRCImpl>> NewCRC32AcceleratedX86ARMCombinedAll() { + return std::vector<std::unique_ptr<CRCImpl>>(); +} + +// no hardware acceleration available +CRCImpl* TryNewCRC32AcceleratedX86ARMCombined() { return nullptr; } + +#endif + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/crc/internal/non_temporal_arm_intrinsics.h b/absl/crc/internal/non_temporal_arm_intrinsics.h new file mode 100644 index 00000000..9e5ccfc4 --- /dev/null +++ b/absl/crc/internal/non_temporal_arm_intrinsics.h @@ -0,0 +1,79 @@ +// Copyright 2022 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_CRC_INTERNAL_NON_TEMPORAL_ARM_INTRINSICS_H_ +#define ABSL_CRC_INTERNAL_NON_TEMPORAL_ARM_INTRINSICS_H_ + +#include "absl/base/config.h" + +#ifdef __aarch64__ +#include <arm_neon.h> + +typedef int64x2_t __m128i; /* 128-bit vector containing integers */ +#define vreinterpretq_m128i_s32(x) vreinterpretq_s64_s32(x) +#define vreinterpretq_s64_m128i(x) (x) + +// Guarantees that every preceding store is globally visible before any +// subsequent store. +// https://msdn.microsoft.com/en-us/library/5h2w73d1%28v=vs.90%29.aspx +static inline __attribute__((always_inline)) void _mm_sfence(void) { + __sync_synchronize(); +} + +// Load 128-bits of integer data from unaligned memory into dst. This intrinsic +// may perform better than _mm_loadu_si128 when the data crosses a cache line +// boundary. +// +// dst[127:0] := MEM[mem_addr+127:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_lddqu_si128 +#define _mm_lddqu_si128 _mm_loadu_si128 + +// Loads 128-bit value. : +// https://msdn.microsoft.com/zh-cn/library/f4k12ae8(v=vs.90).aspx +static inline __attribute__((always_inline)) __m128i _mm_loadu_si128( + const __m128i *p) { + return vreinterpretq_m128i_s32(vld1q_s32((const int32_t *)p)); +} + +// Stores the data in a to the address p without polluting the caches. If the +// cache line containing address p is already in the cache, the cache will be +// updated. +// https://msdn.microsoft.com/en-us/library/ba08y07y%28v=vs.90%29.aspx +static inline __attribute__((always_inline)) void _mm_stream_si128(__m128i *p, + __m128i a) { +#if ABSL_HAVE_BUILTIN(__builtin_nontemporal_store) + __builtin_nontemporal_store(a, p); +#else + vst1q_s64((int64_t *)p, vreinterpretq_s64_m128i(a)); +#endif +} + +// Sets the 16 signed 8-bit integer values. +// https://msdn.microsoft.com/en-us/library/x0cx8zd3(v=vs.90).aspx +static inline __attribute__((always_inline)) __m128i _mm_set_epi8( + signed char b15, signed char b14, signed char b13, signed char b12, + signed char b11, signed char b10, signed char b9, signed char b8, + signed char b7, signed char b6, signed char b5, signed char b4, + signed char b3, signed char b2, signed char b1, signed char b0) { + int8_t __attribute__((aligned(16))) + data[16] = {(int8_t)b0, (int8_t)b1, (int8_t)b2, (int8_t)b3, + (int8_t)b4, (int8_t)b5, (int8_t)b6, (int8_t)b7, + (int8_t)b8, (int8_t)b9, (int8_t)b10, (int8_t)b11, + (int8_t)b12, (int8_t)b13, (int8_t)b14, (int8_t)b15}; + return (__m128i)vld1q_s8(data); +} +#endif // __aarch64__ + +#endif // ABSL_CRC_INTERNAL_NON_TEMPORAL_ARM_INTRINSICS_H_ diff --git a/absl/crc/internal/non_temporal_memcpy.h b/absl/crc/internal/non_temporal_memcpy.h new file mode 100644 index 00000000..b3d94bad --- /dev/null +++ b/absl/crc/internal/non_temporal_memcpy.h @@ -0,0 +1,180 @@ +// Copyright 2022 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_CRC_INTERNAL_NON_TEMPORAL_MEMCPY_H_ +#define ABSL_CRC_INTERNAL_NON_TEMPORAL_MEMCPY_H_ + +#ifdef _MSC_VER +#include <intrin.h> +#endif + +#ifdef __SSE__ +#include <xmmintrin.h> +#endif + +#ifdef __SSE2__ +#include <emmintrin.h> +#endif + +#ifdef __SSE3__ +#include <pmmintrin.h> +#endif + +#ifdef __AVX__ +#include <immintrin.h> +#endif + +#ifdef __aarch64__ +#include "absl/crc/internal/non_temporal_arm_intrinsics.h" +#endif + +#include <algorithm> +#include <cassert> +#include <cstdint> +#include <cstring> + +#include "absl/base/config.h" +#include "absl/base/optimization.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +// This non-temporal memcpy does regular load and non-temporal store memory +// copy. It is compatible to both 16-byte aligned and unaligned addresses. If +// data at the destination is not immediately accessed, using non-temporal +// memcpy can save 1 DRAM load of the destination cacheline. +constexpr size_t kCacheLineSize = ABSL_CACHELINE_SIZE; + +// If the objects overlap, the behavior is undefined. +inline void *non_temporal_store_memcpy(void *__restrict dst, + const void *__restrict src, size_t len) { +#if defined(__SSE3__) || defined(__aarch64__) || \ + (defined(_MSC_VER) && defined(__AVX__)) + // This implementation requires SSE3. + // MSVC cannot target SSE3 directly, but when MSVC targets AVX, + // SSE3 support is implied. + uint8_t *d = reinterpret_cast<uint8_t *>(dst); + const uint8_t *s = reinterpret_cast<const uint8_t *>(src); + + // memcpy() the misaligned header. At the end of this if block, <d> is + // aligned to a 64-byte cacheline boundary or <len> == 0. + if (reinterpret_cast<uintptr_t>(d) & (kCacheLineSize - 1)) { + uintptr_t bytes_before_alignment_boundary = + kCacheLineSize - + (reinterpret_cast<uintptr_t>(d) & (kCacheLineSize - 1)); + size_t header_len = (std::min)(bytes_before_alignment_boundary, len); + assert(bytes_before_alignment_boundary < kCacheLineSize); + memcpy(d, s, header_len); + d += header_len; + s += header_len; + len -= header_len; + } + + if (len >= kCacheLineSize) { + _mm_sfence(); + __m128i *dst_cacheline = reinterpret_cast<__m128i *>(d); + const __m128i *src_cacheline = reinterpret_cast<const __m128i *>(s); + constexpr int kOpsPerCacheLine = kCacheLineSize / sizeof(__m128i); + size_t loops = len / kCacheLineSize; + + while (len >= kCacheLineSize) { + __m128i temp1, temp2, temp3, temp4; + temp1 = _mm_lddqu_si128(src_cacheline + 0); + temp2 = _mm_lddqu_si128(src_cacheline + 1); + temp3 = _mm_lddqu_si128(src_cacheline + 2); + temp4 = _mm_lddqu_si128(src_cacheline + 3); + _mm_stream_si128(dst_cacheline + 0, temp1); + _mm_stream_si128(dst_cacheline + 1, temp2); + _mm_stream_si128(dst_cacheline + 2, temp3); + _mm_stream_si128(dst_cacheline + 3, temp4); + src_cacheline += kOpsPerCacheLine; + dst_cacheline += kOpsPerCacheLine; + len -= kCacheLineSize; + } + d += loops * kCacheLineSize; + s += loops * kCacheLineSize; + _mm_sfence(); + } + + // memcpy the tail. + if (len) { + memcpy(d, s, len); + } + return dst; +#else + // Fallback to regular memcpy. + return memcpy(dst, src, len); +#endif // __SSE3__ || __aarch64__ || (_MSC_VER && __AVX__) +} + +inline void *non_temporal_store_memcpy_avx(void *__restrict dst, + const void *__restrict src, + size_t len) { +#ifdef __AVX__ + uint8_t *d = reinterpret_cast<uint8_t *>(dst); + const uint8_t *s = reinterpret_cast<const uint8_t *>(src); + + // memcpy() the misaligned header. At the end of this if block, <d> is + // aligned to a 64-byte cacheline boundary or <len> == 0. + if (reinterpret_cast<uintptr_t>(d) & (kCacheLineSize - 1)) { + uintptr_t bytes_before_alignment_boundary = + kCacheLineSize - + (reinterpret_cast<uintptr_t>(d) & (kCacheLineSize - 1)); + size_t header_len = (std::min)(bytes_before_alignment_boundary, len); + assert(bytes_before_alignment_boundary < kCacheLineSize); + memcpy(d, s, header_len); + d += header_len; + s += header_len; + len -= header_len; + } + + if (len >= kCacheLineSize) { + _mm_sfence(); + __m256i *dst_cacheline = reinterpret_cast<__m256i *>(d); + const __m256i *src_cacheline = reinterpret_cast<const __m256i *>(s); + constexpr int kOpsPerCacheLine = kCacheLineSize / sizeof(__m256i); + size_t loops = len / kCacheLineSize; + + while (len >= kCacheLineSize) { + __m256i temp1, temp2; + temp1 = _mm256_lddqu_si256(src_cacheline + 0); + temp2 = _mm256_lddqu_si256(src_cacheline + 1); + _mm256_stream_si256(dst_cacheline + 0, temp1); + _mm256_stream_si256(dst_cacheline + 1, temp2); + src_cacheline += kOpsPerCacheLine; + dst_cacheline += kOpsPerCacheLine; + len -= kCacheLineSize; + } + d += loops * kCacheLineSize; + s += loops * kCacheLineSize; + _mm_sfence(); + } + + // memcpy the tail. + if (len) { + memcpy(d, s, len); + } + return dst; +#else + // Fallback to regular memcpy when AVX is not available. + return memcpy(dst, src, len); +#endif // __AVX__ +} + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CRC_INTERNAL_NON_TEMPORAL_MEMCPY_H_ diff --git a/absl/crc/internal/non_temporal_memcpy_test.cc b/absl/crc/internal/non_temporal_memcpy_test.cc new file mode 100644 index 00000000..eb07a559 --- /dev/null +++ b/absl/crc/internal/non_temporal_memcpy_test.cc @@ -0,0 +1,88 @@ +// Copyright 2022 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/crc/internal/non_temporal_memcpy.h" + +#include <algorithm> +#include <cstdint> +#include <iostream> +#include <vector> + +#include "gtest/gtest.h" + +namespace { + +struct TestParam { + size_t copy_size; + uint32_t src_offset; + uint32_t dst_offset; +}; + +class NonTemporalMemcpyTest : public testing::TestWithParam<TestParam> { + protected: + void SetUp() override { + // Make buf_size multiple of 16 bytes. + size_t buf_size = ((std::max(GetParam().src_offset, GetParam().dst_offset) + + GetParam().copy_size) + + 15) / + 16 * 16; + a_.resize(buf_size); + b_.resize(buf_size); + for (size_t i = 0; i < buf_size; i++) { + a_[i] = static_cast<uint8_t>(i % 256); + b_[i] = ~a_[i]; + } + } + + std::vector<uint8_t> a_, b_; +}; + +TEST_P(NonTemporalMemcpyTest, SSEEquality) { + uint8_t *src = a_.data() + GetParam().src_offset; + uint8_t *dst = b_.data() + GetParam().dst_offset; + absl::crc_internal::non_temporal_store_memcpy(dst, src, GetParam().copy_size); + for (size_t i = 0; i < GetParam().copy_size; i++) { + EXPECT_EQ(src[i], dst[i]); + } +} + +TEST_P(NonTemporalMemcpyTest, AVXEquality) { + uint8_t* src = a_.data() + GetParam().src_offset; + uint8_t* dst = b_.data() + GetParam().dst_offset; + + absl::crc_internal::non_temporal_store_memcpy_avx(dst, src, + GetParam().copy_size); + for (size_t i = 0; i < GetParam().copy_size; i++) { + EXPECT_EQ(src[i], dst[i]); + } +} + +// 63B is smaller than one cacheline operation thus the non-temporal routine +// will not be called. +// 4352B is sufficient for testing 4092B data copy with room for offsets. +constexpr TestParam params[] = { + {63, 0, 0}, {58, 5, 5}, {61, 2, 0}, {61, 0, 2}, + {58, 5, 2}, {4096, 0, 0}, {4096, 0, 1}, {4096, 0, 2}, + {4096, 0, 3}, {4096, 0, 4}, {4096, 0, 5}, {4096, 0, 6}, + {4096, 0, 7}, {4096, 0, 8}, {4096, 0, 9}, {4096, 0, 10}, + {4096, 0, 11}, {4096, 0, 12}, {4096, 0, 13}, {4096, 0, 14}, + {4096, 0, 15}, {4096, 7, 7}, {4096, 3, 0}, {4096, 1, 0}, + {4096, 9, 3}, {4096, 9, 11}, {8192, 0, 0}, {8192, 5, 2}, + {1024768, 7, 11}, {1, 0, 0}, {1, 0, 1}, {1, 1, 0}, + {1, 1, 1}}; + +INSTANTIATE_TEST_SUITE_P(ParameterizedNonTemporalMemcpyTest, + NonTemporalMemcpyTest, testing::ValuesIn(params)); + +} // namespace diff --git a/absl/debugging/internal/stacktrace_x86-inl.inc b/absl/debugging/internal/stacktrace_x86-inl.inc index 9fbfcf76..2f8bf428 100644 --- a/absl/debugging/internal/stacktrace_x86-inl.inc +++ b/absl/debugging/internal/stacktrace_x86-inl.inc @@ -29,6 +29,7 @@ #include <cstdint> #include <limits> +#include "absl/base/attributes.h" #include "absl/base/macros.h" #include "absl/base/port.h" #include "absl/debugging/internal/address_is_readable.h" diff --git a/absl/flags/flag_benchmark.cc b/absl/flags/flag_benchmark.cc index fc572d9c..758a6a55 100644 --- a/absl/flags/flag_benchmark.cc +++ b/absl/flags/flag_benchmark.cc @@ -241,10 +241,11 @@ BENCHMARK(BM_ThreadedFindCommandLineFlag)->ThreadRange(1, 16); } // namespace +#ifdef __llvm__ +// To view disassembly use: gdb ${BINARY} -batch -ex "disassemble /s $FUNC" #define InvokeGetFlag(T) \ T AbslInvokeGetFlag##T() { return absl::GetFlag(SINGLE_FLAG(T)); } \ int odr##T = (benchmark::DoNotOptimize(AbslInvokeGetFlag##T), 1); BENCHMARKED_TYPES(InvokeGetFlag) - -// To veiw disassembly use: gdb ${BINARY} -batch -ex "disassemble /s $FUNC" +#endif // __llvm__ diff --git a/absl/flags/internal/usage.cc b/absl/flags/internal/usage.cc index a3b13ed3..5efc7b07 100644 --- a/absl/flags/internal/usage.cc +++ b/absl/flags/internal/usage.cc @@ -17,7 +17,9 @@ #include <stdint.h> +#include <algorithm> #include <functional> +#include <iterator> #include <map> #include <ostream> #include <string> @@ -33,6 +35,7 @@ #include "absl/flags/internal/program_name.h" #include "absl/flags/internal/registry.h" #include "absl/flags/usage_config.h" +#include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" @@ -343,7 +346,7 @@ void FlagHelp(std::ostream& out, const CommandLineFlag& flag, void FlagsHelp(std::ostream& out, absl::string_view filter, HelpFormat format, absl::string_view program_usage_message) { flags_internal::FlagKindFilter filter_cb = [&](absl::string_view filename) { - return filter.empty() || filename.find(filter) != absl::string_view::npos; + return filter.empty() || absl::StrContains(filename, filter); }; flags_internal::FlagsHelpImpl(out, filter_cb, format, program_usage_message); } @@ -465,7 +468,7 @@ void SetFlagsHelpFormat(HelpFormat format) { // function. bool DeduceUsageFlags(absl::string_view name, absl::string_view value) { if (absl::ConsumePrefix(&name, "help")) { - if (name == "") { + if (name.empty()) { if (value.empty()) { SetFlagsHelpMode(HelpMode::kImportant); } else { diff --git a/absl/hash/BUILD.bazel b/absl/hash/BUILD.bazel index bcc316f9..4a95f054 100644 --- a/absl/hash/BUILD.bazel +++ b/absl/hash/BUILD.bazel @@ -157,7 +157,6 @@ cc_library( deps = [ "//absl/base:config", "//absl/base:endian", - "//absl/numeric:bits", "//absl/numeric:int128", ], ) diff --git a/absl/hash/CMakeLists.txt b/absl/hash/CMakeLists.txt index 15691254..0514c296 100644 --- a/absl/hash/CMakeLists.txt +++ b/absl/hash/CMakeLists.txt @@ -140,7 +140,6 @@ absl_cc_library( COPTS ${ABSL_DEFAULT_COPTS} DEPS - absl::bits absl::config absl::endian absl::int128 diff --git a/absl/hash/internal/low_level_hash.cc b/absl/hash/internal/low_level_hash.cc index e05e7885..c917457a 100644 --- a/absl/hash/internal/low_level_hash.cc +++ b/absl/hash/internal/low_level_hash.cc @@ -15,7 +15,6 @@ #include "absl/hash/internal/low_level_hash.h" #include "absl/base/internal/unaligned_access.h" -#include "absl/numeric/bits.h" #include "absl/numeric/int128.h" namespace absl { @@ -23,20 +22,9 @@ ABSL_NAMESPACE_BEGIN namespace hash_internal { static uint64_t Mix(uint64_t v0, uint64_t v1) { -#if !defined(__aarch64__) - // The default bit-mixer uses 64x64->128-bit multiplication. absl::uint128 p = v0; p *= v1; return absl::Uint128Low64(p) ^ absl::Uint128High64(p); -#else - // The default bit-mixer above would perform poorly on some ARM microarchs, - // where calculating a 128-bit product requires a sequence of two - // instructions with a high combined latency and poor throughput. - // Instead, we mix bits using only 64-bit arithmetic, which is faster. - uint64_t p = v0 ^ absl::rotl(v1, 40); - p *= v1 ^ absl::rotl(v0, 39); - return p ^ (p >> 11); -#endif } uint64_t LowLevelHash(const void* data, size_t len, uint64_t seed, diff --git a/absl/hash/internal/low_level_hash_test.cc b/absl/hash/internal/low_level_hash_test.cc index ae930b34..589a3d8f 100644 --- a/absl/hash/internal/low_level_hash_test.cc +++ b/absl/hash/internal/low_level_hash_test.cc @@ -452,54 +452,6 @@ TEST(LowLevelHashTest, VerifyGolden) { 0xdd497891465a2cc1, 0x6f1fe8c57a33072e, 0x2c9f4ec078c460c0, 0x9a725bde8f6a1437, 0x6ce545fa3ef61e4d, }; -#elif defined(__aarch64__) - constexpr uint64_t kGolden[kNumGoldenOutputs] = { - 0x45c0aadee165dcbe, 0x25ed8587f6f20d06, 0x5f23ae668ce7926d, - 0xfef74d1da0846719, 0x54478408e68cb7d4, 0xee27ddaf88c6fe68, - 0xb7ac7031e81867ca, 0xf1168f818ec6c36d, 0x1dd0b734a83b019a, - 0xd6ae30d4142b54fe, 0xcd860c721ccb80fb, 0x068acf8493794756, - 0xd4ada0be58681307, 0x13ffe0f64ca540ed, 0xffc1d7a3401aec02, - 0xd81c4d865cf95fb9, 0x1dd0793acede62e0, 0xa6722abbca8fe4cf, - 0x5453d3e4111a7e40, 0xf29b3e3204c9dcd2, 0x23be2980e43117f7, - 0x74e2ccbc286f08eb, 0x19ef7c0f9496003a, 0xbfbf1c3e49b27987, - 0x6e6c179eb4a82c70, 0x07f4e184216bc4fc, 0xf17fbc4254927554, - 0xe57696b70a45b1b6, 0x6d3b144631b320e8, 0xccf8729792c75a2d, - 0xe832495b41fa980b, 0x5c96cfdc7b227d34, 0xc4dca234ef4e43f4, - 0x5fc801abf9abe307, 0xe41e3c5076d88f4d, 0x522346200ddec3c3, - 0x72bed1946fd7aaa4, 0x0ac1f84dcc335f96, 0x3af78db5e0a47670, - 0x6100ebf1481f1caf, 0xf5fd10037fc651a3, 0xa01227d8944665f3, - 0x7217681c4bbc9420, 0x4adee538e3eb10d1, 0x35e1761ad96de9a7, - 0x8b370aef9613bfba, 0x824506f749eeaf59, 0x85e805fa04423991, - 0xb61e9c33283c3de7, 0xc79721bbcb039ed6, 0x04e1c19a3a1e6639, - 0x6aaf6346b68dd638, 0x601a4b496be6d0c4, 0x3ece355f91c41787, - 0xd2fc8998448d7888, 0xd7529804f843efa9, 0xabdcc38a288536aa, - 0xdd323e48a9718648, 0x2090279c0030a52a, 0xe2f90faca88a3cd1, - 0x3e0c4e92fc50e4aa, 0xa26d308798e801dd, 0x432eefeedee8c02e, - 0xca4ce494614b77df, 0xbba82911e838066d, 0x4b00821016adee4b, - 0x4cf6e526dfb5a20f, 0x5b8466495142cba2, 0xe28ac1406e88a68c, - 0x8511e5f9d3100999, 0x05acbfe02798890b, 0x74c249c7ce4a8425, - 0xdbe7468d09bc34bc, 0x11079ab10e3b9b58, 0xb7788dec9032035a, - 0xb7e8daa786513f80, 0x34c3288831f46b45, 0x014cce5f0c21ecc6, - 0xc6a8f7b024551a28, 0x49784e902e207fd8, 0x4720d32af0b55158, - 0x8df3ec5de0c1da00, 0xf4db677b2c9e6853, 0xaa419abea78d312d, - 0x181e0f91bd757443, 0xa8c45136fada083b, 0x91303b93f5f0582c, - 0x883b95c6ddc62a08, 0x93186a8875fe952b, 0xd94f533928e957e2, - 0x6ba343003e10c172, 0xc8623b620c715d6a, 0x8ca0c512e180e244, - 0xdc9b74c2536b6216, 0x8eb5fdc61b295d96, 0x2ad83966b37c95ba, - 0xb90bf154ac5edec9, 0x902cf847b326cfb3, 0x7b02d0c0ca7808ca, - 0x492f310d003ea15f, 0x3eb6497a47c95990, 0x5d46b0ced31428b7, - 0x081afa67d1986157, 0x043482ec286b20eb, 0xc103c8f18c1a2a53, - 0xe8e9995a81481e83, 0x6bb3295822bc90b5, 0xeec75297a3fa5672, - 0x591c8440c4857412, 0x74947f455aaf24ad, 0xcf0e571586ec77a9, - 0x0c2553ea8c0400ad, 0x380219118066255f, 0x7595adb88b15ebe2, - 0xb33c00696c64ae23, 0xa143516ddd7c9857, 0x39179af229248d26, - 0x65d387a6f2ee2079, 0x89f8a9b21cd2f195, 0xbfef032d25df92e6, - 0x6b7e18a36c69da71, 0x4b3b15f6c28974e6, 0x032a75917f6c544c, - 0xe3b97ecca6d287cd, 0xa4a563110d3cda81, 0x35e09e8134f4e7f1, - 0xc9419dd03a9a390e, 0x7b86fae9000fd329, 0x1e044f8d54fe74c3, - 0x9c4991d7a47e9666, 0xfb485f3a1df4fdb6, 0xb11519969eeb94ff, - 0x3224ea1c44caeb8d, 0x86570bbd7cc6b80d, - }; #else constexpr uint64_t kGolden[kNumGoldenOutputs] = { 0xe5a40d39ab796423, 0x1766974bf7527d81, 0x5c3bbbe230db17a8, diff --git a/absl/log/BUILD.bazel b/absl/log/BUILD.bazel index dadc8856..10916446 100644 --- a/absl/log/BUILD.bazel +++ b/absl/log/BUILD.bazel @@ -26,13 +26,34 @@ package(default_visibility = ["//visibility:public"]) licenses(["notice"]) # Public targets + +cc_library( + name = "absl_check", + hdrs = ["absl_check.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/log/internal:check_impl", + ], +) + +cc_library( + name = "absl_log", + hdrs = ["absl_log.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/log/internal:log_impl", + ], +) + cc_library( name = "check", hdrs = ["check.h"], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ - "//absl/base:core_headers", + "//absl/log/internal:check_impl", "//absl/log/internal:check_op", "//absl/log/internal:conditions", "//absl/log/internal:log_message", @@ -114,9 +135,7 @@ cc_library( copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ - "//absl/log/internal:conditions", - "//absl/log/internal:log_message", - "//absl/log/internal:strip", + "//absl/log/internal:log_impl", ], ) @@ -196,23 +215,47 @@ cc_library( ], ) +cc_library( + name = "structured", + hdrs = ["structured.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:config", + "//absl/log/internal:structured", + "//absl/strings", + ], +) + # Test targets + cc_test( - name = "basic_log_test", + name = "absl_check_test", size = "small", - srcs = ["basic_log_test.cc"], + srcs = ["absl_check_test.cc"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + tags = [ + "no_test:os:ios", + "no_test_ios", + "no_test_wasm", + ], deps = [ - ":globals", - ":log", - ":log_entry", - ":scoped_mock_log", - "//absl/base", - "//absl/base:log_severity", - "//absl/log/internal:test_actions", - "//absl/log/internal:test_helpers", - "//absl/log/internal:test_matchers", + ":absl_check", + ":check_test_impl", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "absl_log_basic_test", + size = "small", + srcs = ["absl_log_basic_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":absl_log", + ":log_basic_test_impl", "@com_google_googletest//:gtest_main", ], ) @@ -230,10 +273,29 @@ cc_test( ], deps = [ ":check", + ":check_test_impl", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "check_test_impl", + testonly = True, + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = [ + "no_test:os:ios", + "no_test_ios", + "no_test_wasm", + ], + textual_hdrs = ["check_test_impl.h"], + visibility = ["//visibility:private"], + deps = [ "//absl/base:config", "//absl/base:core_headers", "//absl/log/internal:test_helpers", - "@com_google_googletest//:gtest_main", + "//absl/status", + "@com_google_googletest//:gtest", ], ) @@ -293,6 +355,39 @@ cc_test( ) cc_test( + name = "log_basic_test", + size = "small", + srcs = ["log_basic_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log", + ":log_basic_test_impl", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "log_basic_test_impl", + testonly = True, + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + textual_hdrs = ["log_basic_test_impl.h"], + visibility = ["//visibility:private"], + deps = [ + "//absl/base", + "//absl/base:log_severity", + "//absl/log:globals", + "//absl/log:log_entry", + "//absl/log:scoped_mock_log", + "//absl/log/internal:test_actions", + "//absl/log/internal:test_helpers", + "//absl/log/internal:test_matchers", + "@com_google_googletest//:gtest", + ], +) + +cc_test( name = "log_entry_test", size = "small", srcs = ["log_entry_test.cc"], @@ -303,6 +398,7 @@ cc_test( "//absl/base:config", "//absl/base:core_headers", "//absl/base:log_severity", + "//absl/log/internal:append_truncated", "//absl/log/internal:format", "//absl/log/internal:test_helpers", "//absl/strings", @@ -319,11 +415,12 @@ cc_test( copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ + ":check", ":log", ":scoped_mock_log", - "//absl/log/internal:config", "//absl/log/internal:test_matchers", "//absl/strings", + "//absl/strings:str_format", "@com_google_googletest//:gtest_main", ], ) @@ -457,6 +554,23 @@ cc_test( ], ) +cc_test( + name = "structured_test", + size = "small", + srcs = ["structured_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log", + ":scoped_mock_log", + ":structured", + "//absl/base:core_headers", + "//absl/log/internal:test_helpers", + "//absl/log/internal:test_matchers", + "@com_google_googletest//:gtest_main", + ], +) + cc_binary( name = "log_benchmark", testonly = 1, diff --git a/absl/log/CMakeLists.txt b/absl/log/CMakeLists.txt index 09e4ca0c..de2eaf39 100644 --- a/absl/log/CMakeLists.txt +++ b/absl/log/CMakeLists.txt @@ -17,6 +17,24 @@ # Internal targets absl_cc_library( NAME + log_internal_check_impl + SRCS + HDRS + "internal/check_impl.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::log_internal_check_op + absl::log_internal_conditions + absl::log_internal_message + absl::log_internal_strip +) + +absl_cc_library( + NAME log_internal_check_op SRCS "internal/check_op.cc" @@ -96,6 +114,7 @@ absl_cc_library( DEPS absl::config absl::core_headers + absl::log_internal_append_truncated absl::log_internal_config absl::log_internal_globals absl::log_severity @@ -127,6 +146,41 @@ absl_cc_library( absl_cc_library( NAME + log_internal_log_impl + SRCS + HDRS + "internal/log_impl.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::log_internal_conditions + absl::log_internal_message + absl::log_internal_strip +) + +absl_cc_library( + NAME + log_internal_proto + SRCS + "internal/proto.cc" + HDRS + "internal/proto.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::base + absl::config + absl::core_headers + absl::strings + absl::span +) + +absl_cc_library( + NAME log_internal_message SRCS "internal/log_message.cc" @@ -143,9 +197,10 @@ absl_cc_library( absl::errno_saver absl::inlined_vector absl::examine_stack - absl::log_internal_config + absl::log_internal_append_truncated absl::log_internal_format absl::log_internal_globals + absl::log_internal_proto absl::log_internal_log_sink_set absl::log_internal_nullguard absl::log_globals @@ -157,7 +212,6 @@ absl_cc_library( absl::raw_logging_internal absl::strings absl::strerror - absl::str_format absl::time absl::span ) @@ -251,8 +305,8 @@ absl_cc_library( ${ABSL_DEFAULT_LINKOPTS} DEPS absl::config + absl::core_headers absl::log_entry - absl::log_internal_config absl::log_severity absl::strings absl::time @@ -293,8 +347,8 @@ absl_cc_library( ${ABSL_DEFAULT_LINKOPTS} DEPS absl::config + absl::core_headers absl::log_entry - absl::log_internal_config absl::log_internal_test_helpers absl::log_severity absl::strings @@ -318,9 +372,55 @@ absl_cc_library( absl::config ) +absl_cc_library( + NAME + log_internal_append_truncated + SRCS + HDRS + "internal/append_truncated.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::strings + absl::span +) + # Public targets absl_cc_library( NAME + absl_check + SRCS + HDRS + "absl_check.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::log_internal_check_impl + PUBLIC +) + +absl_cc_library( + NAME + absl_log + SRCS + HDRS + "absl_log.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::log_internal_log_impl + PUBLIC +) + +absl_cc_library( + NAME check SRCS HDRS @@ -330,6 +430,7 @@ absl_cc_library( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS + absl::log_internal_check_impl absl::core_headers absl::log_internal_check_op absl::log_internal_conditions @@ -431,9 +532,7 @@ absl_cc_library( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS - absl::log_internal_conditions - absl::log_internal_message - absl::log_internal_strip + absl::log_internal_log_impl PUBLIC ) @@ -539,19 +638,72 @@ absl_cc_library( TESTONLY ) +absl_cc_library( + NAME + log_internal_structured + HDRS + "internal/structured.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::log_internal_message + absl::strings +) + +absl_cc_library( + NAME + log_structured + HDRS + "structured.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::log_internal_structured + absl::strings + PUBLIC +) + # Test targets + absl_cc_test( NAME - basic_log_test + absl_check_test SRCS - "basic_log_test.cc" + "absl_check_test.cc" + "check_test_impl.h" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::absl_check + absl::config + absl::core_headers + absl::log_internal_test_helpers + absl::status + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + absl_log_basic_test + SRCS + "log_basic_test.cc" + "log_basic_test_impl.h" COPTS ${ABSL_TEST_COPTS} LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS absl::base - absl::log + absl::absl_log absl::log_entry absl::log_globals absl::log_severity @@ -568,6 +720,7 @@ absl_cc_test( check_test SRCS "check_test.cc" + "check_test_impl.h" COPTS ${ABSL_TEST_COPTS} LINKOPTS @@ -577,6 +730,7 @@ absl_cc_test( absl::config absl::core_headers absl::log_internal_test_helpers + absl::status GTest::gmock GTest::gtest_main ) @@ -599,26 +753,24 @@ absl_cc_test( absl_cc_test( NAME - log_flags_test + log_basic_test SRCS - "flags_test.cc" + "log_basic_test.cc" + "log_basic_test_impl.h" COPTS ${ABSL_TEST_COPTS} LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS - absl::core_headers + absl::base absl::log - absl::log_flags + absl::log_entry absl::log_globals - absl::log_internal_flags + absl::log_severity + absl::log_internal_test_actions absl::log_internal_test_helpers absl::log_internal_test_matchers - absl::log_severity - absl::flags - absl::flags_reflection absl::scoped_mock_log - absl::strings GTest::gmock GTest::gtest_main ) @@ -636,6 +788,7 @@ absl_cc_test( absl::config absl::core_headers absl::log_entry + absl::log_internal_append_truncated absl::log_internal_format absl::log_internal_globals absl::log_internal_test_helpers @@ -649,6 +802,32 @@ absl_cc_test( absl_cc_test( NAME + log_flags_test + SRCS + "flags_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::log + absl::log_flags + absl::log_globals + absl::log_internal_flags + absl::log_internal_test_helpers + absl::log_internal_test_matchers + absl::log_severity + absl::flags + absl::flags_reflection + absl::scoped_mock_log + absl::strings + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME log_globals_test SRCS "globals_test.cc" @@ -677,10 +856,11 @@ absl_cc_test( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS + absl::check absl::log - absl::log_internal_config absl::log_internal_test_matchers absl::scoped_mock_log + absl::str_format absl::strings GTest::gmock GTest::gtest_main @@ -837,3 +1017,23 @@ absl_cc_test( GTest::gmock GTest::gtest_main ) + +absl_cc_test( + NAME + log_structured_test + SRCS + "structured_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::log + absl::log_internal_test_helpers + absl::log_internal_test_matchers + absl::log_structured + absl::scoped_mock_log + GTest::gmock + GTest::gtest_main +) diff --git a/absl/log/absl_check.h b/absl/log/absl_check.h new file mode 100644 index 00000000..14a2307f --- /dev/null +++ b/absl/log/absl_check.h @@ -0,0 +1,105 @@ +// Copyright 2022 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: log/absl_check.h +// ----------------------------------------------------------------------------- +// +// This header declares a family of `ABSL_CHECK` macros as alternative spellings +// for `CHECK` macros in `check.h`. +// +// Except for those whose names begin with `ABSL_DCHECK`, these macros are not +// controlled by `NDEBUG` (cf. `assert`), so the check will be executed +// regardless of compilation mode. `ABSL_CHECK` and friends are thus useful for +// confirming invariants in situations where continuing to run would be worse +// than terminating, e.g., due to risk of data corruption or security +// compromise. It is also more robust and portable to deliberately terminate +// at a particular place with a useful message and backtrace than to assume some +// ultimately unspecified and unreliable crashing behavior (such as a +// "segmentation fault"). +// +// For full documentation of each macro, see comments in `check.h`, which has an +// identical set of macros without the ABSL_* prefix. + +#ifndef ABSL_LOG_ABSL_CHECK_H_ +#define ABSL_LOG_ABSL_CHECK_H_ + +#include "absl/log/internal/check_impl.h" + +#define ABSL_CHECK(condition) ABSL_CHECK_IMPL((condition), #condition) +#define ABSL_QCHECK(condition) ABSL_QCHECK_IMPL((condition), #condition) +#define ABSL_PCHECK(condition) ABSL_PCHECK_IMPL((condition), #condition) +#define ABSL_DCHECK(condition) ABSL_DCHECK_IMPL((condition), #condition) + +#define ABSL_CHECK_EQ(val1, val2) \ + ABSL_CHECK_EQ_IMPL((val1), #val1, (val2), #val2) +#define ABSL_CHECK_NE(val1, val2) \ + ABSL_CHECK_NE_IMPL((val1), #val1, (val2), #val2) +#define ABSL_CHECK_LE(val1, val2) \ + ABSL_CHECK_LE_IMPL((val1), #val1, (val2), #val2) +#define ABSL_CHECK_LT(val1, val2) \ + ABSL_CHECK_LT_IMPL((val1), #val1, (val2), #val2) +#define ABSL_CHECK_GE(val1, val2) \ + ABSL_CHECK_GE_IMPL((val1), #val1, (val2), #val2) +#define ABSL_CHECK_GT(val1, val2) \ + ABSL_CHECK_GT_IMPL((val1), #val1, (val2), #val2) +#define ABSL_QCHECK_EQ(val1, val2) \ + ABSL_QCHECK_EQ_IMPL((val1), #val1, (val2), #val2) +#define ABSL_QCHECK_NE(val1, val2) \ + ABSL_QCHECK_NE_IMPL((val1), #val1, (val2), #val2) +#define ABSL_QCHECK_LE(val1, val2) \ + ABSL_QCHECK_LE_IMPL((val1), #val1, (val2), #val2) +#define ABSL_QCHECK_LT(val1, val2) \ + ABSL_QCHECK_LT_IMPL((val1), #val1, (val2), #val2) +#define ABSL_QCHECK_GE(val1, val2) \ + ABSL_QCHECK_GE_IMPL((val1), #val1, (val2), #val2) +#define ABSL_QCHECK_GT(val1, val2) \ + ABSL_QCHECK_GT_IMPL((val1), #val1, (val2), #val2) +#define ABSL_DCHECK_EQ(val1, val2) \ + ABSL_DCHECK_EQ_IMPL((val1), #val1, (val2), #val2) +#define ABSL_DCHECK_NE(val1, val2) \ + ABSL_DCHECK_NE_IMPL((val1), #val1, (val2), #val2) +#define ABSL_DCHECK_LE(val1, val2) \ + ABSL_DCHECK_LE_IMPL((val1), #val1, (val2), #val2) +#define ABSL_DCHECK_LT(val1, val2) \ + ABSL_DCHECK_LT_IMPL((val1), #val1, (val2), #val2) +#define ABSL_DCHECK_GE(val1, val2) \ + ABSL_DCHECK_GE_IMPL((val1), #val1, (val2), #val2) +#define ABSL_DCHECK_GT(val1, val2) \ + ABSL_DCHECK_GT_IMPL((val1), #val1, (val2), #val2) + +#define ABSL_CHECK_OK(status) ABSL_CHECK_OK_IMPL((status), #status) +#define ABSL_QCHECK_OK(status) ABSL_QCHECK_OK_IMPL((status), #status) +#define ABSL_DCHECK_OK(status) ABSL_DCHECK_OK_IMPL((status), #status) + +#define ABSL_CHECK_STREQ(s1, s2) ABSL_CHECK_STREQ_IMPL((s1), #s1, (s2), #s2) +#define ABSL_CHECK_STRNE(s1, s2) ABSL_CHECK_STRNE_IMPL((s1), #s1, (s2), #s2) +#define ABSL_CHECK_STRCASEEQ(s1, s2) \ + ABSL_CHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2) +#define ABSL_CHECK_STRCASENE(s1, s2) \ + ABSL_CHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2) +#define ABSL_QCHECK_STREQ(s1, s2) ABSL_QCHECK_STREQ_IMPL((s1), #s1, (s2), #s2) +#define ABSL_QCHECK_STRNE(s1, s2) ABSL_QCHECK_STRNE_IMPL((s1), #s1, (s2), #s2) +#define ABSL_QCHECK_STRCASEEQ(s1, s2) \ + ABSL_QCHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2) +#define ABSL_QCHECK_STRCASENE(s1, s2) \ + ABSL_QCHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2) +#define ABSL_DCHECK_STREQ(s1, s2) ABSL_DCHECK_STREQ_IMPL((s1), #s1, (s2), #s2) +#define ABSL_DCHECK_STRNE(s1, s2) ABSL_DCHECK_STRNE_IMPL((s1), #s1, (s2), #s2) +#define ABSL_DCHECK_STRCASEEQ(s1, s2) \ + ABSL_DCHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2) +#define ABSL_DCHECK_STRCASENE(s1, s2) \ + ABSL_DCHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2) + +#endif // ABSL_LOG_ABSL_CHECK_H_ diff --git a/absl/log/absl_check_test.cc b/absl/log/absl_check_test.cc new file mode 100644 index 00000000..8ddacdb1 --- /dev/null +++ b/absl/log/absl_check_test.cc @@ -0,0 +1,58 @@ +// +// Copyright 2022 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/log/absl_check.h" + +#define ABSL_TEST_CHECK ABSL_CHECK +#define ABSL_TEST_CHECK_OK ABSL_CHECK_OK +#define ABSL_TEST_CHECK_EQ ABSL_CHECK_EQ +#define ABSL_TEST_CHECK_NE ABSL_CHECK_NE +#define ABSL_TEST_CHECK_GE ABSL_CHECK_GE +#define ABSL_TEST_CHECK_LE ABSL_CHECK_LE +#define ABSL_TEST_CHECK_GT ABSL_CHECK_GT +#define ABSL_TEST_CHECK_LT ABSL_CHECK_LT +#define ABSL_TEST_CHECK_STREQ ABSL_CHECK_STREQ +#define ABSL_TEST_CHECK_STRNE ABSL_CHECK_STRNE +#define ABSL_TEST_CHECK_STRCASEEQ ABSL_CHECK_STRCASEEQ +#define ABSL_TEST_CHECK_STRCASENE ABSL_CHECK_STRCASENE + +#define ABSL_TEST_DCHECK ABSL_DCHECK +#define ABSL_TEST_DCHECK_OK ABSL_DCHECK_OK +#define ABSL_TEST_DCHECK_EQ ABSL_DCHECK_EQ +#define ABSL_TEST_DCHECK_NE ABSL_DCHECK_NE +#define ABSL_TEST_DCHECK_GE ABSL_DCHECK_GE +#define ABSL_TEST_DCHECK_LE ABSL_DCHECK_LE +#define ABSL_TEST_DCHECK_GT ABSL_DCHECK_GT +#define ABSL_TEST_DCHECK_LT ABSL_DCHECK_LT +#define ABSL_TEST_DCHECK_STREQ ABSL_DCHECK_STREQ +#define ABSL_TEST_DCHECK_STRNE ABSL_DCHECK_STRNE +#define ABSL_TEST_DCHECK_STRCASEEQ ABSL_DCHECK_STRCASEEQ +#define ABSL_TEST_DCHECK_STRCASENE ABSL_DCHECK_STRCASENE + +#define ABSL_TEST_QCHECK ABSL_QCHECK +#define ABSL_TEST_QCHECK_OK ABSL_QCHECK_OK +#define ABSL_TEST_QCHECK_EQ ABSL_QCHECK_EQ +#define ABSL_TEST_QCHECK_NE ABSL_QCHECK_NE +#define ABSL_TEST_QCHECK_GE ABSL_QCHECK_GE +#define ABSL_TEST_QCHECK_LE ABSL_QCHECK_LE +#define ABSL_TEST_QCHECK_GT ABSL_QCHECK_GT +#define ABSL_TEST_QCHECK_LT ABSL_QCHECK_LT +#define ABSL_TEST_QCHECK_STREQ ABSL_QCHECK_STREQ +#define ABSL_TEST_QCHECK_STRNE ABSL_QCHECK_STRNE +#define ABSL_TEST_QCHECK_STRCASEEQ ABSL_QCHECK_STRCASEEQ +#define ABSL_TEST_QCHECK_STRCASENE ABSL_QCHECK_STRCASENE + +#include "gtest/gtest.h" +#include "absl/log/check_test_impl.h" diff --git a/absl/log/absl_log.h b/absl/log/absl_log.h new file mode 100644 index 00000000..1c6cf263 --- /dev/null +++ b/absl/log/absl_log.h @@ -0,0 +1,94 @@ +// Copyright 2022 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: log/absl_log.h +// ----------------------------------------------------------------------------- +// +// This header declares a family of `ABSL_LOG` macros as alternative spellings +// for macros in `log.h`. +// +// Basic invocation looks like this: +// +// ABSL_LOG(INFO) << "Found " << num_cookies << " cookies"; +// +// Most `ABSL_LOG` macros take a severity level argument. The severity levels +// are `INFO`, `WARNING`, `ERROR`, and `FATAL`. +// +// For full documentation, see comments in `log.h`, which includes full +// reference documentation on use of the equivalent `LOG` macro and has an +// identical set of macros without the ABSL_* prefix. + +#ifndef ABSL_LOG_ABSL_LOG_H_ +#define ABSL_LOG_ABSL_LOG_H_ + +#include "absl/log/internal/log_impl.h" + +#define ABSL_LOG(severity) ABSL_LOG_IMPL(_##severity) +#define ABSL_PLOG(severity) ABSL_PLOG_IMPL(_##severity) +#define ABSL_DLOG(severity) ABSL_DLOG_IMPL(_##severity) + +#define ABSL_LOG_IF(severity, condition) \ + ABSL_LOG_IF_IMPL(_##severity, condition) +#define ABSL_PLOG_IF(severity, condition) \ + ABSL_PLOG_IF_IMPL(_##severity, condition) +#define ABSL_DLOG_IF(severity, condition) \ + ABSL_DLOG_IF_IMPL(_##severity, condition) + +#define ABSL_LOG_EVERY_N(severity, n) ABSL_LOG_EVERY_N_IMPL(_##severity, n) +#define ABSL_LOG_FIRST_N(severity, n) ABSL_LOG_FIRST_N_IMPL(_##severity, n) +#define ABSL_LOG_EVERY_POW_2(severity) ABSL_LOG_EVERY_POW_2_IMPL(_##severity) +#define ABSL_LOG_EVERY_N_SEC(severity, n_seconds) \ + ABSL_LOG_EVERY_N_SEC_IMPL(_##severity, n_seconds) + +#define ABSL_PLOG_EVERY_N(severity, n) ABSL_PLOG_EVERY_N_IMPL(_##severity, n) +#define ABSL_PLOG_FIRST_N(severity, n) ABSL_PLOG_FIRST_N_IMPL(_##severity, n) +#define ABSL_PLOG_EVERY_POW_2(severity) ABSL_PLOG_EVERY_POW_2_IMPL(_##severity) +#define ABSL_PLOG_EVERY_N_SEC(severity, n_seconds) \ + ABSL_PLOG_EVERY_N_SEC_IMPL(_##severity, n_seconds) + +#define ABSL_DLOG_EVERY_N(severity, n) ABSL_DLOG_EVERY_N_IMPL(_##severity, n) +#define ABSL_DLOG_FIRST_N(severity, n) ABSL_DLOG_FIRST_N_IMPL(_##severity, n) +#define ABSL_DLOG_EVERY_POW_2(severity) ABSL_DLOG_EVERY_POW_2_IMPL(_##severity) +#define ABSL_DLOG_EVERY_N_SEC(severity, n_seconds) \ + ABSL_DLOG_EVERY_N_SEC_IMPL(_##severity, n_seconds) + +#define ABSL_LOG_IF_EVERY_N(severity, condition, n) \ + ABSL_LOG_IF_EVERY_N_IMPL(_##severity, condition, n) +#define ABSL_LOG_IF_FIRST_N(severity, condition, n) \ + ABSL_LOG_IF_FIRST_N_IMPL(_##severity, condition, n) +#define ABSL_LOG_IF_EVERY_POW_2(severity, condition) \ + ABSL_LOG_IF_EVERY_POW_2_IMPL(_##severity, condition) +#define ABSL_LOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ + ABSL_LOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds) + +#define ABSL_PLOG_IF_EVERY_N(severity, condition, n) \ + ABSL_PLOG_IF_EVERY_N_IMPL(_##severity, condition, n) +#define ABSL_PLOG_IF_FIRST_N(severity, condition, n) \ + ABSL_PLOG_IF_FIRST_N_IMPL(_##severity, condition, n) +#define ABSL_PLOG_IF_EVERY_POW_2(severity, condition) \ + ABSL_PLOG_IF_EVERY_POW_2_IMPL(_##severity, condition) +#define ABSL_PLOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ + ABSL_PLOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds) + +#define ABSL_DLOG_IF_EVERY_N(severity, condition, n) \ + ABSL_DLOG_IF_EVERY_N_IMPL(_##severity, condition, n) +#define ABSL_DLOG_IF_FIRST_N(severity, condition, n) \ + ABSL_DLOG_IF_FIRST_N_IMPL(_##severity, condition, n) +#define ABSL_DLOG_IF_EVERY_POW_2(severity, condition) \ + ABSL_DLOG_IF_EVERY_POW_2_IMPL(_##severity, condition) +#define ABSL_DLOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ + ABSL_DLOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds) + +#endif // ABSL_LOG_ABSL_LOG_H_ diff --git a/absl/log/absl_log_basic_test.cc b/absl/log/absl_log_basic_test.cc new file mode 100644 index 00000000..bc8a787d --- /dev/null +++ b/absl/log/absl_log_basic_test.cc @@ -0,0 +1,21 @@ +// +// Copyright 2022 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/log/absl_log.h" + +#define ABSL_TEST_LOG ABSL_LOG + +#include "gtest/gtest.h" +#include "absl/log/log_basic_test_impl.h" diff --git a/absl/log/check.h b/absl/log/check.h index c7303b8d..33145a57 100644 --- a/absl/log/check.h +++ b/absl/log/check.h @@ -34,7 +34,7 @@ #ifndef ABSL_LOG_CHECK_H_ #define ABSL_LOG_CHECK_H_ -#include "absl/base/optimization.h" +#include "absl/log/internal/check_impl.h" #include "absl/log/internal/check_op.h" // IWYU pragma: export #include "absl/log/internal/conditions.h" // IWYU pragma: export #include "absl/log/internal/log_message.h" // IWYU pragma: export @@ -54,10 +54,7 @@ // Might produce a message like: // // Check failed: !cheese.empty() Out of Cheese -#define CHECK(condition) \ - ABSL_LOG_INTERNAL_CONDITION_FATAL(STATELESS, \ - ABSL_PREDICT_FALSE(!(condition))) \ - ABSL_LOG_INTERNAL_CHECK(#condition).InternalStream() +#define CHECK(condition) ABSL_CHECK_IMPL((condition), #condition) // QCHECK() // @@ -65,10 +62,7 @@ // not run registered error handlers (as `QFATAL`). It is useful when the // problem is definitely unrelated to program flow, e.g. when validating user // input. -#define QCHECK(condition) \ - ABSL_LOG_INTERNAL_CONDITION_QFATAL(STATELESS, \ - ABSL_PREDICT_FALSE(!(condition))) \ - ABSL_LOG_INTERNAL_QCHECK(#condition).InternalStream() +#define QCHECK(condition) ABSL_QCHECK_IMPL((condition), #condition) // PCHECK() // @@ -83,7 +77,7 @@ // Might produce a message like: // // Check failed: fd != -1 posix is difficult: No such file or directory [2] -#define PCHECK(condition) CHECK(condition).WithPerror() +#define PCHECK(condition) ABSL_PCHECK_IMPL((condition), #condition) // DCHECK() // @@ -91,11 +85,7 @@ // `DLOG`). Unlike with `CHECK` (but as with `assert`), it is not safe to rely // on evaluation of `condition`: when `NDEBUG` is enabled, DCHECK does not // evaluate the condition. -#ifndef NDEBUG -#define DCHECK(condition) CHECK(condition) -#else -#define DCHECK(condition) CHECK(true || (condition)) -#endif +#define DCHECK(condition) ABSL_DCHECK_IMPL((condition), #condition) // `CHECK_EQ` and friends are syntactic sugar for `CHECK(x == y)` that // automatically output the expression being tested and the evaluated values on @@ -123,43 +113,24 @@ // // WARNING: Passing `NULL` as an argument to `CHECK_EQ` and similar macros does // not compile. Use `nullptr` instead. -#define CHECK_EQ(val1, val2) \ - ABSL_LOG_INTERNAL_CHECK_OP(Check_EQ, ==, val1, val2) -#define CHECK_NE(val1, val2) \ - ABSL_LOG_INTERNAL_CHECK_OP(Check_NE, !=, val1, val2) -#define CHECK_LE(val1, val2) \ - ABSL_LOG_INTERNAL_CHECK_OP(Check_LE, <=, val1, val2) -#define CHECK_LT(val1, val2) ABSL_LOG_INTERNAL_CHECK_OP(Check_LT, <, val1, val2) -#define CHECK_GE(val1, val2) \ - ABSL_LOG_INTERNAL_CHECK_OP(Check_GE, >=, val1, val2) -#define CHECK_GT(val1, val2) ABSL_LOG_INTERNAL_CHECK_OP(Check_GT, >, val1, val2) -#define QCHECK_EQ(val1, val2) \ - ABSL_LOG_INTERNAL_QCHECK_OP(Check_EQ, ==, val1, val2) -#define QCHECK_NE(val1, val2) \ - ABSL_LOG_INTERNAL_QCHECK_OP(Check_NE, !=, val1, val2) -#define QCHECK_LE(val1, val2) \ - ABSL_LOG_INTERNAL_QCHECK_OP(Check_LE, <=, val1, val2) -#define QCHECK_LT(val1, val2) \ - ABSL_LOG_INTERNAL_QCHECK_OP(Check_LT, <, val1, val2) -#define QCHECK_GE(val1, val2) \ - ABSL_LOG_INTERNAL_QCHECK_OP(Check_GE, >=, val1, val2) -#define QCHECK_GT(val1, val2) \ - ABSL_LOG_INTERNAL_QCHECK_OP(Check_GT, >, val1, val2) -#ifndef NDEBUG -#define DCHECK_EQ(val1, val2) CHECK_EQ(val1, val2) -#define DCHECK_NE(val1, val2) CHECK_NE(val1, val2) -#define DCHECK_LE(val1, val2) CHECK_LE(val1, val2) -#define DCHECK_LT(val1, val2) CHECK_LT(val1, val2) -#define DCHECK_GE(val1, val2) CHECK_GE(val1, val2) -#define DCHECK_GT(val1, val2) CHECK_GT(val1, val2) -#else // ndef NDEBUG -#define DCHECK_EQ(val1, val2) ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) -#define DCHECK_NE(val1, val2) ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) -#define DCHECK_LE(val1, val2) ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) -#define DCHECK_LT(val1, val2) ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) -#define DCHECK_GE(val1, val2) ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) -#define DCHECK_GT(val1, val2) ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) -#endif // def NDEBUG +#define CHECK_EQ(val1, val2) ABSL_CHECK_EQ_IMPL((val1), #val1, (val2), #val2) +#define CHECK_NE(val1, val2) ABSL_CHECK_NE_IMPL((val1), #val1, (val2), #val2) +#define CHECK_LE(val1, val2) ABSL_CHECK_LE_IMPL((val1), #val1, (val2), #val2) +#define CHECK_LT(val1, val2) ABSL_CHECK_LT_IMPL((val1), #val1, (val2), #val2) +#define CHECK_GE(val1, val2) ABSL_CHECK_GE_IMPL((val1), #val1, (val2), #val2) +#define CHECK_GT(val1, val2) ABSL_CHECK_GT_IMPL((val1), #val1, (val2), #val2) +#define QCHECK_EQ(val1, val2) ABSL_QCHECK_EQ_IMPL((val1), #val1, (val2), #val2) +#define QCHECK_NE(val1, val2) ABSL_QCHECK_NE_IMPL((val1), #val1, (val2), #val2) +#define QCHECK_LE(val1, val2) ABSL_QCHECK_LE_IMPL((val1), #val1, (val2), #val2) +#define QCHECK_LT(val1, val2) ABSL_QCHECK_LT_IMPL((val1), #val1, (val2), #val2) +#define QCHECK_GE(val1, val2) ABSL_QCHECK_GE_IMPL((val1), #val1, (val2), #val2) +#define QCHECK_GT(val1, val2) ABSL_QCHECK_GT_IMPL((val1), #val1, (val2), #val2) +#define DCHECK_EQ(val1, val2) ABSL_DCHECK_EQ_IMPL((val1), #val1, (val2), #val2) +#define DCHECK_NE(val1, val2) ABSL_DCHECK_NE_IMPL((val1), #val1, (val2), #val2) +#define DCHECK_LE(val1, val2) ABSL_DCHECK_LE_IMPL((val1), #val1, (val2), #val2) +#define DCHECK_LT(val1, val2) ABSL_DCHECK_LT_IMPL((val1), #val1, (val2), #val2) +#define DCHECK_GE(val1, val2) ABSL_DCHECK_GE_IMPL((val1), #val1, (val2), #val2) +#define DCHECK_GT(val1, val2) ABSL_DCHECK_GT_IMPL((val1), #val1, (val2), #val2) // `CHECK_OK` and friends validate that the provided `absl::Status` or // `absl::StatusOr<T>` is OK. If it isn't, they print a failure message that @@ -175,13 +146,9 @@ // Might produce a message like: // // Check failed: FunctionReturnsStatus(x, y, z) is OK (ABORTED: timeout) oops! -#define CHECK_OK(status) ABSL_LOG_INTERNAL_CHECK_OK(status) -#define QCHECK_OK(status) ABSL_LOG_INTERNAL_QCHECK_OK(status) -#ifndef NDEBUG -#define DCHECK_OK(status) ABSL_LOG_INTERNAL_CHECK_OK(status) -#else -#define DCHECK_OK(status) ABSL_LOG_INTERNAL_DCHECK_NOP(status, nullptr) -#endif +#define CHECK_OK(status) ABSL_CHECK_OK_IMPL((status), #status) +#define QCHECK_OK(status) ABSL_QCHECK_OK_IMPL((status), #status) +#define DCHECK_OK(status) ABSL_DCHECK_OK_IMPL((status), #status) // `CHECK_STREQ` and friends provide `CHECK_EQ` functionality for C strings, // i.e., nul-terminated char arrays. The `CASE` versions are case-insensitive. @@ -196,32 +163,21 @@ // Example: // // CHECK_STREQ(Foo().c_str(), Bar().c_str()); -#define CHECK_STREQ(s1, s2) \ - ABSL_LOG_INTERNAL_CHECK_STROP(strcmp, ==, true, s1, s2) -#define CHECK_STRNE(s1, s2) \ - ABSL_LOG_INTERNAL_CHECK_STROP(strcmp, !=, false, s1, s2) -#define CHECK_STRCASEEQ(s1, s2) \ - ABSL_LOG_INTERNAL_CHECK_STROP(strcasecmp, ==, true, s1, s2) -#define CHECK_STRCASENE(s1, s2) \ - ABSL_LOG_INTERNAL_CHECK_STROP(strcasecmp, !=, false, s1, s2) -#define QCHECK_STREQ(s1, s2) \ - ABSL_LOG_INTERNAL_QCHECK_STROP(strcmp, ==, true, s1, s2) -#define QCHECK_STRNE(s1, s2) \ - ABSL_LOG_INTERNAL_QCHECK_STROP(strcmp, !=, false, s1, s2) +#define CHECK_STREQ(s1, s2) ABSL_CHECK_STREQ_IMPL((s1), #s1, (s2), #s2) +#define CHECK_STRNE(s1, s2) ABSL_CHECK_STRNE_IMPL((s1), #s1, (s2), #s2) +#define CHECK_STRCASEEQ(s1, s2) ABSL_CHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2) +#define CHECK_STRCASENE(s1, s2) ABSL_CHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2) +#define QCHECK_STREQ(s1, s2) ABSL_QCHECK_STREQ_IMPL((s1), #s1, (s2), #s2) +#define QCHECK_STRNE(s1, s2) ABSL_QCHECK_STRNE_IMPL((s1), #s1, (s2), #s2) #define QCHECK_STRCASEEQ(s1, s2) \ - ABSL_LOG_INTERNAL_QCHECK_STROP(strcasecmp, ==, true, s1, s2) + ABSL_QCHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2) #define QCHECK_STRCASENE(s1, s2) \ - ABSL_LOG_INTERNAL_QCHECK_STROP(strcasecmp, !=, false, s1, s2) -#ifndef NDEBUG -#define DCHECK_STREQ(s1, s2) CHECK_STREQ(s1, s2) -#define DCHECK_STRCASEEQ(s1, s2) CHECK_STRCASEEQ(s1, s2) -#define DCHECK_STRNE(s1, s2) CHECK_STRNE(s1, s2) -#define DCHECK_STRCASENE(s1, s2) CHECK_STRCASENE(s1, s2) -#else // ndef NDEBUG -#define DCHECK_STREQ(s1, s2) ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2) -#define DCHECK_STRCASEEQ(s1, s2) ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2) -#define DCHECK_STRNE(s1, s2) ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2) -#define DCHECK_STRCASENE(s1, s2) ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2) -#endif // def NDEBUG + ABSL_QCHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2) +#define DCHECK_STREQ(s1, s2) ABSL_DCHECK_STREQ_IMPL((s1), #s1, (s2), #s2) +#define DCHECK_STRNE(s1, s2) ABSL_DCHECK_STRNE_IMPL((s1), #s1, (s2), #s2) +#define DCHECK_STRCASEEQ(s1, s2) \ + ABSL_DCHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2) +#define DCHECK_STRCASENE(s1, s2) \ + ABSL_DCHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2) #endif // ABSL_LOG_CHECK_H_ diff --git a/absl/log/check_test.cc b/absl/log/check_test.cc index 4ce9d872..f44a686e 100644 --- a/absl/log/check_test.cc +++ b/absl/log/check_test.cc @@ -15,419 +15,44 @@ #include "absl/log/check.h" -#include <ostream> -#include <string> +#define ABSL_TEST_CHECK CHECK +#define ABSL_TEST_CHECK_OK CHECK_OK +#define ABSL_TEST_CHECK_EQ CHECK_EQ +#define ABSL_TEST_CHECK_NE CHECK_NE +#define ABSL_TEST_CHECK_GE CHECK_GE +#define ABSL_TEST_CHECK_LE CHECK_LE +#define ABSL_TEST_CHECK_GT CHECK_GT +#define ABSL_TEST_CHECK_LT CHECK_LT +#define ABSL_TEST_CHECK_STREQ CHECK_STREQ +#define ABSL_TEST_CHECK_STRNE CHECK_STRNE +#define ABSL_TEST_CHECK_STRCASEEQ CHECK_STRCASEEQ +#define ABSL_TEST_CHECK_STRCASENE CHECK_STRCASENE + +#define ABSL_TEST_DCHECK DCHECK +#define ABSL_TEST_DCHECK_OK DCHECK_OK +#define ABSL_TEST_DCHECK_EQ DCHECK_EQ +#define ABSL_TEST_DCHECK_NE DCHECK_NE +#define ABSL_TEST_DCHECK_GE DCHECK_GE +#define ABSL_TEST_DCHECK_LE DCHECK_LE +#define ABSL_TEST_DCHECK_GT DCHECK_GT +#define ABSL_TEST_DCHECK_LT DCHECK_LT +#define ABSL_TEST_DCHECK_STREQ DCHECK_STREQ +#define ABSL_TEST_DCHECK_STRNE DCHECK_STRNE +#define ABSL_TEST_DCHECK_STRCASEEQ DCHECK_STRCASEEQ +#define ABSL_TEST_DCHECK_STRCASENE DCHECK_STRCASENE + +#define ABSL_TEST_QCHECK QCHECK +#define ABSL_TEST_QCHECK_OK QCHECK_OK +#define ABSL_TEST_QCHECK_EQ QCHECK_EQ +#define ABSL_TEST_QCHECK_NE QCHECK_NE +#define ABSL_TEST_QCHECK_GE QCHECK_GE +#define ABSL_TEST_QCHECK_LE QCHECK_LE +#define ABSL_TEST_QCHECK_GT QCHECK_GT +#define ABSL_TEST_QCHECK_LT QCHECK_LT +#define ABSL_TEST_QCHECK_STREQ QCHECK_STREQ +#define ABSL_TEST_QCHECK_STRNE QCHECK_STRNE +#define ABSL_TEST_QCHECK_STRCASEEQ QCHECK_STRCASEEQ +#define ABSL_TEST_QCHECK_STRCASENE QCHECK_STRCASENE -#include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/base/attributes.h" -#include "absl/base/config.h" -#include "absl/log/internal/test_helpers.h" - -namespace { -using ::testing::AllOf; -using ::testing::HasSubstr; -using ::testing::Not; - -auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( - new absl::log_internal::LogTestEnvironment); - -#if GTEST_HAS_DEATH_TEST - -TEST(CHECKDeathTest, TestBasicValues) { - CHECK(true); - - EXPECT_DEATH(CHECK(false), "Check failed: false"); - - int i = 2; - CHECK(i != 3); // NOLINT -} - -#endif // GTEST_HAS_DEATH_TEST - -TEST(CHECKTest, TestLogicExpressions) { - int i = 5; - CHECK(i > 0 && i < 10); - CHECK(i < 0 || i > 3); -} - -#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L -ABSL_CONST_INIT const auto global_var_check = [](int i) { - CHECK(i > 0); // NOLINT - return i + 1; -}(3); - -ABSL_CONST_INIT const auto global_var = [](int i) { - CHECK_GE(i, 0); // NOLINT - return i + 1; -}(global_var_check); -#endif // ABSL_INTERNAL_CPLUSPLUS_LANG - -TEST(CHECKTest, TestPlacementsInCompoundStatements) { - // check placement inside if/else clauses - if (true) CHECK(true); - - if (false) - ; // NOLINT - else - CHECK(true); - - switch (0) - case 0: - CHECK(true); // NOLINT - -#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L - constexpr auto var = [](int i) { - CHECK(i > 0); // NOLINT - return i + 1; - }(global_var); - (void)var; -#endif // ABSL_INTERNAL_CPLUSPLUS_LANG -} - -TEST(CHECKTest, TestBoolConvertible) { - struct Tester { - } tester; - CHECK([&]() { return &tester; }()); -} - -#if GTEST_HAS_DEATH_TEST - -TEST(CHECKDeathTest, TestChecksWithSideeffects) { - int var = 0; - CHECK([&var]() { - ++var; - return true; - }()); - EXPECT_EQ(var, 1); - - EXPECT_DEATH(CHECK([&var]() { - ++var; - return false; - }()) << var, - "Check failed: .* 2"); -} - -#endif // GTEST_HAS_DEATH_TEST - -#if GTEST_HAS_DEATH_TEST - -TEST(CHECKDeachTest, TestOrderOfInvocationsBetweenCheckAndMessage) { - int counter = 0; - - auto GetStr = [&counter]() -> std::string { - return counter++ == 0 ? "" : "non-empty"; - }; - - EXPECT_DEATH(CHECK(!GetStr().empty()) << GetStr(), HasSubstr("non-empty")); -} - -TEST(CHECKTest, TestSecondaryFailure) { - auto FailingRoutine = []() { - CHECK(false) << "Secondary"; - return false; - }; - EXPECT_DEATH(CHECK(FailingRoutine()) << "Primary", - AllOf(HasSubstr("Secondary"), Not(HasSubstr("Primary")))); -} - -TEST(CHECKTest, TestSecondaryFailureInMessage) { - auto MessageGen = []() { - CHECK(false) << "Secondary"; - return "Primary"; - }; - EXPECT_DEATH(CHECK(false) << MessageGen(), - AllOf(HasSubstr("Secondary"), Not(HasSubstr("Primary")))); -} - -#endif // GTEST_HAS_DEATH_TEST - -TEST(CHECKTest, TestBinaryChecksWithPrimitives) { - CHECK_EQ(1, 1); - CHECK_NE(1, 2); - CHECK_GE(1, 1); - CHECK_GE(2, 1); - CHECK_LE(1, 1); - CHECK_LE(1, 2); - CHECK_GT(2, 1); - CHECK_LT(1, 2); -} - -// For testing using CHECK*() on anonymous enums. -enum { CASE_A, CASE_B }; - -TEST(CHECKTest, TestBinaryChecksWithEnumValues) { - // Tests using CHECK*() on anonymous enums. - CHECK_EQ(CASE_A, CASE_A); - CHECK_NE(CASE_A, CASE_B); - CHECK_GE(CASE_A, CASE_A); - CHECK_GE(CASE_B, CASE_A); - CHECK_LE(CASE_A, CASE_A); - CHECK_LE(CASE_A, CASE_B); - CHECK_GT(CASE_B, CASE_A); - CHECK_LT(CASE_A, CASE_B); -} - -TEST(CHECKTest, TestBinaryChecksWithNullptr) { - const void* p_null = nullptr; - const void* p_not_null = &p_null; - CHECK_EQ(p_null, nullptr); - CHECK_EQ(nullptr, p_null); - CHECK_NE(p_not_null, nullptr); - CHECK_NE(nullptr, p_not_null); -} - -#if GTEST_HAS_DEATH_TEST - -// Test logging of various char-typed values by failing CHECK*(). -TEST(CHECKDeathTest, TestComparingCharsValues) { - { - char a = ';'; - char b = 'b'; - EXPECT_DEATH(CHECK_EQ(a, b), "Check failed: a == b \\(';' vs. 'b'\\)"); - b = 1; - EXPECT_DEATH(CHECK_EQ(a, b), - "Check failed: a == b \\(';' vs. char value 1\\)"); - } - { - signed char a = ';'; - signed char b = 'b'; - EXPECT_DEATH(CHECK_EQ(a, b), "Check failed: a == b \\(';' vs. 'b'\\)"); - b = -128; - EXPECT_DEATH(CHECK_EQ(a, b), - "Check failed: a == b \\(';' vs. signed char value -128\\)"); - } - { - unsigned char a = ';'; - unsigned char b = 'b'; - EXPECT_DEATH(CHECK_EQ(a, b), "Check failed: a == b \\(';' vs. 'b'\\)"); - b = 128; - EXPECT_DEATH(CHECK_EQ(a, b), - "Check failed: a == b \\(';' vs. unsigned char value 128\\)"); - } -} - -TEST(CHECKDeathTest, TestNullValuesAreReportedCleanly) { - const char* a = nullptr; - const char* b = nullptr; - EXPECT_DEATH(CHECK_NE(a, b), - "Check failed: a != b \\(\\(null\\) vs. \\(null\\)\\)"); - - a = "xx"; - EXPECT_DEATH(CHECK_EQ(a, b), "Check failed: a == b \\(xx vs. \\(null\\)\\)"); - EXPECT_DEATH(CHECK_EQ(b, a), "Check failed: b == a \\(\\(null\\) vs. xx\\)"); - - std::nullptr_t n{}; - EXPECT_DEATH(CHECK_NE(n, nullptr), - "Check failed: n != nullptr \\(\\(null\\) vs. \\(null\\)\\)"); -} - -#endif // GTEST_HAS_DEATH_TEST - -TEST(CHECKTest, TestSTREQ) { - CHECK_STREQ("this", "this"); - CHECK_STREQ(nullptr, nullptr); - CHECK_STRCASEEQ("this", "tHiS"); - CHECK_STRCASEEQ(nullptr, nullptr); - CHECK_STRNE("this", "tHiS"); - CHECK_STRNE("this", nullptr); - CHECK_STRCASENE("this", "that"); - CHECK_STRCASENE(nullptr, "that"); - CHECK_STREQ((std::string("a") + "b").c_str(), "ab"); - CHECK_STREQ(std::string("test").c_str(), - (std::string("te") + std::string("st")).c_str()); -} - -TEST(CHECKTest, TestComparisonPlacementsInCompoundStatements) { - // check placement inside if/else clauses - if (true) CHECK_EQ(1, 1); - if (true) CHECK_STREQ("c", "c"); - - if (false) - ; // NOLINT - else - CHECK_LE(0, 1); - - if (false) - ; // NOLINT - else - CHECK_STRNE("a", "b"); - - switch (0) - case 0: - CHECK_NE(1, 0); - - switch (0) - case 0: - CHECK_STRCASEEQ("A", "a"); - -#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L - constexpr auto var = [](int i) { - CHECK_GT(i, 0); - return i + 1; - }(global_var); - (void)var; - - // CHECK_STR... checks are not supported in constexpr routines. - // constexpr auto var2 = [](int i) { - // CHECK_STRNE("c", "d"); - // return i + 1; - // }(global_var); - -#if defined(__GNUC__) - int var3 = (({ CHECK_LE(1, 2); }), global_var < 10) ? 1 : 0; - (void)var3; - - int var4 = (({ CHECK_STREQ("a", "a"); }), global_var < 10) ? 1 : 0; - (void)var4; -#endif // __GNUC__ -#endif // ABSL_INTERNAL_CPLUSPLUS_LANG -} - -TEST(CHECKTest, TestDCHECK) { -#ifdef NDEBUG - DCHECK(1 == 2) << " DCHECK's shouldn't be compiled in normal mode"; -#endif - DCHECK(1 == 1); // NOLINT(readability/check) - DCHECK_EQ(1, 1); - DCHECK_NE(1, 2); - DCHECK_GE(1, 1); - DCHECK_GE(2, 1); - DCHECK_LE(1, 1); - DCHECK_LE(1, 2); - DCHECK_GT(2, 1); - DCHECK_LT(1, 2); - - // Test DCHECK on std::nullptr_t - const void* p_null = nullptr; - const void* p_not_null = &p_null; - DCHECK_EQ(p_null, nullptr); - DCHECK_EQ(nullptr, p_null); - DCHECK_NE(p_not_null, nullptr); - DCHECK_NE(nullptr, p_not_null); -} - -TEST(CHECKTest, TestQCHECK) { - // The tests that QCHECK does the same as CHECK - QCHECK(1 == 1); // NOLINT(readability/check) - QCHECK_EQ(1, 1); - QCHECK_NE(1, 2); - QCHECK_GE(1, 1); - QCHECK_GE(2, 1); - QCHECK_LE(1, 1); - QCHECK_LE(1, 2); - QCHECK_GT(2, 1); - QCHECK_LT(1, 2); - - // Tests using QCHECK*() on anonymous enums. - QCHECK_EQ(CASE_A, CASE_A); - QCHECK_NE(CASE_A, CASE_B); - QCHECK_GE(CASE_A, CASE_A); - QCHECK_GE(CASE_B, CASE_A); - QCHECK_LE(CASE_A, CASE_A); - QCHECK_LE(CASE_A, CASE_B); - QCHECK_GT(CASE_B, CASE_A); - QCHECK_LT(CASE_A, CASE_B); -} - -TEST(CHECKTest, TestQCHECKPlacementsInCompoundStatements) { - // check placement inside if/else clauses - if (true) QCHECK(true); - - if (false) - ; // NOLINT - else - QCHECK(true); - - if (false) - ; // NOLINT - else - QCHECK(true); - - switch (0) - case 0: - QCHECK(true); - -#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L - constexpr auto var = [](int i) { - QCHECK(i > 0); // NOLINT - return i + 1; - }(global_var); - (void)var; - -#if defined(__GNUC__) - int var2 = (({ CHECK_LE(1, 2); }), global_var < 10) ? 1 : 0; - (void)var2; -#endif // __GNUC__ -#endif // ABSL_INTERNAL_CPLUSPLUS_LANG -} - -class ComparableType { - public: - explicit ComparableType(int v) : v_(v) {} - - void MethodWithCheck(int i) { - CHECK_EQ(*this, i); - CHECK_EQ(i, *this); - } - - int Get() const { return v_; } - - private: - friend bool operator==(const ComparableType& lhs, const ComparableType& rhs) { - return lhs.v_ == rhs.v_; - } - friend bool operator!=(const ComparableType& lhs, const ComparableType& rhs) { - return lhs.v_ != rhs.v_; - } - friend bool operator<(const ComparableType& lhs, const ComparableType& rhs) { - return lhs.v_ < rhs.v_; - } - friend bool operator<=(const ComparableType& lhs, const ComparableType& rhs) { - return lhs.v_ <= rhs.v_; - } - friend bool operator>(const ComparableType& lhs, const ComparableType& rhs) { - return lhs.v_ > rhs.v_; - } - friend bool operator>=(const ComparableType& lhs, const ComparableType& rhs) { - return lhs.v_ >= rhs.v_; - } - friend bool operator==(const ComparableType& lhs, int rhs) { - return lhs.v_ == rhs; - } - friend bool operator==(int lhs, const ComparableType& rhs) { - return lhs == rhs.v_; - } - - friend std::ostream& operator<<(std::ostream& out, const ComparableType& v) { - return out << "ComparableType{" << v.Get() << "}"; - } - - int v_; -}; - -TEST(CHECKTest, TestUserDefinedCompOp) { - CHECK_EQ(ComparableType{0}, ComparableType{0}); - CHECK_NE(ComparableType{1}, ComparableType{2}); - CHECK_LT(ComparableType{1}, ComparableType{2}); - CHECK_LE(ComparableType{1}, ComparableType{2}); - CHECK_GT(ComparableType{2}, ComparableType{1}); - CHECK_GE(ComparableType{2}, ComparableType{2}); -} - -TEST(CHECKTest, TestCheckInMethod) { - ComparableType v{1}; - v.MethodWithCheck(1); -} - -TEST(CHECKDeathTest, TestUserDefinedStreaming) { - ComparableType v1{1}; - ComparableType v2{2}; - - EXPECT_DEATH( - CHECK_EQ(v1, v2), - HasSubstr( - "Check failed: v1 == v2 (ComparableType{1} vs. ComparableType{2})")); -} - -} // namespace +#include "absl/log/check_test_impl.h" diff --git a/absl/log/check_test_impl.h b/absl/log/check_test_impl.h new file mode 100644 index 00000000..d5c0aee4 --- /dev/null +++ b/absl/log/check_test_impl.h @@ -0,0 +1,528 @@ +// +// Copyright 2022 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_LOG_CHECK_TEST_IMPL_H_ +#define ABSL_LOG_CHECK_TEST_IMPL_H_ + +// Verify that both sets of macros behave identically by parameterizing the +// entire test file. +#ifndef ABSL_TEST_CHECK +#error ABSL_TEST_CHECK must be defined for these tests to work. +#endif + +#include <ostream> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/status/status.h" + +// NOLINTBEGIN(misc-definitions-in-headers) + +namespace absl_log_internal { + +using ::testing::AllOf; +using ::testing::HasSubstr; +using ::testing::Not; + +auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); + +#if GTEST_HAS_DEATH_TEST + +TEST(CHECKDeathTest, TestBasicValues) { + ABSL_TEST_CHECK(true); + + EXPECT_DEATH(ABSL_TEST_CHECK(false), "Check failed: false"); + + int i = 2; + ABSL_TEST_CHECK(i != 3); // NOLINT +} + +#endif // GTEST_HAS_DEATH_TEST + +TEST(CHECKTest, TestLogicExpressions) { + int i = 5; + ABSL_TEST_CHECK(i > 0 && i < 10); + ABSL_TEST_CHECK(i < 0 || i > 3); +} + +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L +ABSL_CONST_INIT const auto global_var_check = [](int i) { + ABSL_TEST_CHECK(i > 0); // NOLINT + return i + 1; +}(3); + +ABSL_CONST_INIT const auto global_var = [](int i) { + ABSL_TEST_CHECK_GE(i, 0); // NOLINT + return i + 1; +}(global_var_check); +#endif // ABSL_INTERNAL_CPLUSPLUS_LANG + +TEST(CHECKTest, TestPlacementsInCompoundStatements) { + // check placement inside if/else clauses + if (true) ABSL_TEST_CHECK(true); + + if (false) + ; // NOLINT + else + ABSL_TEST_CHECK(true); + + switch (0) + case 0: + ABSL_TEST_CHECK(true); // NOLINT + +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L + constexpr auto var = [](int i) { + ABSL_TEST_CHECK(i > 0); // NOLINT + return i + 1; + }(global_var); + (void)var; +#endif // ABSL_INTERNAL_CPLUSPLUS_LANG +} + +TEST(CHECKTest, TestBoolConvertible) { + struct Tester { + } tester; + ABSL_TEST_CHECK([&]() { return &tester; }()); +} + +#if GTEST_HAS_DEATH_TEST + +TEST(CHECKDeathTest, TestChecksWithSideEffects) { + int var = 0; + ABSL_TEST_CHECK([&var]() { + ++var; + return true; + }()); + EXPECT_EQ(var, 1); + + EXPECT_DEATH(ABSL_TEST_CHECK([&var]() { + ++var; + return false; + }()) << var, + "Check failed: .* 2"); +} + +#endif // GTEST_HAS_DEATH_TEST + +template <int a, int b> +constexpr int sum() { + return a + b; +} +#define MACRO_ONE 1 +#define TEMPLATE_SUM(a, b) sum<a, b>() +#define CONCAT(a, b) a b +#define IDENTITY(x) x + +TEST(CHECKTest, TestPassingMacroExpansion) { + ABSL_TEST_CHECK(IDENTITY(true)); + ABSL_TEST_CHECK_EQ(TEMPLATE_SUM(MACRO_ONE, 2), 3); + ABSL_TEST_CHECK_STREQ(CONCAT("x", "y"), "xy"); +} + +#if GTEST_HAS_DEATH_TEST + +TEST(CHECKTest, TestMacroExpansionInMessage) { + auto MessageGen = []() { ABSL_TEST_CHECK(IDENTITY(false)); }; + EXPECT_DEATH(MessageGen(), HasSubstr("IDENTITY(false)")); +} + +TEST(CHECKTest, TestNestedMacroExpansionInMessage) { + EXPECT_DEATH(ABSL_TEST_CHECK(IDENTITY(false)), HasSubstr("IDENTITY(false)")); +} + +TEST(CHECKTest, TestMacroExpansionCompare) { + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(IDENTITY(false), IDENTITY(true)), + HasSubstr("IDENTITY(false) == IDENTITY(true)")); + EXPECT_DEATH(ABSL_TEST_CHECK_GT(IDENTITY(1), IDENTITY(2)), + HasSubstr("IDENTITY(1) > IDENTITY(2)")); +} + +TEST(CHECKTest, TestMacroExpansionStrCompare) { + EXPECT_DEATH(ABSL_TEST_CHECK_STREQ(IDENTITY("x"), IDENTITY("y")), + HasSubstr("IDENTITY(\"x\") == IDENTITY(\"y\")")); + EXPECT_DEATH(ABSL_TEST_CHECK_STRCASENE(IDENTITY("a"), IDENTITY("A")), + HasSubstr("IDENTITY(\"a\") != IDENTITY(\"A\")")); +} + +TEST(CHECKTest, TestMacroExpansionStatus) { + EXPECT_DEATH( + ABSL_TEST_CHECK_OK(IDENTITY(absl::FailedPreconditionError("message"))), + HasSubstr("IDENTITY(absl::FailedPreconditionError(\"message\"))")); +} + +TEST(CHECKTest, TestMacroExpansionComma) { + EXPECT_DEATH(ABSL_TEST_CHECK(TEMPLATE_SUM(MACRO_ONE, 2) == 4), + HasSubstr("TEMPLATE_SUM(MACRO_ONE, 2) == 4")); +} + +TEST(CHECKTest, TestMacroExpansionCommaCompare) { + EXPECT_DEATH( + ABSL_TEST_CHECK_EQ(TEMPLATE_SUM(2, MACRO_ONE), TEMPLATE_SUM(3, 2)), + HasSubstr("TEMPLATE_SUM(2, MACRO_ONE) == TEMPLATE_SUM(3, 2)")); + EXPECT_DEATH( + ABSL_TEST_CHECK_GT(TEMPLATE_SUM(2, MACRO_ONE), TEMPLATE_SUM(3, 2)), + HasSubstr("TEMPLATE_SUM(2, MACRO_ONE) > TEMPLATE_SUM(3, 2)")); +} + +TEST(CHECKTest, TestMacroExpansionCommaStrCompare) { + EXPECT_DEATH(ABSL_TEST_CHECK_STREQ(CONCAT("x", "y"), "z"), + HasSubstr("CONCAT(\"x\", \"y\") == \"z\"")); + EXPECT_DEATH(ABSL_TEST_CHECK_STRNE(CONCAT("x", "y"), "xy"), + HasSubstr("CONCAT(\"x\", \"y\") != \"xy\"")); +} + +#endif // GTEST_HAS_DEATH_TEST + +#undef TEMPLATE_SUM +#undef CONCAT +#undef MACRO +#undef ONE + +#if GTEST_HAS_DEATH_TEST + +TEST(CHECKDeachTest, TestOrderOfInvocationsBetweenCheckAndMessage) { + int counter = 0; + + auto GetStr = [&counter]() -> std::string { + return counter++ == 0 ? "" : "non-empty"; + }; + + EXPECT_DEATH(ABSL_TEST_CHECK(!GetStr().empty()) << GetStr(), + HasSubstr("non-empty")); +} + +TEST(CHECKTest, TestSecondaryFailure) { + auto FailingRoutine = []() { + ABSL_TEST_CHECK(false) << "Secondary"; + return false; + }; + EXPECT_DEATH(ABSL_TEST_CHECK(FailingRoutine()) << "Primary", + AllOf(HasSubstr("Secondary"), Not(HasSubstr("Primary")))); +} + +TEST(CHECKTest, TestSecondaryFailureInMessage) { + auto MessageGen = []() { + ABSL_TEST_CHECK(false) << "Secondary"; + return "Primary"; + }; + EXPECT_DEATH(ABSL_TEST_CHECK(false) << MessageGen(), + AllOf(HasSubstr("Secondary"), Not(HasSubstr("Primary")))); +} + +#endif // GTEST_HAS_DEATH_TEST + +TEST(CHECKTest, TestBinaryChecksWithPrimitives) { + ABSL_TEST_CHECK_EQ(1, 1); + ABSL_TEST_CHECK_NE(1, 2); + ABSL_TEST_CHECK_GE(1, 1); + ABSL_TEST_CHECK_GE(2, 1); + ABSL_TEST_CHECK_LE(1, 1); + ABSL_TEST_CHECK_LE(1, 2); + ABSL_TEST_CHECK_GT(2, 1); + ABSL_TEST_CHECK_LT(1, 2); +} + +// For testing using CHECK*() on anonymous enums. +enum { CASE_A, CASE_B }; + +TEST(CHECKTest, TestBinaryChecksWithEnumValues) { + // Tests using CHECK*() on anonymous enums. + ABSL_TEST_CHECK_EQ(CASE_A, CASE_A); + ABSL_TEST_CHECK_NE(CASE_A, CASE_B); + ABSL_TEST_CHECK_GE(CASE_A, CASE_A); + ABSL_TEST_CHECK_GE(CASE_B, CASE_A); + ABSL_TEST_CHECK_LE(CASE_A, CASE_A); + ABSL_TEST_CHECK_LE(CASE_A, CASE_B); + ABSL_TEST_CHECK_GT(CASE_B, CASE_A); + ABSL_TEST_CHECK_LT(CASE_A, CASE_B); +} + +TEST(CHECKTest, TestBinaryChecksWithNullptr) { + const void* p_null = nullptr; + const void* p_not_null = &p_null; + ABSL_TEST_CHECK_EQ(p_null, nullptr); + ABSL_TEST_CHECK_EQ(nullptr, p_null); + ABSL_TEST_CHECK_NE(p_not_null, nullptr); + ABSL_TEST_CHECK_NE(nullptr, p_not_null); +} + +#if GTEST_HAS_DEATH_TEST + +// Test logging of various char-typed values by failing CHECK*(). +TEST(CHECKDeathTest, TestComparingCharsValues) { + { + char a = ';'; + char b = 'b'; + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b), + "Check failed: a == b \\(';' vs. 'b'\\)"); + b = 1; + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b), + "Check failed: a == b \\(';' vs. char value 1\\)"); + } + { + signed char a = ';'; + signed char b = 'b'; + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b), + "Check failed: a == b \\(';' vs. 'b'\\)"); + b = -128; + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b), + "Check failed: a == b \\(';' vs. signed char value -128\\)"); + } + { + unsigned char a = ';'; + unsigned char b = 'b'; + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b), + "Check failed: a == b \\(';' vs. 'b'\\)"); + b = 128; + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b), + "Check failed: a == b \\(';' vs. unsigned char value 128\\)"); + } +} + +TEST(CHECKDeathTest, TestNullValuesAreReportedCleanly) { + const char* a = nullptr; + const char* b = nullptr; + EXPECT_DEATH(ABSL_TEST_CHECK_NE(a, b), + "Check failed: a != b \\(\\(null\\) vs. \\(null\\)\\)"); + + a = "xx"; + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b), + "Check failed: a == b \\(xx vs. \\(null\\)\\)"); + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(b, a), + "Check failed: b == a \\(\\(null\\) vs. xx\\)"); + + std::nullptr_t n{}; + EXPECT_DEATH(ABSL_TEST_CHECK_NE(n, nullptr), + "Check failed: n != nullptr \\(\\(null\\) vs. \\(null\\)\\)"); +} + +#endif // GTEST_HAS_DEATH_TEST + +TEST(CHECKTest, TestSTREQ) { + ABSL_TEST_CHECK_STREQ("this", "this"); + ABSL_TEST_CHECK_STREQ(nullptr, nullptr); + ABSL_TEST_CHECK_STRCASEEQ("this", "tHiS"); + ABSL_TEST_CHECK_STRCASEEQ(nullptr, nullptr); + ABSL_TEST_CHECK_STRNE("this", "tHiS"); + ABSL_TEST_CHECK_STRNE("this", nullptr); + ABSL_TEST_CHECK_STRCASENE("this", "that"); + ABSL_TEST_CHECK_STRCASENE(nullptr, "that"); + ABSL_TEST_CHECK_STREQ((std::string("a") + "b").c_str(), "ab"); + ABSL_TEST_CHECK_STREQ(std::string("test").c_str(), + (std::string("te") + std::string("st")).c_str()); +} + +TEST(CHECKTest, TestComparisonPlacementsInCompoundStatements) { + // check placement inside if/else clauses + if (true) ABSL_TEST_CHECK_EQ(1, 1); + if (true) ABSL_TEST_CHECK_STREQ("c", "c"); + + if (false) + ; // NOLINT + else + ABSL_TEST_CHECK_LE(0, 1); + + if (false) + ; // NOLINT + else + ABSL_TEST_CHECK_STRNE("a", "b"); + + switch (0) + case 0: + ABSL_TEST_CHECK_NE(1, 0); + + switch (0) + case 0: + ABSL_TEST_CHECK_STRCASEEQ("A", "a"); + +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L + constexpr auto var = [](int i) { + ABSL_TEST_CHECK_GT(i, 0); + return i + 1; + }(global_var); + (void)var; + + // CHECK_STR... checks are not supported in constexpr routines. + // constexpr auto var2 = [](int i) { + // ABSL_TEST_CHECK_STRNE("c", "d"); + // return i + 1; + // }(global_var); + +#if defined(__GNUC__) + int var3 = (({ ABSL_TEST_CHECK_LE(1, 2); }), global_var < 10) ? 1 : 0; + (void)var3; + + int var4 = (({ ABSL_TEST_CHECK_STREQ("a", "a"); }), global_var < 10) ? 1 : 0; + (void)var4; +#endif // __GNUC__ +#endif // ABSL_INTERNAL_CPLUSPLUS_LANG +} + +TEST(CHECKTest, TestDCHECK) { +#ifdef NDEBUG + ABSL_TEST_DCHECK(1 == 2) << " DCHECK's shouldn't be compiled in normal mode"; +#endif + ABSL_TEST_DCHECK(1 == 1); // NOLINT(readability/check) + ABSL_TEST_DCHECK_EQ(1, 1); + ABSL_TEST_DCHECK_NE(1, 2); + ABSL_TEST_DCHECK_GE(1, 1); + ABSL_TEST_DCHECK_GE(2, 1); + ABSL_TEST_DCHECK_LE(1, 1); + ABSL_TEST_DCHECK_LE(1, 2); + ABSL_TEST_DCHECK_GT(2, 1); + ABSL_TEST_DCHECK_LT(1, 2); + + // Test DCHECK on std::nullptr_t + const void* p_null = nullptr; + const void* p_not_null = &p_null; + ABSL_TEST_DCHECK_EQ(p_null, nullptr); + ABSL_TEST_DCHECK_EQ(nullptr, p_null); + ABSL_TEST_DCHECK_NE(p_not_null, nullptr); + ABSL_TEST_DCHECK_NE(nullptr, p_not_null); +} + +TEST(CHECKTest, TestQCHECK) { + // The tests that QCHECK does the same as CHECK + ABSL_TEST_QCHECK(1 == 1); // NOLINT(readability/check) + ABSL_TEST_QCHECK_EQ(1, 1); + ABSL_TEST_QCHECK_NE(1, 2); + ABSL_TEST_QCHECK_GE(1, 1); + ABSL_TEST_QCHECK_GE(2, 1); + ABSL_TEST_QCHECK_LE(1, 1); + ABSL_TEST_QCHECK_LE(1, 2); + ABSL_TEST_QCHECK_GT(2, 1); + ABSL_TEST_QCHECK_LT(1, 2); + + // Tests using QCHECK*() on anonymous enums. + ABSL_TEST_QCHECK_EQ(CASE_A, CASE_A); + ABSL_TEST_QCHECK_NE(CASE_A, CASE_B); + ABSL_TEST_QCHECK_GE(CASE_A, CASE_A); + ABSL_TEST_QCHECK_GE(CASE_B, CASE_A); + ABSL_TEST_QCHECK_LE(CASE_A, CASE_A); + ABSL_TEST_QCHECK_LE(CASE_A, CASE_B); + ABSL_TEST_QCHECK_GT(CASE_B, CASE_A); + ABSL_TEST_QCHECK_LT(CASE_A, CASE_B); +} + +TEST(CHECKTest, TestQCHECKPlacementsInCompoundStatements) { + // check placement inside if/else clauses + if (true) ABSL_TEST_QCHECK(true); + + if (false) + ; // NOLINT + else + ABSL_TEST_QCHECK(true); + + if (false) + ; // NOLINT + else + ABSL_TEST_QCHECK(true); + + switch (0) + case 0: + ABSL_TEST_QCHECK(true); + +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L + constexpr auto var = [](int i) { + ABSL_TEST_QCHECK(i > 0); // NOLINT + return i + 1; + }(global_var); + (void)var; + +#if defined(__GNUC__) + int var2 = (({ ABSL_TEST_CHECK_LE(1, 2); }), global_var < 10) ? 1 : 0; + (void)var2; +#endif // __GNUC__ +#endif // ABSL_INTERNAL_CPLUSPLUS_LANG +} + +class ComparableType { + public: + explicit ComparableType(int v) : v_(v) {} + + void MethodWithCheck(int i) { + ABSL_TEST_CHECK_EQ(*this, i); + ABSL_TEST_CHECK_EQ(i, *this); + } + + int Get() const { return v_; } + + private: + friend bool operator==(const ComparableType& lhs, const ComparableType& rhs) { + return lhs.v_ == rhs.v_; + } + friend bool operator!=(const ComparableType& lhs, const ComparableType& rhs) { + return lhs.v_ != rhs.v_; + } + friend bool operator<(const ComparableType& lhs, const ComparableType& rhs) { + return lhs.v_ < rhs.v_; + } + friend bool operator<=(const ComparableType& lhs, const ComparableType& rhs) { + return lhs.v_ <= rhs.v_; + } + friend bool operator>(const ComparableType& lhs, const ComparableType& rhs) { + return lhs.v_ > rhs.v_; + } + friend bool operator>=(const ComparableType& lhs, const ComparableType& rhs) { + return lhs.v_ >= rhs.v_; + } + friend bool operator==(const ComparableType& lhs, int rhs) { + return lhs.v_ == rhs; + } + friend bool operator==(int lhs, const ComparableType& rhs) { + return lhs == rhs.v_; + } + + friend std::ostream& operator<<(std::ostream& out, const ComparableType& v) { + return out << "ComparableType{" << v.Get() << "}"; + } + + int v_; +}; + +TEST(CHECKTest, TestUserDefinedCompOp) { + ABSL_TEST_CHECK_EQ(ComparableType{0}, ComparableType{0}); + ABSL_TEST_CHECK_NE(ComparableType{1}, ComparableType{2}); + ABSL_TEST_CHECK_LT(ComparableType{1}, ComparableType{2}); + ABSL_TEST_CHECK_LE(ComparableType{1}, ComparableType{2}); + ABSL_TEST_CHECK_GT(ComparableType{2}, ComparableType{1}); + ABSL_TEST_CHECK_GE(ComparableType{2}, ComparableType{2}); +} + +TEST(CHECKTest, TestCheckInMethod) { + ComparableType v{1}; + v.MethodWithCheck(1); +} + +TEST(CHECKDeathTest, TestUserDefinedStreaming) { + ComparableType v1{1}; + ComparableType v2{2}; + + EXPECT_DEATH( + ABSL_TEST_CHECK_EQ(v1, v2), + HasSubstr( + "Check failed: v1 == v2 (ComparableType{1} vs. ComparableType{2})")); +} + +} // namespace absl_log_internal + +// NOLINTEND(misc-definitions-in-headers) + +#endif // ABSL_LOG_CHECK_TEST_IMPL_H_ diff --git a/absl/log/internal/BUILD.bazel b/absl/log/internal/BUILD.bazel index 19243a58..08276887 100644 --- a/absl/log/internal/BUILD.bazel +++ b/absl/log/internal/BUILD.bazel @@ -28,6 +28,20 @@ package(default_visibility = [ licenses(["notice"]) cc_library( + name = "check_impl", + hdrs = ["check_impl.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":check_op", + ":conditions", + ":log_message", + ":strip", + "//absl/base:core_headers", + ], +) + +cc_library( name = "check_op", srcs = ["check_op.cc"], hdrs = ["check_op.h"], @@ -91,6 +105,7 @@ cc_library( copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ + ":append_truncated", ":config", ":globals", "//absl/base:config", @@ -123,6 +138,18 @@ cc_library( ) cc_library( + name = "log_impl", + hdrs = ["log_impl.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":conditions", + ":log_message", + ":strip", + ], +) + +cc_library( name = "log_message", srcs = ["log_message.cc"], hdrs = ["log_message.h"], @@ -132,11 +159,12 @@ cc_library( "//absl/log:__pkg__", ], deps = [ - ":config", + ":append_truncated", ":format", ":globals", ":log_sink_set", ":nullguard", + ":proto", "//absl/base", "//absl/base:config", "//absl/base:core_headers", @@ -152,13 +180,24 @@ cc_library( "//absl/log:log_sink_registry", "//absl/memory", "//absl/strings", - "//absl/strings:str_format", "//absl/time", "//absl/types:span", ], ) cc_library( + name = "append_truncated", + hdrs = ["append_truncated.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:config", + "//absl/strings", + "//absl/types:span", + ], +) + +cc_library( name = "log_sink_set", srcs = ["log_sink_set.cc"], hdrs = ["log_sink_set.h"], @@ -221,6 +260,18 @@ cc_library( ) cc_library( + name = "structured", + hdrs = ["structured.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log_message", + "//absl/base:config", + "//absl/strings", + ], +) + +cc_library( name = "test_actions", testonly = True, srcs = ["test_actions.cc"], @@ -228,13 +279,17 @@ cc_library( copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ - ":config", "//absl/base:config", + "//absl/base:core_headers", "//absl/base:log_severity", "//absl/log:log_entry", "//absl/strings", "//absl/time", - ], + ] + select({ + "//absl:msvc_compiler": [], + "//conditions:default": [ + ], + }), ) cc_library( @@ -262,15 +317,19 @@ cc_library( copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ - ":config", ":test_helpers", + "@com_google_googletest//:gtest", "//absl/base:config", + "//absl/base:core_headers", "//absl/base:log_severity", "//absl/log:log_entry", "//absl/strings", "//absl/time", - "@com_google_googletest//:gtest", - ], + ] + select({ + "//absl:msvc_compiler": [], + "//conditions:default": [ + ], + }), ) cc_library( @@ -281,6 +340,21 @@ cc_library( deps = ["//absl/base:config"], ) +cc_library( + name = "proto", + srcs = ["proto.cc"], + hdrs = ["proto.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/strings", + "//absl/types:span", + ], +) + # Test targets cc_test( name = "stderr_log_sink_test", diff --git a/absl/log/internal/append_truncated.h b/absl/log/internal/append_truncated.h new file mode 100644 index 00000000..f0e7912c --- /dev/null +++ b/absl/log/internal/append_truncated.h @@ -0,0 +1,47 @@ +// Copyright 2022 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_LOG_INTERNAL_APPEND_TRUNCATED_H_ +#define ABSL_LOG_INTERNAL_APPEND_TRUNCATED_H_ + +#include <cstddef> +#include <cstring> + +#include "absl/base/config.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { +// Copies into `dst` as many bytes of `src` as will fit, then truncates the +// copied bytes from the front of `dst` and returns the number of bytes written. +inline size_t AppendTruncated(absl::string_view src, absl::Span<char> &dst) { + if (src.size() > dst.size()) src = src.substr(0, dst.size()); + memcpy(dst.data(), src.data(), src.size()); + dst.remove_prefix(src.size()); + return src.size(); +} +// Likewise, but `n` copies of `c`. +inline size_t AppendTruncated(char c, size_t n, absl::Span<char> &dst) { + if (n > dst.size()) n = dst.size(); + memset(dst.data(), c, n); + dst.remove_prefix(n); + return n; +} +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_APPEND_TRUNCATED_H_ diff --git a/absl/log/internal/check_impl.h b/absl/log/internal/check_impl.h new file mode 100644 index 00000000..c9c28e3e --- /dev/null +++ b/absl/log/internal/check_impl.h @@ -0,0 +1,150 @@ +// Copyright 2022 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_LOG_INTERNAL_CHECK_IMPL_H_ +#define ABSL_LOG_INTERNAL_CHECK_IMPL_H_ + +#include "absl/base/optimization.h" +#include "absl/log/internal/check_op.h" +#include "absl/log/internal/conditions.h" +#include "absl/log/internal/log_message.h" +#include "absl/log/internal/strip.h" + +// CHECK +#define ABSL_CHECK_IMPL(condition, condition_text) \ + ABSL_LOG_INTERNAL_CONDITION_FATAL(STATELESS, \ + ABSL_PREDICT_FALSE(!(condition))) \ + ABSL_LOG_INTERNAL_CHECK(condition_text).InternalStream() + +#define ABSL_QCHECK_IMPL(condition, condition_text) \ + ABSL_LOG_INTERNAL_CONDITION_QFATAL(STATELESS, \ + ABSL_PREDICT_FALSE(!(condition))) \ + ABSL_LOG_INTERNAL_QCHECK(condition_text).InternalStream() + +#define ABSL_PCHECK_IMPL(condition, condition_text) \ + ABSL_CHECK_IMPL(condition, condition_text).WithPerror() + +#ifndef NDEBUG +#define ABSL_DCHECK_IMPL(condition, condition_text) \ + ABSL_CHECK_IMPL(condition, condition_text) +#else +#define ABSL_DCHECK_IMPL(condition, condition_text) \ + ABSL_CHECK_IMPL(true || (condition), "true") +#endif + +// CHECK_EQ +#define ABSL_CHECK_EQ_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_CHECK_OP(Check_EQ, ==, val1, val1_text, val2, val2_text) +#define ABSL_CHECK_NE_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_CHECK_OP(Check_NE, !=, val1, val1_text, val2, val2_text) +#define ABSL_CHECK_LE_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_CHECK_OP(Check_LE, <=, val1, val1_text, val2, val2_text) +#define ABSL_CHECK_LT_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_CHECK_OP(Check_LT, <, val1, val1_text, val2, val2_text) +#define ABSL_CHECK_GE_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_CHECK_OP(Check_GE, >=, val1, val1_text, val2, val2_text) +#define ABSL_CHECK_GT_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_CHECK_OP(Check_GT, >, val1, val1_text, val2, val2_text) +#define ABSL_QCHECK_EQ_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_QCHECK_OP(Check_EQ, ==, val1, val1_text, val2, val2_text) +#define ABSL_QCHECK_NE_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_QCHECK_OP(Check_NE, !=, val1, val1_text, val2, val2_text) +#define ABSL_QCHECK_LE_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_QCHECK_OP(Check_LE, <=, val1, val1_text, val2, val2_text) +#define ABSL_QCHECK_LT_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_QCHECK_OP(Check_LT, <, val1, val1_text, val2, val2_text) +#define ABSL_QCHECK_GE_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_QCHECK_OP(Check_GE, >=, val1, val1_text, val2, val2_text) +#define ABSL_QCHECK_GT_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_QCHECK_OP(Check_GT, >, val1, val1_text, val2, val2_text) +#ifndef NDEBUG +#define ABSL_DCHECK_EQ_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_CHECK_EQ_IMPL(val1, val1_text, val2, val2_text) +#define ABSL_DCHECK_NE_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_CHECK_NE_IMPL(val1, val1_text, val2, val2_text) +#define ABSL_DCHECK_LE_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_CHECK_LE_IMPL(val1, val1_text, val2, val2_text) +#define ABSL_DCHECK_LT_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_CHECK_LT_IMPL(val1, val1_text, val2, val2_text) +#define ABSL_DCHECK_GE_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_CHECK_GE_IMPL(val1, val1_text, val2, val2_text) +#define ABSL_DCHECK_GT_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_CHECK_GT_IMPL(val1, val1_text, val2, val2_text) +#else // ndef NDEBUG +#define ABSL_DCHECK_EQ_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) +#define ABSL_DCHECK_NE_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) +#define ABSL_DCHECK_LE_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) +#define ABSL_DCHECK_LT_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) +#define ABSL_DCHECK_GE_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) +#define ABSL_DCHECK_GT_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) +#endif // def NDEBUG + +// CHECK_OK +#define ABSL_CHECK_OK_IMPL(status, status_text) \ + ABSL_LOG_INTERNAL_CHECK_OK(status, status_text) +#define ABSL_QCHECK_OK_IMPL(status, status_text) \ + ABSL_LOG_INTERNAL_QCHECK_OK(status, status_text) +#ifndef NDEBUG +#define ABSL_DCHECK_OK_IMPL(status, status_text) \ + ABSL_LOG_INTERNAL_CHECK_OK(status, status_text) +#else +#define ABSL_DCHECK_OK_IMPL(status, status_text) \ + ABSL_LOG_INTERNAL_DCHECK_NOP(status, nullptr) +#endif + +// CHECK_STREQ +#define ABSL_CHECK_STREQ_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_LOG_INTERNAL_CHECK_STROP(strcmp, ==, true, s1, s1_text, s2, s2_text) +#define ABSL_CHECK_STRNE_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_LOG_INTERNAL_CHECK_STROP(strcmp, !=, false, s1, s1_text, s2, s2_text) +#define ABSL_CHECK_STRCASEEQ_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_LOG_INTERNAL_CHECK_STROP(strcasecmp, ==, true, s1, s1_text, s2, s2_text) +#define ABSL_CHECK_STRCASENE_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_LOG_INTERNAL_CHECK_STROP(strcasecmp, !=, false, s1, s1_text, s2, s2_text) +#define ABSL_QCHECK_STREQ_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_LOG_INTERNAL_QCHECK_STROP(strcmp, ==, true, s1, s1_text, s2, s2_text) +#define ABSL_QCHECK_STRNE_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_LOG_INTERNAL_QCHECK_STROP(strcmp, !=, false, s1, s1_text, s2, s2_text) +#define ABSL_QCHECK_STRCASEEQ_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_LOG_INTERNAL_QCHECK_STROP(strcasecmp, ==, true, s1, s1_text, s2, s2_text) +#define ABSL_QCHECK_STRCASENE_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_LOG_INTERNAL_QCHECK_STROP(strcasecmp, !=, false, s1, s1_text, s2, \ + s2_text) +#ifndef NDEBUG +#define ABSL_DCHECK_STREQ_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_CHECK_STREQ_IMPL(s1, s1_text, s2, s2_text) +#define ABSL_DCHECK_STRCASEEQ_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_CHECK_STRCASEEQ_IMPL(s1, s1_text, s2, s2_text) +#define ABSL_DCHECK_STRNE_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_CHECK_STRNE_IMPL(s1, s1_text, s2, s2_text) +#define ABSL_DCHECK_STRCASENE_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_CHECK_STRCASENE_IMPL(s1, s1_text, s2, s2_text) +#else // ndef NDEBUG +#define ABSL_DCHECK_STREQ_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2) +#define ABSL_DCHECK_STRCASEEQ_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2) +#define ABSL_DCHECK_STRNE_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2) +#define ABSL_DCHECK_STRCASENE_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2) +#endif // def NDEBUG + +#endif // ABSL_LOG_INTERNAL_CHECK_IMPL_H_ diff --git a/absl/log/internal/check_op.h b/absl/log/internal/check_op.h index 559e5afc..4907b89b 100644 --- a/absl/log/internal/check_op.h +++ b/absl/log/internal/check_op.h @@ -57,35 +57,40 @@ ::absl::log_internal::NullStream().InternalStream() #endif -#define ABSL_LOG_INTERNAL_CHECK_OP(name, op, val1, val2) \ +#define ABSL_LOG_INTERNAL_CHECK_OP(name, op, val1, val1_text, val2, val2_text) \ while ( \ ::std::string* absl_log_internal_check_op_result ABSL_ATTRIBUTE_UNUSED = \ ::absl::log_internal::name##Impl( \ ::absl::log_internal::GetReferenceableValue(val1), \ ::absl::log_internal::GetReferenceableValue(val2), \ - ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(#val1 " " #op \ - " " #val2))) \ + ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(val1_text \ + " " #op " " val2_text))) \ ABSL_LOG_INTERNAL_CHECK(*absl_log_internal_check_op_result).InternalStream() -#define ABSL_LOG_INTERNAL_QCHECK_OP(name, op, val1, val2) \ - while (::std::string* absl_log_internal_qcheck_op_result = \ - ::absl::log_internal::name##Impl( \ - ::absl::log_internal::GetReferenceableValue(val1), \ - ::absl::log_internal::GetReferenceableValue(val2), \ - ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(#val1 " " #op \ - " " #val2))) \ +#define ABSL_LOG_INTERNAL_QCHECK_OP(name, op, val1, val1_text, val2, \ + val2_text) \ + while (::std::string* absl_log_internal_qcheck_op_result = \ + ::absl::log_internal::name##Impl( \ + ::absl::log_internal::GetReferenceableValue(val1), \ + ::absl::log_internal::GetReferenceableValue(val2), \ + ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL( \ + val1_text " " #op " " val2_text))) \ ABSL_LOG_INTERNAL_QCHECK(*absl_log_internal_qcheck_op_result).InternalStream() -#define ABSL_LOG_INTERNAL_CHECK_STROP(func, op, expected, s1, s2) \ +#define ABSL_LOG_INTERNAL_CHECK_STROP(func, op, expected, s1, s1_text, s2, \ + s2_text) \ while (::std::string* absl_log_internal_check_strop_result = \ ::absl::log_internal::Check##func##expected##Impl( \ (s1), (s2), \ - ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(#s1 " " #op " " #s2))) \ + ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(s1_text " " #op \ + " " s2_text))) \ ABSL_LOG_INTERNAL_CHECK(*absl_log_internal_check_strop_result) \ .InternalStream() -#define ABSL_LOG_INTERNAL_QCHECK_STROP(func, op, expected, s1, s2) \ +#define ABSL_LOG_INTERNAL_QCHECK_STROP(func, op, expected, s1, s1_text, s2, \ + s2_text) \ while (::std::string* absl_log_internal_qcheck_strop_result = \ ::absl::log_internal::Check##func##expected##Impl( \ (s1), (s2), \ - ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(#s1 " " #op " " #s2))) \ + ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(s1_text " " #op \ + " " s2_text))) \ ABSL_LOG_INTERNAL_QCHECK(*absl_log_internal_qcheck_strop_result) \ .InternalStream() // This one is tricky: @@ -108,33 +113,35 @@ // * As usual, no braces so we can stream into the expansion with `operator<<`. // * Also as usual, it must expand to a single (partial) statement with no // ambiguous-else problems. -#define ABSL_LOG_INTERNAL_CHECK_OK(val) \ - for (::std::pair<const ::absl::Status*, ::std::string*> \ - absl_log_internal_check_ok_goo; \ - absl_log_internal_check_ok_goo.first = \ - ::absl::log_internal::AsStatus(val), \ - absl_log_internal_check_ok_goo.second = \ - ABSL_PREDICT_TRUE(absl_log_internal_check_ok_goo.first->ok()) \ - ? nullptr \ - : ::absl::status_internal::MakeCheckFailString( \ - absl_log_internal_check_ok_goo.first, \ - ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(#val " is OK")), \ - !ABSL_PREDICT_TRUE(absl_log_internal_check_ok_goo.first->ok());) \ - ABSL_LOG_INTERNAL_CHECK(*absl_log_internal_check_ok_goo.second) \ +#define ABSL_LOG_INTERNAL_CHECK_OK(val, val_text) \ + for (::std::pair<const ::absl::Status*, ::std::string*> \ + absl_log_internal_check_ok_goo; \ + absl_log_internal_check_ok_goo.first = \ + ::absl::log_internal::AsStatus(val), \ + absl_log_internal_check_ok_goo.second = \ + ABSL_PREDICT_TRUE(absl_log_internal_check_ok_goo.first->ok()) \ + ? nullptr \ + : ::absl::status_internal::MakeCheckFailString( \ + absl_log_internal_check_ok_goo.first, \ + ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(val_text \ + " is OK")), \ + !ABSL_PREDICT_TRUE(absl_log_internal_check_ok_goo.first->ok());) \ + ABSL_LOG_INTERNAL_CHECK(*absl_log_internal_check_ok_goo.second) \ .InternalStream() -#define ABSL_LOG_INTERNAL_QCHECK_OK(val) \ - for (::std::pair<const ::absl::Status*, ::std::string*> \ - absl_log_internal_check_ok_goo; \ - absl_log_internal_check_ok_goo.first = \ - ::absl::log_internal::AsStatus(val), \ - absl_log_internal_check_ok_goo.second = \ - ABSL_PREDICT_TRUE(absl_log_internal_check_ok_goo.first->ok()) \ - ? nullptr \ - : ::absl::status_internal::MakeCheckFailString( \ - absl_log_internal_check_ok_goo.first, \ - ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(#val " is OK")), \ - !ABSL_PREDICT_TRUE(absl_log_internal_check_ok_goo.first->ok());) \ - ABSL_LOG_INTERNAL_QCHECK(*absl_log_internal_check_ok_goo.second) \ +#define ABSL_LOG_INTERNAL_QCHECK_OK(val, val_text) \ + for (::std::pair<const ::absl::Status*, ::std::string*> \ + absl_log_internal_check_ok_goo; \ + absl_log_internal_check_ok_goo.first = \ + ::absl::log_internal::AsStatus(val), \ + absl_log_internal_check_ok_goo.second = \ + ABSL_PREDICT_TRUE(absl_log_internal_check_ok_goo.first->ok()) \ + ? nullptr \ + : ::absl::status_internal::MakeCheckFailString( \ + absl_log_internal_check_ok_goo.first, \ + ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(val_text \ + " is OK")), \ + !ABSL_PREDICT_TRUE(absl_log_internal_check_ok_goo.first->ok());) \ + ABSL_LOG_INTERNAL_QCHECK(*absl_log_internal_check_ok_goo.second) \ .InternalStream() namespace absl { diff --git a/absl/log/internal/log_format.cc b/absl/log/internal/log_format.cc index 5b280a2d..b8cd5ac4 100644 --- a/absl/log/internal/log_format.cc +++ b/absl/log/internal/log_format.cc @@ -32,6 +32,7 @@ #include "absl/base/config.h" #include "absl/base/log_severity.h" #include "absl/base/optimization.h" +#include "absl/log/internal/append_truncated.h" #include "absl/log/internal/config.h" #include "absl/log/internal/globals.h" #include "absl/strings/numbers.h" @@ -143,15 +144,6 @@ size_t FormatBoundedFields(absl::LogSeverity severity, absl::Time timestamp, return bytes_formatted; } -// Copies into `dst` as many bytes of `src` as will fit, then advances `dst` -// past the copied bytes and returns the number of bytes written. -size_t AppendTruncated(absl::string_view src, absl::Span<char>& dst) { - if (src.size() > dst.size()) src = src.substr(0, dst.size()); - memcpy(dst.data(), src.data(), src.size()); - dst.remove_prefix(src.size()); - return src.size(); -} - size_t FormatLineNumber(int line, absl::Span<char>& buf) { constexpr size_t kLineFieldMaxLen = sizeof(":] ") + (1 + std::numeric_limits<int>::digits10 + 1) - sizeof(""); @@ -177,13 +169,13 @@ std::string FormatLogMessage(absl::LogSeverity severity, absl::CivilSecond civil_second, absl::Duration subsecond, log_internal::Tid tid, absl::string_view basename, int line, - absl::string_view message) { + PrefixFormat format, absl::string_view message) { return absl::StrFormat( - "%c%02d%02d %02d:%02d:%02d.%06d %7d %s:%d] %s", + "%c%02d%02d %02d:%02d:%02d.%06d %7d %s:%d] %s%s", absl::LogSeverityName(severity)[0], civil_second.month(), civil_second.day(), civil_second.hour(), civil_second.minute(), civil_second.second(), absl::ToInt64Microseconds(subsecond), tid, - basename, line, message); + basename, line, format == PrefixFormat::kRaw ? "RAW: " : "", message); } // This method is fairly hot, and the library always passes a huge `buf`, so we @@ -197,10 +189,12 @@ std::string FormatLogMessage(absl::LogSeverity severity, // 3. line number and bracket size_t FormatLogPrefix(absl::LogSeverity severity, absl::Time timestamp, log_internal::Tid tid, absl::string_view basename, - int line, absl::Span<char>& buf) { + int line, PrefixFormat format, absl::Span<char>& buf) { auto prefix_size = FormatBoundedFields(severity, timestamp, tid, buf); - prefix_size += AppendTruncated(basename, buf); + prefix_size += log_internal::AppendTruncated(basename, buf); prefix_size += FormatLineNumber(line, buf); + if (format == PrefixFormat::kRaw) + prefix_size += log_internal::AppendTruncated("RAW: ", buf); return prefix_size; } diff --git a/absl/log/internal/log_format.h b/absl/log/internal/log_format.h index a016328f..95a45edf 100644 --- a/absl/log/internal/log_format.h +++ b/absl/log/internal/log_format.h @@ -38,12 +38,17 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace log_internal { +enum class PrefixFormat { + kNotRaw, + kRaw, +}; + // Formats log message based on provided data. std::string FormatLogMessage(absl::LogSeverity severity, absl::CivilSecond civil_second, absl::Duration subsecond, log_internal::Tid tid, absl::string_view basename, int line, - absl::string_view message); + PrefixFormat format, absl::string_view message); // Formats various entry metadata into a text string meant for use as a // prefix on a log message string. Writes into `buf`, advances `buf` to point @@ -64,7 +69,7 @@ std::string FormatLogMessage(absl::LogSeverity severity, // see a thread ID. size_t FormatLogPrefix(absl::LogSeverity severity, absl::Time timestamp, log_internal::Tid tid, absl::string_view basename, - int line, absl::Span<char>& buf); + int line, PrefixFormat format, absl::Span<char>& buf); } // namespace log_internal ABSL_NAMESPACE_END diff --git a/absl/log/internal/log_impl.h b/absl/log/internal/log_impl.h new file mode 100644 index 00000000..82b5ed84 --- /dev/null +++ b/absl/log/internal/log_impl.h @@ -0,0 +1,212 @@ +// Copyright 2022 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_LOG_INTERNAL_LOG_IMPL_H_ +#define ABSL_LOG_INTERNAL_LOG_IMPL_H_ + +#include "absl/log/internal/conditions.h" +#include "absl/log/internal/log_message.h" +#include "absl/log/internal/strip.h" + +// ABSL_LOG() +#define ABSL_LOG_IMPL(severity) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, true) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +// ABSL_PLOG() +#define ABSL_PLOG_IMPL(severity) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, true) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ + .WithPerror() + +// ABSL_DLOG() +#ifndef NDEBUG +#define ABSL_DLOG_IMPL(severity) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, true) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() +#else +#define ABSL_DLOG_IMPL(severity) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, false) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() +#endif + +#define ABSL_LOG_IF_IMPL(severity, condition) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, condition) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() +#define ABSL_PLOG_IF_IMPL(severity, condition) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, condition) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ + .WithPerror() + +#ifndef NDEBUG +#define ABSL_DLOG_IF_IMPL(severity, condition) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, condition) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() +#else +#define ABSL_DLOG_IF_IMPL(severity, condition) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, false && (condition)) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() +#endif + +// ABSL_LOG_EVERY_N +#define ABSL_LOG_EVERY_N_IMPL(severity, n) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryN, n) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +// ABSL_LOG_FIRST_N +#define ABSL_LOG_FIRST_N_IMPL(severity, n) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(FirstN, n) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +// ABSL_LOG_EVERY_POW_2 +#define ABSL_LOG_EVERY_POW_2_IMPL(severity) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryPow2) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +// ABSL_LOG_EVERY_N_SEC +#define ABSL_LOG_EVERY_N_SEC_IMPL(severity, n_seconds) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryNSec, n_seconds) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_PLOG_EVERY_N_IMPL(severity, n) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryN, n) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ + .WithPerror() + +#define ABSL_PLOG_FIRST_N_IMPL(severity, n) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(FirstN, n) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ + .WithPerror() + +#define ABSL_PLOG_EVERY_POW_2_IMPL(severity) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryPow2) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ + .WithPerror() + +#define ABSL_PLOG_EVERY_N_SEC_IMPL(severity, n_seconds) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryNSec, n_seconds) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ + .WithPerror() + +#ifndef NDEBUG +#define ABSL_DLOG_EVERY_N_IMPL(severity, n) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ + (EveryN, n) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_DLOG_FIRST_N_IMPL(severity, n) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ + (FirstN, n) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_DLOG_EVERY_POW_2_IMPL(severity) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ + (EveryPow2) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_DLOG_EVERY_N_SEC_IMPL(severity, n_seconds) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ + (EveryNSec, n_seconds) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#else // def NDEBUG +#define ABSL_DLOG_EVERY_N_IMPL(severity, n) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ + (EveryN, n) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_DLOG_FIRST_N_IMPL(severity, n) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ + (FirstN, n) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_DLOG_EVERY_POW_2_IMPL(severity) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ + (EveryPow2) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_DLOG_EVERY_N_SEC_IMPL(severity, n_seconds) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ + (EveryNSec, n_seconds) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() +#endif // def NDEBUG + +#define ABSL_LOG_IF_EVERY_N_IMPL(severity, condition, n) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryN, n) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_LOG_IF_FIRST_N_IMPL(severity, condition, n) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(FirstN, n) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_LOG_IF_EVERY_POW_2_IMPL(severity, condition) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryPow2) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_LOG_IF_EVERY_N_SEC_IMPL(severity, condition, n_seconds) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryNSec, \ + n_seconds) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_PLOG_IF_EVERY_N_IMPL(severity, condition, n) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryN, n) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ + .WithPerror() + +#define ABSL_PLOG_IF_FIRST_N_IMPL(severity, condition, n) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(FirstN, n) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ + .WithPerror() + +#define ABSL_PLOG_IF_EVERY_POW_2_IMPL(severity, condition) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryPow2) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ + .WithPerror() + +#define ABSL_PLOG_IF_EVERY_N_SEC_IMPL(severity, condition, n_seconds) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryNSec, \ + n_seconds) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ + .WithPerror() + +#ifndef NDEBUG +#define ABSL_DLOG_IF_EVERY_N_IMPL(severity, condition, n) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryN, n) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_DLOG_IF_FIRST_N_IMPL(severity, condition, n) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(FirstN, n) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_DLOG_IF_EVERY_POW_2_IMPL(severity, condition) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryPow2) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_DLOG_IF_EVERY_N_SEC_IMPL(severity, condition, n_seconds) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryNSec, \ + n_seconds) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#else // def NDEBUG +#define ABSL_DLOG_IF_EVERY_N_IMPL(severity, condition, n) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, false && (condition))( \ + EveryN, n) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_DLOG_IF_FIRST_N_IMPL(severity, condition, n) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, false && (condition))( \ + FirstN, n) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_DLOG_IF_EVERY_POW_2_IMPL(severity, condition) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, false && (condition))( \ + EveryPow2) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_DLOG_IF_EVERY_N_SEC_IMPL(severity, condition, n_seconds) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, false && (condition))( \ + EveryNSec, n_seconds) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() +#endif // def NDEBUG + +#endif // ABSL_LOG_INTERNAL_LOG_IMPL_H_ diff --git a/absl/log/internal/log_message.cc b/absl/log/internal/log_message.cc index 82833af0..a8080610 100644 --- a/absl/log/internal/log_message.cc +++ b/absl/log/internal/log_message.cc @@ -41,15 +41,15 @@ #include "absl/container/inlined_vector.h" #include "absl/debugging/internal/examine_stack.h" #include "absl/log/globals.h" -#include "absl/log/internal/config.h" +#include "absl/log/internal/append_truncated.h" #include "absl/log/internal/globals.h" #include "absl/log/internal/log_format.h" #include "absl/log/internal/log_sink_set.h" +#include "absl/log/internal/proto.h" #include "absl/log/log_entry.h" #include "absl/log/log_sink.h" #include "absl/log/log_sink_registry.h" #include "absl/memory/memory.h" -#include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "absl/time/clock.h" #include "absl/time/time.h" @@ -65,13 +65,39 @@ ABSL_NAMESPACE_BEGIN namespace log_internal { namespace { -// Copies into `dst` as many bytes of `src` as will fit, then truncates the -// copied bytes from the front of `dst` and returns the number of bytes written. -size_t AppendTruncated(absl::string_view src, absl::Span<char>* dst) { - if (src.size() > dst->size()) src = src.substr(0, dst->size()); - memcpy(dst->data(), src.data(), src.size()); - dst->remove_prefix(src.size()); - return src.size(); +// message `logging.proto.Event` +enum EventTag : uint8_t { + kValue = 7, +}; + +// message `logging.proto.Value` +enum ValueTag : uint8_t { + kString = 1, + kStringLiteral = 6, +}; + +// Decodes a `logging.proto.Value` from `buf` and writes a string representation +// into `dst`. The string representation will be truncated if `dst` is not +// large enough to hold it. Returns false if `dst` has size zero or one (i.e. +// sufficient only for a nul-terminator) and no decoded data could be written. +// This function may or may not write a nul-terminator into `dst`, and it may or +// may not truncate the data it writes in order to do make space for that nul +// terminator. In any case, `dst` will be advanced to point at the byte where +// subsequent writes should begin. +bool PrintValue(absl::Span<char>& dst, absl::Span<const char> buf) { + if (dst.size() <= 1) return false; + ProtoField field; + while (field.DecodeFrom(&buf)) { + switch (field.tag()) { + case ValueTag::kString: + case ValueTag::kStringLiteral: + if (field.type() == WireType::kLengthDelimited) + if (log_internal::AppendTruncated(field.string_value(), dst) < + field.string_value().size()) + return false; + } + } + return true; } absl::string_view Basename(absl::string_view filepath) { @@ -93,87 +119,6 @@ void WriteToStream(const char* data, void* os) { } } // namespace -// A write-only `std::streambuf` that writes into an `absl::Span<char>`. -// -// This class is responsible for writing a metadata prefix just before the first -// data are streamed in. The metadata are subject to change (cf. -// `LogMessage::AtLocation`) until then, so we wait as long as possible. -// -// This class is also responsible for reserving space for a trailing newline -// so that one can be added later by `Finalize` no matter how many data are -// streamed in. -class LogEntryStreambuf final : public std::streambuf { - public: - explicit LogEntryStreambuf(absl::Span<char> buf, const absl::LogEntry& entry) - : buf_(buf), entry_(entry), prefix_len_(0), finalized_(false) { - // To detect when data are first written, we leave the put area null, - // override `overflow`, and check ourselves in `xsputn`. - } - - LogEntryStreambuf(LogEntryStreambuf&&) = delete; - LogEntryStreambuf& operator=(LogEntryStreambuf&&) = delete; - - absl::Span<const char> Finalize() { - assert(!finalized_); - // If no data were ever streamed in, this is where we must write the prefix. - if (pbase() == nullptr) Initialize(); - // Here we reclaim the two bytes we reserved. - ptrdiff_t idx = pptr() - pbase(); - setp(buf_.data(), buf_.data() + buf_.size()); - pbump(static_cast<int>(idx)); - sputc('\n'); - sputc('\0'); - finalized_ = true; - return absl::Span<const char>(pbase(), - static_cast<size_t>(pptr() - pbase())); - } - size_t prefix_len() const { return prefix_len_; } - - protected: - std::streamsize xsputn(const char* s, std::streamsize n) override { - if (n < 0) return 0; - if (pbase() == nullptr) Initialize(); - return static_cast<std::streamsize>( - Append(absl::string_view(s, static_cast<size_t>(n)))); - } - - int overflow(int ch = EOF) override { - if (pbase() == nullptr) Initialize(); - if (ch == EOF) return 0; - if (pptr() == epptr()) return EOF; - *pptr() = static_cast<char>(ch); - pbump(1); - return 1; - } - - private: - void Initialize() { - // Here we reserve two bytes in our buffer to guarantee `Finalize` space to - // add a trailing "\n\0". - assert(buf_.size() >= 2); - setp(buf_.data(), buf_.data() + buf_.size() - 2); - if (entry_.prefix()) { - absl::Span<char> remaining = buf_; - prefix_len_ = log_internal::FormatLogPrefix( - entry_.log_severity(), entry_.timestamp(), entry_.tid(), - entry_.source_basename(), entry_.source_line(), remaining); - pbump(static_cast<int>(prefix_len_)); - } - } - - size_t Append(absl::string_view data) { - absl::Span<char> remaining(pptr(), static_cast<size_t>(epptr() - pptr())); - const size_t written = AppendTruncated(data, &remaining); - pbump(static_cast<int>(written)); - return written; - } - - const absl::Span<char> buf_; - const absl::LogEntry& entry_; - size_t prefix_len_; - bool finalized_; -}; - struct LogMessage::LogMessageData final { LogMessageData(const char* file, int line, absl::LogSeverity severity, absl::Time timestamp); @@ -196,18 +141,31 @@ struct LogMessage::LogMessageData final { // non-sink targets (e.g. stderr, log files). bool extra_sinks_only; + std::ostream manipulated; // ostream with IO manipulators applied + + // A `logging.proto.Event` proto message is built into `encoded_buf`. + std::array<char, kLogMessageBufferSize> encoded_buf; + // `encoded_remaining` is the suffix of `encoded_buf` that has not been filled + // yet. If a datum to be encoded does not fit into `encoded_remaining` and + // cannot be truncated to fit, the size of `encoded_remaining` will be zeroed + // to prevent encoding of any further data. Note that in this case its data() + // pointer will not point past the end of `encoded_buf`. + absl::Span<char> encoded_remaining; + // A formatted string message is built in `string_buf`. std::array<char, kLogMessageBufferSize> string_buf; - // A `std::streambuf` that stores into `string_buf`. - LogEntryStreambuf streambuf_; + void FinalizeEncodingAndFormat(); }; LogMessage::LogMessageData::LogMessageData(const char* file, int line, absl::LogSeverity severity, absl::Time timestamp) : extra_sinks_only(false), - streambuf_(absl::MakeSpan(string_buf), entry) { + manipulated(nullptr), + encoded_remaining(encoded_buf) { + // Legacy defaults for LOG's ostream: + manipulated.setf(std::ios_base::showbase | std::ios_base::boolalpha); entry.full_filename_ = file; entry.base_filename_ = Basename(file); entry.line_ = line; @@ -218,21 +176,57 @@ LogMessage::LogMessageData::LogMessageData(const char* file, int line, entry.tid_ = absl::base_internal::GetCachedTID(); } +void LogMessage::LogMessageData::FinalizeEncodingAndFormat() { + // Note that `encoded_remaining` may have zero size without pointing past the + // end of `encoded_buf`, so the difference between `data()` pointers is used + // to compute the size of `encoded_data`. + absl::Span<const char> encoded_data( + encoded_buf.data(), + static_cast<size_t>(encoded_remaining.data() - encoded_buf.data())); + // `string_remaining` is the suffix of `string_buf` that has not been filled + // yet. + absl::Span<char> string_remaining(string_buf); + // We may need to write a newline and nul-terminator at the end of the decoded + // string data. Rather than worry about whether those should overwrite the + // end of the string (if the buffer is full) or be appended, we avoid writing + // into the last two bytes so we always have space to append. + string_remaining.remove_suffix(2); + entry.prefix_len_ = + entry.prefix() ? log_internal::FormatLogPrefix( + entry.log_severity(), entry.timestamp(), entry.tid(), + entry.source_basename(), entry.source_line(), + log_internal::ThreadIsLoggingToLogSink() + ? PrefixFormat::kRaw + : PrefixFormat::kNotRaw, + string_remaining) + : 0; + // Decode data from `encoded_buf` until we run out of data or we run out of + // `string_remaining`. + ProtoField field; + while (field.DecodeFrom(&encoded_data)) { + switch (field.tag()) { + case EventTag::kValue: + if (field.type() != WireType::kLengthDelimited) continue; + if (PrintValue(string_remaining, field.bytes_value())) continue; + break; + } + break; + } + auto chars_written = + static_cast<size_t>(string_remaining.data() - string_buf.data()); + string_buf[chars_written++] = '\n'; + string_buf[chars_written++] = '\0'; + entry.text_message_with_prefix_and_newline_and_nul_ = + absl::MakeSpan(string_buf).subspan(0, chars_written); +} + LogMessage::LogMessage(const char* file, int line, absl::LogSeverity severity) - : data_( - absl::make_unique<LogMessageData>(file, line, severity, absl::Now())) - , - stream_(&data_->streambuf_) -{ + : data_(absl::make_unique<LogMessageData>(file, line, severity, + absl::Now())) { data_->first_fatal = false; data_->is_perror = false; data_->fail_quietly = false; - // Legacy defaults for LOG's ostream: - stream_.setf(std::ios_base::showbase | std::ios_base::boolalpha); - // `fill('0')` is omitted here because its effects are very different without - // structured logging. Resolution is tracked in b/111310488. - // This logs a backtrace even if the location is subsequently changed using // AtLocation. This quirk, and the behavior when AtLocation is called twice, // are fixable but probably not worth fixing. @@ -350,6 +344,25 @@ void LogMessage::FailQuietly() { _exit(1); } +LogMessage& LogMessage::operator<<(const std::string& v) { + CopyToEncodedBuffer(v, StringType::kNotLiteral); + return *this; +} + +LogMessage& LogMessage::operator<<(absl::string_view v) { + CopyToEncodedBuffer(v, StringType::kNotLiteral); + return *this; +} +LogMessage& LogMessage::operator<<(std::ostream& (*m)(std::ostream& os)) { + OstreamView view(*data_); + data_->manipulated << m; + return *this; +} +LogMessage& LogMessage::operator<<(std::ios_base& (*m)(std::ios_base& os)) { + OstreamView view(*data_); + data_->manipulated << m; + return *this; +} template LogMessage& LogMessage::operator<<(const char& v); template LogMessage& LogMessage::operator<<(const signed char& v); template LogMessage& LogMessage::operator<<(const unsigned char& v); @@ -367,8 +380,6 @@ template LogMessage& LogMessage::operator<<(const void* const& v); template LogMessage& LogMessage::operator<<(const float& v); template LogMessage& LogMessage::operator<<(const double& v); template LogMessage& LogMessage::operator<<(const bool& v); -template LogMessage& LogMessage::operator<<(const std::string& v); -template LogMessage& LogMessage::operator<<(const absl::string_view& v); void LogMessage::Flush() { if (data_->entry.log_severity() < absl::MinLogLevel()) @@ -380,22 +391,67 @@ void LogMessage::Flush() { } // Have we already seen a fatal message? - ABSL_CONST_INIT static std::atomic_flag seen_fatal = ATOMIC_FLAG_INIT; + ABSL_CONST_INIT static std::atomic<bool> seen_fatal(false); if (data_->entry.log_severity() == absl::LogSeverity::kFatal && absl::log_internal::ExitOnDFatal()) { // Exactly one LOG(FATAL) message is responsible for aborting the process, // even if multiple threads LOG(FATAL) concurrently. - data_->first_fatal = !seen_fatal.test_and_set(std::memory_order_relaxed); + bool expected_seen_fatal = false; + if (seen_fatal.compare_exchange_strong(expected_seen_fatal, true, + std::memory_order_relaxed)) { + data_->first_fatal = true; + } } - data_->entry.text_message_with_prefix_and_newline_and_nul_ = - data_->streambuf_.Finalize(); - data_->entry.prefix_len_ = data_->streambuf_.prefix_len(); + data_->FinalizeEncodingAndFormat(); + data_->entry.encoding_ = + absl::string_view(data_->encoded_buf.data(), + static_cast<size_t>(data_->encoded_remaining.data() - + data_->encoded_buf.data())); SendToLog(); } void LogMessage::SetFailQuietly() { data_->fail_quietly = true; } +LogMessage::OstreamView::OstreamView(LogMessageData& message_data) + : data_(message_data), encoded_remaining_copy_(data_.encoded_remaining) { + // This constructor sets the `streambuf` up so that streaming into an attached + // ostream encodes string data in-place. To do that, we write appropriate + // headers into the buffer using a copy of the buffer view so that we can + // decide not to keep them later if nothing is ever streamed in. We don't + // know how much data we'll get, but we can use the size of the remaining + // buffer as an upper bound and fill in the right size once we know it. + message_start_ = + EncodeMessageStart(EventTag::kValue, encoded_remaining_copy_.size(), + &encoded_remaining_copy_); + string_start_ = + EncodeMessageStart(ValueTag::kString, encoded_remaining_copy_.size(), + &encoded_remaining_copy_); + setp(encoded_remaining_copy_.data(), + encoded_remaining_copy_.data() + encoded_remaining_copy_.size()); + data_.manipulated.rdbuf(this); +} + +LogMessage::OstreamView::~OstreamView() { + data_.manipulated.rdbuf(nullptr); + if (!string_start_.data()) { + // The second field header didn't fit. Whether the first one did or not, we + // shouldn't commit `encoded_remaining_copy_`, and we also need to zero the + // size of `data_->encoded_remaining` so that no more data are encoded. + data_.encoded_remaining.remove_suffix(data_.encoded_remaining.size()); + return; + } + const absl::Span<const char> contents(pbase(), + static_cast<size_t>(pptr() - pbase())); + if (contents.empty()) return; + encoded_remaining_copy_.remove_prefix(contents.size()); + EncodeMessageLength(string_start_, &encoded_remaining_copy_); + EncodeMessageLength(message_start_, &encoded_remaining_copy_); + data_.encoded_remaining = encoded_remaining_copy_; +} + +std::ostream& LogMessage::OstreamView::stream() { return data_.manipulated; } + bool LogMessage::IsFatal() const { return data_->entry.log_severity() == absl::LogSeverity::kFatal && absl::log_internal::ExitOnDFatal(); @@ -449,11 +505,61 @@ void LogMessage::LogBacktraceIfNeeded() { if (!absl::log_internal::ShouldLogBacktraceAt(data_->entry.source_basename(), data_->entry.source_line())) return; - stream_ << " (stacktrace:\n"; + OstreamView view(*data_); + view.stream() << " (stacktrace:\n"; debugging_internal::DumpStackTrace( 1, log_internal::MaxFramesInLogStackTrace(), - log_internal::ShouldSymbolizeLogStackTrace(), WriteToStream, &stream_); - stream_ << ") "; + log_internal::ShouldSymbolizeLogStackTrace(), WriteToStream, + &view.stream()); + view.stream() << ") "; +} + +// Encodes into `data_->encoded_remaining` a partial `logging.proto.Event` +// containing the specified string data using a `Value` field appropriate to +// `str_type`. Truncates `str` if necessary, but emits nothing and marks the +// buffer full if even the field headers do not fit. +void LogMessage::CopyToEncodedBuffer(absl::string_view str, + StringType str_type) { + auto encoded_remaining_copy = data_->encoded_remaining; + auto start = EncodeMessageStart( + EventTag::kValue, BufferSizeFor(WireType::kLengthDelimited) + str.size(), + &encoded_remaining_copy); + // If the `logging.proto.Event.value` field header did not fit, + // `EncodeMessageStart` will have zeroed `encoded_remaining_copy`'s size and + // `EncodeStringTruncate` will fail too. + if (EncodeStringTruncate(str_type == StringType::kLiteral + ? ValueTag::kStringLiteral + : ValueTag::kString, + str, &encoded_remaining_copy)) { + // The string may have been truncated, but the field header fit. + EncodeMessageLength(start, &encoded_remaining_copy); + data_->encoded_remaining = encoded_remaining_copy; + } else { + // The field header(s) did not fit; zero `encoded_remaining` so we don't + // write anything else later. + data_->encoded_remaining.remove_suffix(data_->encoded_remaining.size()); + } +} +void LogMessage::CopyToEncodedBuffer(char ch, size_t num, StringType str_type) { + auto encoded_remaining_copy = data_->encoded_remaining; + auto value_start = EncodeMessageStart( + EventTag::kValue, BufferSizeFor(WireType::kLengthDelimited) + num, + &encoded_remaining_copy); + auto str_start = EncodeMessageStart(str_type == StringType::kLiteral + ? ValueTag::kStringLiteral + : ValueTag::kString, + num, &encoded_remaining_copy); + if (str_start.data()) { + // The field headers fit. + log_internal::AppendTruncated(ch, num, encoded_remaining_copy); + EncodeMessageLength(str_start, &encoded_remaining_copy); + EncodeMessageLength(value_start, &encoded_remaining_copy); + data_->encoded_remaining = encoded_remaining_copy; + } else { + // The field header(s) did not fit; zero `encoded_remaining` so we don't + // write anything else later. + data_->encoded_remaining.remove_suffix(data_->encoded_remaining.size()); + } } LogMessageFatal::LogMessageFatal(const char* file, int line) @@ -467,7 +573,7 @@ LogMessageFatal::LogMessageFatal(const char* file, int line, // ABSL_ATTRIBUTE_NORETURN doesn't seem to work on destructors with msvc, so // disable msvc's warning about the d'tor never returning. -#ifdef _MSC_VER +#if defined(_MSC_VER) && !defined(__clang__) #pragma warning(push) #pragma warning(disable : 4722) #endif @@ -475,7 +581,7 @@ LogMessageFatal::~LogMessageFatal() { Flush(); FailWithoutStackTrace(); } -#ifdef _MSC_VER +#if defined(_MSC_VER) && !defined(__clang__) #pragma warning(pop) #endif @@ -493,7 +599,7 @@ LogMessageQuietlyFatal::LogMessageQuietlyFatal(const char* file, int line, // ABSL_ATTRIBUTE_NORETURN doesn't seem to work on destructors with msvc, so // disable msvc's warning about the d'tor never returning. -#ifdef _MSC_VER +#if defined(_MSC_VER) && !defined(__clang__) #pragma warning(push) #pragma warning(disable : 4722) #endif @@ -501,7 +607,7 @@ LogMessageQuietlyFatal::~LogMessageQuietlyFatal() { Flush(); FailQuietly(); } -#ifdef _MSC_VER +#if defined(_MSC_VER) && !defined(__clang__) #pragma warning(pop) #endif diff --git a/absl/log/internal/log_message.h b/absl/log/internal/log_message.h index 37a267c0..3744276b 100644 --- a/absl/log/internal/log_message.h +++ b/absl/log/internal/log_message.h @@ -37,17 +37,16 @@ #include "absl/base/config.h" #include "absl/base/internal/errno_saver.h" #include "absl/base/log_severity.h" -#include "absl/log/internal/config.h" #include "absl/log/internal/nullguard.h" #include "absl/log/log_entry.h" #include "absl/log/log_sink.h" +#include "absl/strings/internal/has_absl_stringify.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace log_internal { - constexpr int kLogMessageBufferSize = 15000; class LogMessage { @@ -130,6 +129,10 @@ class LogMessage { LogMessage& operator<<(bool v) { return operator<< <bool>(v); } // clang-format on + // These overloads are more efficient since no `ostream` is involved. + LogMessage& operator<<(const std::string& v); + LogMessage& operator<<(absl::string_view v); + // Handle stream manipulators e.g. std::endl. LogMessage& operator<<(std::ostream& (*m)(std::ostream& os)); LogMessage& operator<<(std::ios_base& (*m)(std::ios_base& os)); @@ -153,8 +156,17 @@ class LogMessage { template <int SIZE> LogMessage& operator<<(char (&buf)[SIZE]) ABSL_ATTRIBUTE_NOINLINE; - // Default: uses `ostream` logging to convert `v` to a string. - template <typename T> + // Types that support `AbslStringify()` are serialized that way. + template <typename T, + typename std::enable_if< + strings_internal::HasAbslStringify<T>::value, int>::type = 0> + LogMessage& operator<<(const T& v) ABSL_ATTRIBUTE_NOINLINE; + + // Types that don't support `AbslStringify()` but do support streaming into a + // `std::ostream&` are serialized that way. + template <typename T, + typename std::enable_if< + !strings_internal::HasAbslStringify<T>::value, int>::type = 0> LogMessage& operator<<(const T& v) ABSL_ATTRIBUTE_NOINLINE; // Note: We explicitly do not support `operator<<` for non-const references @@ -180,6 +192,37 @@ class LogMessage { private: struct LogMessageData; // Opaque type containing message state + friend class AsLiteralImpl; + friend class StringifySink; + + // This streambuf writes directly into the structured logging buffer so that + // arbitrary types can be encoded as string data (using + // `operator<<(std::ostream &, ...)` without any extra allocation or copying. + // Space is reserved before the data to store the length field, which is + // filled in by `~OstreamView`. + class OstreamView final : public std::streambuf { + public: + explicit OstreamView(LogMessageData& message_data); + ~OstreamView() override; + OstreamView(const OstreamView&) = delete; + OstreamView& operator=(const OstreamView&) = delete; + std::ostream& stream(); + + private: + LogMessageData& data_; + absl::Span<char> encoded_remaining_copy_; + absl::Span<char> message_start_; + absl::Span<char> string_start_; + }; + + enum class StringType { + kLiteral, + kNotLiteral, + }; + void CopyToEncodedBuffer(absl::string_view str, + StringType str_type) ABSL_ATTRIBUTE_NOINLINE; + void CopyToEncodedBuffer(char ch, size_t num, + StringType str_type) ABSL_ATTRIBUTE_NOINLINE; // Returns `true` if the message is fatal or enabled debug-fatal. bool IsFatal() const; @@ -201,35 +244,62 @@ class LogMessage { // We keep the data in a separate struct so that each instance of `LogMessage` // uses less stack space. std::unique_ptr<LogMessageData> data_; +}; + +// Helper class so that `AbslStringify()` can modify the LogMessage. +class StringifySink final { + public: + explicit StringifySink(LogMessage& message) : message_(message) {} + + void Append(size_t count, char ch) { + message_.CopyToEncodedBuffer(ch, count, + LogMessage::StringType::kNotLiteral); + } + + void Append(absl::string_view v) { + message_.CopyToEncodedBuffer(v, LogMessage::StringType::kNotLiteral); + } - std::ostream stream_; + // For types that implement `AbslStringify` using `absl::Format()`. + friend void AbslFormatFlush(StringifySink* sink, absl::string_view v) { + sink->Append(v); + } + + private: + LogMessage& message_; }; // Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE` -template <typename T> +template <typename T, + typename std::enable_if<strings_internal::HasAbslStringify<T>::value, + int>::type> LogMessage& LogMessage::operator<<(const T& v) { - stream_ << log_internal::NullGuard<T>().Guard(v); - return *this; -} -inline LogMessage& LogMessage::operator<<( - std::ostream& (*m)(std::ostream& os)) { - stream_ << m; + StringifySink sink(*this); + // Replace with public API. + AbslStringify(sink, v); return *this; } -inline LogMessage& LogMessage::operator<<( - std::ios_base& (*m)(std::ios_base& os)) { - stream_ << m; + +// Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE` +template <typename T, + typename std::enable_if<!strings_internal::HasAbslStringify<T>::value, + int>::type> +LogMessage& LogMessage::operator<<(const T& v) { + OstreamView view(*data_); + view.stream() << log_internal::NullGuard<T>().Guard(v); return *this; } + template <int SIZE> LogMessage& LogMessage::operator<<(const char (&buf)[SIZE]) { - stream_ << buf; + CopyToEncodedBuffer(buf, StringType::kLiteral); return *this; } + // Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE` template <int SIZE> LogMessage& LogMessage::operator<<(char (&buf)[SIZE]) { - stream_ << buf; + CopyToEncodedBuffer(buf, StringType::kNotLiteral); return *this; } // We instantiate these specializations in the library's TU to save space in @@ -256,8 +326,6 @@ extern template LogMessage& LogMessage::operator<<(const void* const& v); extern template LogMessage& LogMessage::operator<<(const float& v); extern template LogMessage& LogMessage::operator<<(const double& v); extern template LogMessage& LogMessage::operator<<(const bool& v); -extern template LogMessage& LogMessage::operator<<(const std::string& v); -extern template LogMessage& LogMessage::operator<<(const absl::string_view& v); // `LogMessageFatal` ensures the process will exit in failure after logging this // message. diff --git a/absl/log/internal/nullstream.h b/absl/log/internal/nullstream.h index 80c62c9e..8ed63d52 100644 --- a/absl/log/internal/nullstream.h +++ b/absl/log/internal/nullstream.h @@ -117,12 +117,12 @@ class NullStreamFatal final : public NullStream { NullStreamFatal() {} // ABSL_ATTRIBUTE_NORETURN doesn't seem to work on destructors with msvc, so // disable msvc's warning about the d'tor never returning. -#ifdef _MSC_VER +#if defined(_MSC_VER) && !defined(__clang__) #pragma warning(push) #pragma warning(disable : 4722) #endif ABSL_ATTRIBUTE_NORETURN ~NullStreamFatal() { _exit(1); } -#ifdef _MSC_VER +#if defined(_MSC_VER) && !defined(__clang__) #pragma warning(pop) #endif }; diff --git a/absl/log/internal/proto.cc b/absl/log/internal/proto.cc new file mode 100644 index 00000000..9a2b1a39 --- /dev/null +++ b/absl/log/internal/proto.cc @@ -0,0 +1,216 @@ +// 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/log/internal/proto.h" + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <cstring> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { +namespace { +void EncodeRawVarint(uint64_t value, size_t size, absl::Span<char> *buf) { + for (size_t s = 0; s < size; s++) { + (*buf)[s] = static_cast<char>((value & 0x7f) | (s + 1 == size ? 0 : 0x80)); + value >>= 7; + } + buf->remove_prefix(size); +} +constexpr uint64_t MakeTagType(uint64_t tag, WireType type) { + return tag << 3 | static_cast<uint64_t>(type); +} +} // namespace + +bool EncodeVarint(uint64_t tag, uint64_t value, absl::Span<char> *buf) { + const uint64_t tag_type = MakeTagType(tag, WireType::kVarint); + const uint64_t tag_type_size = VarintSize(tag_type); + const uint64_t value_size = VarintSize(value); + if (tag_type_size + value_size > buf->size()) { + buf->remove_suffix(buf->size()); + return false; + } + EncodeRawVarint(tag_type, tag_type_size, buf); + EncodeRawVarint(value, value_size, buf); + return true; +} + +bool Encode64Bit(uint64_t tag, uint64_t value, absl::Span<char> *buf) { + const uint64_t tag_type = MakeTagType(tag, WireType::k64Bit); + const uint64_t tag_type_size = VarintSize(tag_type); + if (tag_type_size + sizeof(value) > buf->size()) { + buf->remove_suffix(buf->size()); + return false; + } + EncodeRawVarint(tag_type, tag_type_size, buf); + for (size_t s = 0; s < sizeof(value); s++) { + (*buf)[s] = static_cast<char>(value & 0xff); + value >>= 8; + } + buf->remove_prefix(sizeof(value)); + return true; +} + +bool Encode32Bit(uint64_t tag, uint32_t value, absl::Span<char> *buf) { + const uint64_t tag_type = MakeTagType(tag, WireType::k32Bit); + const uint64_t tag_type_size = VarintSize(tag_type); + if (tag_type_size + sizeof(value) > buf->size()) { + buf->remove_suffix(buf->size()); + return false; + } + EncodeRawVarint(tag_type, tag_type_size, buf); + for (size_t s = 0; s < sizeof(value); s++) { + (*buf)[s] = static_cast<char>(value & 0xff); + value >>= 8; + } + buf->remove_prefix(sizeof(value)); + return true; +} + +bool EncodeBytes(uint64_t tag, absl::Span<const char> value, + absl::Span<char> *buf) { + const uint64_t tag_type = MakeTagType(tag, WireType::kLengthDelimited); + const uint64_t tag_type_size = VarintSize(tag_type); + uint64_t length = value.size(); + const uint64_t length_size = VarintSize(length); + if (tag_type_size + length_size + value.size() > buf->size()) { + buf->remove_suffix(buf->size()); + return false; + } + EncodeRawVarint(tag_type, tag_type_size, buf); + EncodeRawVarint(length, length_size, buf); + memcpy(buf->data(), value.data(), value.size()); + buf->remove_prefix(value.size()); + return true; +} + +bool EncodeBytesTruncate(uint64_t tag, absl::Span<const char> value, + absl::Span<char> *buf) { + const uint64_t tag_type = MakeTagType(tag, WireType::kLengthDelimited); + const uint64_t tag_type_size = VarintSize(tag_type); + uint64_t length = value.size(); + const uint64_t length_size = + VarintSize(std::min<uint64_t>(length, buf->size())); + if (tag_type_size + length_size <= buf->size() && + tag_type_size + length_size + value.size() > buf->size()) { + value.remove_suffix(tag_type_size + length_size + value.size() - + buf->size()); + length = value.size(); + } + if (tag_type_size + length_size + value.size() > buf->size()) { + buf->remove_suffix(buf->size()); + return false; + } + EncodeRawVarint(tag_type, tag_type_size, buf); + EncodeRawVarint(length, length_size, buf); + memcpy(buf->data(), value.data(), value.size()); + buf->remove_prefix(value.size()); + return true; +} + +ABSL_MUST_USE_RESULT absl::Span<char> EncodeMessageStart( + uint64_t tag, uint64_t max_size, absl::Span<char> *buf) { + const uint64_t tag_type = MakeTagType(tag, WireType::kLengthDelimited); + const uint64_t tag_type_size = VarintSize(tag_type); + max_size = std::min<uint64_t>(max_size, buf->size()); + const uint64_t length_size = VarintSize(max_size); + if (tag_type_size + length_size > buf->size()) { + buf->remove_suffix(buf->size()); + return absl::Span<char>(); + } + EncodeRawVarint(tag_type, tag_type_size, buf); + const absl::Span<char> ret = buf->subspan(0, length_size); + EncodeRawVarint(0, length_size, buf); + return ret; +} + +void EncodeMessageLength(absl::Span<char> msg, const absl::Span<char> *buf) { + if (!msg.data()) return; + const size_t length_size = msg.size(); + EncodeRawVarint(static_cast<uint64_t>(buf->data() - msg.data()) - length_size, + length_size, &msg); +} + +namespace { +uint64_t DecodeVarint(absl::Span<const char> *buf) { + uint64_t value = 0; + size_t s = 0; + while (s < buf->size()) { + value |= static_cast<uint64_t>(static_cast<unsigned char>((*buf)[s]) & 0x7f) + << 7 * s; + if (!((*buf)[s++] & 0x80)) break; + } + buf->remove_prefix(s); + return value; +} + +uint64_t Decode64Bit(absl::Span<const char> *buf) { + uint64_t value = 0; + size_t s = 0; + while (s < buf->size()) { + value |= static_cast<uint64_t>(static_cast<unsigned char>((*buf)[s])) + << 8 * s; + if (++s == sizeof(value)) break; + } + buf->remove_prefix(s); + return value; +} + +uint32_t Decode32Bit(absl::Span<const char> *buf) { + uint32_t value = 0; + size_t s = 0; + while (s < buf->size()) { + value |= static_cast<uint32_t>(static_cast<unsigned char>((*buf)[s])) + << 8 * s; + if (++s == sizeof(value)) break; + } + buf->remove_prefix(s); + return value; +} +} // namespace + +bool ProtoField::DecodeFrom(absl::Span<const char> *data) { + if (data->empty()) return false; + const uint64_t tag_type = DecodeVarint(data); + tag_ = tag_type >> 3; + type_ = static_cast<WireType>(tag_type & 0x07); + switch (type_) { + case WireType::kVarint: + value_ = DecodeVarint(data); + break; + case WireType::k64Bit: + value_ = Decode64Bit(data); + break; + case WireType::kLengthDelimited: { + value_ = DecodeVarint(data); + data_ = data->subspan(0, std::min<size_t>(value_, data->size())); + data->remove_prefix(data_.size()); + break; + } + case WireType::k32Bit: + value_ = Decode32Bit(data); + break; + } + return true; +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/proto.h b/absl/log/internal/proto.h new file mode 100644 index 00000000..63c4e986 --- /dev/null +++ b/absl/log/internal/proto.h @@ -0,0 +1,267 @@ +// 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: internal/proto.h +// ----------------------------------------------------------------------------- +// +// Declares functions for serializing and deserializing data to and from memory +// buffers in protocol buffer wire format. This library takes no steps to +// ensure that the encoded data matches with any message specification. + +#ifndef ABSL_LOG_INTERNAL_PROTO_H_ +#define ABSL_LOG_INTERNAL_PROTO_H_ + +#include <cstddef> +#include <cstdint> +#include <limits> + +#include "absl/base/attributes.h" +#include "absl/base/casts.h" +#include "absl/base/config.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// absl::Span<char> represents a view into the available space in a mutable +// buffer during encoding. Encoding functions shrink the span as they go so +// that the same view can be passed to a series of Encode functions. If the +// data do not fit, nothing is encoded, the view is set to size zero (so that +// all subsequent encode calls fail), and false is returned. Otherwise true is +// returned. + +// In particular, attempting to encode a series of data into an insufficient +// buffer has consistent and efficient behavior without any caller-side error +// checking. Individual values will be encoded in their entirety or not at all +// (unless one of the `Truncate` functions is used). Once a value is omitted +// because it does not fit, no subsequent values will be encoded to preserve +// ordering; the decoded sequence will be a prefix of the original sequence. + +// There are two ways to encode a message-typed field: +// +// * Construct its contents in a separate buffer and use `EncodeBytes` to copy +// it into the primary buffer with type, tag, and length. +// * Use `EncodeMessageStart` to write type and tag fields and reserve space for +// the length field, then encode the contents directly into the buffer, then +// use `EncodeMessageLength` to write the actual length into the reserved +// bytes. This works fine if the actual length takes fewer bytes to encode +// than were reserved, although you don't get your extra bytes back. +// This approach will always produce a valid encoding, but your protocol may +// require that the whole message field by omitted if the buffer is too small +// to contain all desired subfields. In this case, operate on a copy of the +// buffer view and assign back only if everything fit, i.e. if the last +// `Encode` call returned true. + +// Encodes the specified integer as a varint field and returns true if it fits. +// Used for int32_t, int64_t, uint32_t, uint64_t, bool, and enum field types. +// Consumes up to kMaxVarintSize * 2 bytes (20). +bool EncodeVarint(uint64_t tag, uint64_t value, absl::Span<char> *buf); + +// Encodes the specified integer as a varint field using ZigZag encoding and +// returns true if it fits. +// Used for sint32 and sint64 field types. +// Consumes up to kMaxVarintSize * 2 bytes (20). +inline bool EncodeVarintZigZag(uint64_t tag, int64_t value, + absl::Span<char> *buf) { + if (value < 0) + return EncodeVarint(tag, 2 * static_cast<uint64_t>(-(value + 1)) + 1, buf); + return EncodeVarint(tag, 2 * static_cast<uint64_t>(value), buf); +} + +// Encodes the specified integer as a 64-bit field and returns true if it fits. +// Used for fixed64 and sfixed64 field types. +// Consumes up to kMaxVarintSize + 8 bytes (18). +bool Encode64Bit(uint64_t tag, uint64_t value, absl::Span<char> *buf); + +// Encodes the specified double as a 64-bit field and returns true if it fits. +// Used for double field type. +// Consumes up to kMaxVarintSize + 8 bytes (18). +inline bool EncodeDouble(uint64_t tag, double value, absl::Span<char> *buf) { + return Encode64Bit(tag, absl::bit_cast<uint64_t>(value), buf); +} + +// Encodes the specified integer as a 32-bit field and returns true if it fits. +// Used for fixed32 and sfixed32 field types. +// Consumes up to kMaxVarintSize + 4 bytes (14). +bool Encode32Bit(uint64_t tag, uint32_t value, absl::Span<char> *buf); + +// Encodes the specified float as a 32-bit field and returns true if it fits. +// Used for float field type. +// Consumes up to kMaxVarintSize + 4 bytes (14). +inline bool EncodeFloat(uint64_t tag, float value, absl::Span<char> *buf) { + return Encode32Bit(tag, absl::bit_cast<uint32_t>(value), buf); +} + +// Encodes the specified bytes as a length-delimited field and returns true if +// they fit. +// Used for string, bytes, message, and packed-repeated field type. +// Consumes up to kMaxVarintSize * 2 + value.size() bytes (20 + value.size()). +bool EncodeBytes(uint64_t tag, absl::Span<const char> value, + absl::Span<char> *buf); + +// Encodes as many of the specified bytes as will fit as a length-delimited +// field and returns true as long as the field header (`tag_type` and `length`) +// fits. +// Used for string, bytes, message, and packed-repeated field type. +// Consumes up to kMaxVarintSize * 2 + value.size() bytes (20 + value.size()). +bool EncodeBytesTruncate(uint64_t tag, absl::Span<const char> value, + absl::Span<char> *buf); + +// Encodes the specified string as a length-delimited field and returns true if +// it fits. +// Used for string, bytes, message, and packed-repeated field type. +// Consumes up to kMaxVarintSize * 2 + value.size() bytes (20 + value.size()). +inline bool EncodeString(uint64_t tag, absl::string_view value, + absl::Span<char> *buf) { + return EncodeBytes(tag, value, buf); +} + +// Encodes as much of the specified string as will fit as a length-delimited +// field and returns true as long as the field header (`tag_type` and `length`) +// fits. +// Used for string, bytes, message, and packed-repeated field type. +// Consumes up to kMaxVarintSize * 2 + value.size() bytes (20 + value.size()). +inline bool EncodeStringTruncate(uint64_t tag, absl::string_view value, + absl::Span<char> *buf) { + return EncodeBytesTruncate(tag, value, buf); +} + +// Encodes the header for a length-delimited field containing up to `max_size` +// bytes or the number remaining in the buffer, whichever is less. If the +// header fits, a non-nullptr `Span` is returned; this must be passed to +// `EncodeMessageLength` after all contents are encoded to finalize the length +// field. If the header does not fit, a nullptr `Span` is returned which is +// safe to pass to `EncodeMessageLength` but need not be. +// Used for string, bytes, message, and packed-repeated field type. +// Consumes up to kMaxVarintSize * 2 bytes (20). +ABSL_MUST_USE_RESULT absl::Span<char> EncodeMessageStart(uint64_t tag, + uint64_t max_size, + absl::Span<char> *buf); + +// Finalizes the length field in `msg` so that it encompasses all data encoded +// since the call to `EncodeMessageStart` which returned `msg`. Does nothing if +// `msg` is a `nullptr` `Span`. +void EncodeMessageLength(absl::Span<char> msg, const absl::Span<char> *buf); + +enum class WireType : uint64_t { + kVarint = 0, + k64Bit = 1, + kLengthDelimited = 2, + k32Bit = 5, +}; + +constexpr uint64_t VarintSize(uint64_t value) { + return value < 128 ? 1 : 1 + VarintSize(value >> 7); +} +constexpr uint64_t MinVarintSize() { + return VarintSize((std::numeric_limits<uint64_t>::min)()); +} +constexpr uint64_t MaxVarintSize() { + return VarintSize((std::numeric_limits<uint64_t>::max)()); +} + +constexpr uint64_t MaxVarintForSize(size_t size) { + return size >= 10 ? (std::numeric_limits<uint64_t>::max)() + : (static_cast<uint64_t>(1) << size * 7) - 1; +} + +// `BufferSizeFor` returns a number of bytes guaranteed to be sufficient to +// store encoded fields of the specified WireTypes regardless of tag numbers and +// data values. This only makes sense for `WireType::kLengthDelimited` if you +// add in the length of the contents yourself, e.g. for string and bytes fields +// by adding the lengths of any encoded strings to the return value or for +// submessage fields by enumerating the fields you may encode into their +// contents. +constexpr size_t BufferSizeFor() { return 0; } +template <typename... T> +constexpr size_t BufferSizeFor(WireType type, T... tail) { + // tag_type + data + ... + return MaxVarintSize() + + (type == WireType::kVarint ? MaxVarintSize() : // + type == WireType::k64Bit ? 8 : // + type == WireType::k32Bit ? 4 : MaxVarintSize()) + // + BufferSizeFor(tail...); +} + +// absl::Span<const char> represents a view into the un-processed space in a +// buffer during decoding. Decoding functions shrink the span as they go so +// that the same view can be decoded iteratively until all data are processed. +// In general, if the buffer is exhausted but additional bytes are expected by +// the decoder, it will return values as if the additional bytes were zeros. +// Length-delimited fields are an exception - if the encoded length field +// indicates more data bytes than are available in the buffer, the `bytes_value` +// and `string_value` accessors will return truncated views. + +class ProtoField final { + public: + // Consumes bytes from `data` and returns true if there were any bytes to + // decode. + bool DecodeFrom(absl::Span<const char> *data); + uint64_t tag() const { return tag_; } + WireType type() const { return type_; } + + // These value accessors will return nonsense if the data were not encoded in + // the corresponding wiretype from the corresponding C++ (or other language) + // type. + + double double_value() const { return absl::bit_cast<double>(value_); } + float float_value() const { + return absl::bit_cast<float>(static_cast<uint32_t>(value_)); + } + int32_t int32_value() const { return static_cast<int32_t>(value_); } + int64_t int64_value() const { return static_cast<int64_t>(value_); } + int32_t sint32_value() const { + if (value_ % 2) return static_cast<int32_t>(0 - ((value_ - 1) / 2) - 1); + return static_cast<int32_t>(value_ / 2); + } + int64_t sint64_value() const { + if (value_ % 2) return 0 - ((value_ - 1) / 2) - 1; + return value_ / 2; + } + uint32_t uint32_value() const { return static_cast<uint32_t>(value_); } + uint64_t uint64_value() const { return value_; } + bool bool_value() const { return value_ != 0; } + // To decode an enum, call int32_value() and cast to the appropriate type. + // Note that the official C++ proto compiler treats enum fields with values + // that do not correspond to a defined enumerator as unknown fields. + + // To decode fields within a submessage field, call + // `DecodeNextField(field.BytesValue())`. + absl::Span<const char> bytes_value() const { return data_; } + absl::string_view string_value() const { + const auto data = bytes_value(); + return absl::string_view(data.data(), data.size()); + } + // Returns the encoded length of a length-delimited field. This equals + // `bytes_value().size()` except when the latter has been truncated due to + // buffer underrun. + uint64_t encoded_length() const { return value_; } + + private: + uint64_t tag_; + WireType type_; + // For `kTypeVarint`, `kType64Bit`, and `kType32Bit`, holds the decoded value. + // For `kTypeLengthDelimited`, holds the decoded length. + uint64_t value_; + absl::Span<const char> data_; +}; + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_PROTO_H_ diff --git a/absl/log/internal/structured.h b/absl/log/internal/structured.h new file mode 100644 index 00000000..08caea66 --- /dev/null +++ b/absl/log/internal/structured.h @@ -0,0 +1,58 @@ +// Copyright 2022 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: log/internal/structured.h +// ----------------------------------------------------------------------------- + +#ifndef ABSL_LOG_INTERNAL_STRUCTURED_H_ +#define ABSL_LOG_INTERNAL_STRUCTURED_H_ + +#include <ostream> + +#include "absl/base/config.h" +#include "absl/log/internal/log_message.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +class ABSL_MUST_USE_RESULT AsLiteralImpl final { + public: + explicit AsLiteralImpl(absl::string_view str) : str_(str) {} + AsLiteralImpl(const AsLiteralImpl&) = default; + AsLiteralImpl& operator=(const AsLiteralImpl&) = default; + + private: + absl::string_view str_; + + friend std::ostream& operator<<(std::ostream& os, AsLiteralImpl as_literal) { + return os << as_literal.str_; + } + void AddToMessage(log_internal::LogMessage& m) { + m.CopyToEncodedBuffer(str_, log_internal::LogMessage::StringType::kLiteral); + } + friend log_internal::LogMessage& operator<<(log_internal::LogMessage& m, + AsLiteralImpl as_literal) { + as_literal.AddToMessage(m); + return m; + } +}; + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_STRUCTURED_H_ diff --git a/absl/log/internal/test_actions.cc b/absl/log/internal/test_actions.cc index 41ca9887..bdfd6377 100644 --- a/absl/log/internal/test_actions.cc +++ b/absl/log/internal/test_actions.cc @@ -18,10 +18,12 @@ #include <cassert> #include <iostream> #include <string> +#include <type_traits> +#include "absl/base/attributes.h" #include "absl/base/config.h" -#include "absl/log/internal/config.h" #include "absl/strings/escaping.h" +#include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" @@ -30,37 +32,42 @@ ABSL_NAMESPACE_BEGIN namespace log_internal { void WriteToStderrWithFilename::operator()(const absl::LogEntry& entry) const { - std::cerr << message << " (file: " << entry.source_filename() << ")" - << std::endl; + std::cerr << message << " (file: " << entry.source_filename() << ")\n"; } void WriteEntryToStderr::operator()(const absl::LogEntry& entry) const { - if (!message.empty()) std::cerr << message << std::endl; + if (!message.empty()) std::cerr << message << "\n"; - std::cerr << "LogEntry{\n" - << " source_filename: \"" - << absl::CHexEscape(entry.source_filename()) << "\"\n" - << " source_basename: \"" - << absl::CHexEscape(entry.source_basename()) << "\"\n" - << " source_line: " << entry.source_line() << "\n" - << " prefix: " << (entry.prefix() ? "true\n" : "false\n") - << " log_severity: " << entry.log_severity() << "\n" - << " timestamp: " << entry.timestamp() << "\n" - << " text_message: \"" << absl::CHexEscape(entry.text_message()) - << "\"\n verbosity: " << entry.verbosity() << "\n" - << "}" << std::endl; + const std::string source_filename = absl::CHexEscape(entry.source_filename()); + const std::string source_basename = absl::CHexEscape(entry.source_basename()); + const std::string text_message = absl::CHexEscape(entry.text_message()); + const std::string encoded_message = absl::CHexEscape(entry.encoded_message()); + std::string encoded_message_str; + std::cerr << "LogEntry{\n" // + << " source_filename: \"" << source_filename << "\"\n" // + << " source_basename: \"" << source_basename << "\"\n" // + << " source_line: " << entry.source_line() << "\n" // + << " prefix: " << (entry.prefix() ? "true\n" : "false\n") // + << " log_severity: " << entry.log_severity() << "\n" // + << " timestamp: " << entry.timestamp() << "\n" // + << " text_message: \"" << text_message << "\"\n" // + << " verbosity: " << entry.verbosity() << "\n" // + << " encoded_message (raw): \"" << encoded_message << "\"\n" // + << encoded_message_str // + << "}\n"; } void WriteEntryToStderr::operator()(absl::LogSeverity severity, absl::string_view filename, absl::string_view log_message) const { - if (!message.empty()) std::cerr << message << std::endl; - - std::cerr << "LogEntry{\n" - << " source_filename: \"" << absl::CHexEscape(filename) << "\"\n" - << " log_severity: " << severity << "\n" - << " text_message: \"" << absl::CHexEscape(log_message) << "}" - << std::endl; + if (!message.empty()) std::cerr << message << "\n"; + const std::string source_filename = absl::CHexEscape(filename); + const std::string text_message = absl::CHexEscape(log_message); + std::cerr << "LogEntry{\n" // + << " source_filename: \"" << source_filename << "\"\n" // + << " log_severity: " << severity << "\n" // + << " text_message: \"" << text_message << "\"\n" // + << "}\n"; } } // namespace log_internal diff --git a/absl/log/internal/test_matchers.cc b/absl/log/internal/test_matchers.cc index ee32617b..8c6515c4 100644 --- a/absl/log/internal/test_matchers.cc +++ b/absl/log/internal/test_matchers.cc @@ -15,14 +15,16 @@ #include "absl/log/internal/test_matchers.h" +#include <ostream> #include <sstream> #include <string> +#include <type_traits> #include <utility> #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/attributes.h" #include "absl/base/config.h" -#include "absl/log/internal/config.h" #include "absl/log/internal/test_helpers.h" #include "absl/strings/string_view.h" #include "absl/time/clock.h" @@ -31,74 +33,138 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace log_internal { +namespace { +using ::testing::_; +using ::testing::AllOf; +using ::testing::Ge; +using ::testing::HasSubstr; +using ::testing::MakeMatcher; +using ::testing::Matcher; +using ::testing::MatcherInterface; +using ::testing::MatchResultListener; +using ::testing::Not; +using ::testing::Property; +using ::testing::ResultOf; +using ::testing::Truly; + +class AsStringImpl final + : public MatcherInterface<absl::string_view> { + public: + explicit AsStringImpl( + const Matcher<const std::string&>& str_matcher) + : str_matcher_(str_matcher) {} + bool MatchAndExplain( + absl::string_view actual, + MatchResultListener* listener) const override { + return str_matcher_.MatchAndExplain(std::string(actual), listener); + } + void DescribeTo(std::ostream* os) const override { + return str_matcher_.DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const override { + return str_matcher_.DescribeNegationTo(os); + } + + private: + const Matcher<const std::string&> str_matcher_; +}; + +class MatchesOstreamImpl final + : public MatcherInterface<absl::string_view> { + public: + explicit MatchesOstreamImpl(std::string expected) + : expected_(std::move(expected)) {} + bool MatchAndExplain(absl::string_view actual, + MatchResultListener*) const override { + return actual == expected_; + } + void DescribeTo(std::ostream* os) const override { + *os << "matches the contents of the ostringstream, which are \"" + << expected_ << "\""; + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "does not match the contents of the ostringstream, which are \"" + << expected_ << "\""; + } -::testing::Matcher<const absl::LogEntry&> SourceFilename( - const ::testing::Matcher<absl::string_view>& source_filename) { + private: + const std::string expected_; +}; +} // namespace + +Matcher<absl::string_view> AsString( + const Matcher<const std::string&>& str_matcher) { + return MakeMatcher(new AsStringImpl(str_matcher)); +} + +Matcher<const absl::LogEntry&> SourceFilename( + const Matcher<absl::string_view>& source_filename) { return Property("source_filename", &absl::LogEntry::source_filename, source_filename); } -::testing::Matcher<const absl::LogEntry&> SourceBasename( - const ::testing::Matcher<absl::string_view>& source_basename) { +Matcher<const absl::LogEntry&> SourceBasename( + const Matcher<absl::string_view>& source_basename) { return Property("source_basename", &absl::LogEntry::source_basename, source_basename); } -::testing::Matcher<const absl::LogEntry&> SourceLine( - const ::testing::Matcher<int>& source_line) { +Matcher<const absl::LogEntry&> SourceLine( + const Matcher<int>& source_line) { return Property("source_line", &absl::LogEntry::source_line, source_line); } -::testing::Matcher<const absl::LogEntry&> Prefix( - const ::testing::Matcher<bool>& prefix) { +Matcher<const absl::LogEntry&> Prefix( + const Matcher<bool>& prefix) { return Property("prefix", &absl::LogEntry::prefix, prefix); } -::testing::Matcher<const absl::LogEntry&> LogSeverity( - const ::testing::Matcher<absl::LogSeverity>& log_severity) { +Matcher<const absl::LogEntry&> LogSeverity( + const Matcher<absl::LogSeverity>& log_severity) { return Property("log_severity", &absl::LogEntry::log_severity, log_severity); } -::testing::Matcher<const absl::LogEntry&> Timestamp( - const ::testing::Matcher<absl::Time>& timestamp) { +Matcher<const absl::LogEntry&> Timestamp( + const Matcher<absl::Time>& timestamp) { return Property("timestamp", &absl::LogEntry::timestamp, timestamp); } -::testing::Matcher<const absl::LogEntry&> TimestampInMatchWindow() { +Matcher<const absl::LogEntry&> TimestampInMatchWindow() { return Property("timestamp", &absl::LogEntry::timestamp, - ::testing::AllOf(::testing::Ge(absl::Now()), - ::testing::Truly([](absl::Time arg) { - return arg <= absl::Now(); - }))); + AllOf(Ge(absl::Now()), Truly([](absl::Time arg) { + return arg <= absl::Now(); + }))); } -::testing::Matcher<const absl::LogEntry&> ThreadID( - const ::testing::Matcher<absl::LogEntry::tid_t>& tid) { +Matcher<const absl::LogEntry&> ThreadID( + const Matcher<absl::LogEntry::tid_t>& tid) { return Property("tid", &absl::LogEntry::tid, tid); } -::testing::Matcher<const absl::LogEntry&> TextMessageWithPrefixAndNewline( - const ::testing::Matcher<absl::string_view>& +Matcher<const absl::LogEntry&> TextMessageWithPrefixAndNewline( + const Matcher<absl::string_view>& text_message_with_prefix_and_newline) { return Property("text_message_with_prefix_and_newline", &absl::LogEntry::text_message_with_prefix_and_newline, text_message_with_prefix_and_newline); } -::testing::Matcher<const absl::LogEntry&> TextMessageWithPrefix( - const ::testing::Matcher<absl::string_view>& text_message_with_prefix) { +Matcher<const absl::LogEntry&> TextMessageWithPrefix( + const Matcher<absl::string_view>& text_message_with_prefix) { return Property("text_message_with_prefix", &absl::LogEntry::text_message_with_prefix, text_message_with_prefix); } -::testing::Matcher<const absl::LogEntry&> TextMessage( - const ::testing::Matcher<absl::string_view>& text_message) { +Matcher<const absl::LogEntry&> TextMessage( + const Matcher<absl::string_view>& text_message) { return Property("text_message", &absl::LogEntry::text_message, text_message); } -::testing::Matcher<const absl::LogEntry&> TextPrefix( - const ::testing::Matcher<absl::string_view>& text_prefix) { +Matcher<const absl::LogEntry&> TextPrefix( + const Matcher<absl::string_view>& text_prefix) { return ResultOf( [](const absl::LogEntry& entry) { absl::string_view msg = entry.text_message_with_prefix(); @@ -107,42 +173,25 @@ namespace log_internal { }, text_prefix); } +Matcher<const absl::LogEntry&> RawEncodedMessage( + const Matcher<absl::string_view>& raw_encoded_message) { + return Property("encoded_message", &absl::LogEntry::encoded_message, + raw_encoded_message); +} -::testing::Matcher<const absl::LogEntry&> Verbosity( - const ::testing::Matcher<int>& verbosity) { +Matcher<const absl::LogEntry&> Verbosity( + const Matcher<int>& verbosity) { return Property("verbosity", &absl::LogEntry::verbosity, verbosity); } -::testing::Matcher<const absl::LogEntry&> Stacktrace( - const ::testing::Matcher<absl::string_view>& stacktrace) { +Matcher<const absl::LogEntry&> Stacktrace( + const Matcher<absl::string_view>& stacktrace) { return Property("stacktrace", &absl::LogEntry::stacktrace, stacktrace); } -class MatchesOstreamImpl final - : public ::testing::MatcherInterface<absl::string_view> { - public: - explicit MatchesOstreamImpl(std::string expected) - : expected_(std::move(expected)) {} - bool MatchAndExplain(absl::string_view actual, - ::testing::MatchResultListener*) const override { - return actual == expected_; - } - void DescribeTo(std::ostream* os) const override { - *os << "matches the contents of the ostringstream, which are \"" - << expected_ << "\""; - } - - void DescribeNegationTo(std::ostream* os) const override { - *os << "does not match the contents of the ostringstream, which are \"" - << expected_ << "\""; - } - - private: - const std::string expected_; -}; -::testing::Matcher<absl::string_view> MatchesOstream( +Matcher<absl::string_view> MatchesOstream( const std::ostringstream& stream) { - return ::testing::MakeMatcher(new MatchesOstreamImpl(stream.str())); + return MakeMatcher(new MatchesOstreamImpl(stream.str())); } // We need to validate what is and isn't logged as the process dies due to @@ -151,16 +200,16 @@ class MatchesOstreamImpl final // Instead, we use the mock actions `DeathTestExpectedLogging` and // `DeathTestUnexpectedLogging` to write specific phrases to `stderr` that we // can validate in the parent process using this matcher. -::testing::Matcher<const std::string&> DeathTestValidateExpectations() { +Matcher<const std::string&> DeathTestValidateExpectations() { if (log_internal::LoggingEnabledAt(absl::LogSeverity::kFatal)) { - return ::testing::Matcher<const std::string&>(::testing::AllOf( - ::testing::HasSubstr("Mock received expected entry"), - Not(::testing::HasSubstr("Mock received unexpected entry")))); + return Matcher<const std::string&>( + AllOf(HasSubstr("Mock received expected entry"), + Not(HasSubstr("Mock received unexpected entry")))); } // If `FATAL` logging is disabled, neither message should have been written. - return ::testing::Matcher<const std::string&>(::testing::AllOf( - Not(::testing::HasSubstr("Mock received expected entry")), - Not(::testing::HasSubstr("Mock received unexpected entry")))); + return Matcher<const std::string&>( + AllOf(Not(HasSubstr("Mock received expected entry")), + Not(HasSubstr("Mock received unexpected entry")))); } } // namespace log_internal diff --git a/absl/log/internal/test_matchers.h b/absl/log/internal/test_matchers.h index b8179ccc..fc653a91 100644 --- a/absl/log/internal/test_matchers.h +++ b/absl/log/internal/test_matchers.h @@ -30,7 +30,6 @@ #include "gtest/gtest.h" #include "absl/base/config.h" #include "absl/base/log_severity.h" -#include "absl/log/internal/config.h" #include "absl/log/internal/test_helpers.h" #include "absl/log/log_entry.h" #include "absl/strings/string_view.h" @@ -39,6 +38,10 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace log_internal { +// In some configurations, Googletest's string matchers (e.g. +// `::testing::EndsWith`) need help to match `absl::string_view`. +::testing::Matcher<absl::string_view> AsString( + const ::testing::Matcher<const std::string&>& str_matcher); // These matchers correspond to the components of `absl::LogEntry`. ::testing::Matcher<const absl::LogEntry&> SourceFilename( @@ -80,7 +83,8 @@ namespace log_internal { const std::ostringstream& stream); ::testing::Matcher<const std::string&> DeathTestValidateExpectations(); -// This feature coming soon =). +::testing::Matcher<const absl::LogEntry&> RawEncodedMessage( + const ::testing::Matcher<absl::string_view>& raw_encoded_message); #define ENCODED_MESSAGE(message_matcher) ::testing::_ } // namespace log_internal diff --git a/absl/log/log.h b/absl/log/log.h index 5331fdbf..e060a0b6 100644 --- a/absl/log/log.h +++ b/absl/log/log.h @@ -132,10 +132,45 @@ // as they would be if streamed into a `std::ostream`, however it should be // noted that their actual type is unspecified. // -// To implement a custom formatting operator for a type you own, define +// To implement a custom formatting operator for a type you own, there are two +// options: `AbslStringify()` or `std::ostream& operator<<(std::ostream&, ...)`. +// It is recommended that users make their types loggable through +// `AbslStringify()` as it is a universal stringification extension that also +// enables `absl::StrFormat` and `absl::StrCat` support. If both +// `AbslStringify()` and `std::ostream& operator<<(std::ostream&, ...)` are +// defined, `AbslStringify()` will be used. +// +// To use the `AbslStringify()` API, define a friend function template in your +// type's namespace with the following signature: +// +// template <typename Sink> +// void AbslStringify(Sink& sink, const UserDefinedType& value); +// +// `Sink` has the same interface as `absl::FormatSink`, but without +// `PutPaddedString()`. +// +// Example: +// +// struct Point { +// template <typename Sink> +// friend void AbslStringify(Sink& sink, const Point& p) { +// absl::Format(&sink, "(%v, %v)", p.x, p.y); +// } +// +// int x; +// int y; +// }; +// +// To use `std::ostream& operator<<(std::ostream&, ...)`, define // `std::ostream& operator<<(std::ostream&, ...)` in your type's namespace (for // ADL) just as you would to stream it to `std::cout`. // +// Currently `AbslStringify()` ignores output manipulators but this is not +// guaranteed behavior and may be subject to change in the future. If you would +// like guaranteed behavior regarding output manipulators, please use +// `std::ostream& operator<<(std::ostream&, ...)` to make custom types loggable +// instead. +// // Those macros that support streaming honor output manipulators and `fmtflag` // changes that output data (e.g. `std::ends`) or control formatting of data // (e.g. `std::hex` and `std::fixed`), however flushing such a stream is @@ -152,9 +187,7 @@ #ifndef ABSL_LOG_LOG_H_ #define ABSL_LOG_LOG_H_ -#include "absl/log/internal/conditions.h" -#include "absl/log/internal/log_message.h" -#include "absl/log/internal/strip.h" +#include "absl/log/internal/log_impl.h" // LOG() // @@ -163,56 +196,29 @@ // Example: // // LOG(INFO) << "Found " << num_cookies << " cookies"; -#define LOG(severity) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATELESS, true) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() +#define LOG(severity) ABSL_LOG_IMPL(_##severity) // PLOG() // // `PLOG` behaves like `LOG` except that a description of the current state of // `errno` is appended to the streamed message. -#define PLOG(severity) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATELESS, true) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() \ - .WithPerror() +#define PLOG(severity) ABSL_PLOG_IMPL(_##severity) // DLOG() // // `DLOG` behaves like `LOG` in debug mode (i.e. `#ifndef NDEBUG`). Otherwise // it compiles away and does nothing. Note that `DLOG(FATAL)` does not // terminate the program if `NDEBUG` is defined. -#ifndef NDEBUG -#define DLOG(severity) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATELESS, true) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() -#else -#define DLOG(severity) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATELESS, false) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() -#endif +#define DLOG(severity) ABSL_DLOG_IMPL(_##severity) // `LOG_IF` and friends add a second argument which specifies a condition. If // the condition is false, nothing is logged. // Example: // // LOG_IF(INFO, num_cookies > 10) << "Got lots of cookies"; -#define LOG_IF(severity, condition) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATELESS, condition) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() -#define PLOG_IF(severity, condition) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATELESS, condition) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() \ - .WithPerror() - -#ifndef NDEBUG -#define DLOG_IF(severity, condition) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATELESS, condition) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() -#else -#define DLOG_IF(severity, condition) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATELESS, false && (condition)) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() -#endif +#define LOG_IF(severity, condition) ABSL_LOG_IF_IMPL(_##severity, condition) +#define PLOG_IF(severity, condition) ABSL_PLOG_IF_IMPL(_##severity, condition) +#define DLOG_IF(severity, condition) ABSL_DLOG_IF_IMPL(_##severity, condition) // LOG_EVERY_N // @@ -225,27 +231,21 @@ // // LOG_EVERY_N(WARNING, 1000) << "Got a packet with a bad CRC (" << COUNTER // << " total)"; -#define LOG_EVERY_N(severity, n) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, true)(EveryN, n) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() +#define LOG_EVERY_N(severity, n) ABSL_LOG_EVERY_N_IMPL(_##severity, n) // LOG_FIRST_N // // `LOG_FIRST_N` behaves like `LOG_EVERY_N` except that the specified message is // logged when the counter's value is less than `n`. `LOG_FIRST_N` is // thread-safe. -#define LOG_FIRST_N(severity, n) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, true)(FirstN, n) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() +#define LOG_FIRST_N(severity, n) ABSL_LOG_FIRST_N_IMPL(_##severity, n) // LOG_EVERY_POW_2 // // `LOG_EVERY_POW_2` behaves like `LOG_EVERY_N` except that the specified // message is logged when the counter's value is a power of 2. // `LOG_EVERY_POW_2` is thread-safe. -#define LOG_EVERY_POW_2(severity) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, true)(EveryPow2) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() +#define LOG_EVERY_POW_2(severity) ABSL_LOG_EVERY_POW_2_IMPL(_##severity) // LOG_EVERY_N_SEC // @@ -256,64 +256,20 @@ // Example: // // LOG_EVERY_N_SEC(INFO, 2.5) << "Got " << COUNTER << " cookies so far"; -#define LOG_EVERY_N_SEC(severity, n_seconds) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, true)(EveryNSec, n_seconds) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() - -#define PLOG_EVERY_N(severity, n) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, true)(EveryN, n) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() \ - .WithPerror() - -#define PLOG_FIRST_N(severity, n) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, true)(FirstN, n) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() \ - .WithPerror() - -#define PLOG_EVERY_POW_2(severity) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, true)(EveryPow2) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() \ - .WithPerror() - -#define PLOG_EVERY_N_SEC(severity, n_seconds) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, true)(EveryNSec, n_seconds) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() \ - .WithPerror() - -#ifndef NDEBUG -#define DLOG_EVERY_N(severity, n) \ - ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ - (EveryN, n) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() - -#define DLOG_FIRST_N(severity, n) \ - ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ - (FirstN, n) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() - -#define DLOG_EVERY_POW_2(severity) \ - ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ - (EveryPow2) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() +#define LOG_EVERY_N_SEC(severity, n_seconds) \ + ABSL_LOG_EVERY_N_SEC_IMPL(_##severity, n_seconds) -#define DLOG_EVERY_N_SEC(severity, n_seconds) \ - ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ - (EveryNSec, n_seconds) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() +#define PLOG_EVERY_N(severity, n) ABSL_PLOG_EVERY_N_IMPL(_##severity, n) +#define PLOG_FIRST_N(severity, n) ABSL_PLOG_FIRST_N_IMPL(_##severity, n) +#define PLOG_EVERY_POW_2(severity) ABSL_PLOG_EVERY_POW_2_IMPL(_##severity) +#define PLOG_EVERY_N_SEC(severity, n_seconds) \ + ABSL_PLOG_EVERY_N_SEC_IMPL(_##severity, n_seconds) -#else // def NDEBUG -#define DLOG_EVERY_N(severity, n) \ - ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ - (EveryN, n) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() - -#define DLOG_FIRST_N(severity, n) \ - ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ - (FirstN, n) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() - -#define DLOG_EVERY_POW_2(severity) \ - ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ - (EveryPow2) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() - -#define DLOG_EVERY_N_SEC(severity, n_seconds) \ - ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ - (EveryNSec, n_seconds) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() -#endif // def NDEBUG +#define DLOG_EVERY_N(severity, n) ABSL_DLOG_EVERY_N_IMPL(_##severity, n) +#define DLOG_FIRST_N(severity, n) ABSL_DLOG_FIRST_N_IMPL(_##severity, n) +#define DLOG_EVERY_POW_2(severity) ABSL_DLOG_EVERY_POW_2_IMPL(_##severity) +#define DLOG_EVERY_N_SEC(severity, n_seconds) \ + ABSL_DLOG_EVERY_N_SEC_IMPL(_##severity, n_seconds) // `LOG_IF_EVERY_N` and friends behave as the corresponding `LOG_EVERY_N` // but neither increment a counter nor log a message if condition is false (as @@ -322,79 +278,31 @@ // // LOG_IF_EVERY_N(INFO, (size > 1024), 10) << "Got the " << COUNTER // << "th big cookie"; -#define LOG_IF_EVERY_N(severity, condition, n) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(EveryN, n) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() - -#define LOG_IF_FIRST_N(severity, condition, n) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(FirstN, n) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() - -#define LOG_IF_EVERY_POW_2(severity, condition) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(EveryPow2) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() - -#define LOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(EveryNSec, \ - n_seconds) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() - -#define PLOG_IF_EVERY_N(severity, condition, n) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(EveryN, n) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() \ - .WithPerror() - -#define PLOG_IF_FIRST_N(severity, condition, n) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(FirstN, n) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() \ - .WithPerror() - -#define PLOG_IF_EVERY_POW_2(severity, condition) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(EveryPow2) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() \ - .WithPerror() - -#define PLOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(EveryNSec, \ - n_seconds) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() \ - .WithPerror() - -#ifndef NDEBUG -#define DLOG_IF_EVERY_N(severity, condition, n) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(EveryN, n) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() - -#define DLOG_IF_FIRST_N(severity, condition, n) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(FirstN, n) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() - -#define DLOG_IF_EVERY_POW_2(severity, condition) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(EveryPow2) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() - -#define DLOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, condition)(EveryNSec, \ - n_seconds) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() - -#else // def NDEBUG -#define DLOG_IF_EVERY_N(severity, condition, n) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, false && (condition))( \ - EveryN, n) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() - -#define DLOG_IF_FIRST_N(severity, condition, n) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, false && (condition))( \ - FirstN, n) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() - -#define DLOG_IF_EVERY_POW_2(severity, condition) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, false && (condition))( \ - EveryPow2) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() - -#define DLOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ - ABSL_LOG_INTERNAL_CONDITION_##severity(STATEFUL, false && (condition))( \ - EveryNSec, n_seconds) \ - ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() -#endif // def NDEBUG +#define LOG_IF_EVERY_N(severity, condition, n) \ + ABSL_LOG_IF_EVERY_N_IMPL(_##severity, condition, n) +#define LOG_IF_FIRST_N(severity, condition, n) \ + ABSL_LOG_IF_FIRST_N_IMPL(_##severity, condition, n) +#define LOG_IF_EVERY_POW_2(severity, condition) \ + ABSL_LOG_IF_EVERY_POW_2_IMPL(_##severity, condition) +#define LOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ + ABSL_LOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds) + +#define PLOG_IF_EVERY_N(severity, condition, n) \ + ABSL_PLOG_IF_EVERY_N_IMPL(_##severity, condition, n) +#define PLOG_IF_FIRST_N(severity, condition, n) \ + ABSL_PLOG_IF_FIRST_N_IMPL(_##severity, condition, n) +#define PLOG_IF_EVERY_POW_2(severity, condition) \ + ABSL_PLOG_IF_EVERY_POW_2_IMPL(_##severity, condition) +#define PLOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ + ABSL_PLOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds) + +#define DLOG_IF_EVERY_N(severity, condition, n) \ + ABSL_DLOG_IF_EVERY_N_IMPL(_##severity, condition, n) +#define DLOG_IF_FIRST_N(severity, condition, n) \ + ABSL_DLOG_IF_FIRST_N_IMPL(_##severity, condition, n) +#define DLOG_IF_EVERY_POW_2(severity, condition) \ + ABSL_DLOG_IF_EVERY_POW_2_IMPL(_##severity, condition) +#define DLOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ + ABSL_DLOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds) #endif // ABSL_LOG_LOG_H_ diff --git a/absl/log/log_basic_test.cc b/absl/log/log_basic_test.cc new file mode 100644 index 00000000..b8d87c94 --- /dev/null +++ b/absl/log/log_basic_test.cc @@ -0,0 +1,21 @@ +// +// Copyright 2022 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/log/log.h" + +#define ABSL_TEST_LOG LOG + +#include "gtest/gtest.h" +#include "absl/log/log_basic_test_impl.h" diff --git a/absl/log/basic_log_test.cc b/absl/log/log_basic_test_impl.h index bc40f0d0..35c0b690 100644 --- a/absl/log/basic_log_test.cc +++ b/absl/log/log_basic_test_impl.h @@ -16,6 +16,15 @@ // The testcases in this file are expected to pass or be skipped with any value // of ABSL_MIN_LOG_LEVEL +#ifndef ABSL_LOG_LOG_BASIC_TEST_IMPL_H_ +#define ABSL_LOG_LOG_BASIC_TEST_IMPL_H_ + +// Verify that both sets of macros behave identically by parameterizing the +// entire test file. +#ifndef ABSL_TEST_LOG +#error ABSL_TEST_LOG must be defined for these tests to work. +#endif + #include <cerrno> #include <sstream> #include <string> @@ -28,11 +37,10 @@ #include "absl/log/internal/test_actions.h" #include "absl/log/internal/test_helpers.h" #include "absl/log/internal/test_matchers.h" -#include "absl/log/log.h" #include "absl/log/log_entry.h" #include "absl/log/scoped_mock_log.h" -namespace { +namespace absl_log_internal { #if GTEST_HAS_DEATH_TEST using ::absl::log_internal::DeathTestExpectedLogging; using ::absl::log_internal::DeathTestUnexpectedLogging; @@ -80,13 +88,13 @@ TEST_P(BasicLogTest, Info) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); const int log_line = __LINE__ + 1; - auto do_log = [] { LOG(INFO) << "hello world"; }; + auto do_log = [] { ABSL_TEST_LOG(INFO) << "hello world"; }; if (LoggingEnabledAt(absl::LogSeverity::kInfo)) { EXPECT_CALL( test_sink, Send(AllOf(SourceFilename(Eq(__FILE__)), - SourceBasename(Eq("basic_log_test.cc")), + SourceBasename(Eq("log_basic_test_impl.h")), SourceLine(Eq(log_line)), Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kInfo)), TimestampInMatchWindow(), @@ -109,13 +117,13 @@ TEST_P(BasicLogTest, Warning) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); const int log_line = __LINE__ + 1; - auto do_log = [] { LOG(WARNING) << "hello world"; }; + auto do_log = [] { ABSL_TEST_LOG(WARNING) << "hello world"; }; if (LoggingEnabledAt(absl::LogSeverity::kWarning)) { EXPECT_CALL( test_sink, Send(AllOf(SourceFilename(Eq(__FILE__)), - SourceBasename(Eq("basic_log_test.cc")), + SourceBasename(Eq("log_basic_test_impl.h")), SourceLine(Eq(log_line)), Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kWarning)), TimestampInMatchWindow(), @@ -138,13 +146,13 @@ TEST_P(BasicLogTest, Error) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); const int log_line = __LINE__ + 1; - auto do_log = [] { LOG(ERROR) << "hello world"; }; + auto do_log = [] { ABSL_TEST_LOG(ERROR) << "hello world"; }; if (LoggingEnabledAt(absl::LogSeverity::kError)) { EXPECT_CALL( test_sink, Send(AllOf(SourceFilename(Eq(__FILE__)), - SourceBasename(Eq("basic_log_test.cc")), + SourceBasename(Eq("log_basic_test_impl.h")), SourceLine(Eq(log_line)), Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kError)), TimestampInMatchWindow(), @@ -174,7 +182,7 @@ TEST_P(BasicLogDeathTest, Fatal) { absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); const int log_line = __LINE__ + 1; - auto do_log = [] { LOG(FATAL) << "hello world"; }; + auto do_log = [] { ABSL_TEST_LOG(FATAL) << "hello world"; }; EXPECT_EXIT( { @@ -195,7 +203,7 @@ TEST_P(BasicLogDeathTest, Fatal) { EXPECT_CALL( test_sink, Send(AllOf(SourceFilename(Eq(__FILE__)), - SourceBasename(Eq("basic_log_test.cc")), + SourceBasename(Eq("log_basic_test_impl.h")), SourceLine(Eq(log_line)), Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kFatal)), TimestampInMatchWindow(), @@ -211,7 +219,7 @@ TEST_P(BasicLogDeathTest, Fatal) { EXPECT_CALL( test_sink, Send(AllOf(SourceFilename(Eq(__FILE__)), - SourceBasename(Eq("basic_log_test.cc")), + SourceBasename(Eq("log_basic_test_impl.h")), SourceLine(Eq(log_line)), Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kFatal)), TimestampInMatchWindow(), @@ -234,7 +242,7 @@ TEST_P(BasicLogDeathTest, QFatal) { absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); const int log_line = __LINE__ + 1; - auto do_log = [] { LOG(QFATAL) << "hello world"; }; + auto do_log = [] { ABSL_TEST_LOG(QFATAL) << "hello world"; }; EXPECT_EXIT( { @@ -249,7 +257,7 @@ TEST_P(BasicLogDeathTest, QFatal) { EXPECT_CALL( test_sink, Send(AllOf(SourceFilename(Eq(__FILE__)), - SourceBasename(Eq("basic_log_test.cc")), + SourceBasename(Eq("log_basic_test_impl.h")), SourceLine(Eq(log_line)), Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kFatal)), TimestampInMatchWindow(), @@ -276,14 +284,16 @@ TEST_P(BasicLogTest, Level) { absl::LogSeverity::kError}) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); - const int log_line = __LINE__ + 1; - auto do_log = [severity] { LOG(LEVEL(severity)) << "hello world"; }; + const int log_line = __LINE__ + 2; + auto do_log = [severity] { + ABSL_TEST_LOG(LEVEL(severity)) << "hello world"; + }; if (LoggingEnabledAt(severity)) { EXPECT_CALL( test_sink, Send(AllOf(SourceFilename(Eq(__FILE__)), - SourceBasename(Eq("basic_log_test.cc")), + SourceBasename(Eq("log_basic_test_impl.h")), SourceLine(Eq(log_line)), Prefix(IsTrue()), LogSeverity(Eq(severity)), TimestampInMatchWindow(), ThreadID(Eq(absl::base_internal::GetTID())), @@ -309,7 +319,7 @@ TEST_P(BasicLogDeathTest, Level) { auto volatile severity = absl::LogSeverity::kFatal; const int log_line = __LINE__ + 1; - auto do_log = [severity] { LOG(LEVEL(severity)) << "hello world"; }; + auto do_log = [severity] { ABSL_TEST_LOG(LEVEL(severity)) << "hello world"; }; EXPECT_EXIT( { @@ -326,7 +336,7 @@ TEST_P(BasicLogDeathTest, Level) { EXPECT_CALL( test_sink, Send(AllOf(SourceFilename(Eq(__FILE__)), - SourceBasename(Eq("basic_log_test.cc")), + SourceBasename(Eq("log_basic_test_impl.h")), SourceLine(Eq(log_line)), Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kFatal)), TimestampInMatchWindow(), @@ -341,7 +351,7 @@ TEST_P(BasicLogDeathTest, Level) { EXPECT_CALL( test_sink, Send(AllOf(SourceFilename(Eq(__FILE__)), - SourceBasename(Eq("basic_log_test.cc")), + SourceBasename(Eq("log_basic_test_impl.h")), SourceLine(Eq(log_line)), Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kFatal)), TimestampInMatchWindow(), @@ -374,7 +384,7 @@ TEST_P(BasicLogTest, LevelClampsNegativeValues) { EXPECT_CALL(test_sink, Send(LogSeverity(Eq(absl::LogSeverity::kInfo)))); test_sink.StartCapturingLogs(); - LOG(LEVEL(-1)) << "hello world"; + ABSL_TEST_LOG(LEVEL(-1)) << "hello world"; } TEST_P(BasicLogTest, LevelClampsLargeValues) { @@ -390,13 +400,14 @@ TEST_P(BasicLogTest, LevelClampsLargeValues) { EXPECT_CALL(test_sink, Send(LogSeverity(Eq(absl::LogSeverity::kError)))); test_sink.StartCapturingLogs(); - LOG(LEVEL(static_cast<int>(absl::LogSeverity::kFatal) + 1)) << "hello world"; + ABSL_TEST_LOG(LEVEL(static_cast<int>(absl::LogSeverity::kFatal) + 1)) + << "hello world"; } TEST(ErrnoPreservationTest, InSeverityExpression) { errno = 77; int saved_errno; - LOG(LEVEL((saved_errno = errno, absl::LogSeverity::kInfo))); + ABSL_TEST_LOG(LEVEL((saved_errno = errno, absl::LogSeverity::kInfo))); EXPECT_THAT(saved_errno, Eq(77)); } @@ -408,13 +419,13 @@ TEST(ErrnoPreservationTest, InStreamedExpression) { errno = 77; int saved_errno = 0; - LOG(INFO) << (saved_errno = errno, "hello world"); + ABSL_TEST_LOG(INFO) << (saved_errno = errno, "hello world"); EXPECT_THAT(saved_errno, Eq(77)); } TEST(ErrnoPreservationTest, AfterStatement) { errno = 77; - LOG(INFO); + ABSL_TEST_LOG(INFO); const int saved_errno = errno; EXPECT_THAT(saved_errno, Eq(77)); } @@ -427,14 +438,18 @@ class UnusedVariableWarningCompileTest { // `kInfo`. static void LoggedVariable() { const int x = 0; - LOG(INFO) << x; + ABSL_TEST_LOG(INFO) << x; } - static void LoggedParameter(const int x) { LOG(INFO) << x; } + static void LoggedParameter(const int x) { ABSL_TEST_LOG(INFO) << x; } static void SeverityVariable() { const int x = 0; - LOG(LEVEL(x)) << "hello world"; + ABSL_TEST_LOG(LEVEL(x)) << "hello world"; + } + static void SeverityParameter(const int x) { + ABSL_TEST_LOG(LEVEL(x)) << "hello world"; } - static void SeverityParameter(const int x) { LOG(LEVEL(x)) << "hello world"; } }; -} // namespace +} // namespace absl_log_internal + +#endif // ABSL_LOG_LOG_BASIC_TEST_IMPL_H_ diff --git a/absl/log/log_entry.h b/absl/log/log_entry.h index 30114c33..9e4ae8eb 100644 --- a/absl/log/log_entry.h +++ b/absl/log/log_entry.h @@ -49,7 +49,7 @@ class LogMessage; // Represents a single entry in a log, i.e., one `LOG` statement or failed // `CHECK`. // -// `LogEntry` is copyable and thread-compatible. +// `LogEntry` is thread-compatible. class LogEntry final { public: using tid_t = log_internal::Tid; @@ -169,6 +169,15 @@ class LogEntry final { return text_message_with_prefix_and_newline_and_nul_.data(); } + // Returns a serialized protobuf holding the operands streamed into this + // log message. The message definition is not yet published. + // + // The buffer does not outlive the entry; if you need the data later, you must + // copy them. + absl::string_view encoded_message() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return encoding_; + } + // LogEntry::stacktrace() // // Optional stacktrace, e.g. for `FATAL` logs and failed `CHECK`s. @@ -198,6 +207,7 @@ class LogEntry final { tid_t tid_; absl::Span<const char> text_message_with_prefix_and_newline_and_nul_; size_t prefix_len_; + absl::string_view encoding_; std::string stacktrace_; friend class log_internal::LogEntryTestPeer; diff --git a/absl/log/log_entry_test.cc b/absl/log/log_entry_test.cc index 7238356e..d9bfa1f4 100644 --- a/absl/log/log_entry_test.cc +++ b/absl/log/log_entry_test.cc @@ -30,6 +30,7 @@ #include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/log_severity.h" +#include "absl/log/internal/append_truncated.h" #include "absl/log/internal/log_format.h" #include "absl/log/internal/test_helpers.h" #include "absl/strings/numbers.h" @@ -40,7 +41,6 @@ #include "absl/types/span.h" namespace { - using ::absl::log_internal::LogEntryTestPeer; using ::testing::Eq; using ::testing::IsTrue; @@ -49,16 +49,6 @@ using ::testing::StrEq; auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( new absl::log_internal::LogTestEnvironment); - -// Copies into `dst` as many bytes of `src` as will fit, then truncates the -// copied bytes from the front of `dst` and returns the number of bytes written. -size_t AppendTruncated(absl::string_view src, absl::Span<char>& dst) { - if (src.size() > dst.size()) src = src.substr(0, dst.size()); - memcpy(dst.data(), src.data(), src.size()); - dst.remove_prefix(src.size()); - return src.size(); -} - } // namespace namespace absl { @@ -69,8 +59,9 @@ class LogEntryTestPeer { public: LogEntryTestPeer(absl::string_view base_filename, int line, bool prefix, absl::LogSeverity severity, absl::string_view timestamp, - absl::LogEntry::tid_t tid, absl::string_view text_message) - : buf_(15000, '\0') { + absl::LogEntry::tid_t tid, PrefixFormat format, + absl::string_view text_message) + : format_{format}, buf_(15000, '\0') { entry_.base_filename_ = base_filename; entry_.line_ = line; entry_.prefix_ = prefix; @@ -98,12 +89,12 @@ class LogEntryTestPeer { entry_.prefix_ ? log_internal::FormatLogPrefix( entry_.log_severity(), entry_.timestamp(), entry_.tid(), - entry_.source_basename(), entry_.source_line(), view) + entry_.source_basename(), entry_.source_line(), format_, view) : 0; EXPECT_THAT(entry_.prefix_len_, Eq(static_cast<size_t>(view.data() - buf_.data()))); - AppendTruncated(text_message, view); + log_internal::AppendTruncated(text_message, view); view = absl::Span<char>(view.data(), view.size() + 2); view[0] = '\n'; view[1] = '\0'; @@ -117,14 +108,15 @@ class LogEntryTestPeer { std::string FormatLogMessage() const { return log_internal::FormatLogMessage( entry_.log_severity(), ci_.cs, ci_.subsecond, entry_.tid(), - entry_.source_basename(), entry_.source_line(), entry_.text_message()); + entry_.source_basename(), entry_.source_line(), format_, + entry_.text_message()); } std::string FormatPrefixIntoSizedBuffer(size_t sz) { std::string str(sz, '\0'); absl::Span<char> buf(&str[0], str.size()); const size_t prefix_size = log_internal::FormatLogPrefix( entry_.log_severity(), entry_.timestamp(), entry_.tid(), - entry_.source_basename(), entry_.source_line(), buf); + entry_.source_basename(), entry_.source_line(), format_, buf); EXPECT_THAT(prefix_size, Eq(static_cast<size_t>(buf.data() - str.data()))); str.resize(prefix_size); return str; @@ -133,6 +125,7 @@ class LogEntryTestPeer { private: absl::LogEntry entry_; + PrefixFormat format_; absl::TimeZone::CivilInfo ci_; std::vector<char> buf_; }; @@ -146,7 +139,9 @@ constexpr bool kUsePrefix = true, kNoPrefix = false; TEST(LogEntryTest, Baseline) { LogEntryTestPeer entry("foo.cc", 1234, kUsePrefix, absl::LogSeverity::kInfo, - "2020-01-02T03:04:05.6789", 451, "hello world"); + "2020-01-02T03:04:05.6789", 451, + absl::log_internal::PrefixFormat::kNotRaw, + "hello world"); EXPECT_THAT(entry.FormatLogMessage(), Eq("I0102 03:04:05.678900 451 foo.cc:1234] hello world")); EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), @@ -168,7 +163,9 @@ TEST(LogEntryTest, Baseline) { TEST(LogEntryTest, NoPrefix) { LogEntryTestPeer entry("foo.cc", 1234, kNoPrefix, absl::LogSeverity::kInfo, - "2020-01-02T03:04:05.6789", 451, "hello world"); + "2020-01-02T03:04:05.6789", 451, + absl::log_internal::PrefixFormat::kNotRaw, + "hello world"); EXPECT_THAT(entry.FormatLogMessage(), Eq("I0102 03:04:05.678900 451 foo.cc:1234] hello world")); // These methods are not responsible for honoring `prefix()`. @@ -189,7 +186,8 @@ TEST(LogEntryTest, NoPrefix) { TEST(LogEntryTest, EmptyFields) { LogEntryTestPeer entry("", 0, kUsePrefix, absl::LogSeverity::kInfo, - "2020-01-02T03:04:05", 0, ""); + "2020-01-02T03:04:05", 0, + absl::log_internal::PrefixFormat::kNotRaw, ""); const std::string format_message = entry.FormatLogMessage(); EXPECT_THAT(format_message, Eq("I0102 03:04:05.000000 0 :0] ")); EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), Eq(format_message)); @@ -211,10 +209,10 @@ TEST(LogEntryTest, NegativeFields) { // When Abseil's minimum C++ version is C++17, this conditional can be // converted to a constexpr if and the static_cast below removed. if (std::is_signed<absl::LogEntry::tid_t>::value) { - LogEntryTestPeer entry("foo.cc", -1234, kUsePrefix, - absl::LogSeverity::kInfo, "2020-01-02T03:04:05.6789", - static_cast<absl::LogEntry::tid_t>(-451), - "hello world"); + LogEntryTestPeer entry( + "foo.cc", -1234, kUsePrefix, absl::LogSeverity::kInfo, + "2020-01-02T03:04:05.6789", static_cast<absl::LogEntry::tid_t>(-451), + absl::log_internal::PrefixFormat::kNotRaw, "hello world"); EXPECT_THAT(entry.FormatLogMessage(), Eq("I0102 03:04:05.678900 -451 foo.cc:-1234] hello world")); EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), @@ -237,7 +235,8 @@ TEST(LogEntryTest, NegativeFields) { } else { LogEntryTestPeer entry("foo.cc", -1234, kUsePrefix, absl::LogSeverity::kInfo, "2020-01-02T03:04:05.6789", - 451, "hello world"); + 451, absl::log_internal::PrefixFormat::kNotRaw, + "hello world"); EXPECT_THAT(entry.FormatLogMessage(), Eq("I0102 03:04:05.678900 451 foo.cc:-1234] hello world")); EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), @@ -266,6 +265,7 @@ TEST(LogEntryTest, LongFields) { "I've information vegetable, animal, and mineral.", 2147483647, kUsePrefix, absl::LogSeverity::kInfo, "2020-01-02T03:04:05.678967896789", 2147483647, + absl::log_internal::PrefixFormat::kNotRaw, "I know the kings of England, and I quote the fights historical / " "From Marathon to Waterloo, in order categorical."); EXPECT_THAT(entry.FormatLogMessage(), @@ -325,6 +325,7 @@ TEST(LogEntryTest, LongNegativeFields) { -2147483647, kUsePrefix, absl::LogSeverity::kInfo, "2020-01-02T03:04:05.678967896789", static_cast<absl::LogEntry::tid_t>(-2147483647), + absl::log_internal::PrefixFormat::kNotRaw, "I know the kings of England, and I quote the fights historical / " "From Marathon to Waterloo, in order categorical."); EXPECT_THAT( @@ -382,6 +383,7 @@ TEST(LogEntryTest, LongNegativeFields) { "I've information vegetable, animal, and mineral.", -2147483647, kUsePrefix, absl::LogSeverity::kInfo, "2020-01-02T03:04:05.678967896789", 2147483647, + absl::log_internal::PrefixFormat::kNotRaw, "I know the kings of England, and I quote the fights historical / " "From Marathon to Waterloo, in order categorical."); EXPECT_THAT( @@ -436,4 +438,31 @@ TEST(LogEntryTest, LongNegativeFields) { } } +TEST(LogEntryTest, Raw) { + LogEntryTestPeer entry("foo.cc", 1234, kUsePrefix, absl::LogSeverity::kInfo, + "2020-01-02T03:04:05.6789", 451, + absl::log_internal::PrefixFormat::kRaw, "hello world"); + EXPECT_THAT( + entry.FormatLogMessage(), + Eq("I0102 03:04:05.678900 451 foo.cc:1234] RAW: hello world")); + EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), + Eq("I0102 03:04:05.678900 451 foo.cc:1234] RAW: ")); + for (size_t sz = + strlen("I0102 03:04:05.678900 451 foo.cc:1234] RAW: ") + 20; + sz != std::numeric_limits<size_t>::max(); sz--) + EXPECT_THAT("I0102 03:04:05.678900 451 foo.cc:1234] RAW: ", + StartsWith(entry.FormatPrefixIntoSizedBuffer(sz))); + + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline(), + Eq("I0102 03:04:05.678900 451 foo.cc:1234] RAW: hello world\n")); + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline_c_str(), + StrEq("I0102 03:04:05.678900 451 foo.cc:1234] RAW: hello world\n")); + EXPECT_THAT( + entry.entry().text_message_with_prefix(), + Eq("I0102 03:04:05.678900 451 foo.cc:1234] RAW: hello world")); + EXPECT_THAT(entry.entry().text_message(), Eq("hello world")); +} + } // namespace diff --git a/absl/log/log_format_test.cc b/absl/log/log_format_test.cc index c629fce7..5ff6f159 100644 --- a/absl/log/log_format_test.cc +++ b/absl/log/log_format_test.cc @@ -16,7 +16,9 @@ #include <math.h> #include <iomanip> +#include <ios> #include <limits> +#include <ostream> #include <sstream> #include <string> #include <type_traits> @@ -26,30 +28,31 @@ #endif #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/log/internal/config.h" +#include "absl/log/check.h" #include "absl/log/internal/test_matchers.h" #include "absl/log/log.h" #include "absl/log/scoped_mock_log.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" #include "absl/strings/string_view.h" namespace { +using ::absl::log_internal::AsString; using ::absl::log_internal::MatchesOstream; +using ::absl::log_internal::RawEncodedMessage; using ::absl::log_internal::TextMessage; using ::absl::log_internal::TextPrefix; - using ::testing::AllOf; using ::testing::AnyOf; -using ::testing::Eq; -using ::testing::IsEmpty; -using ::testing::Truly; -using ::testing::Types; - using ::testing::Each; +using ::testing::EndsWith; +using ::testing::Eq; using ::testing::Ge; +using ::testing::IsEmpty; using ::testing::Le; using ::testing::SizeIs; +using ::testing::Types; // Some aspects of formatting streamed data (e.g. pointer handling) are // implementation-defined. Others are buggy in supported implementations. @@ -69,15 +72,12 @@ TEST(LogFormatTest, NoMessage) { const int log_line = __LINE__ + 1; auto do_log = [] { LOG(INFO); }; - EXPECT_CALL( - test_sink, - Send(AllOf( - TextMessage(MatchesOstream(ComparisonStream())), - TextPrefix(Truly([=](absl::string_view msg) { - return absl::EndsWith( - msg, absl::StrCat(" log_format_test.cc:", log_line, "] ")); - })), - TextMessage(IsEmpty()), ENCODED_MESSAGE(EqualsProto(R"pb()pb"))))); + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(ComparisonStream())), + TextPrefix(AsString(EndsWith(absl::StrCat( + " log_format_test.cc:", log_line, "] ")))), + TextMessage(IsEmpty()), + ENCODED_MESSAGE(EqualsProto(R"pb()pb"))))); test_sink.StartCapturingLogs(); do_log(); @@ -612,8 +612,10 @@ TYPED_TEST(FloatingPointLogFormatTest, NegativeNaN) { Send(AllOf( TextMessage(MatchesOstream(comparison_stream)), TextMessage(AnyOf(Eq("-nan"), Eq("nan"), Eq("NaN"), Eq("-nan(ind)"))), - ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "-nan" })pb"))))); - + ENCODED_MESSAGE( + AnyOf(EqualsProto(R"pb(value { str: "-nan" })pb"), + EqualsProto(R"pb(value { str: "nan" })pb"), + EqualsProto(R"pb(value { str: "-nan(ind)" })pb")))))); test_sink.StartCapturingLogs(); LOG(INFO) << value; } @@ -647,13 +649,15 @@ TYPED_TEST(VoidPtrLogFormatTest, NonNull) { auto comparison_stream = ComparisonStream(); comparison_stream << value; - EXPECT_CALL(test_sink, - Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), - TextMessage(AnyOf(Eq("0xdeadbeef"), Eq("DEADBEEF"), - Eq("00000000DEADBEEF"))), - ENCODED_MESSAGE(EqualsProto(R"pb(value { - str: "0xdeadbeef" - })pb"))))); + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(MatchesOstream(comparison_stream)), + TextMessage( + AnyOf(Eq("0xdeadbeef"), Eq("DEADBEEF"), Eq("00000000DEADBEEF"))), + ENCODED_MESSAGE(AnyOf( + EqualsProto(R"pb(value { str: "0xdeadbeef" })pb"), + EqualsProto(R"pb(value { str: "00000000DEADBEEF" })pb")))))); test_sink.StartCapturingLogs(); LOG(INFO) << value; @@ -865,6 +869,111 @@ TEST(LogFormatTest, CustomNonCopyable) { LOG(INFO) << value; } +struct Point { + template <typename Sink> + friend void AbslStringify(Sink& sink, const Point& p) { + absl::Format(&sink, "(%d, %d)", p.x, p.y); + } + + int x = 10; + int y = 20; +}; + +TEST(LogFormatTest, AbslStringifyExample) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + Point p; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(Eq("(10, 20)")), TextMessage(Eq(absl::StrCat(p))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(10, 20)" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << p; +} + +struct PointWithAbslStringifiyAndOstream { + template <typename Sink> + friend void AbslStringify(Sink& sink, + const PointWithAbslStringifiyAndOstream& p) { + absl::Format(&sink, "(%d, %d)", p.x, p.y); + } + + int x = 10; + int y = 20; +}; + +ABSL_ATTRIBUTE_UNUSED std::ostream& operator<<( + std::ostream& os, const PointWithAbslStringifiyAndOstream&) { + return os << "Default to AbslStringify()"; +} + +TEST(LogFormatTest, CustomWithAbslStringifyAndOstream) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + PointWithAbslStringifiyAndOstream p; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(Eq("(10, 20)")), TextMessage(Eq(absl::StrCat(p))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(10, 20)" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << p; +} + +struct PointStreamsNothing { + template <typename Sink> + friend void AbslStringify(Sink&, const PointStreamsNothing&) {} + + int x = 10; + int y = 20; +}; + +TEST(LogFormatTest, AbslStringifyStreamsNothing) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + PointStreamsNothing p; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(Eq("77")), TextMessage(Eq(absl::StrCat(p, 77))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << p << 77; +} + +struct PointMultipleAppend { + template <typename Sink> + friend void AbslStringify(Sink& sink, const PointMultipleAppend& p) { + sink.Append("("); + sink.Append(absl::StrCat(p.x, ", ", p.y, ")")); + } + + int x = 10; + int y = 20; +}; + +TEST(LogFormatTest, AbslStringifyMultipleAppend) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + PointMultipleAppend p; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(Eq("(10, 20)")), TextMessage(Eq(absl::StrCat(p))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(" } + value { str: "10, 20)" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << p; +} + TEST(ManipulatorLogFormatTest, BoolAlphaTrue) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); @@ -1180,8 +1289,11 @@ TEST(ManipulatorLogFormatTest, FixedAndScientificFloat) { Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), TextMessage(AnyOf(Eq("0x1.25bb50p+26"), Eq("0x1.25bb5p+26"), Eq("0x1.25bb500000000p+26"))), - ENCODED_MESSAGE(EqualsProto(R"pb( - value { str: "0x1.25bb5p+26" })pb"))))); + ENCODED_MESSAGE( + AnyOf(EqualsProto(R"pb(value { str: "0x1.25bb5p+26" })pb"), + EqualsProto(R"pb(value { + str: "0x1.25bb500000000p+26" + })pb")))))); test_sink.StartCapturingLogs(); @@ -1210,8 +1322,11 @@ TEST(ManipulatorLogFormatTest, HexfloatFloat) { Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), TextMessage(AnyOf(Eq("0x1.25bb50p+26"), Eq("0x1.25bb5p+26"), Eq("0x1.25bb500000000p+26"))), - ENCODED_MESSAGE(EqualsProto(R"pb( - value { str: "0x1.25bb5p+26" })pb"))))); + ENCODED_MESSAGE( + AnyOf(EqualsProto(R"pb(value { str: "0x1.25bb5p+26" })pb"), + EqualsProto(R"pb(value { + str: "0x1.25bb500000000p+26" + })pb")))))); test_sink.StartCapturingLogs(); LOG(INFO) << std::hexfloat << value; @@ -1258,9 +1373,12 @@ TEST(ManipulatorLogFormatTest, Endl) { auto comparison_stream = ComparisonStream(); comparison_stream << std::endl; - EXPECT_CALL(test_sink, - Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), - TextMessage(Eq("\n"))))); + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("\n")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "\n" })pb"))))); test_sink.StartCapturingLogs(); LOG(INFO) << std::endl; @@ -1501,25 +1619,248 @@ TEST(ManipulatorLogFormatTest, CustomClassStreamsNothing) { LOG(INFO) << value << 77; } -// Tests that verify the behavior when more data are streamed into a `LOG` -// statement than fit in the buffer. -// Structured logging scenario is tested in other unit tests since the output is -// significantly different. -TEST(OverflowTest, TruncatesStrings) { +struct PointPercentV { + template <typename Sink> + friend void AbslStringify(Sink& sink, const PointPercentV& p) { + absl::Format(&sink, "(%v, %v)", p.x, p.y); + } + + int x = 10; + int y = 20; +}; + +TEST(ManipulatorLogFormatTest, IOManipsDoNotAffectAbslStringify) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + PointPercentV p; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(Eq("(10, 20)")), TextMessage(Eq(absl::StrCat(p))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(10, 20)" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::hex << p; +} + +TEST(StructuredLoggingOverflowTest, TruncatesStrings) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); // This message is too long and should be truncated to some unspecified size - // no greater than the buffer size but not too much less either. It should be + // no greater than the buffer size but not too much less either. It should be // truncated rather than discarded. - constexpr size_t buffer_size = 15000; - - EXPECT_CALL(test_sink, - Send(TextMessage( - AllOf(SizeIs(AllOf(Ge(buffer_size - 256), Le(buffer_size))), - Each(Eq('x')))))); + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(AllOf( + SizeIs(AllOf(Ge(absl::log_internal::kLogMessageBufferSize - 256), + Le(absl::log_internal::kLogMessageBufferSize))), + Each(Eq('x')))), + ENCODED_MESSAGE(HasOneStrThat(AllOf( + SizeIs(AllOf(Ge(absl::log_internal::kLogMessageBufferSize - 256), + Le(absl::log_internal::kLogMessageBufferSize))), + Each(Eq('x')))))))); test_sink.StartCapturingLogs(); - LOG(INFO) << std::string(2 * buffer_size, 'x'); + LOG(INFO) << std::string(2 * absl::log_internal::kLogMessageBufferSize, 'x'); +} + +struct StringLike { + absl::string_view data; +}; +std::ostream& operator<<(std::ostream& os, StringLike str) { + return os << str.data; +} + +TEST(StructuredLoggingOverflowTest, TruncatesInsertionOperators) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + // This message is too long and should be truncated to some unspecified size + // no greater than the buffer size but not too much less either. It should be + // truncated rather than discarded. + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(AllOf( + SizeIs(AllOf(Ge(absl::log_internal::kLogMessageBufferSize - 256), + Le(absl::log_internal::kLogMessageBufferSize))), + Each(Eq('x')))), + ENCODED_MESSAGE(HasOneStrThat(AllOf( + SizeIs(AllOf(Ge(absl::log_internal::kLogMessageBufferSize - 256), + Le(absl::log_internal::kLogMessageBufferSize))), + Each(Eq('x')))))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << StringLike{ + std::string(2 * absl::log_internal::kLogMessageBufferSize, 'x')}; +} + +// Returns the size of the largest string that will fit in a `LOG` message +// buffer with no prefix. +size_t MaxLogFieldLengthNoPrefix() { + class StringLengthExtractorSink : public absl::LogSink { + public: + void Send(const absl::LogEntry& entry) override { + CHECK_EQ(size_, -1); + CHECK_EQ(entry.text_message().find_first_not_of('x'), + absl::string_view::npos); + size_ = entry.text_message().size(); + } + size_t size() const { + CHECK_GT(size_, 0); + return size_; + } + + private: + size_t size_ = -1; + } extractor_sink; + LOG(INFO).NoPrefix().ToSinkOnly(&extractor_sink) + << std::string(2 * absl::log_internal::kLogMessageBufferSize, 'x'); + return extractor_sink.size(); +} + +TEST(StructuredLoggingOverflowTest, TruncatesStringsCleanly) { + const size_t longest_fit = MaxLogFieldLengthNoPrefix(); + // To log a second value field, we need four bytes: two tag/type bytes and two + // sizes. To put any data in the field we need a fifth byte. + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( + AllOf(SizeIs(longest_fit), Each(Eq('x'))))), + RawEncodedMessage(AsString(EndsWith("x")))))); + test_sink.StartCapturingLogs(); + // x fits exactly, no part of y fits. + LOG(INFO).NoPrefix() << std::string(longest_fit, 'x') << "y"; + } + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( + AllOf(SizeIs(longest_fit - 1), Each(Eq('x'))))), + RawEncodedMessage(AsString(EndsWith("x")))))); + test_sink.StartCapturingLogs(); + // x fits, one byte from y's header fits but shouldn't be visible. + LOG(INFO).NoPrefix() << std::string(longest_fit - 1, 'x') << "y"; + } + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( + AllOf(SizeIs(longest_fit - 2), Each(Eq('x'))))), + RawEncodedMessage(AsString(EndsWith("x")))))); + test_sink.StartCapturingLogs(); + // x fits, two bytes from y's header fit but shouldn't be visible. + LOG(INFO).NoPrefix() << std::string(longest_fit - 2, 'x') << "y"; + } + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( + AllOf(SizeIs(longest_fit - 3), Each(Eq('x'))))), + RawEncodedMessage(AsString(EndsWith("x")))))); + test_sink.StartCapturingLogs(); + // x fits, three bytes from y's header fit but shouldn't be visible. + LOG(INFO).NoPrefix() << std::string(longest_fit - 3, 'x') << "y"; + } + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrAndOneLiteralThat( + AllOf(SizeIs(longest_fit - 4), Each(Eq('x'))), + IsEmpty())), + RawEncodedMessage(Not(AsString(EndsWith("x"))))))); + test_sink.StartCapturingLogs(); + // x fits, all four bytes from y's header fit but no data bytes do, so we + // encode an empty string. + LOG(INFO).NoPrefix() << std::string(longest_fit - 4, 'x') << "y"; + } + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL( + test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrAndOneLiteralThat( + AllOf(SizeIs(longest_fit - 5), Each(Eq('x'))), Eq("y"))), + RawEncodedMessage(AsString(EndsWith("y")))))); + test_sink.StartCapturingLogs(); + // x fits, y fits exactly. + LOG(INFO).NoPrefix() << std::string(longest_fit - 5, 'x') << "y"; + } +} + +TEST(StructuredLoggingOverflowTest, TruncatesInsertionOperatorsCleanly) { + const size_t longest_fit = MaxLogFieldLengthNoPrefix(); + // To log a second value field, we need four bytes: two tag/type bytes and two + // sizes. To put any data in the field we need a fifth byte. + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( + AllOf(SizeIs(longest_fit), Each(Eq('x'))))), + RawEncodedMessage(AsString(EndsWith("x")))))); + test_sink.StartCapturingLogs(); + // x fits exactly, no part of y fits. + LOG(INFO).NoPrefix() << std::string(longest_fit, 'x') << StringLike{"y"}; + } + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( + AllOf(SizeIs(longest_fit - 1), Each(Eq('x'))))), + RawEncodedMessage(AsString(EndsWith("x")))))); + test_sink.StartCapturingLogs(); + // x fits, one byte from y's header fits but shouldn't be visible. + LOG(INFO).NoPrefix() << std::string(longest_fit - 1, 'x') + << StringLike{"y"}; + } + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( + AllOf(SizeIs(longest_fit - 2), Each(Eq('x'))))), + RawEncodedMessage(AsString(EndsWith("x")))))); + test_sink.StartCapturingLogs(); + // x fits, two bytes from y's header fit but shouldn't be visible. + LOG(INFO).NoPrefix() << std::string(longest_fit - 2, 'x') + << StringLike{"y"}; + } + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( + AllOf(SizeIs(longest_fit - 3), Each(Eq('x'))))), + RawEncodedMessage(AsString(EndsWith("x")))))); + test_sink.StartCapturingLogs(); + // x fits, three bytes from y's header fit but shouldn't be visible. + LOG(INFO).NoPrefix() << std::string(longest_fit - 3, 'x') + << StringLike{"y"}; + } + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( + AllOf(SizeIs(longest_fit - 4), Each(Eq('x'))))), + RawEncodedMessage(AsString(EndsWith("x")))))); + test_sink.StartCapturingLogs(); + // x fits, all four bytes from y's header fit but no data bytes do. We + // don't encode an empty string here because every I/O manipulator hits this + // codepath and those shouldn't leave empty strings behind. + LOG(INFO).NoPrefix() << std::string(longest_fit - 4, 'x') + << StringLike{"y"}; + } + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL( + test_sink, + Send(AllOf(ENCODED_MESSAGE(HasTwoStrsThat( + AllOf(SizeIs(longest_fit - 5), Each(Eq('x'))), Eq("y"))), + RawEncodedMessage(AsString(EndsWith("y")))))); + test_sink.StartCapturingLogs(); + // x fits, y fits exactly. + LOG(INFO).NoPrefix() << std::string(longest_fit - 5, 'x') + << StringLike{"y"}; + } } } // namespace diff --git a/absl/log/log_macro_hygiene_test.cc b/absl/log/log_macro_hygiene_test.cc index ab6461f5..dad9389e 100644 --- a/absl/log/log_macro_hygiene_test.cc +++ b/absl/log/log_macro_hygiene_test.cc @@ -139,6 +139,22 @@ TEST(LogHygieneTest, WorksWithINFODefined) { #undef INFO +#define _INFO Bogus +TEST(LogHygieneTest, WorksWithUnderscoreINFODefined) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "Hello world")) + .Times(2 + (IsOptimized ? 2 : 0)); + + test_sink.StartCapturingLogs(); + LOG(INFO) << "Hello world"; + LOG_IF(INFO, true) << "Hello world"; + + DLOG(INFO) << "Hello world"; + DLOG_IF(INFO, true) << "Hello world"; +} +#undef _INFO + TEST(LogHygieneTest, ExpressionEvaluationInLEVELSeverity) { auto i = static_cast<int>(absl::LogSeverity::kInfo); LOG(LEVEL(++i)) << "hello world"; // NOLINT diff --git a/absl/log/stripping_test.cc b/absl/log/stripping_test.cc index 83dfc2dc..d6a6606e 100644 --- a/absl/log/stripping_test.cc +++ b/absl/log/stripping_test.cc @@ -327,7 +327,7 @@ TEST_F(StrippingTest, Level) { // level that shouldn't be stripped. EXPECT_THAT(exe.get(), FileHasSubstr(needle)); } else { -#if defined(_MSC_VER) || defined(__APPLE__) +#if (defined(_MSC_VER) && !defined(__clang__)) || defined(__APPLE__) // Dead code elimination misses this case. #else // All levels should be stripped, so it doesn't matter what the severity diff --git a/absl/log/structured.h b/absl/log/structured.h new file mode 100644 index 00000000..9ad69fbd --- /dev/null +++ b/absl/log/structured.h @@ -0,0 +1,70 @@ +// Copyright 2022 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: log/structured.h +// ----------------------------------------------------------------------------- +// +// This header declares APIs supporting structured logging, allowing log +// statements to be more easily parsed, especially by automated processes. +// +// When structured logging is in use, data streamed into a `LOG` statement are +// encoded as `Value` fields in a `logging.proto.Event` protocol buffer message. +// The individual data are exposed programmatically to `LogSink`s and to the +// user via some log reading tools which are able to query the structured data +// more usefully than would be possible if each message was a single opaque +// string. These helpers allow user code to add additional structure to the +// data they stream. + +#ifndef ABSL_LOG_STRUCTURED_H_ +#define ABSL_LOG_STRUCTURED_H_ + +#include <ostream> + +#include "absl/base/config.h" +#include "absl/log/internal/structured.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// LogAsLiteral() +// +// Annotates its argument as a string literal so that structured logging +// captures it as a `literal` field instead of a `str` field (the default). +// This does not affect the text representation, only the structure. +// +// Streaming `LogAsLiteral(s)` into a `std::ostream` behaves just like streaming +// `s` directly. +// +// Using `LogAsLiteral()` is occasionally appropriate and useful when proxying +// data logged from another system or another language. For example: +// +// void Logger::LogString(absl::string_view str, absl::LogSeverity severity, +// const char *file, int line) { +// LOG(LEVEL(severity)).AtLocation(file, line) << str; +// } +// void Logger::LogStringLiteral(absl::string_view str, +// absl::LogSeverity severity, const char *file, +// int line) { +// LOG(LEVEL(severity)).AtLocation(file, line) << absl::LogAsLiteral(str); +// } +inline log_internal::AsLiteralImpl LogAsLiteral(absl::string_view s) { + return log_internal::AsLiteralImpl(s); +} + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_STRUCTURED_H_ diff --git a/absl/log/structured_test.cc b/absl/log/structured_test.cc new file mode 100644 index 00000000..490a35d8 --- /dev/null +++ b/absl/log/structured_test.cc @@ -0,0 +1,63 @@ +// +// Copyright 2022 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/log/structured.h" + +#include <ios> +#include <sstream> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/log/internal/test_matchers.h" +#include "absl/log/log.h" +#include "absl/log/scoped_mock_log.h" + +namespace { +using ::absl::log_internal::MatchesOstream; +using ::absl::log_internal::TextMessage; +using ::testing::Eq; + +auto *test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); + +// Abseil Logging library uses these by default, so we set them on the +// `std::ostream` we compare against too. +std::ios &LoggingDefaults(std::ios &str) { + str.setf(std::ios_base::showbase | std::ios_base::boolalpha | + std::ios_base::internal); + return str; +} + +TEST(StreamingFormatTest, LogAsLiteral) { + std::ostringstream stream; + const std::string not_a_literal("hello world"); + stream << LoggingDefaults << absl::LogAsLiteral(not_a_literal); + + absl::ScopedMockLog sink; + + EXPECT_CALL(sink, + Send(AllOf(TextMessage(MatchesOstream(stream)), + TextMessage(Eq("hello world")), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { literal: "hello world" })pb"))))); + + sink.StartCapturingLogs(); + LOG(INFO) << absl::LogAsLiteral(not_a_literal); +} + +} // namespace diff --git a/absl/memory/BUILD.bazel b/absl/memory/BUILD.bazel index 389aedf3..a93f54a6 100644 --- a/absl/memory/BUILD.bazel +++ b/absl/memory/BUILD.bazel @@ -50,18 +50,3 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) - -cc_test( - name = "memory_exception_safety_test", - srcs = [ - "memory_exception_safety_test.cc", - ], - copts = ABSL_TEST_COPTS, - linkopts = ABSL_DEFAULT_LINKOPTS, - deps = [ - ":memory", - "//absl/base:config", - "//absl/base:exception_safety_testing", - "@com_google_googletest//:gtest_main", - ], -) diff --git a/absl/memory/CMakeLists.txt b/absl/memory/CMakeLists.txt index 9d50e1dc..c5ed4b42 100644 --- a/absl/memory/CMakeLists.txt +++ b/absl/memory/CMakeLists.txt @@ -39,17 +39,3 @@ absl_cc_test( absl::core_headers GTest::gmock_main ) - -absl_cc_test( - NAME - memory_exception_safety_test - SRCS - "memory_exception_safety_test.cc" - COPTS - ${ABSL_TEST_COPTS} - DEPS - absl::memory - absl::config - absl::exception_safety_testing - GTest::gmock_main -) diff --git a/absl/memory/memory.h b/absl/memory/memory.h index d6332606..e5ff0e65 100644 --- a/absl/memory/memory.h +++ b/absl/memory/memory.h @@ -75,32 +75,6 @@ std::unique_ptr<T> WrapUnique(T* ptr) { return std::unique_ptr<T>(ptr); } -namespace memory_internal { - -// Traits to select proper overload and return type for `absl::make_unique<>`. -template <typename T> -struct MakeUniqueResult { - using scalar = std::unique_ptr<T>; -}; -template <typename T> -struct MakeUniqueResult<T[]> { - using array = std::unique_ptr<T[]>; -}; -template <typename T, size_t N> -struct MakeUniqueResult<T[N]> { - using invalid = void; -}; - -} // namespace memory_internal - -// gcc 4.8 has __cplusplus at 201301 but the libstdc++ shipped with it doesn't -// define make_unique. Other supported compilers either just define __cplusplus -// as 201103 but have make_unique (msvc), or have make_unique whenever -// __cplusplus > 201103 (clang). -#if (__cplusplus > 201103L || defined(_MSC_VER)) && \ - !(defined(__GLIBCXX__) && !defined(__cpp_lib_make_unique)) -using std::make_unique; -#else // ----------------------------------------------------------------------------- // Function Template: make_unique<T>() // ----------------------------------------------------------------------------- @@ -109,82 +83,18 @@ using std::make_unique; // during the construction process. `absl::make_unique<>` also avoids redundant // type declarations, by avoiding the need to explicitly use the `new` operator. // -// This implementation of `absl::make_unique<>` is designed for C++11 code and -// will be replaced in C++14 by the equivalent `std::make_unique<>` abstraction. -// `absl::make_unique<>` is designed to be 100% compatible with -// `std::make_unique<>` so that the eventual migration will involve a simple -// rename operation. +// https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique // // For more background on why `std::unique_ptr<T>(new T(a,b))` is problematic, // see Herb Sutter's explanation on // (Exception-Safe Function Calls)[https://herbsutter.com/gotw/_102/]. // (In general, reviewers should treat `new T(a,b)` with scrutiny.) // -// Example usage: -// -// auto p = make_unique<X>(args...); // 'p' is a std::unique_ptr<X> -// auto pa = make_unique<X[]>(5); // 'pa' is a std::unique_ptr<X[]> -// -// Three overloads of `absl::make_unique` are required: -// -// - For non-array T: -// -// Allocates a T with `new T(std::forward<Args> args...)`, -// forwarding all `args` to T's constructor. -// Returns a `std::unique_ptr<T>` owning that object. -// -// - For an array of unknown bounds T[]: -// -// `absl::make_unique<>` will allocate an array T of type U[] with -// `new U[n]()` and return a `std::unique_ptr<U[]>` owning that array. -// -// Note that 'U[n]()' is different from 'U[n]', and elements will be -// value-initialized. Note as well that `std::unique_ptr` will perform its -// own destruction of the array elements upon leaving scope, even though -// the array [] does not have a default destructor. -// -// NOTE: an array of unknown bounds T[] may still be (and often will be) -// initialized to have a size, and will still use this overload. E.g: -// -// auto my_array = absl::make_unique<int[]>(10); -// -// - For an array of known bounds T[N]: -// -// `absl::make_unique<>` is deleted (like with `std::make_unique<>`) as -// this overload is not useful. -// -// NOTE: an array of known bounds T[N] is not considered a useful -// construction, and may cause undefined behavior in templates. E.g: -// -// auto my_array = absl::make_unique<int[10]>(); -// -// In those cases, of course, you can still use the overload above and -// simply initialize it to its desired size: -// -// auto my_array = absl::make_unique<int[]>(10); - -// `absl::make_unique` overload for non-array types. -template <typename T, typename... Args> -typename memory_internal::MakeUniqueResult<T>::scalar make_unique( - Args&&... args) { - return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); -} - -// `absl::make_unique` overload for an array T[] of unknown bounds. -// The array allocation needs to use the `new T[size]` form and cannot take -// element constructor arguments. The `std::unique_ptr` will manage destructing -// these array elements. -template <typename T> -typename memory_internal::MakeUniqueResult<T>::array make_unique(size_t n) { - return std::unique_ptr<T>(new typename absl::remove_extent_t<T>[n]()); -} - -// `absl::make_unique` overload for an array T[N] of known bounds. -// This construction will be rejected. -template <typename T, typename... Args> -typename memory_internal::MakeUniqueResult<T>::invalid make_unique( - Args&&... /* args */) = delete; -#endif +// Historical note: Abseil once provided a C++11 compatible implementation of +// the C++14's `std::make_unique`. Now that C++11 support has been sunsetted, +// `absl::make_unique` simply uses the STL-provided implementation. New code +// should use `std::make_unique`. +using std::make_unique; // ----------------------------------------------------------------------------- // Function Template: RawPtr() diff --git a/absl/memory/memory_exception_safety_test.cc b/absl/memory/memory_exception_safety_test.cc deleted file mode 100644 index 1df72614..00000000 --- a/absl/memory/memory_exception_safety_test.cc +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2018 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/memory/memory.h" - -#include "absl/base/config.h" - -#ifdef ABSL_HAVE_EXCEPTIONS - -#include "gtest/gtest.h" -#include "absl/base/internal/exception_safety_testing.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace { - -constexpr int kLength = 50; -using Thrower = testing::ThrowingValue<testing::TypeSpec::kEverythingThrows>; - -TEST(MakeUnique, CheckForLeaks) { - constexpr int kValue = 321; - auto tester = testing::MakeExceptionSafetyTester() - .WithInitialValue(Thrower(kValue)) - // Ensures make_unique does not modify the input. The real - // test, though, is ConstructorTracker checking for leaks. - .WithContracts(testing::strong_guarantee); - - EXPECT_TRUE(tester.Test([](Thrower* thrower) { - static_cast<void>(absl::make_unique<Thrower>(*thrower)); - })); - - EXPECT_TRUE(tester.Test([](Thrower* thrower) { - static_cast<void>(absl::make_unique<Thrower>(std::move(*thrower))); - })); - - // Test T[n] overload - EXPECT_TRUE(tester.Test([&](Thrower*) { - static_cast<void>(absl::make_unique<Thrower[]>(kLength)); - })); -} - -} // namespace -ABSL_NAMESPACE_END -} // namespace absl - -#endif // ABSL_HAVE_EXCEPTIONS diff --git a/absl/memory/memory_test.cc b/absl/memory/memory_test.cc index 8ac856b1..6f01cdff 100644 --- a/absl/memory/memory_test.cc +++ b/absl/memory/memory_test.cc @@ -63,12 +63,6 @@ TEST(WrapUniqueTest, WrapUnique) { } EXPECT_EQ(0, DestructorVerifier::instance_count()); } -TEST(MakeUniqueTest, Basic) { - std::unique_ptr<std::string> p = absl::make_unique<std::string>(); - EXPECT_EQ("", *p); - p = absl::make_unique<std::string>("hi"); - EXPECT_EQ("hi", *p); -} // InitializationVerifier fills in a pattern when allocated so we can // distinguish between its default and value initialized states (without @@ -93,65 +87,6 @@ struct InitializationVerifier { int b; }; -TEST(Initialization, MakeUnique) { - auto p = absl::make_unique<InitializationVerifier>(); - - EXPECT_EQ(0, p->a); - EXPECT_EQ(0, p->b); -} - -TEST(Initialization, MakeUniqueArray) { - auto p = absl::make_unique<InitializationVerifier[]>(2); - - EXPECT_EQ(0, p[0].a); - EXPECT_EQ(0, p[0].b); - EXPECT_EQ(0, p[1].a); - EXPECT_EQ(0, p[1].b); -} - -struct MoveOnly { - MoveOnly() = default; - explicit MoveOnly(int i1) : ip1{new int{i1}} {} - MoveOnly(int i1, int i2) : ip1{new int{i1}}, ip2{new int{i2}} {} - std::unique_ptr<int> ip1; - std::unique_ptr<int> ip2; -}; - -struct AcceptMoveOnly { - explicit AcceptMoveOnly(MoveOnly m) : m_(std::move(m)) {} - MoveOnly m_; -}; - -TEST(MakeUniqueTest, MoveOnlyTypeAndValue) { - using ExpectedType = std::unique_ptr<MoveOnly>; - { - auto p = absl::make_unique<MoveOnly>(); - static_assert(std::is_same<decltype(p), ExpectedType>::value, - "unexpected return type"); - EXPECT_TRUE(!p->ip1); - EXPECT_TRUE(!p->ip2); - } - { - auto p = absl::make_unique<MoveOnly>(1); - static_assert(std::is_same<decltype(p), ExpectedType>::value, - "unexpected return type"); - EXPECT_TRUE(p->ip1 && *p->ip1 == 1); - EXPECT_TRUE(!p->ip2); - } - { - auto p = absl::make_unique<MoveOnly>(1, 2); - static_assert(std::is_same<decltype(p), ExpectedType>::value, - "unexpected return type"); - EXPECT_TRUE(p->ip1 && *p->ip1 == 1); - EXPECT_TRUE(p->ip2 && *p->ip2 == 2); - } -} - -TEST(MakeUniqueTest, AcceptMoveOnly) { - auto p = absl::make_unique<AcceptMoveOnly>(MoveOnly()); - p = std::unique_ptr<AcceptMoveOnly>(new AcceptMoveOnly(MoveOnly())); -} - struct ArrayWatch { void* operator new[](size_t n) { allocs().push_back(n); @@ -164,38 +99,6 @@ struct ArrayWatch { } }; -TEST(Make_UniqueTest, Array) { - // Ensure state is clean before we start so that these tests - // are order-agnostic. - ArrayWatch::allocs().clear(); - - auto p = absl::make_unique<ArrayWatch[]>(5); - static_assert(std::is_same<decltype(p), std::unique_ptr<ArrayWatch[]>>::value, - "unexpected return type"); - EXPECT_THAT(ArrayWatch::allocs(), ElementsAre(5 * sizeof(ArrayWatch))); -} - -TEST(Make_UniqueTest, NotAmbiguousWithStdMakeUnique) { - // Ensure that absl::make_unique is not ambiguous with std::make_unique. - // In C++14 mode, the below call to make_unique has both types as candidates. - struct TakesStdType { - explicit TakesStdType(const std::vector<int>& vec) {} - }; - using absl::make_unique; - (void)make_unique<TakesStdType>(std::vector<int>()); -} - -#if 0 -// These tests shouldn't compile. -TEST(MakeUniqueTestNC, AcceptMoveOnlyLvalue) { - auto m = MoveOnly(); - auto p = absl::make_unique<AcceptMoveOnly>(m); -} -TEST(MakeUniqueTestNC, KnownBoundArray) { - auto p = absl::make_unique<ArrayWatch[5]>(); -} -#endif - TEST(RawPtrTest, RawPointer) { int i = 5; EXPECT_EQ(&i, absl::RawPtr(&i)); diff --git a/absl/meta/type_traits.h b/absl/meta/type_traits.h index 6e6001fe..dd9de42f 100644 --- a/absl/meta/type_traits.h +++ b/absl/meta/type_traits.h @@ -114,18 +114,6 @@ struct VoidTImpl { using type = void; }; -// This trick to retrieve a default alignment is necessary for our -// implementation of aligned_storage_t to be consistent with any implementation -// of std::aligned_storage. -template <size_t Len, typename T = std::aligned_storage<Len>> -struct default_alignment_of_aligned_storage; - -template <size_t Len, size_t Align> -struct default_alignment_of_aligned_storage<Len, - std::aligned_storage<Len, Align>> { - static constexpr size_t value = Align; -}; - //////////////////////////////// // Library Fundamentals V2 TS // //////////////////////////////// @@ -642,6 +630,21 @@ using remove_extent_t = typename std::remove_extent<T>::type; template <typename T> using remove_all_extents_t = typename std::remove_all_extents<T>::type; +namespace type_traits_internal { +// This trick to retrieve a default alignment is necessary for our +// implementation of aligned_storage_t to be consistent with any +// implementation of std::aligned_storage. +template <size_t Len, typename T = std::aligned_storage<Len>> +struct default_alignment_of_aligned_storage; + +template <size_t Len, size_t Align> +struct default_alignment_of_aligned_storage< + Len, std::aligned_storage<Len, Align>> { + static constexpr size_t value = Align; +}; +} // namespace type_traits_internal + +// TODO(b/260219225): std::aligned_storage(_t) is deprecated in C++23. template <size_t Len, size_t Align = type_traits_internal:: default_alignment_of_aligned_storage<Len>::value> using aligned_storage_t = typename std::aligned_storage<Len, Align>::type; diff --git a/absl/numeric/BUILD.bazel b/absl/numeric/BUILD.bazel index eaa27dfd..ec0b8701 100644 --- a/absl/numeric/BUILD.bazel +++ b/absl/numeric/BUILD.bazel @@ -95,7 +95,6 @@ cc_test( deps = [ ":int128", "//absl/base", - "//absl/base:core_headers", "//absl/hash:hash_testing", "//absl/meta:type_traits", "@com_google_googletest//:gtest_main", diff --git a/absl/numeric/CMakeLists.txt b/absl/numeric/CMakeLists.txt index 26df5cf7..384c0fc0 100644 --- a/absl/numeric/CMakeLists.txt +++ b/absl/numeric/CMakeLists.txt @@ -70,7 +70,6 @@ absl_cc_test( DEPS absl::int128 absl::base - absl::core_headers absl::hash_testing absl::type_traits GTest::gmock_main diff --git a/absl/numeric/bits_benchmark.cc b/absl/numeric/bits_benchmark.cc index 719bfa81..1b7f35dd 100644 --- a/absl/numeric/bits_benchmark.cc +++ b/absl/numeric/bits_benchmark.cc @@ -34,7 +34,7 @@ static void BM_bitwidth(benchmark::State& state) { values.push_back(absl::Uniform<T>(rng, 0, std::numeric_limits<T>::max())); } - while (state.KeepRunningBatch(count)) { + while (state.KeepRunningBatch(static_cast<int64_t>(count))) { for (size_t i = 0; i < count; ++i) { benchmark::DoNotOptimize(values[i]); } @@ -56,7 +56,7 @@ static void BM_bitwidth_nonzero(benchmark::State& state) { values.push_back(absl::Uniform<T>(rng, 1, std::numeric_limits<T>::max())); } - while (state.KeepRunningBatch(count)) { + while (state.KeepRunningBatch(static_cast<int64_t>(count))) { for (size_t i = 0; i < count; ++i) { const T value = values[i]; ABSL_ASSUME(value > 0); diff --git a/absl/numeric/int128_stream_test.cc b/absl/numeric/int128_stream_test.cc index 479ad66c..81d2adee 100644 --- a/absl/numeric/int128_stream_test.cc +++ b/absl/numeric/int128_stream_test.cc @@ -76,16 +76,6 @@ std::string StreamFormatToString(std::ios_base::fmtflags flags, return msg.str(); } -void CheckUint128Case(const Uint128TestCase& test_case) { - std::ostringstream os; - os.flags(test_case.flags); - os.width(test_case.width); - os.fill(kFill); - os << test_case.value; - SCOPED_TRACE(StreamFormatToString(test_case.flags, test_case.width)); - EXPECT_EQ(test_case.expected, os.str()); -} - constexpr std::ios::fmtflags kDec = std::ios::dec; constexpr std::ios::fmtflags kOct = std::ios::oct; constexpr std::ios::fmtflags kHex = std::ios::hex; @@ -96,6 +86,16 @@ constexpr std::ios::fmtflags kUpper = std::ios::uppercase; constexpr std::ios::fmtflags kBase = std::ios::showbase; constexpr std::ios::fmtflags kPos = std::ios::showpos; +void CheckUint128Case(const Uint128TestCase& test_case) { + std::ostringstream os; + os.flags(test_case.flags); + os.width(test_case.width); + os.fill(kFill); + os << test_case.value; + SCOPED_TRACE(StreamFormatToString(test_case.flags, test_case.width)); + EXPECT_EQ(os.str(), test_case.expected); +} + TEST(Uint128, OStreamValueTest) { CheckUint128Case({1, kDec, /*width = */ 0, "1"}); CheckUint128Case({1, kOct, /*width = */ 0, "1"}); @@ -161,7 +161,7 @@ void CheckInt128Case(const Int128TestCase& test_case) { os.fill(kFill); os << test_case.value; SCOPED_TRACE(StreamFormatToString(test_case.flags, test_case.width)); - EXPECT_EQ(test_case.expected, os.str()); + EXPECT_EQ(os.str(), test_case.expected); } TEST(Int128, OStreamValueTest) { @@ -194,35 +194,33 @@ TEST(Int128, OStreamValueTest) { {absl::MakeInt128(1, 0), kHex, /*width = */ 0, "10000000000000000"}); CheckInt128Case({absl::MakeInt128(std::numeric_limits<int64_t>::max(), std::numeric_limits<uint64_t>::max()), - std::ios::dec, /*width = */ 0, + kDec, /*width = */ 0, "170141183460469231731687303715884105727"}); CheckInt128Case({absl::MakeInt128(std::numeric_limits<int64_t>::max(), std::numeric_limits<uint64_t>::max()), - std::ios::oct, /*width = */ 0, + kOct, /*width = */ 0, "1777777777777777777777777777777777777777777"}); CheckInt128Case({absl::MakeInt128(std::numeric_limits<int64_t>::max(), std::numeric_limits<uint64_t>::max()), - std::ios::hex, /*width = */ 0, - "7fffffffffffffffffffffffffffffff"}); + kHex, /*width = */ 0, "7fffffffffffffffffffffffffffffff"}); CheckInt128Case({absl::MakeInt128(std::numeric_limits<int64_t>::min(), 0), - std::ios::dec, /*width = */ 0, + kDec, /*width = */ 0, "-170141183460469231731687303715884105728"}); CheckInt128Case({absl::MakeInt128(std::numeric_limits<int64_t>::min(), 0), - std::ios::oct, /*width = */ 0, + kOct, /*width = */ 0, "2000000000000000000000000000000000000000000"}); CheckInt128Case({absl::MakeInt128(std::numeric_limits<int64_t>::min(), 0), - std::ios::hex, /*width = */ 0, - "80000000000000000000000000000000"}); - CheckInt128Case({-1, std::ios::dec, /*width = */ 0, "-1"}); - CheckInt128Case({-1, std::ios::oct, /*width = */ 0, + kHex, /*width = */ 0, "80000000000000000000000000000000"}); + CheckInt128Case({-1, kDec, /*width = */ 0, "-1"}); + CheckInt128Case({-1, kOct, /*width = */ 0, "3777777777777777777777777777777777777777777"}); CheckInt128Case( - {-1, std::ios::hex, /*width = */ 0, "ffffffffffffffffffffffffffffffff"}); - CheckInt128Case({-12345, std::ios::dec, /*width = */ 0, "-12345"}); - CheckInt128Case({-12345, std::ios::oct, /*width = */ 0, + {-1, kHex, /*width = */ 0, "ffffffffffffffffffffffffffffffff"}); + CheckInt128Case({-12345, kDec, /*width = */ 0, "-12345"}); + CheckInt128Case({-12345, kOct, /*width = */ 0, "3777777777777777777777777777777777777747707"}); - CheckInt128Case({-12345, std::ios::hex, /*width = */ 0, - "ffffffffffffffffffffffffffffcfc7"}); + CheckInt128Case( + {-12345, kHex, /*width = */ 0, "ffffffffffffffffffffffffffffcfc7"}); } std::vector<Int128TestCase> GetInt128FormatCases(); diff --git a/absl/profiling/internal/sample_recorder.h b/absl/profiling/internal/sample_recorder.h index ef1489b1..371f6c47 100644 --- a/absl/profiling/internal/sample_recorder.h +++ b/absl/profiling/internal/sample_recorder.h @@ -199,6 +199,14 @@ T* SampleRecorder<T>::Register(Targs&&... args) { sample = new T(); { absl::MutexLock sample_lock(&sample->init_mu); + // If flag initialization happens to occur (perhaps in another thread) + // while in this block, it will lock `graveyard_` which is usually always + // locked before any sample. This will appear as a lock inversion. + // However, this code is run exactly once per sample, and this sample + // cannot be accessed until after it is returned from this method. This + // means that this lock state can never be recreated, so we can safely + // inform the deadlock detector to ignore it. + sample->init_mu.ForgetDeadlockInfo(); sample->PrepareForSampling(std::forward<Targs>(args)...); } PushNew(sample); diff --git a/absl/random/internal/BUILD.bazel b/absl/random/internal/BUILD.bazel index fd5b6195..f8419ec9 100644 --- a/absl/random/internal/BUILD.bazel +++ b/absl/random/internal/BUILD.bazel @@ -24,9 +24,11 @@ load( "absl_random_randen_copts_init", ) -package(default_visibility = [ +default_package_visibility = [ "//absl/random:__pkg__", -]) +] + +package(default_visibility = default_package_visibility) licenses(["notice"]) @@ -248,6 +250,8 @@ cc_library( hdrs = ["randen_engine.h"], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = default_package_visibility + [ + ], deps = [ ":iostream_state_saver", ":randen", diff --git a/absl/random/uniform_int_distribution_test.cc b/absl/random/uniform_int_distribution_test.cc index 276d72ad..a830117a 100644 --- a/absl/random/uniform_int_distribution_test.cc +++ b/absl/random/uniform_int_distribution_test.cc @@ -19,6 +19,7 @@ #include <iterator> #include <random> #include <sstream> +#include <string> #include <vector> #include "gmock/gmock.h" @@ -136,7 +137,7 @@ TYPED_TEST(UniformIntDistributionTest, TestMoments) { typename absl::uniform_int_distribution<TypeParam>::param_type; // We use a fixed bit generator for distribution accuracy tests. This allows - // these tests to be deterministic, while still testing the qualify of the + // these tests to be deterministic, while still testing the quality of the // implementation. absl::random_internal::pcg64_2018_engine rng{0x2B7E151628AED2A6}; @@ -172,7 +173,7 @@ TYPED_TEST(UniformIntDistributionTest, ChiSquaredTest50) { using absl::random_internal::kChiSquared; constexpr size_t kTrials = 1000; - constexpr int kBuckets = 50; // inclusive, so actally +1 + constexpr int kBuckets = 50; // inclusive, so actually +1 constexpr double kExpected = static_cast<double>(kTrials) / static_cast<double>(kBuckets); @@ -184,7 +185,7 @@ TYPED_TEST(UniformIntDistributionTest, ChiSquaredTest50) { const TypeParam max = min + kBuckets; // We use a fixed bit generator for distribution accuracy tests. This allows - // these tests to be deterministic, while still testing the qualify of the + // these tests to be deterministic, while still testing the quality of the // implementation. absl::random_internal::pcg64_2018_engine rng{0x2B7E151628AED2A6}; diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index 809f566d..d86f59f0 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -43,6 +43,7 @@ cc_library( "internal/stl_type_traits.h", "internal/str_join_internal.h", "internal/str_split_internal.h", + "internal/stringify_sink.cc", "internal/stringify_sink.h", "match.cc", "numbers.cc", @@ -57,6 +58,7 @@ cc_library( "charconv.h", "escaping.h", "internal/damerau_levenshtein_distance.h", + "internal/has_absl_stringify.h", "internal/string_constant.h", "match.h", "numbers.h", @@ -322,6 +324,7 @@ cc_library( "//absl/container:compressed_tuple", "//absl/container:inlined_vector", "//absl/container:layout", + "//absl/crc:crc_cord_state", "//absl/functional:function_ref", "//absl/meta:type_traits", "//absl/types:span", @@ -403,6 +406,7 @@ cc_test( ":cord_internal", ":cord_rep_test_util", "//absl/base:config", + "//absl/crc:crc_cord_state", "@com_google_googletest//:gtest_main", ], ) @@ -461,6 +465,7 @@ cc_library( "//absl/base:raw_logging_internal", "//absl/container:fixed_array", "//absl/container:inlined_vector", + "//absl/crc:crc_cord_state", "//absl/functional:function_ref", "//absl/meta:type_traits", "//absl/numeric:bits", @@ -657,6 +662,7 @@ cc_test( ":cordz_update_scope", ":cordz_update_tracker", "//absl/base:config", + "//absl/crc:crc_cord_state", "//absl/synchronization", "//absl/synchronization:thread_pool", "@com_google_googletest//:gtest_main", @@ -1148,6 +1154,7 @@ cc_library( "internal/str_format/arg.h", "internal/str_format/bind.h", "internal/str_format/checker.h", + "internal/str_format/constexpr_parser.h", "internal/str_format/extension.h", "internal/str_format/float_conversion.h", "internal/str_format/output.h", diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index 30dedcf5..a23b34a1 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt @@ -23,6 +23,7 @@ absl_cc_library( "escaping.h" "internal/damerau_levenshtein_distance.h" "internal/string_constant.h" + "internal/has_absl_stringify.h" "match.h" "numbers.h" "str_cat.h" @@ -44,6 +45,7 @@ absl_cc_library( "internal/memutil.cc" "internal/memutil.h" "internal/stringify_sink.h" + "internal/stringify_sink.cc" "internal/stl_type_traits.h" "internal/str_join_internal.h" "internal/str_split_internal.h" @@ -411,6 +413,7 @@ absl_cc_library( "internal/str_format/arg.h" "internal/str_format/bind.h" "internal/str_format/checker.h" + "internal/str_format/constexpr_parser.h" "internal/str_format/extension.h" "internal/str_format/float_conversion.h" "internal/str_format/output.h" @@ -602,6 +605,7 @@ absl_cc_library( absl::compressed_tuple absl::config absl::core_headers + absl::crc_cord_state absl::endian absl::inlined_vector absl::layout @@ -780,6 +784,7 @@ absl_cc_test( absl::cordz_statistics absl::cordz_update_scope absl::cordz_update_tracker + absl::crc_cord_state absl::thread_pool GTest::gmock_main ) @@ -879,6 +884,7 @@ absl_cc_library( absl::cordz_update_scope absl::cordz_update_tracker absl::core_headers + absl::crc_cord_state absl::endian absl::fixed_array absl::function_ref @@ -1051,6 +1057,7 @@ absl_cc_test( absl::config absl::cord_internal absl::cord_rep_test_util + absl::crc_cord_state GTest::gmock_main ) diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index 66f45fef..0bac4164 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc @@ -35,6 +35,7 @@ #include "absl/base/port.h" #include "absl/container/fixed_array.h" #include "absl/container/inlined_vector.h" +#include "absl/crc/internal/crc_cord_state.h" #include "absl/strings/cord_buffer.h" #include "absl/strings/escaping.h" #include "absl/strings/internal/cord_data_edge.h" @@ -420,6 +421,7 @@ Cord& Cord::operator=(absl::string_view src) { // we keep it here to make diffs easier. void Cord::InlineRep::AppendArray(absl::string_view src, MethodIdentifier method) { + MaybeRemoveEmptyCrcNode(); if (src.empty()) return; // memcpy(_, nullptr, 0) is undefined. size_t appended = 0; @@ -479,6 +481,10 @@ inline CordRep* Cord::TakeRep() && { template <typename C> inline void Cord::AppendImpl(C&& src) { auto constexpr method = CordzUpdateTracker::kAppendCord; + + contents_.MaybeRemoveEmptyCrcNode(); + if (src.empty()) return; + if (empty()) { // Since destination is empty, we can avoid allocating a node, if (src.contents_.is_tree()) { @@ -591,6 +597,9 @@ void Cord::Append(T&& src) { template void Cord::Append(std::string&& src); void Cord::Prepend(const Cord& src) { + contents_.MaybeRemoveEmptyCrcNode(); + if (src.empty()) return; + CordRep* src_tree = src.contents_.tree(); if (src_tree != nullptr) { CordRep::Ref(src_tree); @@ -605,7 +614,9 @@ void Cord::Prepend(const Cord& src) { } void Cord::PrependArray(absl::string_view src, MethodIdentifier method) { + contents_.MaybeRemoveEmptyCrcNode(); if (src.empty()) return; // memcpy(_, nullptr, 0) is undefined. + if (!contents_.is_tree()) { size_t cur_size = contents_.inline_size(); if (cur_size + src.size() <= InlineRep::kMaxInline) { @@ -665,6 +676,7 @@ void Cord::RemovePrefix(size_t n) { ABSL_INTERNAL_CHECK(n <= size(), absl::StrCat("Requested prefix size ", n, " exceeds Cord's size ", size())); + contents_.MaybeRemoveEmptyCrcNode(); CordRep* tree = contents_.tree(); if (tree == nullptr) { contents_.remove_prefix(n); @@ -695,6 +707,7 @@ void Cord::RemoveSuffix(size_t n) { ABSL_INTERNAL_CHECK(n <= size(), absl::StrCat("Requested suffix size ", n, " exceeds Cord's size ", size())); + contents_.MaybeRemoveEmptyCrcNode(); CordRep* tree = contents_.tree(); if (tree == nullptr) { contents_.reduce_size(n); @@ -842,26 +855,44 @@ inline absl::string_view Cord::InlineRep::FindFlatStartPiece() const { return absl::string_view(node->external()->base + offset, length); } -void Cord::SetExpectedChecksum(uint32_t crc) { +void Cord::SetCrcCordState(crc_internal::CrcCordState state) { auto constexpr method = CordzUpdateTracker::kSetExpectedChecksum; - if (empty()) return; - - if (!contents_.is_tree()) { + if (empty()) { + contents_.MaybeRemoveEmptyCrcNode(); + CordRep* rep = CordRepCrc::New(nullptr, std::move(state)); + contents_.EmplaceTree(rep, method); + } else if (!contents_.is_tree()) { CordRep* rep = contents_.MakeFlatWithExtraCapacity(0); - rep = CordRepCrc::New(rep, crc); + rep = CordRepCrc::New(rep, std::move(state)); contents_.EmplaceTree(rep, method); } else { const CordzUpdateScope scope(contents_.data_.cordz_info(), method); - CordRep* rep = CordRepCrc::New(contents_.data_.as_tree(), crc); + CordRep* rep = CordRepCrc::New(contents_.data_.as_tree(), std::move(state)); contents_.SetTree(rep, scope); } } +void Cord::SetExpectedChecksum(uint32_t crc) { + // Construct a CrcCordState with a single chunk. + crc_internal::CrcCordState state; + state.mutable_rep()->prefix_crc.push_back( + crc_internal::CrcCordState::PrefixCrc(size(), absl::crc32c_t{crc})); + SetCrcCordState(std::move(state)); +} + +const crc_internal::CrcCordState* Cord::MaybeGetCrcCordState() const { + if (!contents_.is_tree() || !contents_.tree()->IsCrc()) { + return nullptr; + } + return &contents_.tree()->crc()->crc_cord_state; +} + absl::optional<uint32_t> Cord::ExpectedChecksum() const { if (!contents_.is_tree() || !contents_.tree()->IsCrc()) { return absl::nullopt; } - return contents_.tree()->crc()->crc; + return static_cast<uint32_t>( + contents_.tree()->crc()->crc_cord_state.Checksum()); } inline int Cord::CompareSlowPath(absl::string_view rhs, size_t compared_size, @@ -929,6 +960,7 @@ inline int Cord::CompareSlowPath(const Cord& rhs, size_t compared_size, } inline absl::string_view Cord::GetFirstChunk(const Cord& c) { + if (c.empty()) return {}; return c.contents_.FindFlatStartPiece(); } inline absl::string_view Cord::GetFirstChunk(absl::string_view sv) { @@ -1166,6 +1198,10 @@ absl::string_view Cord::FlattenSlowPath() { /* static */ bool Cord::GetFlatAux(CordRep* rep, absl::string_view* fragment) { assert(rep != nullptr); + if (rep->length == 0) { + *fragment = absl::string_view(); + return true; + } rep = cord_internal::SkipCrcNode(rep); if (rep->IsFlat()) { *fragment = absl::string_view(rep->flat()->Data(), rep->length); @@ -1197,6 +1233,7 @@ absl::string_view Cord::FlattenSlowPath() { absl::cord_internal::CordRep* rep, absl::FunctionRef<void(absl::string_view)> callback) { assert(rep != nullptr); + if (rep->length == 0) return; rep = cord_internal::SkipCrcNode(rep); if (rep->IsBtree()) { @@ -1230,8 +1267,12 @@ static void DumpNode(CordRep* rep, bool include_data, std::ostream* os, if (include_data) *os << static_cast<void*>(rep); *os << "]"; *os << " " << std::setw(indent) << ""; - if (rep->IsCrc()) { - *os << "CRC crc=" << rep->crc()->crc << "\n"; + bool leaf = false; + if (rep == nullptr) { + *os << "NULL\n"; + leaf = true; + } else if (rep->IsCrc()) { + *os << "CRC crc=" << rep->crc()->crc_cord_state.Checksum() << "\n"; indent += kIndentStep; rep = rep->crc()->child; } else if (rep->IsSubstring()) { @@ -1239,6 +1280,7 @@ static void DumpNode(CordRep* rep, bool include_data, std::ostream* os, indent += kIndentStep; rep = rep->substring()->child; } else { // Leaf or ring + leaf = true; if (rep->IsExternal()) { *os << "EXTERNAL ["; if (include_data) @@ -1252,6 +1294,8 @@ static void DumpNode(CordRep* rep, bool include_data, std::ostream* os, } else { CordRepBtree::Dump(rep, /*label=*/ "", include_data, *os); } + } + if (leaf) { if (stack.empty()) break; rep = stack.back(); stack.pop_back(); @@ -1297,11 +1341,14 @@ static bool VerifyNode(CordRep* root, CordRep* start_node, node->substring()->child->length, ReportError(root, node)); } else if (node->IsCrc()) { - ABSL_INTERNAL_CHECK(node->crc()->child != nullptr, - ReportError(root, node)); - ABSL_INTERNAL_CHECK(node->crc()->length == node->crc()->child->length, - ReportError(root, node)); - worklist.push_back(node->crc()->child); + ABSL_INTERNAL_CHECK( + node->crc()->child != nullptr || node->crc()->length == 0, + ReportError(root, node)); + if (node->crc()->child != nullptr) { + ABSL_INTERNAL_CHECK(node->crc()->length == node->crc()->child->length, + ReportError(root, node)); + worklist.push_back(node->crc()->child); + } } } while (!worklist.empty()); return true; diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 88e1c85d..1349b115 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -76,6 +76,7 @@ #include "absl/base/macros.h" #include "absl/base/port.h" #include "absl/container/inlined_vector.h" +#include "absl/crc/internal/crc_cord_state.h" #include "absl/functional/function_ref.h" #include "absl/meta/type_traits.h" #include "absl/strings/cord_analysis.h" @@ -926,6 +927,13 @@ class Cord { void set_inline_size(size_t size) { data_.set_inline_size(size); } size_t inline_size() const { return data_.inline_size(); } + // Empty cords that carry a checksum have a CordRepCrc node with a null + // child node. The code can avoid lots of special cases where it would + // otherwise transition from tree to inline storage if we just remove the + // CordRepCrc node before mutations. Must never be called inside a + // CordzUpdateScope since it untracks the cordz info. + void MaybeRemoveEmptyCrcNode(); + cord_internal::InlineData data_; }; InlineRep contents_; @@ -995,6 +1003,10 @@ class Cord { }); return H::combine(combiner.finalize(std::move(hash_state)), size()); } + + friend class CrcCord; + void SetCrcCordState(crc_internal::CrcCordState state); + const crc_internal::CrcCordState* MaybeGetCrcCordState() const; }; ABSL_NAMESPACE_END @@ -1236,6 +1248,18 @@ inline void Cord::InlineRep::CopyToArray(char* dst) const { cord_internal::SmallMemmove(dst, data_.as_chars(), n); } +inline void Cord::InlineRep::MaybeRemoveEmptyCrcNode() { + CordRep* rep = tree(); + if (rep == nullptr || ABSL_PREDICT_TRUE(rep->length > 0)) { + return; + } + assert(rep->IsCrc()); + assert(rep->crc()->child == nullptr); + CordzInfo::MaybeUntrackCord(cordz_info()); + CordRep::Unref(rep); + ResetToEmpty(); +} + constexpr inline Cord::Cord() noexcept {} inline Cord::Cord(absl::string_view src) @@ -1285,7 +1309,7 @@ inline size_t Cord::size() const { return contents_.size(); } -inline bool Cord::empty() const { return contents_.empty(); } +inline bool Cord::empty() const { return size() == 0; } inline size_t Cord::EstimatedMemoryUsage( CordMemoryAccounting accounting_method) const { @@ -1411,7 +1435,11 @@ inline Cord::ChunkIterator::ChunkIterator(cord_internal::CordRep* tree) { inline Cord::ChunkIterator::ChunkIterator(const Cord* cord) { if (CordRep* tree = cord->contents_.tree()) { bytes_remaining_ = tree->length; - InitTree(tree); + if (ABSL_PREDICT_TRUE(bytes_remaining_ != 0)) { + InitTree(tree); + } else { + current_chunk_ = {}; + } } else { bytes_remaining_ = cord->contents_.inline_size(); current_chunk_ = {cord->contents_.data(), bytes_remaining_}; @@ -1580,7 +1608,7 @@ inline void Cord::ForEachChunk( if (rep == nullptr) { callback(absl::string_view(contents_.data(), contents_.size())); } else { - return ForEachChunkAux(rep, callback); + ForEachChunkAux(rep, callback); } } diff --git a/absl/strings/cord_test.cc b/absl/strings/cord_test.cc index 1fc4be6e..a4fa8955 100644 --- a/absl/strings/cord_test.cc +++ b/absl/strings/cord_test.cc @@ -1988,6 +1988,12 @@ TEST_P(CordTest, HugeCord) { // Tests that Append() works ok when handed a self reference TEST_P(CordTest, AppendSelf) { + // Test the empty case. + absl::Cord empty; + MaybeHarden(empty); + empty.Append(empty); + ASSERT_EQ(empty, ""); + // We run the test until data is ~16K // This guarantees it covers small, medium and large data. std::string control_data = "Abc"; @@ -2712,7 +2718,7 @@ class CordMutator { // clang-format off // This array is constant-initialized in conformant compilers. -CordMutator cord_mutators[] ={ +CordMutator cord_mutators[] = { {"clear", [](absl::Cord& c) { c.Clear(); }}, {"overwrite", [](absl::Cord& c) { c = "overwritten"; }}, { @@ -2742,6 +2748,25 @@ CordMutator cord_mutators[] ={ [](absl::Cord& c) { c.RemoveSuffix(c.size() / 2); } }, { + "append empty string", + [](absl::Cord& c) { c.Append(""); }, + [](absl::Cord& c) { } + }, + { + "append empty cord", + [](absl::Cord& c) { c.Append(absl::Cord()); }, + [](absl::Cord& c) { } + }, + { + "append empty checksummed cord", + [](absl::Cord& c) { + absl::Cord to_append; + to_append.SetExpectedChecksum(999); + c.Append(to_append); + }, + [](absl::Cord& c) { } + }, + { "prepend string", [](absl::Cord& c) { c.Prepend("9876543210"); }, [](absl::Cord& c) { c.RemovePrefix(10); } @@ -2763,12 +2788,33 @@ CordMutator cord_mutators[] ={ [](absl::Cord& c) { c.RemovePrefix(10); } }, { + "prepend empty string", + [](absl::Cord& c) { c.Prepend(""); }, + [](absl::Cord& c) { } + }, + { + "prepend empty cord", + [](absl::Cord& c) { c.Prepend(absl::Cord()); }, + [](absl::Cord& c) { } + }, + { + "prepend empty checksummed cord", + [](absl::Cord& c) { + absl::Cord to_prepend; + to_prepend.SetExpectedChecksum(999); + c.Prepend(to_prepend); + }, + [](absl::Cord& c) { } + }, + { "prepend self", [](absl::Cord& c) { c.Prepend(c); }, [](absl::Cord& c) { c.RemovePrefix(c.size() / 2); } }, - {"remove prefix", [](absl::Cord& c) { c.RemovePrefix(2); }}, - {"remove suffix", [](absl::Cord& c) { c.RemoveSuffix(2); }}, + {"remove prefix", [](absl::Cord& c) { c.RemovePrefix(c.size() / 2); }}, + {"remove suffix", [](absl::Cord& c) { c.RemoveSuffix(c.size() / 2); }}, + {"remove 0-prefix", [](absl::Cord& c) { c.RemovePrefix(0); }}, + {"remove 0-suffix", [](absl::Cord& c) { c.RemoveSuffix(0); }}, {"subcord", [](absl::Cord& c) { c = c.Subcord(1, c.size() - 2); }}, { "swap inline", @@ -2810,6 +2856,12 @@ TEST_P(CordTest, ExpectedChecksum) { EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); EXPECT_EQ(c1, base_value); + // Test that setting an expected checksum again doesn't crash or leak + // memory. + c1.SetExpectedChecksum(12345); + EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); + EXPECT_EQ(c1, base_value); + // CRC persists through copies, assignments, and moves: absl::Cord c1_copy_construct = c1; EXPECT_EQ(c1_copy_construct.ExpectedChecksum().value_or(0), 12345); @@ -2834,6 +2886,13 @@ TEST_P(CordTest, ExpectedChecksum) { c2.SetExpectedChecksum(24680); mutator.Mutate(c2); + + if (c1 == c2) { + // Not a mutation (for example, appending the empty string). + // Whether the checksum is removed is not defined. + continue; + } + EXPECT_EQ(c2.ExpectedChecksum(), absl::nullopt); if (mutator.CanUndo()) { @@ -2903,3 +2962,98 @@ TEST_P(CordTest, ExpectedChecksum) { } } } + +// Test the special cases encountered with an empty checksummed cord. +TEST_P(CordTest, ChecksummedEmptyCord) { + absl::Cord c1; + EXPECT_FALSE(c1.ExpectedChecksum().has_value()); + + // Setting an expected checksum works. + c1.SetExpectedChecksum(12345); + EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); + EXPECT_EQ(c1, ""); + EXPECT_TRUE(c1.empty()); + + // Test that setting an expected checksum again doesn't crash or leak memory. + c1.SetExpectedChecksum(12345); + EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); + EXPECT_EQ(c1, ""); + EXPECT_TRUE(c1.empty()); + + // CRC persists through copies, assignments, and moves: + absl::Cord c1_copy_construct = c1; + EXPECT_EQ(c1_copy_construct.ExpectedChecksum().value_or(0), 12345); + + absl::Cord c1_copy_assign; + c1_copy_assign = c1; + EXPECT_EQ(c1_copy_assign.ExpectedChecksum().value_or(0), 12345); + + absl::Cord c1_move(std::move(c1_copy_assign)); + EXPECT_EQ(c1_move.ExpectedChecksum().value_or(0), 12345); + + EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); + + // A CRC Cord compares equal to its non-CRC value. + EXPECT_EQ(c1, absl::Cord()); + + for (const CordMutator& mutator : cord_mutators) { + SCOPED_TRACE(mutator.Name()); + + // Exercise mutating an empty checksummed cord to catch crashes and exercise + // memory sanitizers. + absl::Cord c2; + c2.SetExpectedChecksum(24680); + mutator.Mutate(c2); + + if (c2.empty()) { + // Not a mutation + continue; + } + EXPECT_EQ(c2.ExpectedChecksum(), absl::nullopt); + + if (mutator.CanUndo()) { + mutator.Undo(c2); + } + } + + absl::Cord c3; + c3.SetExpectedChecksum(999); + const absl::Cord& cc3 = c3; + + // Test that all cord reading operations function in the face of an + // expected checksum. + EXPECT_TRUE(cc3.StartsWith("")); + EXPECT_TRUE(cc3.EndsWith("")); + EXPECT_TRUE(cc3.empty()); + EXPECT_EQ(cc3, ""); + EXPECT_EQ(cc3, absl::Cord()); + EXPECT_EQ(cc3.size(), 0); + EXPECT_EQ(cc3.Compare(absl::Cord()), 0); + EXPECT_EQ(cc3.Compare(c1), 0); + EXPECT_EQ(cc3.Compare(cc3), 0); + EXPECT_EQ(cc3.Compare(""), 0); + EXPECT_EQ(cc3.Compare("wxyz"), -1); + EXPECT_EQ(cc3.Compare(absl::Cord("wxyz")), -1); + EXPECT_EQ(absl::Cord("wxyz").Compare(cc3), 1); + EXPECT_EQ(std::string(cc3), ""); + + std::string dest; + absl::CopyCordToString(cc3, &dest); + EXPECT_EQ(dest, ""); + + for (absl::string_view chunk : cc3.Chunks()) { // NOLINT(unreachable loop) + static_cast<void>(chunk); + GTEST_FAIL() << "no chunks expected"; + } + EXPECT_TRUE(cc3.chunk_begin() == cc3.chunk_end()); + + for (char ch : cc3.Chars()) { // NOLINT(unreachable loop) + static_cast<void>(ch); + GTEST_FAIL() << "no chars expected"; + } + EXPECT_TRUE(cc3.char_begin() == cc3.char_end()); + + EXPECT_EQ(cc3.TryFlat(), ""); + EXPECT_EQ(absl::HashOf(c3), absl::HashOf(absl::Cord())); + EXPECT_EQ(absl::HashOf(c3), absl::HashOf(absl::string_view())); +} diff --git a/absl/strings/escaping.cc b/absl/strings/escaping.cc index 7d97944e..93966846 100644 --- a/absl/strings/escaping.cc +++ b/absl/strings/escaping.cc @@ -784,9 +784,7 @@ template <typename String> bool Base64UnescapeInternal(const char* src, size_t slen, String* dest, const signed char* unbase64) { // 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: - // https://datatracker.ietf.org/doc/html/rfc3548 + // 4 characters. Any leftover chars are added directly for good measure. const size_t dest_len = 3 * (slen / 4) + (slen % 4); strings_internal::STLStringResizeUninitialized(dest, dest_len); diff --git a/absl/strings/escaping.h b/absl/strings/escaping.h index f5ca26c5..d2c18e02 100644 --- a/absl/strings/escaping.h +++ b/absl/strings/escaping.h @@ -117,11 +117,29 @@ std::string Utf8SafeCEscape(absl::string_view src); // conversion. std::string Utf8SafeCHexEscape(absl::string_view src); +// Base64Escape() +// +// Encodes a `src` string into a base64-encoded 'dest' string with padding +// characters. This function conforms with RFC 4648 section 4 (base64) and RFC +// 2045. See also CalculateBase64EscapedLen(). +void Base64Escape(absl::string_view src, std::string* dest); +std::string Base64Escape(absl::string_view src); + +// WebSafeBase64Escape() +// +// Encodes a `src` string into a base64 string, like Base64Escape() does, but +// outputs '-' instead of '+' and '_' instead of '/', and does not pad 'dest'. +// This function conforms with RFC 4648 section 5 (base64url). +void WebSafeBase64Escape(absl::string_view src, std::string* dest); +std::string WebSafeBase64Escape(absl::string_view src); + // Base64Unescape() // // Converts a `src` string encoded in Base64 to its binary equivalent, writing // it to a `dest` buffer, returning `true` on success. If `src` contains invalid // characters, `dest` is cleared and returns `false`. +// Padding is optional. If padding is included, it must be correct. In the +// padding, '=' and '.' are treated identically. bool Base64Unescape(absl::string_view src, std::string* dest); // WebSafeBase64Unescape() @@ -129,23 +147,10 @@ bool Base64Unescape(absl::string_view src, std::string* dest); // Converts a `src` string encoded in Base64 to its binary equivalent, writing // it to a `dest` buffer, but using '-' instead of '+', and '_' instead of '/'. // If `src` contains invalid characters, `dest` is cleared and returns `false`. +// Padding is optional. If padding is included, it must be correct. In the +// padding, '=' and '.' are treated identically. bool WebSafeBase64Unescape(absl::string_view src, std::string* dest); -// Base64Escape() -// -// Encodes a `src` string into a base64-encoded string, with padding characters. -// This function conforms with RFC 4648 section 4 (base64). -void Base64Escape(absl::string_view src, std::string* dest); -std::string Base64Escape(absl::string_view src); - -// WebSafeBase64Escape() -// -// Encodes a `src` string into a base64-like string, using '-' instead of '+' -// and '_' instead of '/', and without padding. This function conforms with RFC -// 4648 section 5 (base64url). -void WebSafeBase64Escape(absl::string_view src, std::string* dest); -std::string WebSafeBase64Escape(absl::string_view src); - // HexStringToBytes() // // Converts an ASCII hex string into bytes, returning binary data of length diff --git a/absl/strings/escaping_test.cc b/absl/strings/escaping_test.cc index 45671a0e..44ffcba7 100644 --- a/absl/strings/escaping_test.cc +++ b/absl/strings/escaping_test.cc @@ -617,6 +617,48 @@ TEST(Base64, EscapeAndUnescape) { TestEscapeAndUnescape<std::string>(); } +TEST(Base64, Padding) { + // Padding is optional. + // '.' is an acceptable padding character, just like '='. + std::initializer_list<absl::string_view> good_padding = { + "YQ", + "YQ==", + "YQ=.", + "YQ.=", + "YQ..", + }; + for (absl::string_view b64 : good_padding) { + std::string decoded; + EXPECT_TRUE(absl::Base64Unescape(b64, &decoded)); + EXPECT_EQ(decoded, "a"); + std::string websafe_decoded; + EXPECT_TRUE(absl::WebSafeBase64Unescape(b64, &websafe_decoded)); + EXPECT_EQ(websafe_decoded, "a"); + } + std::initializer_list<absl::string_view> bad_padding = { + "YQ=", + "YQ.", + "YQ===", + "YQ==.", + "YQ=.=", + "YQ=..", + "YQ.==", + "YQ.=.", + "YQ..=", + "YQ...", + "YQ====", + "YQ....", + "YQ=====", + "YQ.....", + }; + for (absl::string_view b64 : bad_padding) { + std::string decoded; + EXPECT_FALSE(absl::Base64Unescape(b64, &decoded)); + std::string websafe_decoded; + EXPECT_FALSE(absl::WebSafeBase64Unescape(b64, &websafe_decoded)); + } +} + TEST(Base64, DISABLED_HugeData) { const size_t kSize = size_t(3) * 1000 * 1000 * 1000; static_assert(kSize % 3 == 0, "kSize must be divisible by 3"); diff --git a/absl/strings/internal/char_map.h b/absl/strings/internal/char_map.h index 5aabc1fc..70a90343 100644 --- a/absl/strings/internal/char_map.h +++ b/absl/strings/internal/char_map.h @@ -73,10 +73,10 @@ class Charmap { } // 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) { - return *s == 0 ? Charmap() : (Char(*s) | FromString(s + 1)); + Charmap ret; + while (*s) ret = ret | Char(*s++); + return ret; } // Containing all the chars in the closed interval [lo,hi]. diff --git a/absl/strings/internal/cord_internal.h b/absl/strings/internal/cord_internal.h index fcca3a28..6022c1df 100644 --- a/absl/strings/internal/cord_internal.h +++ b/absl/strings/internal/cord_internal.h @@ -225,7 +225,11 @@ struct CordRep { : length(l), refcount(immortal), tag(EXTERNAL), storage{} {} // The following three fields have to be less than 32 bytes since - // that is the smallest supported flat node size. + // that is the smallest supported flat node size. Some code optimizations rely + // on the specific layout of these fields. Notably: the non-trivial field + // `refcount` being preceeded by `length`, and being tailed by POD data + // members only. + // # LINT.IfChange size_t length; RefcountAndFlags refcount; // If tag < FLAT, it represents CordRepKind and indicates the type of node. @@ -241,6 +245,7 @@ struct CordRep { // allocate room for these in the derived class, as not all compilers reuse // padding space from the base class (clang and gcc do, MSVC does not, etc) uint8_t storage[3]; + // # LINT.ThenChange(cord_rep_btree.h:copy_raw) // Returns true if this instance's tag matches the requested type. constexpr bool IsRing() const { return tag == RING; } diff --git a/absl/strings/internal/cord_rep_btree.cc b/absl/strings/internal/cord_rep_btree.cc index 7ce36128..985f0724 100644 --- a/absl/strings/internal/cord_rep_btree.cc +++ b/absl/strings/internal/cord_rep_btree.cc @@ -502,7 +502,7 @@ OpResult CordRepBtree::SetEdge(bool owned, CordRep* edge, size_t delta) { // open interval [begin, back) or [begin + 1, end) depending on `edge_type`. // We conveniently cover both case using a constexpr `shift` being 0 or 1 // as `end :== back + 1`. - result = {CopyRaw(), kCopied}; + result = {CopyRaw(length), kCopied}; constexpr int shift = edge_type == kFront ? 1 : 0; for (CordRep* r : Edges(begin() + shift, back() + shift)) { CordRep::Ref(r); diff --git a/absl/strings/internal/cord_rep_btree.h b/absl/strings/internal/cord_rep_btree.h index eed5609e..4209e512 100644 --- a/absl/strings/internal/cord_rep_btree.h +++ b/absl/strings/internal/cord_rep_btree.h @@ -446,9 +446,9 @@ class CordRepBtree : public CordRep { template <EdgeType edge_type> static CordRepBtree* NewLeaf(absl::string_view data, size_t extra); - // Creates a raw copy of this Btree node, copying all properties, but - // without adding any references to existing edges. - CordRepBtree* CopyRaw() const; + // Creates a raw copy of this Btree node with the specified length, copying + // all properties, but without adding any references to existing edges. + CordRepBtree* CopyRaw(size_t new_length) const; // Creates a full copy of this Btree node, adding a reference on all edges. CordRepBtree* Copy() const; @@ -666,15 +666,28 @@ inline void CordRepBtree::Unref(absl::Span<CordRep* const> edges) { } } -inline CordRepBtree* CordRepBtree::CopyRaw() const { - auto* tree = static_cast<CordRepBtree*>(::operator new(sizeof(CordRepBtree))); - memcpy(static_cast<void*>(tree), this, sizeof(CordRepBtree)); - new (&tree->refcount) RefcountAndFlags; +inline CordRepBtree* CordRepBtree::CopyRaw(size_t new_length) const { + CordRepBtree* tree = new CordRepBtree; + + // `length` and `refcount` are the first members of `CordRepBtree`. + // We initialize `length` using the given length, have `refcount` be set to + // ref = 1 through its default constructor, and copy all data beyond + // 'refcount' which starts with `tag` using a single memcpy: all contents + // except `refcount` is trivially copyable, and the compiler does not + // efficiently coalesce member-wise copy of these members. + // See https://gcc.godbolt.org/z/qY8zsca6z + // # LINT.IfChange(copy_raw) + tree->length = new_length; + uint8_t* dst = &tree->tag; + const uint8_t* src = &tag; + const ptrdiff_t offset = src - reinterpret_cast<const uint8_t*>(this); + memcpy(dst, src, sizeof(CordRepBtree) - static_cast<size_t>(offset)); return tree; + // # LINT.ThenChange() } inline CordRepBtree* CordRepBtree::Copy() const { - CordRepBtree* tree = CopyRaw(); + CordRepBtree* tree = CopyRaw(length); for (CordRep* rep : Edges()) CordRep::Ref(rep); return tree; } @@ -683,8 +696,7 @@ inline CordRepBtree* CordRepBtree::CopyToEndFrom(size_t begin, size_t new_length) const { assert(begin >= this->begin()); assert(begin <= this->end()); - CordRepBtree* tree = CopyRaw(); - tree->length = new_length; + CordRepBtree* tree = CopyRaw(new_length); tree->set_begin(begin); for (CordRep* edge : tree->Edges()) CordRep::Ref(edge); return tree; @@ -694,8 +706,7 @@ inline CordRepBtree* CordRepBtree::CopyBeginTo(size_t end, size_t new_length) const { assert(end <= capacity()); assert(end >= this->begin()); - CordRepBtree* tree = CopyRaw(); - tree->length = new_length; + CordRepBtree* tree = CopyRaw(new_length); tree->set_end(end); for (CordRep* edge : tree->Edges()) CordRep::Ref(edge); return tree; diff --git a/absl/strings/internal/cord_rep_crc.cc b/absl/strings/internal/cord_rep_crc.cc index ee140354..dbe54cc4 100644 --- a/absl/strings/internal/cord_rep_crc.cc +++ b/absl/strings/internal/cord_rep_crc.cc @@ -16,6 +16,7 @@ #include <cassert> #include <cstdint> +#include <utility> #include "absl/base/config.h" #include "absl/strings/internal/cord_internal.h" @@ -24,11 +25,10 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace cord_internal { -CordRepCrc* CordRepCrc::New(CordRep* child, uint32_t crc) { - assert(child != nullptr); - if (child->IsCrc()) { +CordRepCrc* CordRepCrc::New(CordRep* child, crc_internal::CrcCordState state) { + if (child != nullptr && child->IsCrc()) { if (child->refcount.IsOne()) { - child->crc()->crc = crc; + child->crc()->crc_cord_state = std::move(state); return child->crc(); } CordRep* old = child; @@ -37,15 +37,17 @@ CordRepCrc* CordRepCrc::New(CordRep* child, uint32_t crc) { CordRep::Unref(old); } auto* new_cordrep = new CordRepCrc; - new_cordrep->length = child->length; + new_cordrep->length = child != nullptr ? child->length : 0; new_cordrep->tag = cord_internal::CRC; new_cordrep->child = child; - new_cordrep->crc = crc; + new_cordrep->crc_cord_state = std::move(state); return new_cordrep; } void CordRepCrc::Destroy(CordRepCrc* node) { - CordRep::Unref(node->child); + if (node->child != nullptr) { + CordRep::Unref(node->child); + } delete node; } diff --git a/absl/strings/internal/cord_rep_crc.h b/absl/strings/internal/cord_rep_crc.h index 5294b0d1..379d7a60 100644 --- a/absl/strings/internal/cord_rep_crc.h +++ b/absl/strings/internal/cord_rep_crc.h @@ -20,6 +20,7 @@ #include "absl/base/config.h" #include "absl/base/optimization.h" +#include "absl/crc/internal/crc_cord_state.h" #include "absl/strings/internal/cord_internal.h" namespace absl { @@ -34,14 +35,14 @@ namespace cord_internal { // the contained checksum is the user's responsibility. struct CordRepCrc : public CordRep { CordRep* child; - uint32_t crc; + absl::crc_internal::CrcCordState crc_cord_state; // Consumes `child` and returns a CordRepCrc prefixed tree containing `child`. // If the specified `child` is itself a CordRepCrc node, then this method - // either replaces the existing node, or directly updates the crc value in it + // either replaces the existing node, or directly updates the crc state in it // depending on the node being shared or not, i.e.: refcount.IsOne(). - // `child` must not be null. Never returns null. - static CordRepCrc* New(CordRep* child, uint32_t crc); + // `child` must only be null if the Cord is empty. Never returns null. + static CordRepCrc* New(CordRep* child, crc_internal::CrcCordState state); // Destroys (deletes) the provided node. `node` must not be null. static void Destroy(CordRepCrc* node); diff --git a/absl/strings/internal/cord_rep_crc_test.cc b/absl/strings/internal/cord_rep_crc_test.cc index d73ea7b3..3d27c33c 100644 --- a/absl/strings/internal/cord_rep_crc_test.cc +++ b/absl/strings/internal/cord_rep_crc_test.cc @@ -17,6 +17,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/config.h" +#include "absl/crc/internal/crc_cord_state.h" #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_rep_test_util.h" @@ -27,47 +28,51 @@ namespace { using ::absl::cordrep_testing::MakeFlat; using ::testing::Eq; +using ::testing::IsNull; using ::testing::Ne; #if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST -TEST(CordRepCrc, NewWithNullPtr) { - EXPECT_DEATH(CordRepCrc::New(nullptr, 0), ""); -} - TEST(CordRepCrc, RemoveCrcWithNullptr) { EXPECT_DEATH(RemoveCrcNode(nullptr), ""); } #endif // !NDEBUG && GTEST_HAS_DEATH_TEST +absl::crc_internal::CrcCordState MakeCrcCordState(uint32_t crc) { + crc_internal::CrcCordState state; + state.mutable_rep()->prefix_crc.push_back( + crc_internal::CrcCordState::PrefixCrc(42, crc32c_t{crc})); + return state; +} + TEST(CordRepCrc, NewDestroy) { CordRep* rep = cordrep_testing::MakeFlat("Hello world"); - CordRepCrc* crc = CordRepCrc::New(rep, 12345); + CordRepCrc* crc = CordRepCrc::New(rep, MakeCrcCordState(12345)); EXPECT_TRUE(crc->refcount.IsOne()); EXPECT_THAT(crc->child, Eq(rep)); - EXPECT_THAT(crc->crc, Eq(12345u)); + EXPECT_THAT(crc->crc_cord_state.Checksum(), Eq(crc32c_t{12345u})); EXPECT_TRUE(rep->refcount.IsOne()); CordRepCrc::Destroy(crc); } TEST(CordRepCrc, NewExistingCrcNotShared) { CordRep* rep = cordrep_testing::MakeFlat("Hello world"); - CordRepCrc* crc = CordRepCrc::New(rep, 12345); - CordRepCrc* new_crc = CordRepCrc::New(crc, 54321); + CordRepCrc* crc = CordRepCrc::New(rep, MakeCrcCordState(12345)); + CordRepCrc* new_crc = CordRepCrc::New(crc, MakeCrcCordState(54321)); EXPECT_THAT(new_crc, Eq(crc)); EXPECT_TRUE(new_crc->refcount.IsOne()); EXPECT_THAT(new_crc->child, Eq(rep)); - EXPECT_THAT(new_crc->crc, Eq(54321u)); + EXPECT_THAT(new_crc->crc_cord_state.Checksum(), Eq(crc32c_t{54321u})); EXPECT_TRUE(rep->refcount.IsOne()); CordRepCrc::Destroy(new_crc); } TEST(CordRepCrc, NewExistingCrcShared) { CordRep* rep = cordrep_testing::MakeFlat("Hello world"); - CordRepCrc* crc = CordRepCrc::New(rep, 12345); + CordRepCrc* crc = CordRepCrc::New(rep, MakeCrcCordState(12345)); CordRep::Ref(crc); - CordRepCrc* new_crc = CordRepCrc::New(crc, 54321); + CordRepCrc* new_crc = CordRepCrc::New(crc, MakeCrcCordState(54321)); EXPECT_THAT(new_crc, Ne(crc)); EXPECT_TRUE(new_crc->refcount.IsOne()); @@ -75,13 +80,23 @@ TEST(CordRepCrc, NewExistingCrcShared) { EXPECT_FALSE(rep->refcount.IsOne()); EXPECT_THAT(crc->child, Eq(rep)); EXPECT_THAT(new_crc->child, Eq(rep)); - EXPECT_THAT(crc->crc, Eq(12345u)); - EXPECT_THAT(new_crc->crc, Eq(54321u)); + EXPECT_THAT(crc->crc_cord_state.Checksum(), Eq(crc32c_t{12345u})); + EXPECT_THAT(new_crc->crc_cord_state.Checksum(), Eq(crc32c_t{54321u})); CordRep::Unref(crc); CordRep::Unref(new_crc); } +TEST(CordRepCrc, NewEmpty) { + CordRepCrc* crc = CordRepCrc::New(nullptr, MakeCrcCordState(12345)); + EXPECT_TRUE(crc->refcount.IsOne()); + EXPECT_THAT(crc->child, IsNull()); + EXPECT_THAT(crc->length, Eq(0u)); + EXPECT_THAT(crc->crc_cord_state.Checksum(), Eq(crc32c_t{12345u})); + EXPECT_TRUE(crc->refcount.IsOne()); + CordRepCrc::Destroy(crc); +} + TEST(CordRepCrc, RemoveCrcNotCrc) { CordRep* rep = cordrep_testing::MakeFlat("Hello world"); CordRep* nocrc = RemoveCrcNode(rep); @@ -91,7 +106,7 @@ TEST(CordRepCrc, RemoveCrcNotCrc) { TEST(CordRepCrc, RemoveCrcNotShared) { CordRep* rep = cordrep_testing::MakeFlat("Hello world"); - CordRepCrc* crc = CordRepCrc::New(rep, 12345); + CordRepCrc* crc = CordRepCrc::New(rep, MakeCrcCordState(12345)); CordRep* nocrc = RemoveCrcNode(crc); EXPECT_THAT(nocrc, Eq(rep)); EXPECT_TRUE(rep->refcount.IsOne()); @@ -100,7 +115,7 @@ TEST(CordRepCrc, RemoveCrcNotShared) { TEST(CordRepCrc, RemoveCrcShared) { CordRep* rep = cordrep_testing::MakeFlat("Hello world"); - CordRepCrc* crc = CordRepCrc::New(rep, 12345); + CordRepCrc* crc = CordRepCrc::New(rep, MakeCrcCordState(12345)); CordRep::Ref(crc); CordRep* nocrc = RemoveCrcNode(crc); EXPECT_THAT(nocrc, Eq(rep)); diff --git a/absl/strings/internal/cordz_info_statistics_test.cc b/absl/strings/internal/cordz_info_statistics_test.cc index 6d6feb52..53d2f2ea 100644 --- a/absl/strings/internal/cordz_info_statistics_test.cc +++ b/absl/strings/internal/cordz_info_statistics_test.cc @@ -19,6 +19,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/config.h" +#include "absl/crc/internal/crc_cord_state.h" #include "absl/strings/cord.h" #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_rep_btree.h" @@ -451,7 +452,8 @@ TEST(CordzInfoStatisticsTest, BtreeNodeShared) { TEST(CordzInfoStatisticsTest, Crc) { RefHelper ref; auto* left = Flat(1000); - auto* crc = ref.NeedsUnref(CordRepCrc::New(left, 12345)); + auto* crc = + ref.NeedsUnref(CordRepCrc::New(left, crc_internal::CrcCordState())); CordzStatistics expected; expected.size = left->length; diff --git a/absl/strings/internal/damerau_levenshtein_distance.cc b/absl/strings/internal/damerau_levenshtein_distance.cc index 7cc23acd..a084568f 100644 --- a/absl/strings/internal/damerau_levenshtein_distance.cc +++ b/absl/strings/internal/damerau_levenshtein_distance.cc @@ -31,8 +31,8 @@ namespace strings_internal { // detected. // When the distance is larger than cutoff, or one of the strings has more // than MAX_SIZE=100 characters, the code returns min(MAX_SIZE, cutoff) + 1. -size_t CappedDamerauLevenshteinDistance(absl::string_view s1, - absl::string_view s2, uint8_t cutoff) { +uint8_t CappedDamerauLevenshteinDistance(absl::string_view s1, + absl::string_view s2, uint8_t cutoff) { const uint8_t MAX_SIZE = 100; const uint8_t _cutoff = std::min(MAX_SIZE, cutoff); const uint8_t cutoff_plus_1 = static_cast<uint8_t>(_cutoff + 1); @@ -42,7 +42,7 @@ size_t CappedDamerauLevenshteinDistance(absl::string_view s1, return cutoff_plus_1; if (s1.empty()) - return std::min(static_cast<size_t>(cutoff_plus_1), s2.size()); + return static_cast<uint8_t>(s2.size()); // Lower diagonal bound: y = x - lower_diag const uint8_t lower_diag = diff --git a/absl/strings/internal/damerau_levenshtein_distance.h b/absl/strings/internal/damerau_levenshtein_distance.h index b9bb6fe1..1a968425 100644 --- a/absl/strings/internal/damerau_levenshtein_distance.h +++ b/absl/strings/internal/damerau_levenshtein_distance.h @@ -25,8 +25,8 @@ ABSL_NAMESPACE_BEGIN namespace strings_internal { // Calculate DamerauLevenshtein distance between two strings. // When the distance is larger than cutoff, the code just returns cutoff + 1. -size_t CappedDamerauLevenshteinDistance(absl::string_view s1, - absl::string_view s2, uint8_t cutoff); +uint8_t CappedDamerauLevenshteinDistance(absl::string_view s1, + absl::string_view s2, uint8_t cutoff); } // namespace strings_internal ABSL_NAMESPACE_END diff --git a/absl/strings/internal/damerau_levenshtein_distance_test.cc b/absl/strings/internal/damerau_levenshtein_distance_test.cc index 45cb5bd9..a342b7db 100644 --- a/absl/strings/internal/damerau_levenshtein_distance_test.cc +++ b/absl/strings/internal/damerau_levenshtein_distance_test.cc @@ -24,76 +24,76 @@ namespace { using absl::strings_internal::CappedDamerauLevenshteinDistance; TEST(Distance, TestDistances) { - EXPECT_THAT(CappedDamerauLevenshteinDistance("ab", "ab", 6), 0u); - EXPECT_THAT(CappedDamerauLevenshteinDistance("a", "b", 6), 1u); - EXPECT_THAT(CappedDamerauLevenshteinDistance("ca", "abc", 6), 3u); - EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "ad", 6), 2u); - EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "cadb", 6), 4u); - EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "bdac", 6), 4u); - EXPECT_THAT(CappedDamerauLevenshteinDistance("ab", "ab", 0), 0u); - EXPECT_THAT(CappedDamerauLevenshteinDistance("", "", 0), 0u); + EXPECT_THAT(CappedDamerauLevenshteinDistance("ab", "ab", 6), uint8_t{0}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("a", "b", 6), uint8_t{1}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("ca", "abc", 6), uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "ad", 6), uint8_t{2}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "cadb", 6), uint8_t{4}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "bdac", 6), uint8_t{4}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("ab", "ab", 0), uint8_t{0}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("", "", 0), uint8_t{0}); // combinations for 3-character strings: // 1, 2, 3 removals, insertions or replacements and transpositions - EXPECT_THAT(CappedDamerauLevenshteinDistance("abc", "abc", 6), 0u); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abc", "abc", 6), uint8_t{0}); for (auto res : {"", "ca", "efg", "ea", "ce", "ceb", "eca", "cae", "cea", "bea"}) { - EXPECT_THAT(CappedDamerauLevenshteinDistance("abc", res, 6), 3u); - EXPECT_THAT(CappedDamerauLevenshteinDistance(res, "abc", 6), 3u); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abc", res, 6), uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(res, "abc", 6), uint8_t{3}); } for (auto res : {"a", "b", "c", "ba", "cb", "bca", "cab", "cba", "ace", "efc", "ebf", "aef", "ae", "be", "eb", "ec", "ecb", "bec", "bce", "cbe", "ace", "eac", "aeb", "bae", "eab", "eba"}) { - EXPECT_THAT(CappedDamerauLevenshteinDistance("abc", res, 6), 2u); - EXPECT_THAT(CappedDamerauLevenshteinDistance(res, "abc", 6), 2u); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abc", res, 6), uint8_t{2}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(res, "abc", 6), uint8_t{2}); } for (auto res : {"ab", "ac", "bc", "acb", "bac", "ebc", "aec", "abe"}) { - EXPECT_THAT(CappedDamerauLevenshteinDistance("abc", res, 6), 1u); - EXPECT_THAT(CappedDamerauLevenshteinDistance(res, "abc", 6), 1u); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abc", res, 6), uint8_t{1}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(res, "abc", 6), uint8_t{1}); } } TEST(Distance, TestCutoff) { // Returing cutoff + 1 if the value is larger than cutoff or string longer // than MAX_SIZE. - EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "a", 3), 3u); - EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "a", 2), 3u); - EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "a", 1), 2u); - EXPECT_THAT(CappedDamerauLevenshteinDistance("abcdefg", "a", 2), 3u); - EXPECT_THAT(CappedDamerauLevenshteinDistance("a", "abcde", 2), 3u); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "a", 3), uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "a", 2), uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "a", 1), uint8_t{2}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcdefg", "a", 2), uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("a", "abcde", 2), uint8_t{3}); EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(102, 'a'), std::string(102, 'a'), 105), - 101u); + uint8_t{101}); EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(100, 'a'), std::string(100, 'a'), 100), - 0u); + uint8_t{0}); EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(100, 'a'), std::string(100, 'b'), 100), - 100u); + uint8_t{100}); EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(100, 'a'), std::string(99, 'a'), 2), - 1u); + uint8_t{1}); EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(100, 'a'), std::string(101, 'a'), 2), - 3u); + uint8_t{3}); EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(100, 'a'), std::string(101, 'a'), 2), - 3u); + uint8_t{3}); EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(UINT8_MAX + 1, 'a'), std::string(UINT8_MAX + 1, 'b'), UINT8_MAX), - 101u); + uint8_t{101}); EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(UINT8_MAX - 1, 'a'), std::string(UINT8_MAX - 1, 'b'), UINT8_MAX), - 101u); + uint8_t{101}); EXPECT_THAT( CappedDamerauLevenshteinDistance(std::string(UINT8_MAX, 'a'), std::string(UINT8_MAX, 'b'), UINT8_MAX), - 101u); + uint8_t{101}); EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(UINT8_MAX - 1, 'a'), std::string(UINT8_MAX - 1, 'a'), UINT8_MAX), - 101u); + uint8_t{101}); } } // namespace diff --git a/absl/strings/internal/has_absl_stringify.h b/absl/strings/internal/has_absl_stringify.h new file mode 100644 index 00000000..55a08508 --- /dev/null +++ b/absl/strings/internal/has_absl_stringify.h @@ -0,0 +1,55 @@ +// Copyright 2022 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_HAS_ABSL_STRINGIFY_H_ +#define ABSL_STRINGS_INTERNAL_HAS_ABSL_STRINGIFY_H_ +#include <string> +#include <type_traits> +#include <utility> + +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +namespace strings_internal { + +// This is an empty class not intended to be used. It exists so that +// `HasAbslStringify` can reference a universal class rather than needing to be +// copied for each new sink. +class UnimplementedSink { + public: + void Append(size_t count, char ch); + + void Append(string_view v); + + // Support `absl::Format(&sink, format, args...)`. + friend void AbslFormatFlush(UnimplementedSink* sink, absl::string_view v); +}; + +template <typename T, typename = void> +struct HasAbslStringify : std::false_type {}; + +template <typename T> +struct HasAbslStringify< + T, std::enable_if_t<std::is_void<decltype(AbslStringify( + std::declval<strings_internal::UnimplementedSink&>(), + std::declval<const T&>()))>::value>> : std::true_type {}; + +} // namespace strings_internal + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_HAS_ABSL_STRINGIFY_H_ diff --git a/absl/strings/internal/str_format/arg.cc b/absl/strings/internal/str_format/arg.cc index 967fe9ca..018dd052 100644 --- a/absl/strings/internal/str_format/arg.cc +++ b/absl/strings/internal/str_format/arg.cc @@ -297,6 +297,37 @@ constexpr auto ConvertV(T) { } template <typename T> +bool ConvertFloatArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) { + if (conv.conversion_char() == FormatConversionCharInternal::v) { + conv.set_conversion_char(FormatConversionCharInternal::g); + } + + return FormatConversionCharIsFloat(conv.conversion_char()) && + ConvertFloatImpl(v, conv, sink); +} + +inline bool ConvertStringArg(string_view v, const FormatConversionSpecImpl conv, + FormatSinkImpl *sink) { + if (conv.is_basic()) { + sink->Append(v); + return true; + } + return sink->PutPaddedString(v, conv.width(), conv.precision(), + conv.has_left_flag()); +} + +} // namespace + +bool ConvertBoolArg(bool v, FormatSinkImpl *sink) { + if (v) { + sink->Append("true"); + } else { + sink->Append("false"); + } + return true; +} + +template <typename T> bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) { using U = typename MakeUnsigned<T>::type; IntDigits as_digits; @@ -354,36 +385,37 @@ bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return ConvertIntImplInnerSlow(as_digits, conv, sink); } -template <typename T> -bool ConvertFloatArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) { - if (conv.conversion_char() == FormatConversionCharInternal::v) { - conv.set_conversion_char(FormatConversionCharInternal::g); - } - - return FormatConversionCharIsFloat(conv.conversion_char()) && - ConvertFloatImpl(v, conv, sink); -} - -inline bool ConvertStringArg(string_view v, const FormatConversionSpecImpl conv, - FormatSinkImpl *sink) { - if (conv.is_basic()) { - sink->Append(v); - return true; - } - return sink->PutPaddedString(v, conv.width(), conv.precision(), - conv.has_left_flag()); -} - -} // namespace - -bool ConvertBoolArg(bool v, FormatSinkImpl *sink) { - if (v) { - sink->Append("true"); - } else { - sink->Append("false"); - } - return true; -} +template bool ConvertIntArg<char>(char v, FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg<signed char>(signed char v, + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg<unsigned char>(unsigned char v, + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg<short>(short v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg<unsigned short>(unsigned short v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg<int>(int v, FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg<unsigned int>(unsigned int v, + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg<long>(long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg<unsigned long>(unsigned long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg<long long>(long long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg<unsigned long long>(unsigned long long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); // ==================== Strings ==================== StringConvertResult FormatConvertImpl(const std::string &v, diff --git a/absl/strings/internal/str_format/arg.h b/absl/strings/internal/str_format/arg.h index b3e4ff15..e4b16628 100644 --- a/absl/strings/internal/str_format/arg.h +++ b/absl/strings/internal/str_format/arg.h @@ -18,6 +18,7 @@ #include <string.h> #include <wchar.h> +#include <algorithm> #include <cstdio> #include <iomanip> #include <limits> @@ -25,10 +26,12 @@ #include <sstream> #include <string> #include <type_traits> +#include <utility> #include "absl/base/port.h" #include "absl/meta/type_traits.h" #include "absl/numeric/int128.h" +#include "absl/strings/internal/has_absl_stringify.h" #include "absl/strings/internal/str_format/extension.h" #include "absl/strings/string_view.h" @@ -50,6 +53,19 @@ struct ArgConvertResult { bool value; }; +using IntegralConvertResult = ArgConvertResult<FormatConversionCharSetUnion( + FormatConversionCharSetInternal::c, + FormatConversionCharSetInternal::kNumeric, + FormatConversionCharSetInternal::kStar, + FormatConversionCharSetInternal::v)>; +using FloatingConvertResult = ArgConvertResult<FormatConversionCharSetUnion( + FormatConversionCharSetInternal::kFloating, + FormatConversionCharSetInternal::v)>; +using CharConvertResult = ArgConvertResult<FormatConversionCharSetUnion( + FormatConversionCharSetInternal::c, + FormatConversionCharSetInternal::kNumeric, + FormatConversionCharSetInternal::kStar)>; + template <typename T, typename = void> struct HasUserDefinedConvert : std::false_type {}; @@ -67,6 +83,44 @@ void AbslFormatConvert(); void AbslStringify(); template <typename T> +bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); + +// Forward declarations of internal `ConvertIntArg` function template +// instantiations are here to avoid including the template body in the headers +// and instantiating it in large numbers of translation units. Explicit +// instantiations can be found in "absl/strings/internal/str_format/arg.cc" +extern template bool ConvertIntArg<char>(char v, FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg<signed char>(signed char v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg<unsigned char>(unsigned char v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg<short>(short v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg<unsigned short>( // NOLINT + unsigned short v, FormatConversionSpecImpl conv, // NOLINT + FormatSinkImpl* sink); +extern template bool ConvertIntArg<int>(int v, FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg<unsigned int>(unsigned int v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg<long>( // NOLINT + long v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); // NOLINT +extern template bool ConvertIntArg<unsigned long>(unsigned long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg<long long>(long long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg<unsigned long long>( // NOLINT + unsigned long long v, FormatConversionSpecImpl conv, // NOLINT + FormatSinkImpl* sink); + +template <typename T> auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv, FormatSinkImpl* sink) -> decltype(AbslFormatConvert(v, @@ -82,10 +136,30 @@ auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv, } template <typename T> +auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv, + FormatSinkImpl* sink) + -> std::enable_if_t<std::is_enum<T>::value && + std::is_void<decltype(AbslStringify( + std::declval<FormatSink&>(), v))>::value, + IntegralConvertResult> { + if (conv.conversion_char() == FormatConversionCharInternal::v) { + using FormatSinkT = + absl::enable_if_t<sizeof(const T& (*)()) != 0, FormatSink>; + auto fs = sink->Wrap<FormatSinkT>(); + AbslStringify(fs, v); + return {true}; + } else { + return {ConvertIntArg( + static_cast<typename std::underlying_type<T>::type>(v), conv, sink)}; + } +} + +template <typename T> auto FormatConvertImpl(const T& v, FormatConversionSpecImpl, FormatSinkImpl* sink) - -> std::enable_if_t<std::is_void<decltype(AbslStringify( - std::declval<FormatSink&>(), v))>::value, + -> std::enable_if_t<!std::is_enum<T>::value && + std::is_void<decltype(AbslStringify( + std::declval<FormatSink&>(), v))>::value, ArgConvertResult<FormatConversionCharSetInternal::v>> { using FormatSinkT = absl::enable_if_t<sizeof(const T& (*)()) != 0, FormatSink>; @@ -191,19 +265,6 @@ StringConvertResult FormatConvertImpl(const AbslCord& value, return {true}; } -using IntegralConvertResult = ArgConvertResult<FormatConversionCharSetUnion( - FormatConversionCharSetInternal::c, - FormatConversionCharSetInternal::kNumeric, - FormatConversionCharSetInternal::kStar, - FormatConversionCharSetInternal::v)>; -using FloatingConvertResult = ArgConvertResult<FormatConversionCharSetUnion( - FormatConversionCharSetInternal::kFloating, - FormatConversionCharSetInternal::v)>; -using CharConvertResult = ArgConvertResult<FormatConversionCharSetUnion( - FormatConversionCharSetInternal::c, - FormatConversionCharSetInternal::kNumeric, - FormatConversionCharSetInternal::kStar)>; - bool ConvertBoolArg(bool v, FormatSinkImpl* sink); // Floats. @@ -271,7 +332,8 @@ IntegralConvertResult FormatConvertImpl(T v, FormatConversionSpecImpl conv, // FormatArgImpl will use the underlying Convert functions instead. template <typename T> typename std::enable_if<std::is_enum<T>::value && - !HasUserDefinedConvert<T>::value, + !HasUserDefinedConvert<T>::value && + !strings_internal::HasAbslStringify<T>::value, IntegralConvertResult>::type FormatConvertImpl(T v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); @@ -384,7 +446,8 @@ class FormatArgImpl { template <typename T, typename = void> struct DecayType { static constexpr bool kHasUserDefined = - str_format_internal::HasUserDefinedConvert<T>::value; + str_format_internal::HasUserDefinedConvert<T>::value || + strings_internal::HasAbslStringify<T>::value; using type = typename std::conditional< !kHasUserDefined && std::is_convertible<T, const char*>::value, const char*, @@ -396,6 +459,7 @@ class FormatArgImpl { struct DecayType<T, typename std::enable_if< !str_format_internal::HasUserDefinedConvert<T>::value && + !strings_internal::HasAbslStringify<T>::value && std::is_enum<T>::value>::type> { using type = typename std::underlying_type<T>::type; }; diff --git a/absl/strings/internal/str_format/checker.h b/absl/strings/internal/str_format/checker.h index aeb9d48d..eab6ab9d 100644 --- a/absl/strings/internal/str_format/checker.h +++ b/absl/strings/internal/str_format/checker.h @@ -15,8 +15,11 @@ #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_ #define ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_ +#include <algorithm> + #include "absl/base/attributes.h" #include "absl/strings/internal/str_format/arg.h" +#include "absl/strings/internal/str_format/constexpr_parser.h" #include "absl/strings/internal/str_format/extension.h" // Compile time check support for entry points. @@ -36,333 +39,56 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace str_format_internal { -constexpr bool AllOf() { return true; } - -template <typename... T> -constexpr bool AllOf(bool b, T... t) { - return b && AllOf(t...); -} - #ifdef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER -constexpr bool ContainsChar(const char* chars, char c) { - return *chars == c || (*chars && ContainsChar(chars + 1, c)); -} - -// A constexpr compatible list of Convs. -struct ConvList { - const FormatConversionCharSet* array; - int count; - - // We do the bound check here to avoid having to do it on the callers. - // Returning an empty FormatConversionCharSet has the same effect as - // short circuiting because it will never match any conversion. - constexpr FormatConversionCharSet operator[](int i) const { - return i < count ? array[i] : FormatConversionCharSet{}; - } - - constexpr ConvList without_front() const { - return count != 0 ? ConvList{array + 1, count - 1} : *this; - } -}; - -template <size_t count> -struct ConvListT { - // Make sure the array has size > 0. - FormatConversionCharSet list[count ? count : 1]; -}; - -constexpr char GetChar(string_view str, size_t index) { - return index < str.size() ? str[index] : char{}; -} - -constexpr string_view ConsumeFront(string_view str, size_t len = 1) { - return len <= str.size() ? string_view(str.data() + len, str.size() - len) - : string_view(); -} - -constexpr string_view ConsumeAnyOf(string_view format, const char* chars) { - while (ContainsChar(chars, GetChar(format, 0))) { - format = ConsumeFront(format); - } - return format; -} - -constexpr bool IsDigit(char c) { return c >= '0' && c <= '9'; } - -// Helper class for the ParseDigits function. -// It encapsulates the two return values we need there. -struct Integer { - string_view format; - int value; - - // If the next character is a '$', consume it. - // Otherwise, make `this` an invalid positional argument. - constexpr Integer ConsumePositionalDollar() const { - if (GetChar(format, 0) == '$') { - return Integer{ConsumeFront(format), value}; - } else { - return Integer{format, 0}; - } - } -}; - -constexpr Integer ParseDigits(string_view format) { - int value = 0; - while (IsDigit(GetChar(format, 0))) { - value = 10 * value + GetChar(format, 0) - '0'; - format = ConsumeFront(format); - } - - return Integer{format, value}; -} - -// Parse digits for a positional argument. -// The parsing also consumes the '$'. -constexpr Integer ParsePositional(string_view format) { - return ParseDigits(format).ConsumePositionalDollar(); -} - -// Parses a single conversion specifier. -// See ConvParser::Run() for post conditions. -class ConvParser { - constexpr ConvParser SetFormat(string_view format) const { - return ConvParser(format, args_, error_, arg_position_, is_positional_); - } - - constexpr ConvParser SetArgs(ConvList args) const { - return ConvParser(format_, args, error_, arg_position_, is_positional_); - } - - constexpr ConvParser SetError(bool error) const { - return ConvParser(format_, args_, error_ || error, arg_position_, - is_positional_); - } - - constexpr ConvParser SetArgPosition(int arg_position) const { - return ConvParser(format_, args_, error_, arg_position, is_positional_); - } - - // Consumes the next arg and verifies that it matches `conv`. - // `error_` is set if there is no next arg or if it doesn't match `conv`. - constexpr ConvParser ConsumeNextArg(char conv) const { - return SetArgs(args_.without_front()).SetError(!Contains(args_[0], conv)); - } - - // Verify that positional argument `i.value` matches `conv`. - // `error_` is set if `i.value` is not a valid argument or if it doesn't - // match. - constexpr ConvParser VerifyPositional(Integer i, char conv) const { - return SetFormat(i.format).SetError(!Contains(args_[i.value - 1], conv)); - } - - // Parse the position of the arg and store it in `arg_position_`. - constexpr ConvParser ParseArgPosition(Integer arg) const { - return SetFormat(arg.format).SetArgPosition(arg.value); - } - - // Consume the flags. - constexpr ConvParser ParseFlags() const { - return SetFormat(ConsumeAnyOf(format_, "-+ #0")); - } - - // Consume the width. - // If it is '*', we verify that it matches `args_`. `error_` is set if it - // doesn't match. - constexpr ConvParser ParseWidth() const { - char first_char = GetChar(format_, 0); - - if (IsDigit(first_char)) { - return SetFormat(ParseDigits(format_).format); - } else if (first_char == '*') { - if (is_positional_) { - return VerifyPositional(ParsePositional(ConsumeFront(format_)), '*'); - } else { - return SetFormat(ConsumeFront(format_)).ConsumeNextArg('*'); - } - } else { - return *this; +template <FormatConversionCharSet... C> +constexpr bool ValidFormatImpl(string_view format) { + int next_arg = 0; + const char* p = format.data(); + const char* const end = p + format.size(); + constexpr FormatConversionCharSet + kAllowedConvs[(std::max)(sizeof...(C), size_t{1})] = {C...}; + bool used[(std::max)(sizeof...(C), size_t{1})]{}; + constexpr int kNumArgs = sizeof...(C); + while (p != end) { + while (p != end && *p != '%') ++p; + if (p == end) { + break; } - } - - // Consume the precision. - // If it is '*', we verify that it matches `args_`. `error_` is set if it - // doesn't match. - constexpr ConvParser ParsePrecision() const { - if (GetChar(format_, 0) != '.') { - return *this; - } else if (GetChar(format_, 1) == '*') { - if (is_positional_) { - return VerifyPositional(ParsePositional(ConsumeFront(format_, 2)), '*'); - } else { - return SetFormat(ConsumeFront(format_, 2)).ConsumeNextArg('*'); - } - } else { - return SetFormat(ParseDigits(ConsumeFront(format_)).format); + if (p + 1 >= end) return false; + if (p[1] == '%') { + // %% + p += 2; + continue; } - } - - // Consume the length characters. - constexpr ConvParser ParseLength() const { - return SetFormat(ConsumeAnyOf(format_, "lLhjztq")); - } - - // Consume the conversion character and verify that it matches `args_`. - // `error_` is set if it doesn't match. - constexpr ConvParser ParseConversion() const { - char first_char = GetChar(format_, 0); - if (first_char == 'v' && *(format_.data() - 1) != '%') { - return SetError(true); + UnboundConversion conv(absl::kConstInit); + p = ConsumeUnboundConversion(p + 1, end, &conv, &next_arg); + if (p == nullptr) return false; + if (conv.arg_position <= 0 || conv.arg_position > kNumArgs) { + return false; } - - if (is_positional_) { - return VerifyPositional({ConsumeFront(format_), arg_position_}, - first_char); - } else { - return ConsumeNextArg(first_char).SetFormat(ConsumeFront(format_)); + if (!Contains(kAllowedConvs[conv.arg_position - 1], conv.conv)) { + return false; } - } - - constexpr ConvParser(string_view format, ConvList args, bool error, - int arg_position, bool is_positional) - : format_(format), - args_(args), - error_(error), - arg_position_(arg_position), - is_positional_(is_positional) {} - - public: - constexpr ConvParser(string_view format, ConvList args, bool is_positional) - : format_(format), - args_(args), - error_(false), - arg_position_(0), - is_positional_(is_positional) {} - - // Consume the whole conversion specifier. - // `format()` will be set to the character after the conversion character. - // `error()` will be set if any of the arguments do not match. - constexpr ConvParser Run() const { - ConvParser parser = *this; - - if (is_positional_) { - parser = ParseArgPosition(ParsePositional(format_)); - } - - return parser.ParseFlags() - .ParseWidth() - .ParsePrecision() - .ParseLength() - .ParseConversion(); - } - - constexpr string_view format() const { return format_; } - constexpr ConvList args() const { return args_; } - constexpr bool error() const { return error_; } - constexpr bool is_positional() const { return is_positional_; } - - private: - string_view format_; - // Current list of arguments. If we are not in positional mode we will consume - // from the front. - ConvList args_; - bool error_; - // Holds the argument position of the conversion character, if we are in - // positional mode. Otherwise, it is unspecified. - int arg_position_; - // Whether we are in positional mode. - // It changes the behavior of '*' and where to find the converted argument. - bool is_positional_; -}; - -// Parses a whole format expression. -// See FormatParser::Run(). -class FormatParser { - static constexpr bool FoundPercent(string_view format) { - return format.empty() || - (GetChar(format, 0) == '%' && GetChar(format, 1) != '%'); - } - - // We use an inner function to increase the recursion limit. - // The inner function consumes up to `limit` characters on every run. - // This increases the limit from 512 to ~512*limit. - static constexpr string_view ConsumeNonPercentInner(string_view format) { - int limit = 20; - while (!FoundPercent(format) && limit != 0) { - size_t len = 0; - - if (GetChar(format, 0) == '%' && GetChar(format, 1) == '%') { - len = 2; - } else { - len = 1; + used[conv.arg_position - 1] = true; + for (auto extra : {conv.width, conv.precision}) { + if (extra.is_from_arg()) { + int pos = extra.get_from_arg(); + if (pos <= 0 || pos > kNumArgs) return false; + used[pos - 1] = true; + if (!Contains(kAllowedConvs[pos - 1], '*')) { + return false; + } } - - format = ConsumeFront(format, len); - --limit; } - - return format; } - - // Consume characters until the next conversion spec %. - // It skips %%. - static constexpr string_view ConsumeNonPercent(string_view format) { - while (!FoundPercent(format)) { - format = ConsumeNonPercentInner(format); + if (sizeof...(C) != 0) { + for (bool b : used) { + if (!b) return false; } - - return format; - } - - static constexpr bool IsPositional(string_view format) { - while (IsDigit(GetChar(format, 0))) { - format = ConsumeFront(format); - } - - return GetChar(format, 0) == '$'; } - - constexpr bool RunImpl(bool is_positional) const { - // In non-positional mode we require all arguments to be consumed. - // In positional mode just reaching the end of the format without errors is - // enough. - return (format_.empty() && (is_positional || args_.count == 0)) || - (!format_.empty() && - ValidateArg( - ConvParser(ConsumeFront(format_), args_, is_positional).Run())); - } - - constexpr bool ValidateArg(ConvParser conv) const { - return !conv.error() && FormatParser(conv.format(), conv.args()) - .RunImpl(conv.is_positional()); - } - - public: - constexpr FormatParser(string_view format, ConvList args) - : format_(ConsumeNonPercent(format)), args_(args) {} - - // Runs the parser for `format` and `args`. - // It verifies that the format is valid and that all conversion specifiers - // match the arguments passed. - // In non-positional mode it also verfies that all arguments are consumed. - constexpr bool Run() const { - return RunImpl(!format_.empty() && IsPositional(ConsumeFront(format_))); - } - - private: - string_view format_; - // Current list of arguments. - // If we are not in positional mode we will consume from the front and will - // have to be empty in the end. - ConvList args_; -}; - -template <FormatConversionCharSet... C> -constexpr bool ValidFormatImpl(string_view format) { - return FormatParser(format, - {ConvListT<sizeof...(C)>{{C...}}.list, sizeof...(C)}) - .Run(); + return true; } #endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER diff --git a/absl/strings/internal/str_format/checker_test.cc b/absl/strings/internal/str_format/checker_test.cc index 680517f7..a86bed38 100644 --- a/absl/strings/internal/str_format/checker_test.cc +++ b/absl/strings/internal/str_format/checker_test.cc @@ -93,6 +93,7 @@ TEST(StrFormatChecker, ValidFormat) { ValidFormat<void (*)(), volatile int*>("%p %p"), // ValidFormat<string_view, const char*, double, void*>( "string_view=%s const char*=%s double=%f void*=%p)"), + ValidFormat<int>("%v"), // ValidFormat<int>("%% %1$d"), // ValidFormat<int>("%1$ld"), // @@ -109,7 +110,9 @@ TEST(StrFormatChecker, ValidFormat) { ValidFormat<int, double>("%2$.*1$f"), // ValidFormat<void*, string_view, const char*, double>( "string_view=%2$s const char*=%3$s double=%4$f void*=%1$p " - "repeat=%3$s)")}; + "repeat=%3$s)"), + ValidFormat<std::string>("%1$v"), + }; for (Case c : trues) { EXPECT_TRUE(c.result) << c.format; @@ -130,6 +133,8 @@ TEST(StrFormatChecker, ValidFormat) { ValidFormat<int>("%*d"), // ValidFormat<std::string>("%p"), // ValidFormat<int (*)(int)>("%d"), // + ValidFormat<int>("%1v"), // + ValidFormat<int>("%.1v"), // ValidFormat<>("%3$d"), // ValidFormat<>("%1$r"), // @@ -138,13 +143,14 @@ TEST(StrFormatChecker, ValidFormat) { ValidFormat<int>("%1$*2$1d"), // ValidFormat<int>("%1$1-d"), // ValidFormat<std::string, int>("%2$*1$s"), // - ValidFormat<std::string>("%1$p"), + ValidFormat<std::string>("%1$p"), // + ValidFormat<int>("%1$*2$v"), // ValidFormat<int, int>("%d %2$d"), // }; for (Case c : falses) { - EXPECT_FALSE(c.result) << c.format; + EXPECT_FALSE(c.result) << "format<" << c.format << ">"; } } diff --git a/absl/strings/internal/str_format/constexpr_parser.h b/absl/strings/internal/str_format/constexpr_parser.h new file mode 100644 index 00000000..3dc1776b --- /dev/null +++ b/absl/strings/internal/str_format/constexpr_parser.h @@ -0,0 +1,351 @@ +// Copyright 2022 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_STR_FORMAT_CONSTEXPR_PARSER_H_ +#define ABSL_STRINGS_INTERNAL_STR_FORMAT_CONSTEXPR_PARSER_H_ + +#include <cassert> +#include <cstdint> +#include <limits> + +#include "absl/base/const_init.h" +#include "absl/strings/internal/str_format/extension.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace str_format_internal { + +enum class LengthMod : std::uint8_t { h, hh, l, ll, L, j, z, t, q, none }; + +// The analyzed properties of a single specified conversion. +struct UnboundConversion { + // This is a user defined default constructor on purpose to skip the + // initialization of parts of the object that are not necessary. + UnboundConversion() {} // NOLINT + + // This constructor is provided for the static checker. We don't want to do + // the unnecessary initialization in the normal case. + explicit constexpr UnboundConversion(absl::ConstInitType) + : arg_position{}, width{}, precision{} {} + + class InputValue { + public: + constexpr void set_value(int value) { + assert(value >= 0); + value_ = value; + } + constexpr int value() const { return value_; } + + // Marks the value as "from arg". aka the '*' format. + // Requires `value >= 1`. + // When set, is_from_arg() return true and get_from_arg() returns the + // original value. + // `value()`'s return value is unspecified in this state. + constexpr void set_from_arg(int value) { + assert(value > 0); + value_ = -value - 1; + } + constexpr bool is_from_arg() const { return value_ < -1; } + constexpr int get_from_arg() const { + assert(is_from_arg()); + return -value_ - 1; + } + + private: + int value_ = -1; + }; + + // No need to initialize. It will always be set in the parser. + int arg_position; + + InputValue width; + InputValue precision; + + Flags flags = Flags::kBasic; + LengthMod length_mod = LengthMod::none; + FormatConversionChar conv = FormatConversionCharInternal::kNone; +}; + +// Helper tag class for the table below. +// It allows fast `char -> ConversionChar/LengthMod/Flags` checking and +// conversions. +class ConvTag { + public: + constexpr ConvTag(FormatConversionChar conversion_char) // NOLINT + : tag_(static_cast<uint8_t>(conversion_char)) {} + constexpr ConvTag(LengthMod length_mod) // NOLINT + : tag_(0x80 | static_cast<uint8_t>(length_mod)) {} + constexpr ConvTag(Flags flags) // NOLINT + : tag_(0xc0 | static_cast<uint8_t>(flags)) {} + constexpr ConvTag() : tag_(0xFF) {} + + constexpr bool is_conv() const { return (tag_ & 0x80) == 0; } + constexpr bool is_length() const { return (tag_ & 0xC0) == 0x80; } + constexpr bool is_flags() const { return (tag_ & 0xE0) == 0xC0; } + + constexpr FormatConversionChar as_conv() const { + assert(is_conv()); + assert(!is_length()); + assert(!is_flags()); + return static_cast<FormatConversionChar>(tag_); + } + constexpr LengthMod as_length() const { + assert(!is_conv()); + assert(is_length()); + assert(!is_flags()); + return static_cast<LengthMod>(tag_ & 0x3F); + } + constexpr Flags as_flags() const { + assert(!is_conv()); + assert(!is_length()); + assert(is_flags()); + return static_cast<Flags>(tag_ & 0x1F); + } + + private: + uint8_t tag_; +}; + +struct ConvTagHolder { + using CC = FormatConversionCharInternal; + using LM = LengthMod; + + // Abbreviations to fit in the table below. + static constexpr auto kFSign = Flags::kSignCol; + static constexpr auto kFAlt = Flags::kAlt; + static constexpr auto kFPos = Flags::kShowPos; + static constexpr auto kFLeft = Flags::kLeft; + static constexpr auto kFZero = Flags::kZero; + + static constexpr ConvTag value[256] = { + {}, {}, {}, {}, {}, {}, {}, {}, // 00-07 + {}, {}, {}, {}, {}, {}, {}, {}, // 08-0f + {}, {}, {}, {}, {}, {}, {}, {}, // 10-17 + {}, {}, {}, {}, {}, {}, {}, {}, // 18-1f + kFSign, {}, {}, kFAlt, {}, {}, {}, {}, // !"#$%&' + {}, {}, {}, kFPos, {}, kFLeft, {}, {}, // ()*+,-./ + kFZero, {}, {}, {}, {}, {}, {}, {}, // 01234567 + {}, {}, {}, {}, {}, {}, {}, {}, // 89:;<=>? + {}, CC::A, {}, {}, {}, CC::E, CC::F, CC::G, // @ABCDEFG + {}, {}, {}, {}, LM::L, {}, {}, {}, // HIJKLMNO + {}, {}, {}, {}, {}, {}, {}, {}, // PQRSTUVW + CC::X, {}, {}, {}, {}, {}, {}, {}, // XYZ[\]^_ + {}, CC::a, {}, CC::c, CC::d, CC::e, CC::f, CC::g, // `abcdefg + LM::h, CC::i, LM::j, {}, LM::l, {}, CC::n, CC::o, // hijklmno + CC::p, LM::q, {}, CC::s, LM::t, CC::u, CC::v, {}, // pqrstuvw + CC::x, {}, LM::z, {}, {}, {}, {}, {}, // xyz{|}! + {}, {}, {}, {}, {}, {}, {}, {}, // 80-87 + {}, {}, {}, {}, {}, {}, {}, {}, // 88-8f + {}, {}, {}, {}, {}, {}, {}, {}, // 90-97 + {}, {}, {}, {}, {}, {}, {}, {}, // 98-9f + {}, {}, {}, {}, {}, {}, {}, {}, // a0-a7 + {}, {}, {}, {}, {}, {}, {}, {}, // a8-af + {}, {}, {}, {}, {}, {}, {}, {}, // b0-b7 + {}, {}, {}, {}, {}, {}, {}, {}, // b8-bf + {}, {}, {}, {}, {}, {}, {}, {}, // c0-c7 + {}, {}, {}, {}, {}, {}, {}, {}, // c8-cf + {}, {}, {}, {}, {}, {}, {}, {}, // d0-d7 + {}, {}, {}, {}, {}, {}, {}, {}, // d8-df + {}, {}, {}, {}, {}, {}, {}, {}, // e0-e7 + {}, {}, {}, {}, {}, {}, {}, {}, // e8-ef + {}, {}, {}, {}, {}, {}, {}, {}, // f0-f7 + {}, {}, {}, {}, {}, {}, {}, {}, // f8-ff + }; +}; + +// Keep a single table for all the conversion chars and length modifiers. +constexpr ConvTag GetTagForChar(char c) { + return ConvTagHolder::value[static_cast<unsigned char>(c)]; +} + +constexpr bool CheckFastPathSetting(const UnboundConversion& conv) { + bool width_precision_needed = + conv.width.value() >= 0 || conv.precision.value() >= 0; + if (width_precision_needed && conv.flags == Flags::kBasic) { +#if defined(__clang__) + // Some compilers complain about this in constexpr even when not executed, + // so only enable the error dump in clang. + fprintf(stderr, + "basic=%d left=%d show_pos=%d sign_col=%d alt=%d zero=%d " + "width=%d precision=%d\n", + conv.flags == Flags::kBasic ? 1 : 0, + FlagsContains(conv.flags, Flags::kLeft) ? 1 : 0, + FlagsContains(conv.flags, Flags::kShowPos) ? 1 : 0, + FlagsContains(conv.flags, Flags::kSignCol) ? 1 : 0, + FlagsContains(conv.flags, Flags::kAlt) ? 1 : 0, + FlagsContains(conv.flags, Flags::kZero) ? 1 : 0, conv.width.value(), + conv.precision.value()); +#endif // defined(__clang__) + return false; + } + return true; +} + +constexpr int ParseDigits(char& c, const char*& pos, const char* const end) { + int digits = c - '0'; + // We do not want to overflow `digits` so we consume at most digits10 + // digits. If there are more digits the parsing will fail later on when the + // digit doesn't match the expected characters. + int num_digits = std::numeric_limits<int>::digits10; + for (;;) { + if (ABSL_PREDICT_FALSE(pos == end)) break; + c = *pos++; + if ('0' > c || c > '9') break; + --num_digits; + if (ABSL_PREDICT_FALSE(!num_digits)) break; + digits = 10 * digits + c - '0'; + } + return digits; +} + +template <bool is_positional> +constexpr const char* ConsumeConversion(const char* pos, const char* const end, + UnboundConversion* conv, + int* next_arg) { + const char* const original_pos = pos; + char c = 0; + // Read the next char into `c` and update `pos`. Returns false if there are + // no more chars to read. +#define ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR() \ + do { \ + if (ABSL_PREDICT_FALSE(pos == end)) return nullptr; \ + c = *pos++; \ + } while (0) + + if (is_positional) { + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr; + conv->arg_position = ParseDigits(c, pos, end); + assert(conv->arg_position > 0); + if (ABSL_PREDICT_FALSE(c != '$')) return nullptr; + } + + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + + // We should start with the basic flag on. + assert(conv->flags == Flags::kBasic); + + // Any non alpha character makes this conversion not basic. + // This includes flags (-+ #0), width (1-9, *) or precision (.). + // All conversion characters and length modifiers are alpha characters. + if (c < 'A') { + while (c <= '0') { + auto tag = GetTagForChar(c); + if (tag.is_flags()) { + conv->flags = conv->flags | tag.as_flags(); + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + } else { + break; + } + } + + if (c <= '9') { + if (c >= '0') { + int maybe_width = ParseDigits(c, pos, end); + if (!is_positional && c == '$') { + if (ABSL_PREDICT_FALSE(*next_arg != 0)) return nullptr; + // Positional conversion. + *next_arg = -1; + return ConsumeConversion<true>(original_pos, end, conv, next_arg); + } + conv->flags = conv->flags | Flags::kNonBasic; + conv->width.set_value(maybe_width); + } else if (c == '*') { + conv->flags = conv->flags | Flags::kNonBasic; + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + if (is_positional) { + if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr; + conv->width.set_from_arg(ParseDigits(c, pos, end)); + if (ABSL_PREDICT_FALSE(c != '$')) return nullptr; + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + } else { + conv->width.set_from_arg(++*next_arg); + } + } + } + + if (c == '.') { + conv->flags = conv->flags | Flags::kNonBasic; + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + if ('0' <= c && c <= '9') { + conv->precision.set_value(ParseDigits(c, pos, end)); + } else if (c == '*') { + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + if (is_positional) { + if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr; + conv->precision.set_from_arg(ParseDigits(c, pos, end)); + if (c != '$') return nullptr; + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + } else { + conv->precision.set_from_arg(++*next_arg); + } + } else { + conv->precision.set_value(0); + } + } + } + + auto tag = GetTagForChar(c); + + if (ABSL_PREDICT_FALSE(c == 'v' && conv->flags != Flags::kBasic)) { + return nullptr; + } + + if (ABSL_PREDICT_FALSE(!tag.is_conv())) { + if (ABSL_PREDICT_FALSE(!tag.is_length())) return nullptr; + + // It is a length modifier. + using str_format_internal::LengthMod; + LengthMod length_mod = tag.as_length(); + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + if (c == 'h' && length_mod == LengthMod::h) { + conv->length_mod = LengthMod::hh; + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + } else if (c == 'l' && length_mod == LengthMod::l) { + conv->length_mod = LengthMod::ll; + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + } else { + conv->length_mod = length_mod; + } + tag = GetTagForChar(c); + + if (ABSL_PREDICT_FALSE(c == 'v')) return nullptr; + if (ABSL_PREDICT_FALSE(!tag.is_conv())) return nullptr; + } + + assert(CheckFastPathSetting(*conv)); + (void)(&CheckFastPathSetting); + + conv->conv = tag.as_conv(); + if (!is_positional) conv->arg_position = ++*next_arg; + return pos; +} + +// Consume conversion spec prefix (not including '%') of [p, end) if valid. +// Examples of valid specs would be e.g.: "s", "d", "-12.6f". +// If valid, it returns the first character following the conversion spec, +// and the spec part is broken down and returned in 'conv'. +// If invalid, returns nullptr. +constexpr const char* ConsumeUnboundConversion(const char* p, const char* end, + UnboundConversion* conv, + int* next_arg) { + if (*next_arg < 0) return ConsumeConversion<true>(p, end, conv, next_arg); + return ConsumeConversion<false>(p, end, conv, next_arg); +} + +} // namespace str_format_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_CONSTEXPR_PARSER_H_ diff --git a/absl/strings/internal/str_format/parser.cc b/absl/strings/internal/str_format/parser.cc index 13731ee2..5aaab698 100644 --- a/absl/strings/internal/str_format/parser.cc +++ b/absl/strings/internal/str_format/parser.cc @@ -31,211 +31,14 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace str_format_internal { -using CC = FormatConversionCharInternal; -using LM = LengthMod; +// Define the array for non-constexpr uses. +constexpr ConvTag ConvTagHolder::value[256]; -// Abbreviations to fit in the table below. -constexpr auto f_sign = Flags::kSignCol; -constexpr auto f_alt = Flags::kAlt; -constexpr auto f_pos = Flags::kShowPos; -constexpr auto f_left = Flags::kLeft; -constexpr auto f_zero = Flags::kZero; - -ABSL_CONST_INIT const ConvTag kTags[256] = { - {}, {}, {}, {}, {}, {}, {}, {}, // 00-07 - {}, {}, {}, {}, {}, {}, {}, {}, // 08-0f - {}, {}, {}, {}, {}, {}, {}, {}, // 10-17 - {}, {}, {}, {}, {}, {}, {}, {}, // 18-1f - f_sign, {}, {}, f_alt, {}, {}, {}, {}, // !"#$%&' - {}, {}, {}, f_pos, {}, f_left, {}, {}, // ()*+,-./ - f_zero, {}, {}, {}, {}, {}, {}, {}, // 01234567 - {}, {}, {}, {}, {}, {}, {}, {}, // 89:;<=>? - {}, CC::A, {}, {}, {}, CC::E, CC::F, CC::G, // @ABCDEFG - {}, {}, {}, {}, LM::L, {}, {}, {}, // HIJKLMNO - {}, {}, {}, {}, {}, {}, {}, {}, // PQRSTUVW - CC::X, {}, {}, {}, {}, {}, {}, {}, // XYZ[\]^_ - {}, CC::a, {}, CC::c, CC::d, CC::e, CC::f, CC::g, // `abcdefg - LM::h, CC::i, LM::j, {}, LM::l, {}, CC::n, CC::o, // hijklmno - CC::p, LM::q, {}, CC::s, LM::t, CC::u, CC::v, {}, // pqrstuvw - CC::x, {}, LM::z, {}, {}, {}, {}, {}, // xyz{|}! - {}, {}, {}, {}, {}, {}, {}, {}, // 80-87 - {}, {}, {}, {}, {}, {}, {}, {}, // 88-8f - {}, {}, {}, {}, {}, {}, {}, {}, // 90-97 - {}, {}, {}, {}, {}, {}, {}, {}, // 98-9f - {}, {}, {}, {}, {}, {}, {}, {}, // a0-a7 - {}, {}, {}, {}, {}, {}, {}, {}, // a8-af - {}, {}, {}, {}, {}, {}, {}, {}, // b0-b7 - {}, {}, {}, {}, {}, {}, {}, {}, // b8-bf - {}, {}, {}, {}, {}, {}, {}, {}, // c0-c7 - {}, {}, {}, {}, {}, {}, {}, {}, // c8-cf - {}, {}, {}, {}, {}, {}, {}, {}, // d0-d7 - {}, {}, {}, {}, {}, {}, {}, {}, // d8-df - {}, {}, {}, {}, {}, {}, {}, {}, // e0-e7 - {}, {}, {}, {}, {}, {}, {}, {}, // e8-ef - {}, {}, {}, {}, {}, {}, {}, {}, // f0-f7 - {}, {}, {}, {}, {}, {}, {}, {}, // f8-ff -}; - -namespace { - -bool CheckFastPathSetting(const UnboundConversion& conv) { - bool width_precision_needed = - conv.width.value() >= 0 || conv.precision.value() >= 0; - if (width_precision_needed && conv.flags == Flags::kBasic) { - fprintf(stderr, - "basic=%d left=%d show_pos=%d sign_col=%d alt=%d zero=%d " - "width=%d precision=%d\n", - conv.flags == Flags::kBasic ? 1 : 0, - FlagsContains(conv.flags, Flags::kLeft) ? 1 : 0, - FlagsContains(conv.flags, Flags::kShowPos) ? 1 : 0, - FlagsContains(conv.flags, Flags::kSignCol) ? 1 : 0, - FlagsContains(conv.flags, Flags::kAlt) ? 1 : 0, - FlagsContains(conv.flags, Flags::kZero) ? 1 : 0, conv.width.value(), - conv.precision.value()); - return false; - } - return true; -} - -template <bool is_positional> -const char *ConsumeConversion(const char *pos, const char *const end, - UnboundConversion *conv, int *next_arg) { - const char* const original_pos = pos; - char c; - // Read the next char into `c` and update `pos`. Returns false if there are - // no more chars to read. -#define ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR() \ - do { \ - if (ABSL_PREDICT_FALSE(pos == end)) return nullptr; \ - c = *pos++; \ - } while (0) - - const auto parse_digits = [&] { - int digits = c - '0'; - // We do not want to overflow `digits` so we consume at most digits10 - // digits. If there are more digits the parsing will fail later on when the - // digit doesn't match the expected characters. - int num_digits = std::numeric_limits<int>::digits10; - for (;;) { - if (ABSL_PREDICT_FALSE(pos == end)) break; - c = *pos++; - if (!std::isdigit(c)) break; - --num_digits; - if (ABSL_PREDICT_FALSE(!num_digits)) break; - digits = 10 * digits + c - '0'; - } - return digits; - }; - - if (is_positional) { - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr; - conv->arg_position = parse_digits(); - assert(conv->arg_position > 0); - if (ABSL_PREDICT_FALSE(c != '$')) return nullptr; - } - - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - - // We should start with the basic flag on. - assert(conv->flags == Flags::kBasic); - - // Any non alpha character makes this conversion not basic. - // This includes flags (-+ #0), width (1-9, *) or precision (.). - // All conversion characters and length modifiers are alpha characters. - if (c < 'A') { - while (c <= '0') { - auto tag = GetTagForChar(c); - if (tag.is_flags()) { - conv->flags = conv->flags | tag.as_flags(); - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - } else { - break; - } - } - - if (c <= '9') { - if (c >= '0') { - int maybe_width = parse_digits(); - if (!is_positional && c == '$') { - if (ABSL_PREDICT_FALSE(*next_arg != 0)) return nullptr; - // Positional conversion. - *next_arg = -1; - return ConsumeConversion<true>(original_pos, end, conv, next_arg); - } - conv->flags = conv->flags | Flags::kNonBasic; - conv->width.set_value(maybe_width); - } else if (c == '*') { - conv->flags = conv->flags | Flags::kNonBasic; - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - if (is_positional) { - if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr; - conv->width.set_from_arg(parse_digits()); - if (ABSL_PREDICT_FALSE(c != '$')) return nullptr; - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - } else { - conv->width.set_from_arg(++*next_arg); - } - } - } - - if (c == '.') { - conv->flags = conv->flags | Flags::kNonBasic; - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - if (std::isdigit(c)) { - conv->precision.set_value(parse_digits()); - } else if (c == '*') { - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - if (is_positional) { - if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr; - conv->precision.set_from_arg(parse_digits()); - if (c != '$') return nullptr; - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - } else { - conv->precision.set_from_arg(++*next_arg); - } - } else { - conv->precision.set_value(0); - } - } - } - - auto tag = GetTagForChar(c); - - if (ABSL_PREDICT_FALSE(c == 'v' && (pos - original_pos) != 1)) return nullptr; - - if (ABSL_PREDICT_FALSE(!tag.is_conv())) { - if (ABSL_PREDICT_FALSE(!tag.is_length())) return nullptr; - - // It is a length modifier. - using str_format_internal::LengthMod; - LengthMod length_mod = tag.as_length(); - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - if (c == 'h' && length_mod == LengthMod::h) { - conv->length_mod = LengthMod::hh; - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - } else if (c == 'l' && length_mod == LengthMod::l) { - conv->length_mod = LengthMod::ll; - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - } else { - conv->length_mod = length_mod; - } - tag = GetTagForChar(c); - - if (ABSL_PREDICT_FALSE(c == 'v')) return nullptr; - if (ABSL_PREDICT_FALSE(!tag.is_conv())) return nullptr; - } - - assert(CheckFastPathSetting(*conv)); - (void)(&CheckFastPathSetting); - - conv->conv = tag.as_conv(); - if (!is_positional) conv->arg_position = ++*next_arg; - return pos; +ABSL_ATTRIBUTE_NOINLINE const char* ConsumeUnboundConversionNoInline( + const char* p, const char* end, UnboundConversion* conv, int* next_arg) { + return ConsumeUnboundConversion(p, end, conv, next_arg); } -} // namespace - std::string LengthModToString(LengthMod v) { switch (v) { case LengthMod::h: @@ -262,12 +65,6 @@ std::string LengthModToString(LengthMod v) { return ""; } -const char *ConsumeUnboundConversion(const char *p, const char *end, - UnboundConversion *conv, int *next_arg) { - if (*next_arg < 0) return ConsumeConversion<true>(p, end, conv, next_arg); - return ConsumeConversion<false>(p, end, conv, next_arg); -} - struct ParsedFormatBase::ParsedFormatConsumer { explicit ParsedFormatConsumer(ParsedFormatBase *parsedformat) : parsed(parsedformat), data_pos(parsedformat->data_.get()) {} diff --git a/absl/strings/internal/str_format/parser.h b/absl/strings/internal/str_format/parser.h index a81bac83..35b6d49c 100644 --- a/absl/strings/internal/str_format/parser.h +++ b/absl/strings/internal/str_format/parser.h @@ -29,111 +29,18 @@ #include <vector> #include "absl/strings/internal/str_format/checker.h" +#include "absl/strings/internal/str_format/constexpr_parser.h" #include "absl/strings/internal/str_format/extension.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace str_format_internal { -enum class LengthMod : std::uint8_t { h, hh, l, ll, L, j, z, t, q, none }; - std::string LengthModToString(LengthMod v); -// The analyzed properties of a single specified conversion. -struct UnboundConversion { - UnboundConversion() {} - - class InputValue { - public: - void set_value(int value) { - assert(value >= 0); - value_ = value; - } - int value() const { return value_; } - - // Marks the value as "from arg". aka the '*' format. - // Requires `value >= 1`. - // When set, is_from_arg() return true and get_from_arg() returns the - // original value. - // `value()`'s return value is unspecfied in this state. - void set_from_arg(int value) { - assert(value > 0); - value_ = -value - 1; - } - bool is_from_arg() const { return value_ < -1; } - int get_from_arg() const { - assert(is_from_arg()); - return -value_ - 1; - } - - private: - int value_ = -1; - }; - - // No need to initialize. It will always be set in the parser. - int arg_position; - - InputValue width; - InputValue precision; - - Flags flags = Flags::kBasic; - LengthMod length_mod = LengthMod::none; - FormatConversionChar conv = FormatConversionCharInternal::kNone; -}; - -// Consume conversion spec prefix (not including '%') of [p, end) if valid. -// Examples of valid specs would be e.g.: "s", "d", "-12.6f". -// If valid, it returns the first character following the conversion spec, -// and the spec part is broken down and returned in 'conv'. -// If invalid, returns nullptr. -const char* ConsumeUnboundConversion(const char* p, const char* end, - UnboundConversion* conv, int* next_arg); - -// Helper tag class for the table below. -// It allows fast `char -> ConversionChar/LengthMod/Flags` checking and -// conversions. -class ConvTag { - public: - constexpr ConvTag(FormatConversionChar conversion_char) // NOLINT - : tag_(static_cast<uint8_t>(conversion_char)) {} - constexpr ConvTag(LengthMod length_mod) // NOLINT - : tag_(0x80 | static_cast<uint8_t>(length_mod)) {} - constexpr ConvTag(Flags flags) // NOLINT - : tag_(0xc0 | static_cast<uint8_t>(flags)) {} - constexpr ConvTag() : tag_(0xFF) {} - - bool is_conv() const { return (tag_ & 0x80) == 0; } - bool is_length() const { return (tag_ & 0xC0) == 0x80; } - bool is_flags() const { return (tag_ & 0xE0) == 0xC0; } - - FormatConversionChar as_conv() const { - assert(is_conv()); - assert(!is_length()); - assert(!is_flags()); - return static_cast<FormatConversionChar>(tag_); - } - LengthMod as_length() const { - assert(!is_conv()); - assert(is_length()); - assert(!is_flags()); - return static_cast<LengthMod>(tag_ & 0x3F); - } - Flags as_flags() const { - assert(!is_conv()); - assert(!is_length()); - assert(is_flags()); - return static_cast<Flags>(tag_ & 0x1F); - } - - private: - uint8_t tag_; -}; - -extern const ConvTag kTags[256]; -// Keep a single table for all the conversion chars and length modifiers. -inline ConvTag GetTagForChar(char c) { - return kTags[static_cast<unsigned char>(c)]; -} +const char* ConsumeUnboundConversionNoInline(const char* p, const char* end, + UnboundConversion* conv, + int* next_arg); // Parse the format string provided in 'src' and pass the identified items into // 'consumer'. @@ -187,7 +94,7 @@ bool ParseFormatString(string_view src, Consumer consumer) { } } else if (percent[1] != '%') { UnboundConversion conv; - p = ConsumeUnboundConversion(percent + 1, end, &conv, &next_arg); + p = ConsumeUnboundConversionNoInline(percent + 1, end, &conv, &next_arg); if (ABSL_PREDICT_FALSE(p == nullptr)) return false; if (ABSL_PREDICT_FALSE(!consumer.ConvertOne( conv, string_view(percent + 1, diff --git a/absl/strings/internal/str_format/parser_test.cc b/absl/strings/internal/str_format/parser_test.cc index c3e825fe..021f6a87 100644 --- a/absl/strings/internal/str_format/parser_test.cc +++ b/absl/strings/internal/str_format/parser_test.cc @@ -117,6 +117,7 @@ TEST_F(ConsumeUnboundConversionTest, ConsumeSpecification) { {__LINE__, "dzz", "d", "zz"}, // length mod as suffix {__LINE__, "3v", "", "3v"}, // 'v' cannot have modifiers {__LINE__, "hv", "", "hv"}, // 'v' cannot have modifiers + {__LINE__, "1$v", "1$v", ""}, // 'v' can have use posix syntax {__LINE__, "1$*2$d", "1$*2$d", "" }, // arg indexing and * allowed. {__LINE__, "0-14.3hhd", "0-14.3hhd", ""}, // precision, width {__LINE__, " 0-+#14.3hhd", " 0-+#14.3hhd", ""}, // flags diff --git a/absl/strings/internal/stringify_sink.cc b/absl/strings/internal/stringify_sink.cc new file mode 100644 index 00000000..7c6995ab --- /dev/null +++ b/absl/strings/internal/stringify_sink.cc @@ -0,0 +1,28 @@ +// Copyright 2022 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/stringify_sink.h" +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace strings_internal { + +void StringifySink::Append(size_t count, char ch) { buffer_.append(count, ch); } + +void StringifySink::Append(string_view v) { + buffer_.append(v.data(), v.size()); +} + +} // namespace strings_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/strings/internal/stringify_sink.h b/absl/strings/internal/stringify_sink.h index a83f70e4..fc3747bb 100644 --- a/absl/strings/internal/stringify_sink.h +++ b/absl/strings/internal/stringify_sink.h @@ -31,8 +31,6 @@ class StringifySink { void Append(string_view v); - bool PutPaddedString(string_view v, int width, int precision, bool left); - // Support `absl::Format(&sink, format, args...)`. friend void AbslFormatFlush(StringifySink* sink, absl::string_view v) { sink->Append(v); @@ -51,15 +49,6 @@ string_view ExtractStringification(StringifySink& sink, const T& v) { return sink.buffer_; } -template <typename T, typename = void> -struct HasAbslStringify : std::false_type {}; - -template <typename T> -struct HasAbslStringify<T, std::enable_if_t<std::is_void<decltype(AbslStringify( - std::declval<strings_internal::StringifySink&>(), - std::declval<const T&>()))>::value>> - : std::true_type {}; - } // namespace strings_internal ABSL_NAMESPACE_END diff --git a/absl/strings/str_cat.cc b/absl/strings/str_cat.cc index 6981347a..e5cb6d84 100644 --- a/absl/strings/str_cat.cc +++ b/absl/strings/str_cat.cc @@ -30,39 +30,6 @@ namespace absl { ABSL_NAMESPACE_BEGIN -namespace strings_internal { -void StringifySink::Append(size_t count, char ch) { buffer_.append(count, ch); } - -void StringifySink::Append(string_view v) { - buffer_.append(v.data(), v.size()); -} - -bool StringifySink::PutPaddedString(string_view v, int width, int precision, - bool left) { - size_t space_remaining = 0; - - if (width >= 0) space_remaining = static_cast<size_t>(width); - - size_t n = v.size(); - - if (precision >= 0) n = (std::min)(n, static_cast<size_t>(precision)); - - string_view shown(v.data(), n); - - if (shown.size() < space_remaining) { - space_remaining = space_remaining - shown.size(); - } else { - space_remaining = 0; - } - - if (!left) Append(space_remaining, ' '); - Append(shown); - if (left) Append(space_remaining, ' '); - return true; -} - -} // namespace strings_internal - AlphaNum::AlphaNum(Hex hex) { static_assert(numbers_internal::kFastToBufferSize >= 32, "This function only works when output buffer >= 32 bytes long"); diff --git a/absl/strings/str_cat.h b/absl/strings/str_cat.h index 1a37faae..5ee26db0 100644 --- a/absl/strings/str_cat.h +++ b/absl/strings/str_cat.h @@ -95,6 +95,7 @@ #include <vector> #include "absl/base/port.h" +#include "absl/strings/internal/has_absl_stringify.h" #include "absl/strings/internal/stringify_sink.h" #include "absl/strings/numbers.h" #include "absl/strings/string_view.h" @@ -317,7 +318,8 @@ class AlphaNum { // This overload matches only scoped enums. template <typename T, typename = typename std::enable_if< - std::is_enum<T>{} && !std::is_convertible<T, int>{}>::type> + std::is_enum<T>{} && !std::is_convertible<T, int>{} && + !strings_internal::HasAbslStringify<T>::value>::type> AlphaNum(T e) // NOLINT(runtime/explicit) : AlphaNum(static_cast<typename std::underlying_type<T>::type>(e)) {} diff --git a/absl/strings/str_cat_test.cc b/absl/strings/str_cat_test.cc index 1b3b7ece..c3fb3170 100644 --- a/absl/strings/str_cat_test.cc +++ b/absl/strings/str_cat_test.cc @@ -650,4 +650,16 @@ TEST(StrCat, AbslStringifyExampleUsingFormat) { EXPECT_EQ(absl::StrCat("a ", p, " z"), "a (10, 20) z"); } +enum class EnumWithStringify { Many = 0, Choices = 1 }; + +template <typename Sink> +void AbslStringify(Sink& sink, EnumWithStringify e) { + absl::Format(&sink, "%s", e == EnumWithStringify::Many ? "Many" : "Choices"); +} + +TEST(StrCat, AbslStringifyWithEnum) { + const auto e = EnumWithStringify::Choices; + EXPECT_EQ(absl::StrCat(e), "Choices"); +} + } // namespace diff --git a/absl/strings/str_format.h b/absl/strings/str_format.h index 43d86186..3536b70e 100644 --- a/absl/strings/str_format.h +++ b/absl/strings/str_format.h @@ -191,9 +191,9 @@ class FormatCountCapture { // absl::StrFormat(formatString, "TheVillage", 6); // // A format string generally follows the POSIX syntax as used within the POSIX -// `printf` specification. +// `printf` specification. (Exceptions are noted below.) // -// (See http://pubs.opengroup.org/onlinepubs/9699919799/functions/fprintf.html.) +// (See http://pubs.opengroup.org/onlinepubs/9699919799/functions/fprintf.html) // // In specific, the `FormatSpec` supports the following type specifiers: // * `c` for characters @@ -211,6 +211,10 @@ class FormatCountCapture { // * `n` for the special case of writing out the number of characters // written to this point. The resulting value must be captured within an // `absl::FormatCountCapture` type. +// * `v` for values using the default format for a deduced type. These deduced +// types include many of the primitive types denoted here as well as +// user-defined types containing the proper extensions. (See below for more +// information.) // // Implementation-defined behavior: // * A null pointer provided to "%s" or "%p" is output as "(nil)". @@ -239,6 +243,15 @@ class FormatCountCapture { // "%s%d%n", "hello", 123, absl::FormatCountCapture(&n)); // EXPECT_EQ(8, n); // +// NOTE: the `v` specifier (for "value") is a type specifier not present in the +// POSIX specification. %v will format values according to their deduced type. +// `v` uses `d` for signed integer values, `u` for unsigned integer values, `g` +// for floating point values, and formats boolean values as "true"/"false" +// (instead of 1 or 0 for booleans formatted using d). `const char*` is not +// supported; please use `std:string` and `string_view`. `char` is also not +// supported due to ambiguity of the type. This specifier does not support +// modifiers. +// // The `FormatSpec` intrinsically supports all of these fundamental C++ types: // // * Characters: `char`, `signed char`, `unsigned char` @@ -807,17 +820,25 @@ enum class FormatConversionCharSet : uint64_t { // FormatSink // -// An abstraction to which conversions write their string data. +// A format sink is a generic abstraction to which conversions may write their +// formatted string data. `absl::FormatConvert()` uses this sink to write its +// formatted string. // class FormatSink { public: - // Appends `count` copies of `ch`. + // FormatSink::Append() + // + // Appends `count` copies of `ch` to the format sink. void Append(size_t count, char ch) { sink_->Append(count, ch); } + // Overload of FormatSink::Append() for appending the characters of a string + // view to a format sink. void Append(string_view v) { sink_->Append(v); } - // Appends the first `precision` bytes of `v`. If this is less than - // `width`, spaces will be appended first (if `left` is false), or + // FormatSink::PutPaddedString() + // + // Appends `precision` number of bytes of `v` to the format sink. If this is + // less than `width`, spaces will be appended first (if `left` is false), or // after (if `left` is true) to ensure the total amount appended is // at least `width`. bool PutPaddedString(string_view v, int width, int precision, bool left) { diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc index 62ed262d..5198fb33 100644 --- a/absl/strings/str_format_test.cc +++ b/absl/strings/str_format_test.cc @@ -143,13 +143,20 @@ TEST_F(FormatEntryPointTest, AppendFormatFailWithV) { } TEST_F(FormatEntryPointTest, ManyArgs) { - EXPECT_EQ("24", StrFormat("%24$d", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24)); - EXPECT_EQ("60", StrFormat("%60$d", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, - 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, - 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, - 53, 54, 55, 56, 57, 58, 59, 60)); + EXPECT_EQ( + "60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 " + "36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 " + "12 11 10 9 8 7 6 5 4 3 2 1", + StrFormat("%60$d %59$d %58$d %57$d %56$d %55$d %54$d %53$d %52$d %51$d " + "%50$d %49$d %48$d %47$d %46$d %45$d %44$d %43$d %42$d %41$d " + "%40$d %39$d %38$d %37$d %36$d %35$d %34$d %33$d %32$d %31$d " + "%30$d %29$d %28$d %27$d %26$d %25$d %24$d %23$d %22$d %21$d " + "%20$d %19$d %18$d %17$d %16$d %15$d %14$d %13$d %12$d %11$d " + "%10$d %9$d %8$d %7$d %6$d %5$d %4$d %3$d %2$d %1$d", + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60)); } TEST_F(FormatEntryPointTest, Preparsed) { @@ -1135,6 +1142,51 @@ TEST_F(FormatExtensionTest, AbslStringifyExampleUsingFormat) { EXPECT_EQ(absl::StrFormat("a %v z", p), "a (10, 20) z"); } +enum class EnumClassWithStringify { Many = 0, Choices = 1 }; + +template <typename Sink> +void AbslStringify(Sink& sink, EnumClassWithStringify e) { + absl::Format(&sink, "%s", + e == EnumClassWithStringify::Many ? "Many" : "Choices"); +} + +enum EnumWithStringify { Many, Choices }; + +template <typename Sink> +void AbslStringify(Sink& sink, EnumWithStringify e) { + absl::Format(&sink, "%s", e == EnumWithStringify::Many ? "Many" : "Choices"); +} + +TEST_F(FormatExtensionTest, AbslStringifyWithEnumWithV) { + const auto e_class = EnumClassWithStringify::Choices; + EXPECT_EQ(absl::StrFormat("My choice is %v", e_class), + "My choice is Choices"); + + const auto e = EnumWithStringify::Choices; + EXPECT_EQ(absl::StrFormat("My choice is %v", e), "My choice is Choices"); +} + +TEST_F(FormatExtensionTest, AbslStringifyEnumWithD) { + const auto e_class = EnumClassWithStringify::Many; + EXPECT_EQ(absl::StrFormat("My choice is %d", e_class), "My choice is 0"); + + const auto e = EnumWithStringify::Choices; + EXPECT_EQ(absl::StrFormat("My choice is %d", e), "My choice is 1"); +} + +enum class EnumWithLargerValue { x = 32 }; + +template <typename Sink> +void AbslStringify(Sink& sink, EnumWithLargerValue e) { + absl::Format(&sink, "%s", "Many"); +} + +TEST_F(FormatExtensionTest, AbslStringifyEnumOtherSpecifiers) { + const auto e = EnumWithLargerValue::x; + EXPECT_EQ(absl::StrFormat("My choice is %g", e), "My choice is 32"); + EXPECT_EQ(absl::StrFormat("My choice is %x", e), "My choice is 20"); +} + } // namespace // Some codegen thunks that we can use to easily dump the generated assembly for diff --git a/absl/strings/substitute.h b/absl/strings/substitute.h index 5c3f6eff..d6a5a690 100644 --- a/absl/strings/substitute.h +++ b/absl/strings/substitute.h @@ -203,7 +203,8 @@ class Arg { // This overload matches only scoped enums. template <typename T, typename = typename std::enable_if< - std::is_enum<T>{} && !std::is_convertible<T, int>{}>::type> + std::is_enum<T>{} && !std::is_convertible<T, int>{} && + !strings_internal::HasAbslStringify<T>::value>::type> Arg(T value) // NOLINT(google-explicit-constructor) : Arg(static_cast<typename std::underlying_type<T>::type>(value)) {} diff --git a/absl/strings/substitute_test.cc b/absl/strings/substitute_test.cc index 9f04545f..9cb37c39 100644 --- a/absl/strings/substitute_test.cc +++ b/absl/strings/substitute_test.cc @@ -253,6 +253,18 @@ TEST(SubstituteTest, Enums) { ScopedEnumUInt16::kEnum1)); } +enum class EnumWithStringify { Many = 0, Choices = 1 }; + +template <typename Sink> +void AbslStringify(Sink& sink, EnumWithStringify e) { + sink.Append(e == EnumWithStringify::Many ? "Many" : "Choices"); +} + +TEST(SubstituteTest, AbslStringifyWithEnum) { + const auto e = EnumWithStringify::Choices; + EXPECT_EQ(absl::Substitute("$0", e), "Choices"); +} + #ifdef GTEST_HAS_DEATH_TEST TEST(SubstituteDeathTest, SubstituteDeath) { diff --git a/absl/synchronization/BUILD.bazel b/absl/synchronization/BUILD.bazel index 66bd8742..ccaee796 100644 --- a/absl/synchronization/BUILD.bazel +++ b/absl/synchronization/BUILD.bazel @@ -200,6 +200,7 @@ cc_library( deps = [ ":synchronization", "//absl/base:core_headers", + "//absl/functional:any_invocable", ], ) @@ -223,6 +224,18 @@ cc_test( ], ) +cc_test( + name = "mutex_method_pointer_test", + srcs = ["mutex_method_pointer_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":synchronization", + "//absl/base:config", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "mutex_benchmark_common", testonly = 1, diff --git a/absl/synchronization/CMakeLists.txt b/absl/synchronization/CMakeLists.txt index 9335c264..f64653bb 100644 --- a/absl/synchronization/CMakeLists.txt +++ b/absl/synchronization/CMakeLists.txt @@ -136,8 +136,9 @@ absl_cc_library( COPTS ${ABSL_DEFAULT_COPTS} DEPS - absl::synchronization + absl::any_invocable absl::core_headers + absl::synchronization TESTONLY ) @@ -162,6 +163,20 @@ absl_cc_test( absl_cc_test( NAME + mutex_method_pointer_test + SRCS + "mutex_method_pointer_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::base + absl::config + absl::synchronization + GTest::gmock_main +) + +absl_cc_test( + NAME notification_test SRCS "notification_test.cc" diff --git a/absl/synchronization/internal/thread_pool.h b/absl/synchronization/internal/thread_pool.h index 0cb96dac..5eb0bb60 100644 --- a/absl/synchronization/internal/thread_pool.h +++ b/absl/synchronization/internal/thread_pool.h @@ -20,9 +20,11 @@ #include <functional> #include <queue> #include <thread> // NOLINT(build/c++11) +#include <utility> #include <vector> #include "absl/base/thread_annotations.h" +#include "absl/functional/any_invocable.h" #include "absl/synchronization/mutex.h" namespace absl { @@ -33,6 +35,7 @@ namespace synchronization_internal { class ThreadPool { public: explicit ThreadPool(int num_threads) { + threads_.reserve(num_threads); for (int i = 0; i < num_threads; ++i) { threads_.push_back(std::thread(&ThreadPool::WorkLoop, this)); } @@ -54,7 +57,7 @@ class ThreadPool { } // Schedule a function to be run on a ThreadPool thread immediately. - void Schedule(std::function<void()> func) { + void Schedule(absl::AnyInvocable<void()> func) { assert(func != nullptr); absl::MutexLock l(&mu_); queue_.push(std::move(func)); @@ -67,7 +70,7 @@ class ThreadPool { void WorkLoop() { while (true) { - std::function<void()> func; + absl::AnyInvocable<void()> func; { absl::MutexLock l(&mu_); mu_.Await(absl::Condition(this, &ThreadPool::WorkAvailable)); @@ -82,7 +85,7 @@ class ThreadPool { } absl::Mutex mu_; - std::queue<std::function<void()>> queue_ ABSL_GUARDED_BY(mu_); + std::queue<absl::AnyInvocable<void()>> queue_ ABSL_GUARDED_BY(mu_); std::vector<std::thread> threads_; }; diff --git a/absl/synchronization/lifetime_test.cc b/absl/synchronization/lifetime_test.cc index cc973a32..e6274232 100644 --- a/absl/synchronization/lifetime_test.cc +++ b/absl/synchronization/lifetime_test.cc @@ -123,10 +123,10 @@ class OnDestruction { }; // These tests require that the compiler correctly supports C++11 constant -// initialization... but MSVC has a known regression since v19.10: +// initialization... but MSVC has a known regression since v19.10 till v19.25: // https://developercommunity.visualstudio.com/content/problem/336946/class-with-constexpr-constructor-not-using-static.html -// TODO(epastor): Limit the affected range once MSVC fixes this bug. -#if defined(__clang__) || !(defined(_MSC_VER) && _MSC_VER > 1900) +#if defined(__clang__) || \ + !(defined(_MSC_VER) && _MSC_VER > 1900 && _MSC_VER < 1925) // kConstInit // Test early usage. (Declaration comes first; definitions must appear after // the test runner.) diff --git a/absl/synchronization/mutex.cc b/absl/synchronization/mutex.cc index 69103ed8..c9d7c41a 100644 --- a/absl/synchronization/mutex.cc +++ b/absl/synchronization/mutex.cc @@ -37,6 +37,8 @@ #include <atomic> #include <cinttypes> #include <cstddef> +#include <cstring> +#include <iterator> #include <thread> // NOLINT(build/c++11) #include "absl/base/attributes.h" @@ -135,25 +137,42 @@ enum DelayMode { AGGRESSIVE, GENTLE }; struct ABSL_CACHELINE_ALIGNED MutexGlobals { absl::once_flag once; int spinloop_iterations = 0; - int32_t mutex_sleep_limit[2] = {}; + int32_t mutex_sleep_spins[2] = {}; + absl::Duration mutex_sleep_time; }; +absl::Duration MeasureTimeToYield() { + absl::Time before = absl::Now(); + ABSL_INTERNAL_C_SYMBOL(AbslInternalMutexYield)(); + return absl::Now() - before; +} + const MutexGlobals &GetMutexGlobals() { ABSL_CONST_INIT static MutexGlobals data; absl::base_internal::LowLevelCallOnce(&data.once, [&]() { const int num_cpus = absl::base_internal::NumCPUs(); data.spinloop_iterations = num_cpus > 1 ? 1500 : 0; - // If this a uniprocessor, only yield/sleep. Otherwise, if the mode is + // If this a uniprocessor, only yield/sleep. + // Real-time threads are often unable to yield, so the sleep time needs + // to be long enough to keep the calling thread asleep until scheduling + // happens. + // If this is multiprocessor, allow spinning. If the mode is // aggressive then spin many times before yielding. If the mode is // 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. if (num_cpus > 1) { - data.mutex_sleep_limit[AGGRESSIVE] = 5000; - data.mutex_sleep_limit[GENTLE] = 250; + data.mutex_sleep_spins[AGGRESSIVE] = 5000; + data.mutex_sleep_spins[GENTLE] = 250; + data.mutex_sleep_time = absl::Microseconds(10); } else { - data.mutex_sleep_limit[AGGRESSIVE] = 0; - data.mutex_sleep_limit[GENTLE] = 0; + data.mutex_sleep_spins[AGGRESSIVE] = 0; + data.mutex_sleep_spins[GENTLE] = 0; + data.mutex_sleep_time = MeasureTimeToYield() * 5; + data.mutex_sleep_time = + std::min(data.mutex_sleep_time, absl::Milliseconds(1)); + data.mutex_sleep_time = + std::max(data.mutex_sleep_time, absl::Microseconds(10)); } }); return data; @@ -164,7 +183,8 @@ namespace synchronization_internal { // Returns the Mutex delay on iteration `c` depending on the given `mode`. // The returned value should be used as `c` for the next call to `MutexDelay`. int MutexDelay(int32_t c, int mode) { - const int32_t limit = GetMutexGlobals().mutex_sleep_limit[mode]; + const int32_t limit = GetMutexGlobals().mutex_sleep_spins[mode]; + const absl::Duration sleep_time = GetMutexGlobals().mutex_sleep_time; if (c < limit) { // Spin. c++; @@ -177,7 +197,7 @@ int MutexDelay(int32_t c, int mode) { c++; } else { // Then wait. - absl::SleepFor(absl::Microseconds(10)); + absl::SleepFor(sleep_time); c = 0; } ABSL_TSAN_MUTEX_POST_DIVERT(nullptr, 0); @@ -571,10 +591,15 @@ static SynchLocksHeld *Synch_GetAllLocks() { void Mutex::IncrementSynchSem(Mutex *mu, PerThreadSynch *w) { if (mu) { ABSL_TSAN_MUTEX_PRE_DIVERT(mu, 0); - } - PerThreadSem::Post(w->thread_identity()); - if (mu) { + // We miss synchronization around passing PerThreadSynch between threads + // since it happens inside of the Mutex code, so we need to ignore all + // accesses to the object. + ABSL_ANNOTATE_IGNORE_READS_AND_WRITES_BEGIN(); + PerThreadSem::Post(w->thread_identity()); + ABSL_ANNOTATE_IGNORE_READS_AND_WRITES_END(); ABSL_TSAN_MUTEX_POST_DIVERT(mu, 0); + } else { + PerThreadSem::Post(w->thread_identity()); } } @@ -2762,25 +2787,31 @@ static bool Dereference(void *arg) { return *(static_cast<bool *>(arg)); } -Condition::Condition() {} // null constructor, used for kTrue only -const Condition Condition::kTrue; +ABSL_CONST_INIT const Condition Condition::kTrue; Condition::Condition(bool (*func)(void *), void *arg) : eval_(&CallVoidPtrFunction), - function_(func), - method_(nullptr), - arg_(arg) {} + arg_(arg) { + static_assert(sizeof(&func) <= sizeof(callback_), + "An overlarge function pointer passed to Condition."); + StoreCallback(func); +} bool Condition::CallVoidPtrFunction(const Condition *c) { - return (*c->function_)(c->arg_); + using FunctionPointer = bool (*)(void *); + FunctionPointer function_pointer; + std::memcpy(&function_pointer, c->callback_, sizeof(function_pointer)); + return (*function_pointer)(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<bool *>(cond)) {} + arg_(const_cast<bool *>(cond)) { + using FunctionPointer = bool (*)(void *); + const FunctionPointer dereference = Dereference; + StoreCallback(dereference); +} bool Condition::Eval() const { // eval_ == null for kTrue @@ -2788,14 +2819,15 @@ bool Condition::Eval() const { } bool Condition::GuaranteedEqual(const Condition *a, const Condition *b) { - if (a == nullptr) { + // kTrue logic. + if (a == nullptr || a->eval_ == nullptr) { return b == nullptr || b->eval_ == nullptr; + }else if (b == nullptr || b->eval_ == nullptr) { + return false; } - if (b == nullptr || b->eval_ == nullptr) { - return a->eval_ == nullptr; - } - return a->eval_ == b->eval_ && a->function_ == b->function_ && - a->arg_ == b->arg_ && a->method_ == b->method_; + // Check equality of the representative fields. + return a->eval_ == b->eval_ && a->arg_ == b->arg_ && + !memcmp(a->callback_, b->callback_, sizeof(a->callback_)); } ABSL_NAMESPACE_END diff --git a/absl/synchronization/mutex.h b/absl/synchronization/mutex.h index 8694bb75..c6a85100 100644 --- a/absl/synchronization/mutex.h +++ b/absl/synchronization/mutex.h @@ -60,6 +60,8 @@ #include <atomic> #include <cstdint> +#include <cstring> +#include <iterator> #include <string> #include "absl/base/const_init.h" @@ -612,12 +614,12 @@ class ABSL_SCOPED_LOCKABLE WriterMutexLock { // Condition // ----------------------------------------------------------------------------- // -// As noted above, `Mutex` contains a number of member functions which take a -// `Condition` as an argument; clients can wait for conditions to become `true` -// before attempting to acquire the mutex. These sections are known as -// "condition critical" sections. To use a `Condition`, you simply need to -// construct it, and use within an appropriate `Mutex` member function; -// everything else in the `Condition` class is an implementation detail. +// `Mutex` contains a number of member functions which take a `Condition` as an +// argument; clients can wait for conditions to become `true` before attempting +// to acquire the mutex. These sections are known as "condition critical" +// sections. To use a `Condition`, you simply need to construct it, and use +// within an appropriate `Mutex` member function; everything else in the +// `Condition` class is an implementation detail. // // A `Condition` is specified as a function pointer which returns a boolean. // `Condition` functions should be pure functions -- their results should depend @@ -727,7 +729,7 @@ class Condition { : Condition(obj, static_cast<bool (T::*)() const>(&T::operator())) {} // A Condition that always returns `true`. - static const Condition kTrue; + ABSL_CONST_INIT static const Condition kTrue; // Evaluates the condition. bool Eval() const; @@ -742,22 +744,54 @@ class Condition { static bool GuaranteedEqual(const Condition *a, const Condition *b); private: - typedef bool (*InternalFunctionType)(void * arg); - typedef bool (Condition::*InternalMethodType)(); - typedef bool (*InternalMethodCallerType)(void * arg, - InternalMethodType internal_method); - - bool (*eval_)(const Condition*); // Actual evaluator - InternalFunctionType function_; // function taking pointer returning bool - InternalMethodType method_; // method returning bool - void *arg_; // arg of function_ or object of method_ - - Condition(); // null constructor used only to create kTrue + // Sizing an allocation for a method pointer can be subtle. In the Itanium + // specifications, a method pointer has a predictable, uniform size. On the + // other hand, MSVC ABI, method pointer sizes vary based on the + // inheritance of the class. Specifically, method pointers from classes with + // multiple inheritance are bigger than those of classes with single + // inheritance. Other variations also exist. + +#ifndef _MSC_VER + // Allocation for a function pointer or method pointer. + // The {0} initializer ensures that all unused bytes of this buffer are + // always zeroed out. This is necessary, because GuaranteedEqual() compares + // all of the bytes, unaware of which bytes are relevant to a given `eval_`. + using MethodPtr = bool (Condition::*)(); + char callback_[sizeof(MethodPtr)] = {0}; +#else + // It is well known that the larget MSVC pointer-to-member is 24 bytes. This + // may be the largest known pointer-to-member of any platform. For this + // reason we will allocate 24 bytes for MSVC platform toolchains. + char callback_[24] = {0}; +#endif + + // Function with which to evaluate callbacks and/or arguments. + bool (*eval_)(const Condition*) = nullptr; + + // Either an argument for a function call or an object for a method call. + void *arg_ = nullptr; // Various functions eval_ can point to: static bool CallVoidPtrFunction(const Condition*); template <typename T> static bool CastAndCallFunction(const Condition* c); template <typename T> static bool CastAndCallMethod(const Condition* c); + + // Helper methods for storing, validating, and reading callback arguments. + template <typename T> + inline void StoreCallback(T callback) { + static_assert( + sizeof(callback) <= sizeof(callback_), + "An overlarge pointer was passed as a callback to Condition."); + std::memcpy(callback_, &callback, sizeof(callback)); + } + + template <typename T> + inline void ReadCallback(T *callback) const { + std::memcpy(callback, callback_, sizeof(*callback)); + } + + // Used only to create kTrue. + constexpr Condition() = default; }; // ----------------------------------------------------------------------------- @@ -949,44 +983,48 @@ inline CondVar::CondVar() : cv_(0) {} // static template <typename T> bool Condition::CastAndCallMethod(const Condition *c) { - typedef bool (T::*MemberType)(); - MemberType rm = reinterpret_cast<MemberType>(c->method_); - T *x = static_cast<T *>(c->arg_); - return (x->*rm)(); + T *object = static_cast<T *>(c->arg_); + bool (T::*method_pointer)(); + c->ReadCallback(&method_pointer); + return (object->*method_pointer)(); } // static template <typename T> bool Condition::CastAndCallFunction(const Condition *c) { - typedef bool (*FuncType)(T *); - FuncType fn = reinterpret_cast<FuncType>(c->function_); - T *x = static_cast<T *>(c->arg_); - return (*fn)(x); + bool (*function)(T *); + c->ReadCallback(&function); + T *argument = static_cast<T *>(c->arg_); + return (*function)(argument); } template <typename T> inline Condition::Condition(bool (*func)(T *), T *arg) : eval_(&CastAndCallFunction<T>), - function_(reinterpret_cast<InternalFunctionType>(func)), - method_(nullptr), - arg_(const_cast<void *>(static_cast<const void *>(arg))) {} + arg_(const_cast<void *>(static_cast<const void *>(arg))) { + static_assert(sizeof(&func) <= sizeof(callback_), + "An overlarge function pointer was passed to Condition."); + StoreCallback(func); +} template <typename T> inline Condition::Condition(T *object, bool (absl::internal::identity<T>::type::*method)()) : eval_(&CastAndCallMethod<T>), - function_(nullptr), - method_(reinterpret_cast<InternalMethodType>(method)), - arg_(object) {} + arg_(object) { + static_assert(sizeof(&method) <= sizeof(callback_), + "An overlarge method pointer was passed to Condition."); + StoreCallback(method); +} template <typename T> inline Condition::Condition(const T *object, bool (absl::internal::identity<T>::type::*method)() const) : eval_(&CastAndCallMethod<T>), - function_(nullptr), - method_(reinterpret_cast<InternalMethodType>(method)), - arg_(reinterpret_cast<void *>(const_cast<T *>(object))) {} + arg_(reinterpret_cast<void *>(const_cast<T *>(object))) { + StoreCallback(method); +} // Register hooks for profiling support. // diff --git a/absl/synchronization/mutex_method_pointer_test.cc b/absl/synchronization/mutex_method_pointer_test.cc new file mode 100644 index 00000000..1ec801a0 --- /dev/null +++ b/absl/synchronization/mutex_method_pointer_test.cc @@ -0,0 +1,138 @@ +// 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/synchronization/mutex.h" + +#include <cstdlib> +#include <string> + +#include "gtest/gtest.h" +#include "absl/base/config.h" + +namespace { + +class IncompleteClass; + +#ifdef _MSC_VER +// These tests verify expectations about sizes of MSVC pointers to methods. +// Pointers to methods are distinguished by whether their class hierachies +// contain single inheritance, multiple inheritance, or virtual inheritence. + +// Declare classes of the various MSVC inheritance types. +class __single_inheritance SingleInheritance{}; +class __multiple_inheritance MultipleInheritance; +class __virtual_inheritance VirtualInheritance; + +TEST(MutexMethodPointerTest, MicrosoftMethodPointerSize) { + void (SingleInheritance::*single_inheritance_method_pointer)(); + void (MultipleInheritance::*multiple_inheritance_method_pointer)(); + void (VirtualInheritance::*virtual_inheritance_method_pointer)(); + +#if defined(_M_IX86) || defined(_M_ARM) + static_assert(sizeof(single_inheritance_method_pointer) == 4, + "Unexpected sizeof(single_inheritance_method_pointer)."); + static_assert(sizeof(multiple_inheritance_method_pointer) == 8, + "Unexpected sizeof(multiple_inheritance_method_pointer)."); + static_assert(sizeof(virtual_inheritance_method_pointer) == 12, + "Unexpected sizeof(virtual_inheritance_method_pointer)."); +#elif defined(_M_X64) || defined(__aarch64__) + static_assert(sizeof(single_inheritance_method_pointer) == 8, + "Unexpected sizeof(single_inheritance_method_pointer)."); + static_assert(sizeof(multiple_inheritance_method_pointer) == 16, + "Unexpected sizeof(multiple_inheritance_method_pointer)."); + static_assert(sizeof(virtual_inheritance_method_pointer) == 16, + "Unexpected sizeof(virtual_inheritance_method_pointer)."); +#endif + void (IncompleteClass::*incomplete_class_method_pointer)(); + static_assert(sizeof(incomplete_class_method_pointer) >= + sizeof(virtual_inheritance_method_pointer), + "Failed invariant: sizeof(incomplete_class_method_pointer) >= " + "sizeof(virtual_inheritance_method_pointer)!"); +} + +class Callback { + bool x = true; + + public: + Callback() {} + bool method() { + x = !x; + return x; + } +}; + +class M2 { + bool x = true; + + public: + M2() {} + bool method2() { + x = !x; + return x; + } +}; + +class MultipleInheritance : public Callback, public M2 {}; + +TEST(MutexMethodPointerTest, ConditionWithMultipleInheritanceMethod) { + // This test ensures that Condition can deal with method pointers from classes + // with multiple inheritance. + MultipleInheritance object = MultipleInheritance(); + absl::Condition condition(&object, &MultipleInheritance::method); + EXPECT_FALSE(condition.Eval()); + EXPECT_TRUE(condition.Eval()); +} + +class __virtual_inheritance VirtualInheritance : virtual public Callback { + bool x = false; + + public: + VirtualInheritance() {} + bool method() { + x = !x; + return x; + } +}; + +TEST(MutexMethodPointerTest, ConditionWithVirtualInheritanceMethod) { + // This test ensures that Condition can deal with method pointers from classes + // with virtual inheritance. + VirtualInheritance object = VirtualInheritance(); + absl::Condition condition(&object, &VirtualInheritance::method); + EXPECT_TRUE(condition.Eval()); + EXPECT_FALSE(condition.Eval()); +} +#endif // #ifdef _MSC_VER + +TEST(MutexMethodPointerTest, ConditionWithIncompleteClassMethod) { + using IncompleteClassMethodPointer = void (IncompleteClass::*)(); + + union CallbackSlot { + void (*anonymous_function_pointer)(); + IncompleteClassMethodPointer incomplete_class_method_pointer; + }; + + static_assert(sizeof(CallbackSlot) >= sizeof(IncompleteClassMethodPointer), + "The callback slot is not big enough for method pointers."); + static_assert( + sizeof(CallbackSlot) == sizeof(IncompleteClassMethodPointer), + "The callback slot is not big enough for anonymous function pointers."); + +#if defined(_MSC_VER) + static_assert(sizeof(IncompleteClassMethodPointer) <= 24, + "The pointer to a method of an incomplete class is too big."); +#endif +} + +} // namespace diff --git a/absl/synchronization/mutex_test.cc b/absl/synchronization/mutex_test.cc index 99bb0175..34751cb1 100644 --- a/absl/synchronization/mutex_test.cc +++ b/absl/synchronization/mutex_test.cc @@ -295,8 +295,9 @@ static void TestTime(TestContext *cxt, int c, bool use_cv) { "TestTime failed"); } elapsed = absl::Now() - start; - ABSL_RAW_CHECK(absl::Seconds(0.9) <= elapsed && - elapsed <= absl::Seconds(2.0), "TestTime failed"); + ABSL_RAW_CHECK( + absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0), + "TestTime failed"); ABSL_RAW_CHECK(cxt->g0 == cxt->threads, "TestTime failed"); } else if (c == 1) { @@ -343,7 +344,7 @@ static void TestMuTime(TestContext *cxt, int c) { TestTime(cxt, c, false); } static void TestCVTime(TestContext *cxt, int c) { TestTime(cxt, c, true); } static void EndTest(int *c0, int *c1, absl::Mutex *mu, absl::CondVar *cv, - const std::function<void(int)>& cb) { + const std::function<void(int)> &cb) { mu->Lock(); int c = (*c0)++; mu->Unlock(); @@ -366,9 +367,9 @@ static int RunTestCommon(TestContext *cxt, void (*test)(TestContext *cxt, int), cxt->threads = threads; absl::synchronization_internal::ThreadPool tp(threads); for (int i = 0; i != threads; i++) { - tp.Schedule(std::bind(&EndTest, &c0, &c1, &mu2, &cv2, - std::function<void(int)>( - std::bind(test, cxt, std::placeholders::_1)))); + tp.Schedule(std::bind( + &EndTest, &c0, &c1, &mu2, &cv2, + std::function<void(int)>(std::bind(test, cxt, std::placeholders::_1)))); } mu2.Lock(); while (c1 != threads) { @@ -682,14 +683,14 @@ struct LockWhenTestStruct { bool waiting = false; }; -static bool LockWhenTestIsCond(LockWhenTestStruct* s) { +static bool LockWhenTestIsCond(LockWhenTestStruct *s) { s->mu2.Lock(); s->waiting = true; s->mu2.Unlock(); return s->cond; } -static void LockWhenTestWaitForIsCond(LockWhenTestStruct* s) { +static void LockWhenTestWaitForIsCond(LockWhenTestStruct *s) { s->mu1.LockWhen(absl::Condition(&LockWhenTestIsCond, s)); s->mu1.Unlock(); } @@ -1694,8 +1695,7 @@ TEST(Mutex, Timed) { TEST(Mutex, CVTime) { int threads = 10; // Use a fixed thread count of 10 int iterations = 1; - EXPECT_EQ(RunTest(&TestCVTime, threads, iterations, 1), - threads * iterations); + EXPECT_EQ(RunTest(&TestCVTime, threads, iterations, 1), threads * iterations); } TEST(Mutex, MuTime) { diff --git a/absl/time/BUILD.bazel b/absl/time/BUILD.bazel index b5f03980..14b7c5b7 100644 --- a/absl/time/BUILD.bazel +++ b/absl/time/BUILD.bazel @@ -59,10 +59,7 @@ cc_library( cc_library( name = "test_util", testonly = 1, - srcs = [ - "internal/test_util.cc", - "internal/zoneinfo.inc", - ], + srcs = ["internal/test_util.cc"], hdrs = ["internal/test_util.h"], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, @@ -71,8 +68,6 @@ cc_library( ":time", "//absl/base:config", "//absl/base:raw_logging_internal", - "//absl/time/internal/cctz:time_zone", - "@com_google_googletest//:gtest", ], ) @@ -87,6 +82,8 @@ cc_test( "time_zone_test.cc", ], copts = ABSL_TEST_COPTS, + data = ["//absl/time/internal/cctz:zoneinfo"], + env = {"TZDIR": "absl/time/internal/cctz/testdata/zoneinfo"}, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":test_util", @@ -133,6 +130,8 @@ cc_test( "time_benchmark.cc", ], copts = ABSL_TEST_COPTS, + data = ["//absl/time/internal/cctz:zoneinfo"], + env = {"TZDIR": "absl/time/internal/cctz/testdata/zoneinfo"}, linkopts = ABSL_DEFAULT_LINKOPTS, tags = [ "benchmark", diff --git a/absl/time/CMakeLists.txt b/absl/time/CMakeLists.txt index 1580a303..7b720540 100644 --- a/absl/time/CMakeLists.txt +++ b/absl/time/CMakeLists.txt @@ -95,7 +95,6 @@ absl_cc_library( "internal/test_util.h" SRCS "internal/test_util.cc" - "internal/zoneinfo.inc" COPTS ${ABSL_DEFAULT_COPTS} DEPS @@ -103,7 +102,6 @@ absl_cc_library( absl::config absl::raw_logging_internal absl::time_zone - GTest::gmock TESTONLY ) diff --git a/absl/time/internal/cctz/BUILD.bazel b/absl/time/internal/cctz/BUILD.bazel index 7304d40d..edeabd81 100644 --- a/absl/time/internal/cctz/BUILD.bazel +++ b/absl/time/internal/cctz/BUILD.bazel @@ -16,25 +16,6 @@ package(features = ["-parse_headers"]) licenses(["notice"]) -filegroup( - name = "zoneinfo", - srcs = glob(["testdata/zoneinfo/**"]), -) - -config_setting( - name = "osx", - constraint_values = [ - "@platforms//os:osx", - ], -) - -config_setting( - name = "ios", - constraint_values = [ - "@platforms//os:ios", - ], -) - ### libraries cc_library( @@ -72,15 +53,10 @@ cc_library( "include/cctz/time_zone.h", "include/cctz/zone_info_source.h", ], - linkopts = select({ - ":osx": [ - "-framework Foundation", - ], - ":ios": [ - "-framework Foundation", - ], - "//conditions:default": [], - }), + # OS X and iOS no longer use `linkopts = ["-framework CoreFoundation"]` + # as (1) bazel adds it automatically, and (2) it caused problems when + # cross-compiling for Android. + # See https://github.com/abseil/abseil-cpp/issues/326 for details. visibility = ["//visibility:public"], deps = [ ":civil_time", @@ -115,6 +91,7 @@ cc_test( size = "small", srcs = ["src/time_zone_format_test.cc"], data = [":zoneinfo"], + env = {"TZDIR": "absl/time/internal/cctz/testdata/zoneinfo"}, tags = [ "no_test_android_arm", "no_test_android_arm64", @@ -135,6 +112,7 @@ cc_test( timeout = "moderate", srcs = ["src/time_zone_lookup_test.cc"], data = [":zoneinfo"], + env = {"TZDIR": "absl/time/internal/cctz/testdata/zoneinfo"}, tags = [ "no_test_android_arm", "no_test_android_arm64", @@ -170,6 +148,12 @@ cc_test( ], ) +filegroup( + name = "zoneinfo", + srcs = glob(["testdata/zoneinfo/**"]), + visibility = ["//absl/time:__subpackages__"], +) + ### examples ### binaries diff --git a/absl/time/internal/cctz/src/time_zone_info.cc b/absl/time/internal/cctz/src/time_zone_info.cc index 8966f7ac..787426f7 100644 --- a/absl/time/internal/cctz/src/time_zone_info.cc +++ b/absl/time/internal/cctz/src/time_zone_info.cc @@ -502,9 +502,9 @@ bool TimeZoneInfo::Load(ZoneInfoSource* zip) { // encoded zoneinfo. The ttisstd/ttisgmt indicators only apply when // interpreting a POSIX spec that does not include start/end rules, and // that isn't the case here (see "zic -p"). - bp += (8 + 4) * hdr.leapcnt; // leap-time + TAI-UTC - bp += 1 * hdr.ttisstdcnt; // UTC/local indicators - bp += 1 * hdr.ttisutcnt; // standard/wall indicators + bp += (time_len + 4) * hdr.leapcnt; // leap-time + TAI-UTC + bp += 1 * hdr.ttisstdcnt; // UTC/local indicators + bp += 1 * hdr.ttisutcnt; // standard/wall indicators assert(bp == tbuf.data() + tbuf.size()); future_spec_.clear(); @@ -533,8 +533,8 @@ bool TimeZoneInfo::Load(ZoneInfoSource* zip) { // Trim redundant transitions. zic may have added these to work around // differences between the glibc and reference implementations (see - // zic.c:dontmerge) and the Qt library (see zic.c:WORK_AROUND_QTBUG_53071). - // For us, they just get in the way when we do future_spec_ extension. + // zic.c:dontmerge) or to avoid bugs in old readers. For us, they just + // get in the way when we do future_spec_ extension. while (hdr.timecnt > 1) { if (!EquivTransitions(transitions_[hdr.timecnt - 1].type_index, transitions_[hdr.timecnt - 2].type_index)) { diff --git a/absl/time/internal/cctz/src/zone_info_source.cc b/absl/time/internal/cctz/src/zone_info_source.cc index 5ab5a59e..b818c213 100644 --- a/absl/time/internal/cctz/src/zone_info_source.cc +++ b/absl/time/internal/cctz/src/zone_info_source.cc @@ -66,41 +66,41 @@ extern ZoneInfoSourceFactory zone_info_source_factory; extern ZoneInfoSourceFactory default_factory; ZoneInfoSourceFactory default_factory = DefaultFactory; #if defined(_M_IX86) || defined(_M_ARM) -#pragma comment( \ - linker, \ - "/alternatename:?zone_info_source_factory@cctz_extension@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ - "@ABV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ - "@@ZA=?default_factory@cctz_extension@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ - "@ABV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ - "@@ZA") +#pragma comment( \ + linker, \ + "/alternatename:?zone_info_source_factory@cctz_extension@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ + "@ABV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ + "@@ZA=?default_factory@cctz_extension@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ + "@ABV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ + "@@ZA") #elif defined(_M_IA_64) || defined(_M_AMD64) || defined(_M_ARM64) -#pragma comment( \ - linker, \ - "/alternatename:?zone_info_source_factory@cctz_extension@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ - "@AEBV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ - "@@ZEA=?default_factory@cctz_extension@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ - "@AEBV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ - "@@ZEA") +#pragma comment( \ + linker, \ + "/alternatename:?zone_info_source_factory@cctz_extension@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ + "@AEBV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ + "@@ZEA=?default_factory@cctz_extension@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ + "@AEBV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ + "@@ZEA") #else #error Unsupported MSVC platform #endif // _M_<PLATFORM> diff --git a/absl/time/internal/cctz/testdata/version b/absl/time/internal/cctz/testdata/version index 13ad873c..b74fa117 100644 --- a/absl/time/internal/cctz/testdata/version +++ b/absl/time/internal/cctz/testdata/version @@ -1 +1 @@ -2022e +2022g diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Bahia_Banderas b/absl/time/internal/cctz/testdata/zoneinfo/America/Bahia_Banderas Binary files differindex 179937ff..48faea2e 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Bahia_Banderas +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Bahia_Banderas diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Bogota b/absl/time/internal/cctz/testdata/zoneinfo/America/Bogota Binary files differindex 6cb53d4e..85b90333 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Bogota +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Bogota diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Cambridge_Bay b/absl/time/internal/cctz/testdata/zoneinfo/America/Cambridge_Bay Binary files differindex 0a222524..1092f4b6 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Cambridge_Bay +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Cambridge_Bay diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Chihuahua b/absl/time/internal/cctz/testdata/zoneinfo/America/Chihuahua Binary files differindex b3317378..5e0a54f0 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Chihuahua +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Chihuahua diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Ciudad_Juarez b/absl/time/internal/cctz/testdata/zoneinfo/America/Ciudad_Juarez Binary files differnew file mode 100644 index 00000000..f636ee64 --- /dev/null +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Ciudad_Juarez diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Godthab b/absl/time/internal/cctz/testdata/zoneinfo/America/Godthab Binary files differindex 4ddc99d8..79d7a454 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Godthab +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Godthab diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Inuvik b/absl/time/internal/cctz/testdata/zoneinfo/America/Inuvik Binary files differindex af3107db..86639f6e 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Inuvik +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Inuvik diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Iqaluit b/absl/time/internal/cctz/testdata/zoneinfo/America/Iqaluit Binary files differindex eb2c99cc..95e055cb 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Iqaluit +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Iqaluit diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Mazatlan b/absl/time/internal/cctz/testdata/zoneinfo/America/Mazatlan Binary files differindex 6b41102b..97d4d36c 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Mazatlan +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Mazatlan diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Merida b/absl/time/internal/cctz/testdata/zoneinfo/America/Merida Binary files differindex d3b0ca12..e5de1131 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Merida +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Merida diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Mexico_City b/absl/time/internal/cctz/testdata/zoneinfo/America/Mexico_City Binary files differindex 6542c9c5..80a415c7 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Mexico_City +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Mexico_City diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Monterrey b/absl/time/internal/cctz/testdata/zoneinfo/America/Monterrey Binary files differindex dea9e3f5..a5822e2c 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Monterrey +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Monterrey diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Nipigon b/absl/time/internal/cctz/testdata/zoneinfo/America/Nipigon Binary files differindex b9f67a9f..fe6be8ea 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Nipigon +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Nipigon diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Nuuk b/absl/time/internal/cctz/testdata/zoneinfo/America/Nuuk Binary files differindex 4ddc99d8..79d7a454 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Nuuk +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Nuuk diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Ojinaga b/absl/time/internal/cctz/testdata/zoneinfo/America/Ojinaga Binary files differindex 2ee585ca..2fc74e94 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Ojinaga +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Ojinaga diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Pangnirtung b/absl/time/internal/cctz/testdata/zoneinfo/America/Pangnirtung Binary files differindex 5be6f9b0..95e055cb 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Pangnirtung +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Pangnirtung diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Rainy_River b/absl/time/internal/cctz/testdata/zoneinfo/America/Rainy_River Binary files differindex d6ddda48..7e646d18 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Rainy_River +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Rainy_River diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Rankin_Inlet b/absl/time/internal/cctz/testdata/zoneinfo/America/Rankin_Inlet Binary files differindex 92e2ed2d..6d1d90de 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Rankin_Inlet +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Rankin_Inlet diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Resolute b/absl/time/internal/cctz/testdata/zoneinfo/America/Resolute Binary files differindex a84d1dfd..97eb8a9c 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Resolute +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Resolute diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Thunder_Bay b/absl/time/internal/cctz/testdata/zoneinfo/America/Thunder_Bay Binary files differindex fcb03280..fe6be8ea 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Thunder_Bay +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Thunder_Bay diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Whitehorse b/absl/time/internal/cctz/testdata/zoneinfo/America/Whitehorse Binary files differindex 878b6a92..40baa9ab 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Whitehorse +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Whitehorse diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Yellowknife b/absl/time/internal/cctz/testdata/zoneinfo/America/Yellowknife Binary files differindex c779cef9..ff3eb878 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Yellowknife +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Yellowknife diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Kuala_Lumpur b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Kuala_Lumpur Binary files differindex e93dd514..b396deca 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Kuala_Lumpur +++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Kuala_Lumpur diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Singapore b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Singapore Binary files differindex 350d77e2..dbbdea3c 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Singapore +++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Singapore diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Canada/Yukon b/absl/time/internal/cctz/testdata/zoneinfo/Canada/Yukon Binary files differindex 878b6a92..40baa9ab 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Canada/Yukon +++ b/absl/time/internal/cctz/testdata/zoneinfo/Canada/Yukon diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaSur b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaSur Binary files differindex 6b41102b..97d4d36c 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaSur +++ b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaSur diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/General b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/General Binary files differindex 6542c9c5..80a415c7 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/General +++ b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/General diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Fiji b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Fiji Binary files differindex 8b2dd52b..610b850b 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Fiji +++ b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Fiji diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Singapore b/absl/time/internal/cctz/testdata/zoneinfo/Singapore Binary files differindex 350d77e2..dbbdea3c 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Singapore +++ b/absl/time/internal/cctz/testdata/zoneinfo/Singapore diff --git a/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab b/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab index a4ff61a4..911af5e8 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab +++ b/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab @@ -3,13 +3,13 @@ # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. # -# From Paul Eggert (2015-05-02): +# From Paul Eggert (2022-11-18): # This file contains a table of two-letter country codes. Columns are # separated by a single tab. Lines beginning with '#' are comments. # All text uses UTF-8 encoding. The columns of the table are as follows: # # 1. ISO 3166-1 alpha-2 country code, current as of -# ISO 3166-1 N976 (2018-11-06). See: Updates on ISO 3166-1 +# ISO 3166-1 N1087 (2022-09-02). See: Updates on ISO 3166-1 # https://isotc.iso.org/livelink/livelink/Open/16944257 # 2. The usual English name for the coded region, # chosen so that alphabetic sorting of subsets produces helpful lists. @@ -238,7 +238,7 @@ SY Syria SZ Eswatini (Swaziland) TC Turks & Caicos Is TD Chad -TF French Southern & Antarctic Lands +TF French Southern Territories TG Togo TH Thailand TJ Tajikistan diff --git a/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab b/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab index cf9cf201..a9b36d36 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab +++ b/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab @@ -102,12 +102,8 @@ CA +4612-05957 America/Glace_Bay Atlantic - NS (Cape Breton) CA +4606-06447 America/Moncton Atlantic - New Brunswick CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas) CA,BS +4339-07923 America/Toronto Eastern - ON, QC (most areas), Bahamas -CA +4901-08816 America/Nipigon Eastern - ON, QC (no DST 1967-73) -CA +4823-08915 America/Thunder_Bay Eastern - ON (Thunder Bay) -CA +6344-06828 America/Iqaluit Eastern - NU (most east areas) -CA +6608-06544 America/Pangnirtung Eastern - NU (Pangnirtung) +CA +6344-06828 America/Iqaluit Eastern - NU (most areas) CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba -CA +4843-09434 America/Rainy_River Central - ON (Rainy R, Ft Frances) CA +744144-0944945 America/Resolute Central - NU (Resolute) CA +624900-0920459 America/Rankin_Inlet Central - NU (central) CA +5024-10439 America/Regina CST - SK (most areas) @@ -217,17 +213,18 @@ MQ +1436-06105 America/Martinique MT +3554+01431 Europe/Malta MU -2010+05730 Indian/Mauritius MV,TF +0410+07330 Indian/Maldives Maldives, Kerguelen, St Paul I, Amsterdam I -MX +1924-09909 America/Mexico_City Central Time -MX +2105-08646 America/Cancun Eastern Standard Time - Quintana Roo -MX +2058-08937 America/Merida Central Time - Campeche, Yucatán -MX +2540-10019 America/Monterrey Central Time - Durango; Coahuila, Nuevo León, Tamaulipas (most areas) -MX +2550-09730 America/Matamoros Central Time US - Coahuila, Nuevo León, Tamaulipas (US border) -MX +2313-10625 America/Mazatlan Mountain Time - Baja California Sur, Nayarit, Sinaloa -MX +2838-10605 America/Chihuahua Mountain Time - Chihuahua (most areas) -MX +2934-10425 America/Ojinaga Mountain Time US - Chihuahua (US border) -MX +2904-11058 America/Hermosillo Mountain Standard Time - Sonora -MX +3232-11701 America/Tijuana Pacific Time US - Baja California -MX +2048-10515 America/Bahia_Banderas Central Time - BahÃa de Banderas +MX +1924-09909 America/Mexico_City Central Mexico +MX +2105-08646 America/Cancun Quintana Roo +MX +2058-08937 America/Merida Campeche, Yucatán +MX +2540-10019 America/Monterrey Durango; Coahuila, Nuevo León, Tamaulipas (most areas) +MX +2550-09730 America/Matamoros Coahuila, Nuevo León, Tamaulipas (US border) +MX +2838-10605 America/Chihuahua Chihuahua (most areas) +MX +3144-10629 America/Ciudad_Juarez Chihuahua (US border - west) +MX +2934-10425 America/Ojinaga Chihuahua (US border - east) +MX +2313-10625 America/Mazatlan Baja California Sur, Nayarit (most areas), Sinaloa +MX +2048-10515 America/Bahia_Banderas BahÃa de Banderas +MX +2904-11058 America/Hermosillo Sonora +MX +3232-11701 America/Tijuana Baja California MY,BN +0133+11020 Asia/Kuching Sabah, Sarawak, Brunei MZ,BI,BW,CD,MW,RW,ZM,ZW -2558+03235 Africa/Maputo Central Africa Time NA -2234+01706 Africa/Windhoek diff --git a/absl/time/internal/test_util.cc b/absl/time/internal/test_util.cc index 4b7849c6..3e2452e9 100644 --- a/absl/time/internal/test_util.cc +++ b/absl/time/internal/test_util.cc @@ -14,16 +14,8 @@ #include "absl/time/internal/test_util.h" -#include <algorithm> -#include <cstddef> -#include <cstring> -#include <memory> - #include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" -#include "absl/time/internal/cctz/include/cctz/zone_info_source.h" - -namespace cctz = absl::time_internal::cctz; namespace absl { ABSL_NAMESPACE_BEGIN @@ -38,95 +30,3 @@ TimeZone LoadTimeZone(const std::string& name) { } // namespace time_internal ABSL_NAMESPACE_END } // namespace absl - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace time_internal { -namespace cctz_extension { -namespace { - -// Embed the zoneinfo data for time zones used during tests and benchmarks. -// The data was generated using "xxd -i zoneinfo-file". There is no need -// to update the data as long as the tests do not depend on recent changes -// (and the past rules remain the same). -#include "absl/time/internal/zoneinfo.inc" - -const struct ZoneInfo { - const char* name; - const char* data; - std::size_t length; -} kZoneInfo[] = { - // The three real time zones used by :time_test and :time_benchmark. - {"America/Los_Angeles", // - reinterpret_cast<char*>(America_Los_Angeles), America_Los_Angeles_len}, - {"America/New_York", // - reinterpret_cast<char*>(America_New_York), America_New_York_len}, - {"Australia/Sydney", // - reinterpret_cast<char*>(Australia_Sydney), Australia_Sydney_len}, - - // Other zones named in tests but which should fail to load. - {"Invalid/TimeZone", nullptr, 0}, - {"", nullptr, 0}, - - // Allows use of the local time zone from a system-specific location. -#ifdef _MSC_VER - {"localtime", // - reinterpret_cast<char*>(America_Los_Angeles), America_Los_Angeles_len}, -#else - {"/etc/localtime", // - reinterpret_cast<char*>(America_Los_Angeles), America_Los_Angeles_len}, -#endif -}; - -class TestZoneInfoSource : public cctz::ZoneInfoSource { - public: - TestZoneInfoSource(const char* data, std::size_t size) - : data_(data), end_(data + size) {} - - std::size_t Read(void* ptr, std::size_t size) override { - const std::size_t len = - std::min(size, static_cast<std::size_t>(end_ - data_)); - memcpy(ptr, data_, len); - data_ += len; - return len; - } - - int Skip(std::size_t offset) override { - data_ += std::min(offset, static_cast<std::size_t>(end_ - data_)); - return 0; - } - - private: - const char* data_; - const char* const end_; -}; - -std::unique_ptr<cctz::ZoneInfoSource> TestFactory( - const std::string& name, - const std::function<std::unique_ptr<cctz::ZoneInfoSource>( - const std::string& name)>& /*fallback_factory*/) { - for (const ZoneInfo& zoneinfo : kZoneInfo) { - if (name == zoneinfo.name) { - if (zoneinfo.data == nullptr) return nullptr; - return std::unique_ptr<cctz::ZoneInfoSource>( - new TestZoneInfoSource(zoneinfo.data, zoneinfo.length)); - } - } - - // The embedded zoneinfo data does not include the zone, so fallback to - // built-in UTC. The tests have been crafted so that this should only - // happen when testing absl::LocalTimeZone() with an unconstrained ${TZ}. - return nullptr; -} - -} // namespace - -#if !defined(__MINGW32__) -// MinGW does not support the weak symbol extension mechanism. -ZoneInfoSourceFactory zone_info_source_factory = TestFactory; -#endif - -} // namespace cctz_extension -} // namespace time_internal -ABSL_NAMESPACE_END -} // namespace absl diff --git a/absl/time/internal/zoneinfo.inc b/absl/time/internal/zoneinfo.inc deleted file mode 100644 index 7d8b3ff2..00000000 --- a/absl/time/internal/zoneinfo.inc +++ /dev/null @@ -1,724 +0,0 @@ -unsigned char America_Los_Angeles[] = { - 0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, - 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xba, - 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x14, 0x80, 0x00, 0x00, 0x00, - 0x9e, 0xa6, 0x48, 0xa0, 0x9f, 0xbb, 0x15, 0x90, 0xa0, 0x86, 0x2a, 0xa0, - 0xa1, 0x9a, 0xf7, 0x90, 0xcb, 0x89, 0x1a, 0xa0, 0xd2, 0x23, 0xf4, 0x70, - 0xd2, 0x61, 0x26, 0x10, 0xd6, 0xfe, 0x74, 0x5c, 0xd8, 0x80, 0xad, 0x90, - 0xda, 0xfe, 0xc3, 0x90, 0xdb, 0xc0, 0x90, 0x10, 0xdc, 0xde, 0xa5, 0x90, - 0xdd, 0xa9, 0xac, 0x90, 0xde, 0xbe, 0x87, 0x90, 0xdf, 0x89, 0x8e, 0x90, - 0xe0, 0x9e, 0x69, 0x90, 0xe1, 0x69, 0x70, 0x90, 0xe2, 0x7e, 0x4b, 0x90, - 0xe3, 0x49, 0x52, 0x90, 0xe4, 0x5e, 0x2d, 0x90, 0xe5, 0x29, 0x34, 0x90, - 0xe6, 0x47, 0x4a, 0x10, 0xe7, 0x12, 0x51, 0x10, 0xe8, 0x27, 0x2c, 0x10, - 0xe8, 0xf2, 0x33, 0x10, 0xea, 0x07, 0x0e, 0x10, 0xea, 0xd2, 0x15, 0x10, - 0xeb, 0xe6, 0xf0, 0x10, 0xec, 0xb1, 0xf7, 0x10, 0xed, 0xc6, 0xd2, 0x10, - 0xee, 0x91, 0xd9, 0x10, 0xef, 0xaf, 0xee, 0x90, 0xf0, 0x71, 0xbb, 0x10, - 0xf1, 0x8f, 0xd0, 0x90, 0xf2, 0x7f, 0xc1, 0x90, 0xf3, 0x6f, 0xb2, 0x90, - 0xf4, 0x5f, 0xa3, 0x90, 0xf5, 0x4f, 0x94, 0x90, 0xf6, 0x3f, 0x85, 0x90, - 0xf7, 0x2f, 0x76, 0x90, 0xf8, 0x28, 0xa2, 0x10, 0xf9, 0x0f, 0x58, 0x90, - 0xfa, 0x08, 0x84, 0x10, 0xfa, 0xf8, 0x83, 0x20, 0xfb, 0xe8, 0x66, 0x10, - 0xfc, 0xd8, 0x65, 0x20, 0xfd, 0xc8, 0x48, 0x10, 0xfe, 0xb8, 0x47, 0x20, - 0xff, 0xa8, 0x2a, 0x10, 0x00, 0x98, 0x29, 0x20, 0x01, 0x88, 0x0c, 0x10, - 0x02, 0x78, 0x0b, 0x20, 0x03, 0x71, 0x28, 0x90, 0x04, 0x61, 0x27, 0xa0, - 0x05, 0x51, 0x0a, 0x90, 0x06, 0x41, 0x09, 0xa0, 0x07, 0x30, 0xec, 0x90, - 0x07, 0x8d, 0x43, 0xa0, 0x09, 0x10, 0xce, 0x90, 0x09, 0xad, 0xbf, 0x20, - 0x0a, 0xf0, 0xb0, 0x90, 0x0b, 0xe0, 0xaf, 0xa0, 0x0c, 0xd9, 0xcd, 0x10, - 0x0d, 0xc0, 0x91, 0xa0, 0x0e, 0xb9, 0xaf, 0x10, 0x0f, 0xa9, 0xae, 0x20, - 0x10, 0x99, 0x91, 0x10, 0x11, 0x89, 0x90, 0x20, 0x12, 0x79, 0x73, 0x10, - 0x13, 0x69, 0x72, 0x20, 0x14, 0x59, 0x55, 0x10, 0x15, 0x49, 0x54, 0x20, - 0x16, 0x39, 0x37, 0x10, 0x17, 0x29, 0x36, 0x20, 0x18, 0x22, 0x53, 0x90, - 0x19, 0x09, 0x18, 0x20, 0x1a, 0x02, 0x35, 0x90, 0x1a, 0xf2, 0x34, 0xa0, - 0x1b, 0xe2, 0x17, 0x90, 0x1c, 0xd2, 0x16, 0xa0, 0x1d, 0xc1, 0xf9, 0x90, - 0x1e, 0xb1, 0xf8, 0xa0, 0x1f, 0xa1, 0xdb, 0x90, 0x20, 0x76, 0x2b, 0x20, - 0x21, 0x81, 0xbd, 0x90, 0x22, 0x56, 0x0d, 0x20, 0x23, 0x6a, 0xda, 0x10, - 0x24, 0x35, 0xef, 0x20, 0x25, 0x4a, 0xbc, 0x10, 0x26, 0x15, 0xd1, 0x20, - 0x27, 0x2a, 0x9e, 0x10, 0x27, 0xfe, 0xed, 0xa0, 0x29, 0x0a, 0x80, 0x10, - 0x29, 0xde, 0xcf, 0xa0, 0x2a, 0xea, 0x62, 0x10, 0x2b, 0xbe, 0xb1, 0xa0, - 0x2c, 0xd3, 0x7e, 0x90, 0x2d, 0x9e, 0x93, 0xa0, 0x2e, 0xb3, 0x60, 0x90, - 0x2f, 0x7e, 0x75, 0xa0, 0x30, 0x93, 0x42, 0x90, 0x31, 0x67, 0x92, 0x20, - 0x32, 0x73, 0x24, 0x90, 0x33, 0x47, 0x74, 0x20, 0x34, 0x53, 0x06, 0x90, - 0x35, 0x27, 0x56, 0x20, 0x36, 0x32, 0xe8, 0x90, 0x37, 0x07, 0x38, 0x20, - 0x38, 0x1c, 0x05, 0x10, 0x38, 0xe7, 0x1a, 0x20, 0x39, 0xfb, 0xe7, 0x10, - 0x3a, 0xc6, 0xfc, 0x20, 0x3b, 0xdb, 0xc9, 0x10, 0x3c, 0xb0, 0x18, 0xa0, - 0x3d, 0xbb, 0xab, 0x10, 0x3e, 0x8f, 0xfa, 0xa0, 0x3f, 0x9b, 0x8d, 0x10, - 0x40, 0x6f, 0xdc, 0xa0, 0x41, 0x84, 0xa9, 0x90, 0x42, 0x4f, 0xbe, 0xa0, - 0x43, 0x64, 0x8b, 0x90, 0x44, 0x2f, 0xa0, 0xa0, 0x45, 0x44, 0x6d, 0x90, - 0x45, 0xf3, 0xd3, 0x20, 0x47, 0x2d, 0x8a, 0x10, 0x47, 0xd3, 0xb5, 0x20, - 0x49, 0x0d, 0x6c, 0x10, 0x49, 0xb3, 0x97, 0x20, 0x4a, 0xed, 0x4e, 0x10, - 0x4b, 0x9c, 0xb3, 0xa0, 0x4c, 0xd6, 0x6a, 0x90, 0x4d, 0x7c, 0x95, 0xa0, - 0x4e, 0xb6, 0x4c, 0x90, 0x4f, 0x5c, 0x77, 0xa0, 0x50, 0x96, 0x2e, 0x90, - 0x51, 0x3c, 0x59, 0xa0, 0x52, 0x76, 0x10, 0x90, 0x53, 0x1c, 0x3b, 0xa0, - 0x54, 0x55, 0xf2, 0x90, 0x54, 0xfc, 0x1d, 0xa0, 0x56, 0x35, 0xd4, 0x90, - 0x56, 0xe5, 0x3a, 0x20, 0x58, 0x1e, 0xf1, 0x10, 0x58, 0xc5, 0x1c, 0x20, - 0x59, 0xfe, 0xd3, 0x10, 0x5a, 0xa4, 0xfe, 0x20, 0x5b, 0xde, 0xb5, 0x10, - 0x5c, 0x84, 0xe0, 0x20, 0x5d, 0xbe, 0x97, 0x10, 0x5e, 0x64, 0xc2, 0x20, - 0x5f, 0x9e, 0x79, 0x10, 0x60, 0x4d, 0xde, 0xa0, 0x61, 0x87, 0x95, 0x90, - 0x62, 0x2d, 0xc0, 0xa0, 0x63, 0x67, 0x77, 0x90, 0x64, 0x0d, 0xa2, 0xa0, - 0x65, 0x47, 0x59, 0x90, 0x65, 0xed, 0x84, 0xa0, 0x67, 0x27, 0x3b, 0x90, - 0x67, 0xcd, 0x66, 0xa0, 0x69, 0x07, 0x1d, 0x90, 0x69, 0xad, 0x48, 0xa0, - 0x6a, 0xe6, 0xff, 0x90, 0x6b, 0x96, 0x65, 0x20, 0x6c, 0xd0, 0x1c, 0x10, - 0x6d, 0x76, 0x47, 0x20, 0x6e, 0xaf, 0xfe, 0x10, 0x6f, 0x56, 0x29, 0x20, - 0x70, 0x8f, 0xe0, 0x10, 0x71, 0x36, 0x0b, 0x20, 0x72, 0x6f, 0xc2, 0x10, - 0x73, 0x15, 0xed, 0x20, 0x74, 0x4f, 0xa4, 0x10, 0x74, 0xff, 0x09, 0xa0, - 0x76, 0x38, 0xc0, 0x90, 0x76, 0xde, 0xeb, 0xa0, 0x78, 0x18, 0xa2, 0x90, - 0x78, 0xbe, 0xcd, 0xa0, 0x79, 0xf8, 0x84, 0x90, 0x7a, 0x9e, 0xaf, 0xa0, - 0x7b, 0xd8, 0x66, 0x90, 0x7c, 0x7e, 0x91, 0xa0, 0x7d, 0xb8, 0x48, 0x90, - 0x7e, 0x5e, 0x73, 0xa0, 0x7f, 0x98, 0x2a, 0x90, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x03, 0x04, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0xff, 0xff, 0x91, 0x26, 0x00, 0x00, 0xff, 0xff, 0x9d, 0x90, - 0x01, 0x04, 0xff, 0xff, 0x8f, 0x80, 0x00, 0x08, 0xff, 0xff, 0x9d, 0x90, - 0x01, 0x0c, 0xff, 0xff, 0x9d, 0x90, 0x01, 0x10, 0x4c, 0x4d, 0x54, 0x00, - 0x50, 0x44, 0x54, 0x00, 0x50, 0x53, 0x54, 0x00, 0x50, 0x57, 0x54, 0x00, - 0x50, 0x50, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xba, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x14, 0xff, 0xff, - 0xff, 0xff, 0x5e, 0x04, 0x1a, 0xc0, 0xff, 0xff, 0xff, 0xff, 0x9e, 0xa6, - 0x48, 0xa0, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xbb, 0x15, 0x90, 0xff, 0xff, - 0xff, 0xff, 0xa0, 0x86, 0x2a, 0xa0, 0xff, 0xff, 0xff, 0xff, 0xa1, 0x9a, - 0xf7, 0x90, 0xff, 0xff, 0xff, 0xff, 0xcb, 0x89, 0x1a, 0xa0, 0xff, 0xff, - 0xff, 0xff, 0xd2, 0x23, 0xf4, 0x70, 0xff, 0xff, 0xff, 0xff, 0xd2, 0x61, - 0x26, 0x10, 0xff, 0xff, 0xff, 0xff, 0xd6, 0xfe, 0x74, 0x5c, 0xff, 0xff, - 0xff, 0xff, 0xd8, 0x80, 0xad, 0x90, 0xff, 0xff, 0xff, 0xff, 0xda, 0xfe, - 0xc3, 0x90, 0xff, 0xff, 0xff, 0xff, 0xdb, 0xc0, 0x90, 0x10, 0xff, 0xff, - 0xff, 0xff, 0xdc, 0xde, 0xa5, 0x90, 0xff, 0xff, 0xff, 0xff, 0xdd, 0xa9, - 0xac, 0x90, 0xff, 0xff, 0xff, 0xff, 0xde, 0xbe, 0x87, 0x90, 0xff, 0xff, - 0xff, 0xff, 0xdf, 0x89, 0x8e, 0x90, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x9e, - 0x69, 0x90, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x69, 0x70, 0x90, 0xff, 0xff, - 0xff, 0xff, 0xe2, 0x7e, 0x4b, 0x90, 0xff, 0xff, 0xff, 0xff, 0xe3, 0x49, - 0x52, 0x90, 0xff, 0xff, 0xff, 0xff, 0xe4, 0x5e, 0x2d, 0x90, 0xff, 0xff, - 0xff, 0xff, 0xe5, 0x29, 0x34, 0x90, 0xff, 0xff, 0xff, 0xff, 0xe6, 0x47, - 0x4a, 0x10, 0xff, 0xff, 0xff, 0xff, 0xe7, 0x12, 0x51, 0x10, 0xff, 0xff, - 0xff, 0xff, 0xe8, 0x27, 0x2c, 0x10, 0xff, 0xff, 0xff, 0xff, 0xe8, 0xf2, - 0x33, 0x10, 0xff, 0xff, 0xff, 0xff, 0xea, 0x07, 0x0e, 0x10, 0xff, 0xff, - 0xff, 0xff, 0xea, 0xd2, 0x15, 0x10, 0xff, 0xff, 0xff, 0xff, 0xeb, 0xe6, - 0xf0, 0x10, 0xff, 0xff, 0xff, 0xff, 0xec, 0xb1, 0xf7, 0x10, 0xff, 0xff, - 0xff, 0xff, 0xed, 0xc6, 0xd2, 0x10, 0xff, 0xff, 0xff, 0xff, 0xee, 0x91, - 0xd9, 0x10, 0xff, 0xff, 0xff, 0xff, 0xef, 0xaf, 0xee, 0x90, 0xff, 0xff, - 0xff, 0xff, 0xf0, 0x71, 0xbb, 0x10, 0xff, 0xff, 0xff, 0xff, 0xf1, 0x8f, - 0xd0, 0x90, 0xff, 0xff, 0xff, 0xff, 0xf2, 0x7f, 0xc1, 0x90, 0xff, 0xff, - 0xff, 0xff, 0xf3, 0x6f, 0xb2, 0x90, 0xff, 0xff, 0xff, 0xff, 0xf4, 0x5f, - 0xa3, 0x90, 0xff, 0xff, 0xff, 0xff, 0xf5, 0x4f, 0x94, 0x90, 0xff, 0xff, - 0xff, 0xff, 0xf6, 0x3f, 0x85, 0x90, 0xff, 0xff, 0xff, 0xff, 0xf7, 0x2f, - 0x76, 0x90, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x28, 0xa2, 0x10, 0xff, 0xff, - 0xff, 0xff, 0xf9, 0x0f, 0x58, 0x90, 0xff, 0xff, 0xff, 0xff, 0xfa, 0x08, - 0x84, 0x10, 0xff, 0xff, 0xff, 0xff, 0xfa, 0xf8, 0x83, 0x20, 0xff, 0xff, - 0xff, 0xff, 0xfb, 0xe8, 0x66, 0x10, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xd8, - 0x65, 0x20, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xc8, 0x48, 0x10, 0xff, 0xff, - 0xff, 0xff, 0xfe, 0xb8, 0x47, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa8, - 0x2a, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x29, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x88, 0x0c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x78, - 0x0b, 0x20, 0x00, 0x00, 0x00, 0x00, 0x03, 0x71, 0x28, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x04, 0x61, 0x27, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x05, 0x51, - 0x0a, 0x90, 0x00, 0x00, 0x00, 0x00, 0x06, 0x41, 0x09, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x07, 0x30, 0xec, 0x90, 0x00, 0x00, 0x00, 0x00, 0x07, 0x8d, - 0x43, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x09, 0x10, 0xce, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x09, 0xad, 0xbf, 0x20, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xf0, - 0xb0, 0x90, 0x00, 0x00, 0x00, 0x00, 0x0b, 0xe0, 0xaf, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x0c, 0xd9, 0xcd, 0x10, 0x00, 0x00, 0x00, 0x00, 0x0d, 0xc0, - 0x91, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xb9, 0xaf, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x0f, 0xa9, 0xae, 0x20, 0x00, 0x00, 0x00, 0x00, 0x10, 0x99, - 0x91, 0x10, 0x00, 0x00, 0x00, 0x00, 0x11, 0x89, 0x90, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x12, 0x79, 0x73, 0x10, 0x00, 0x00, 0x00, 0x00, 0x13, 0x69, - 0x72, 0x20, 0x00, 0x00, 0x00, 0x00, 0x14, 0x59, 0x55, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x15, 0x49, 0x54, 0x20, 0x00, 0x00, 0x00, 0x00, 0x16, 0x39, - 0x37, 0x10, 0x00, 0x00, 0x00, 0x00, 0x17, 0x29, 0x36, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x18, 0x22, 0x53, 0x90, 0x00, 0x00, 0x00, 0x00, 0x19, 0x09, - 0x18, 0x20, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x02, 0x35, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x1a, 0xf2, 0x34, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x1b, 0xe2, - 0x17, 0x90, 0x00, 0x00, 0x00, 0x00, 0x1c, 0xd2, 0x16, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x1d, 0xc1, 0xf9, 0x90, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xb1, - 0xf8, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xa1, 0xdb, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x20, 0x76, 0x2b, 0x20, 0x00, 0x00, 0x00, 0x00, 0x21, 0x81, - 0xbd, 0x90, 0x00, 0x00, 0x00, 0x00, 0x22, 0x56, 0x0d, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x23, 0x6a, 0xda, 0x10, 0x00, 0x00, 0x00, 0x00, 0x24, 0x35, - 0xef, 0x20, 0x00, 0x00, 0x00, 0x00, 0x25, 0x4a, 0xbc, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x26, 0x15, 0xd1, 0x20, 0x00, 0x00, 0x00, 0x00, 0x27, 0x2a, - 0x9e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x27, 0xfe, 0xed, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x29, 0x0a, 0x80, 0x10, 0x00, 0x00, 0x00, 0x00, 0x29, 0xde, - 0xcf, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x2a, 0xea, 0x62, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x2b, 0xbe, 0xb1, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x2c, 0xd3, - 0x7e, 0x90, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x9e, 0x93, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x2e, 0xb3, 0x60, 0x90, 0x00, 0x00, 0x00, 0x00, 0x2f, 0x7e, - 0x75, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x30, 0x93, 0x42, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x31, 0x67, 0x92, 0x20, 0x00, 0x00, 0x00, 0x00, 0x32, 0x73, - 0x24, 0x90, 0x00, 0x00, 0x00, 0x00, 0x33, 0x47, 0x74, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x34, 0x53, 0x06, 0x90, 0x00, 0x00, 0x00, 0x00, 0x35, 0x27, - 0x56, 0x20, 0x00, 0x00, 0x00, 0x00, 0x36, 0x32, 0xe8, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x37, 0x07, 0x38, 0x20, 0x00, 0x00, 0x00, 0x00, 0x38, 0x1c, - 0x05, 0x10, 0x00, 0x00, 0x00, 0x00, 0x38, 0xe7, 0x1a, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x39, 0xfb, 0xe7, 0x10, 0x00, 0x00, 0x00, 0x00, 0x3a, 0xc6, - 0xfc, 0x20, 0x00, 0x00, 0x00, 0x00, 0x3b, 0xdb, 0xc9, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x3c, 0xb0, 0x18, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x3d, 0xbb, - 0xab, 0x10, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x8f, 0xfa, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x3f, 0x9b, 0x8d, 0x10, 0x00, 0x00, 0x00, 0x00, 0x40, 0x6f, - 0xdc, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x41, 0x84, 0xa9, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x42, 0x4f, 0xbe, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x43, 0x64, - 0x8b, 0x90, 0x00, 0x00, 0x00, 0x00, 0x44, 0x2f, 0xa0, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x45, 0x44, 0x6d, 0x90, 0x00, 0x00, 0x00, 0x00, 0x45, 0xf3, - 0xd3, 0x20, 0x00, 0x00, 0x00, 0x00, 0x47, 0x2d, 0x8a, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x47, 0xd3, 0xb5, 0x20, 0x00, 0x00, 0x00, 0x00, 0x49, 0x0d, - 0x6c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x49, 0xb3, 0x97, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x4a, 0xed, 0x4e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x4b, 0x9c, - 0xb3, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x4c, 0xd6, 0x6a, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x4d, 0x7c, 0x95, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x4e, 0xb6, - 0x4c, 0x90, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x5c, 0x77, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x50, 0x96, 0x2e, 0x90, 0x00, 0x00, 0x00, 0x00, 0x51, 0x3c, - 0x59, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x52, 0x76, 0x10, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x53, 0x1c, 0x3b, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x54, 0x55, - 0xf2, 0x90, 0x00, 0x00, 0x00, 0x00, 0x54, 0xfc, 0x1d, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x56, 0x35, 0xd4, 0x90, 0x00, 0x00, 0x00, 0x00, 0x56, 0xe5, - 0x3a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x58, 0x1e, 0xf1, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x58, 0xc5, 0x1c, 0x20, 0x00, 0x00, 0x00, 0x00, 0x59, 0xfe, - 0xd3, 0x10, 0x00, 0x00, 0x00, 0x00, 0x5a, 0xa4, 0xfe, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x5b, 0xde, 0xb5, 0x10, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x84, - 0xe0, 0x20, 0x00, 0x00, 0x00, 0x00, 0x5d, 0xbe, 0x97, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x5e, 0x64, 0xc2, 0x20, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x9e, - 0x79, 0x10, 0x00, 0x00, 0x00, 0x00, 0x60, 0x4d, 0xde, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x61, 0x87, 0x95, 0x90, 0x00, 0x00, 0x00, 0x00, 0x62, 0x2d, - 0xc0, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x63, 0x67, 0x77, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x64, 0x0d, 0xa2, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x65, 0x47, - 0x59, 0x90, 0x00, 0x00, 0x00, 0x00, 0x65, 0xed, 0x84, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x67, 0x27, 0x3b, 0x90, 0x00, 0x00, 0x00, 0x00, 0x67, 0xcd, - 0x66, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x69, 0x07, 0x1d, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x69, 0xad, 0x48, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x6a, 0xe6, - 0xff, 0x90, 0x00, 0x00, 0x00, 0x00, 0x6b, 0x96, 0x65, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x6c, 0xd0, 0x1c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x76, - 0x47, 0x20, 0x00, 0x00, 0x00, 0x00, 0x6e, 0xaf, 0xfe, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x6f, 0x56, 0x29, 0x20, 0x00, 0x00, 0x00, 0x00, 0x70, 0x8f, - 0xe0, 0x10, 0x00, 0x00, 0x00, 0x00, 0x71, 0x36, 0x0b, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x72, 0x6f, 0xc2, 0x10, 0x00, 0x00, 0x00, 0x00, 0x73, 0x15, - 0xed, 0x20, 0x00, 0x00, 0x00, 0x00, 0x74, 0x4f, 0xa4, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x74, 0xff, 0x09, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x76, 0x38, - 0xc0, 0x90, 0x00, 0x00, 0x00, 0x00, 0x76, 0xde, 0xeb, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x78, 0x18, 0xa2, 0x90, 0x00, 0x00, 0x00, 0x00, 0x78, 0xbe, - 0xcd, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x79, 0xf8, 0x84, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x7a, 0x9e, 0xaf, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x7b, 0xd8, - 0x66, 0x90, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x7e, 0x91, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x7d, 0xb8, 0x48, 0x90, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x5e, - 0x73, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x98, 0x2a, 0x90, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x03, 0x04, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0xff, 0xff, 0x91, 0x26, 0x00, 0x00, 0xff, 0xff, - 0x9d, 0x90, 0x01, 0x04, 0xff, 0xff, 0x8f, 0x80, 0x00, 0x08, 0xff, 0xff, - 0x9d, 0x90, 0x01, 0x0c, 0xff, 0xff, 0x9d, 0x90, 0x01, 0x10, 0x4c, 0x4d, - 0x54, 0x00, 0x50, 0x44, 0x54, 0x00, 0x50, 0x53, 0x54, 0x00, 0x50, 0x57, - 0x54, 0x00, 0x50, 0x50, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x0a, 0x50, 0x53, 0x54, 0x38, 0x50, 0x44, 0x54, - 0x2c, 0x4d, 0x33, 0x2e, 0x32, 0x2e, 0x30, 0x2c, 0x4d, 0x31, 0x31, 0x2e, - 0x31, 0x2e, 0x30, 0x0a -}; -unsigned int America_Los_Angeles_len = 2836; -unsigned char America_New_York[] = { - 0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, - 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, - 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x14, 0x80, 0x00, 0x00, 0x00, - 0x9e, 0xa6, 0x1e, 0x70, 0x9f, 0xba, 0xeb, 0x60, 0xa0, 0x86, 0x00, 0x70, - 0xa1, 0x9a, 0xcd, 0x60, 0xa2, 0x65, 0xe2, 0x70, 0xa3, 0x83, 0xe9, 0xe0, - 0xa4, 0x6a, 0xae, 0x70, 0xa5, 0x35, 0xa7, 0x60, 0xa6, 0x53, 0xca, 0xf0, - 0xa7, 0x15, 0x89, 0x60, 0xa8, 0x33, 0xac, 0xf0, 0xa8, 0xfe, 0xa5, 0xe0, - 0xaa, 0x13, 0x8e, 0xf0, 0xaa, 0xde, 0x87, 0xe0, 0xab, 0xf3, 0x70, 0xf0, - 0xac, 0xbe, 0x69, 0xe0, 0xad, 0xd3, 0x52, 0xf0, 0xae, 0x9e, 0x4b, 0xe0, - 0xaf, 0xb3, 0x34, 0xf0, 0xb0, 0x7e, 0x2d, 0xe0, 0xb1, 0x9c, 0x51, 0x70, - 0xb2, 0x67, 0x4a, 0x60, 0xb3, 0x7c, 0x33, 0x70, 0xb4, 0x47, 0x2c, 0x60, - 0xb5, 0x5c, 0x15, 0x70, 0xb6, 0x27, 0x0e, 0x60, 0xb7, 0x3b, 0xf7, 0x70, - 0xb8, 0x06, 0xf0, 0x60, 0xb9, 0x1b, 0xd9, 0x70, 0xb9, 0xe6, 0xd2, 0x60, - 0xbb, 0x04, 0xf5, 0xf0, 0xbb, 0xc6, 0xb4, 0x60, 0xbc, 0xe4, 0xd7, 0xf0, - 0xbd, 0xaf, 0xd0, 0xe0, 0xbe, 0xc4, 0xb9, 0xf0, 0xbf, 0x8f, 0xb2, 0xe0, - 0xc0, 0xa4, 0x9b, 0xf0, 0xc1, 0x6f, 0x94, 0xe0, 0xc2, 0x84, 0x7d, 0xf0, - 0xc3, 0x4f, 0x76, 0xe0, 0xc4, 0x64, 0x5f, 0xf0, 0xc5, 0x2f, 0x58, 0xe0, - 0xc6, 0x4d, 0x7c, 0x70, 0xc7, 0x0f, 0x3a, 0xe0, 0xc8, 0x2d, 0x5e, 0x70, - 0xc8, 0xf8, 0x57, 0x60, 0xca, 0x0d, 0x40, 0x70, 0xca, 0xd8, 0x39, 0x60, - 0xcb, 0x88, 0xf0, 0x70, 0xd2, 0x23, 0xf4, 0x70, 0xd2, 0x60, 0xfb, 0xe0, - 0xd3, 0x75, 0xe4, 0xf0, 0xd4, 0x40, 0xdd, 0xe0, 0xd5, 0x55, 0xc6, 0xf0, - 0xd6, 0x20, 0xbf, 0xe0, 0xd7, 0x35, 0xa8, 0xf0, 0xd8, 0x00, 0xa1, 0xe0, - 0xd9, 0x15, 0x8a, 0xf0, 0xd9, 0xe0, 0x83, 0xe0, 0xda, 0xfe, 0xa7, 0x70, - 0xdb, 0xc0, 0x65, 0xe0, 0xdc, 0xde, 0x89, 0x70, 0xdd, 0xa9, 0x82, 0x60, - 0xde, 0xbe, 0x6b, 0x70, 0xdf, 0x89, 0x64, 0x60, 0xe0, 0x9e, 0x4d, 0x70, - 0xe1, 0x69, 0x46, 0x60, 0xe2, 0x7e, 0x2f, 0x70, 0xe3, 0x49, 0x28, 0x60, - 0xe4, 0x5e, 0x11, 0x70, 0xe5, 0x57, 0x2e, 0xe0, 0xe6, 0x47, 0x2d, 0xf0, - 0xe7, 0x37, 0x10, 0xe0, 0xe8, 0x27, 0x0f, 0xf0, 0xe9, 0x16, 0xf2, 0xe0, - 0xea, 0x06, 0xf1, 0xf0, 0xea, 0xf6, 0xd4, 0xe0, 0xeb, 0xe6, 0xd3, 0xf0, - 0xec, 0xd6, 0xb6, 0xe0, 0xed, 0xc6, 0xb5, 0xf0, 0xee, 0xbf, 0xd3, 0x60, - 0xef, 0xaf, 0xd2, 0x70, 0xf0, 0x9f, 0xb5, 0x60, 0xf1, 0x8f, 0xb4, 0x70, - 0xf2, 0x7f, 0x97, 0x60, 0xf3, 0x6f, 0x96, 0x70, 0xf4, 0x5f, 0x79, 0x60, - 0xf5, 0x4f, 0x78, 0x70, 0xf6, 0x3f, 0x5b, 0x60, 0xf7, 0x2f, 0x5a, 0x70, - 0xf8, 0x28, 0x77, 0xe0, 0xf9, 0x0f, 0x3c, 0x70, 0xfa, 0x08, 0x59, 0xe0, - 0xfa, 0xf8, 0x58, 0xf0, 0xfb, 0xe8, 0x3b, 0xe0, 0xfc, 0xd8, 0x3a, 0xf0, - 0xfd, 0xc8, 0x1d, 0xe0, 0xfe, 0xb8, 0x1c, 0xf0, 0xff, 0xa7, 0xff, 0xe0, - 0x00, 0x97, 0xfe, 0xf0, 0x01, 0x87, 0xe1, 0xe0, 0x02, 0x77, 0xe0, 0xf0, - 0x03, 0x70, 0xfe, 0x60, 0x04, 0x60, 0xfd, 0x70, 0x05, 0x50, 0xe0, 0x60, - 0x06, 0x40, 0xdf, 0x70, 0x07, 0x30, 0xc2, 0x60, 0x07, 0x8d, 0x19, 0x70, - 0x09, 0x10, 0xa4, 0x60, 0x09, 0xad, 0x94, 0xf0, 0x0a, 0xf0, 0x86, 0x60, - 0x0b, 0xe0, 0x85, 0x70, 0x0c, 0xd9, 0xa2, 0xe0, 0x0d, 0xc0, 0x67, 0x70, - 0x0e, 0xb9, 0x84, 0xe0, 0x0f, 0xa9, 0x83, 0xf0, 0x10, 0x99, 0x66, 0xe0, - 0x11, 0x89, 0x65, 0xf0, 0x12, 0x79, 0x48, 0xe0, 0x13, 0x69, 0x47, 0xf0, - 0x14, 0x59, 0x2a, 0xe0, 0x15, 0x49, 0x29, 0xf0, 0x16, 0x39, 0x0c, 0xe0, - 0x17, 0x29, 0x0b, 0xf0, 0x18, 0x22, 0x29, 0x60, 0x19, 0x08, 0xed, 0xf0, - 0x1a, 0x02, 0x0b, 0x60, 0x1a, 0xf2, 0x0a, 0x70, 0x1b, 0xe1, 0xed, 0x60, - 0x1c, 0xd1, 0xec, 0x70, 0x1d, 0xc1, 0xcf, 0x60, 0x1e, 0xb1, 0xce, 0x70, - 0x1f, 0xa1, 0xb1, 0x60, 0x20, 0x76, 0x00, 0xf0, 0x21, 0x81, 0x93, 0x60, - 0x22, 0x55, 0xe2, 0xf0, 0x23, 0x6a, 0xaf, 0xe0, 0x24, 0x35, 0xc4, 0xf0, - 0x25, 0x4a, 0x91, 0xe0, 0x26, 0x15, 0xa6, 0xf0, 0x27, 0x2a, 0x73, 0xe0, - 0x27, 0xfe, 0xc3, 0x70, 0x29, 0x0a, 0x55, 0xe0, 0x29, 0xde, 0xa5, 0x70, - 0x2a, 0xea, 0x37, 0xe0, 0x2b, 0xbe, 0x87, 0x70, 0x2c, 0xd3, 0x54, 0x60, - 0x2d, 0x9e, 0x69, 0x70, 0x2e, 0xb3, 0x36, 0x60, 0x2f, 0x7e, 0x4b, 0x70, - 0x30, 0x93, 0x18, 0x60, 0x31, 0x67, 0x67, 0xf0, 0x32, 0x72, 0xfa, 0x60, - 0x33, 0x47, 0x49, 0xf0, 0x34, 0x52, 0xdc, 0x60, 0x35, 0x27, 0x2b, 0xf0, - 0x36, 0x32, 0xbe, 0x60, 0x37, 0x07, 0x0d, 0xf0, 0x38, 0x1b, 0xda, 0xe0, - 0x38, 0xe6, 0xef, 0xf0, 0x39, 0xfb, 0xbc, 0xe0, 0x3a, 0xc6, 0xd1, 0xf0, - 0x3b, 0xdb, 0x9e, 0xe0, 0x3c, 0xaf, 0xee, 0x70, 0x3d, 0xbb, 0x80, 0xe0, - 0x3e, 0x8f, 0xd0, 0x70, 0x3f, 0x9b, 0x62, 0xe0, 0x40, 0x6f, 0xb2, 0x70, - 0x41, 0x84, 0x7f, 0x60, 0x42, 0x4f, 0x94, 0x70, 0x43, 0x64, 0x61, 0x60, - 0x44, 0x2f, 0x76, 0x70, 0x45, 0x44, 0x43, 0x60, 0x45, 0xf3, 0xa8, 0xf0, - 0x47, 0x2d, 0x5f, 0xe0, 0x47, 0xd3, 0x8a, 0xf0, 0x49, 0x0d, 0x41, 0xe0, - 0x49, 0xb3, 0x6c, 0xf0, 0x4a, 0xed, 0x23, 0xe0, 0x4b, 0x9c, 0x89, 0x70, - 0x4c, 0xd6, 0x40, 0x60, 0x4d, 0x7c, 0x6b, 0x70, 0x4e, 0xb6, 0x22, 0x60, - 0x4f, 0x5c, 0x4d, 0x70, 0x50, 0x96, 0x04, 0x60, 0x51, 0x3c, 0x2f, 0x70, - 0x52, 0x75, 0xe6, 0x60, 0x53, 0x1c, 0x11, 0x70, 0x54, 0x55, 0xc8, 0x60, - 0x54, 0xfb, 0xf3, 0x70, 0x56, 0x35, 0xaa, 0x60, 0x56, 0xe5, 0x0f, 0xf0, - 0x58, 0x1e, 0xc6, 0xe0, 0x58, 0xc4, 0xf1, 0xf0, 0x59, 0xfe, 0xa8, 0xe0, - 0x5a, 0xa4, 0xd3, 0xf0, 0x5b, 0xde, 0x8a, 0xe0, 0x5c, 0x84, 0xb5, 0xf0, - 0x5d, 0xbe, 0x6c, 0xe0, 0x5e, 0x64, 0x97, 0xf0, 0x5f, 0x9e, 0x4e, 0xe0, - 0x60, 0x4d, 0xb4, 0x70, 0x61, 0x87, 0x6b, 0x60, 0x62, 0x2d, 0x96, 0x70, - 0x63, 0x67, 0x4d, 0x60, 0x64, 0x0d, 0x78, 0x70, 0x65, 0x47, 0x2f, 0x60, - 0x65, 0xed, 0x5a, 0x70, 0x67, 0x27, 0x11, 0x60, 0x67, 0xcd, 0x3c, 0x70, - 0x69, 0x06, 0xf3, 0x60, 0x69, 0xad, 0x1e, 0x70, 0x6a, 0xe6, 0xd5, 0x60, - 0x6b, 0x96, 0x3a, 0xf0, 0x6c, 0xcf, 0xf1, 0xe0, 0x6d, 0x76, 0x1c, 0xf0, - 0x6e, 0xaf, 0xd3, 0xe0, 0x6f, 0x55, 0xfe, 0xf0, 0x70, 0x8f, 0xb5, 0xe0, - 0x71, 0x35, 0xe0, 0xf0, 0x72, 0x6f, 0x97, 0xe0, 0x73, 0x15, 0xc2, 0xf0, - 0x74, 0x4f, 0x79, 0xe0, 0x74, 0xfe, 0xdf, 0x70, 0x76, 0x38, 0x96, 0x60, - 0x76, 0xde, 0xc1, 0x70, 0x78, 0x18, 0x78, 0x60, 0x78, 0xbe, 0xa3, 0x70, - 0x79, 0xf8, 0x5a, 0x60, 0x7a, 0x9e, 0x85, 0x70, 0x7b, 0xd8, 0x3c, 0x60, - 0x7c, 0x7e, 0x67, 0x70, 0x7d, 0xb8, 0x1e, 0x60, 0x7e, 0x5e, 0x49, 0x70, - 0x7f, 0x98, 0x00, 0x60, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x03, 0x04, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0xff, 0xff, 0xba, 0x9e, 0x00, 0x00, 0xff, 0xff, 0xc7, 0xc0, 0x01, 0x04, - 0xff, 0xff, 0xb9, 0xb0, 0x00, 0x08, 0xff, 0xff, 0xc7, 0xc0, 0x01, 0x0c, - 0xff, 0xff, 0xc7, 0xc0, 0x01, 0x10, 0x4c, 0x4d, 0x54, 0x00, 0x45, 0x44, - 0x54, 0x00, 0x45, 0x53, 0x54, 0x00, 0x45, 0x57, 0x54, 0x00, 0x45, 0x50, - 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, - 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, - 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x14, 0xff, 0xff, 0xff, 0xff, - 0x5e, 0x03, 0xf0, 0x90, 0xff, 0xff, 0xff, 0xff, 0x9e, 0xa6, 0x1e, 0x70, - 0xff, 0xff, 0xff, 0xff, 0x9f, 0xba, 0xeb, 0x60, 0xff, 0xff, 0xff, 0xff, - 0xa0, 0x86, 0x00, 0x70, 0xff, 0xff, 0xff, 0xff, 0xa1, 0x9a, 0xcd, 0x60, - 0xff, 0xff, 0xff, 0xff, 0xa2, 0x65, 0xe2, 0x70, 0xff, 0xff, 0xff, 0xff, - 0xa3, 0x83, 0xe9, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xa4, 0x6a, 0xae, 0x70, - 0xff, 0xff, 0xff, 0xff, 0xa5, 0x35, 0xa7, 0x60, 0xff, 0xff, 0xff, 0xff, - 0xa6, 0x53, 0xca, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xa7, 0x15, 0x89, 0x60, - 0xff, 0xff, 0xff, 0xff, 0xa8, 0x33, 0xac, 0xf0, 0xff, 0xff, 0xff, 0xff, - 0xa8, 0xfe, 0xa5, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xaa, 0x13, 0x8e, 0xf0, - 0xff, 0xff, 0xff, 0xff, 0xaa, 0xde, 0x87, 0xe0, 0xff, 0xff, 0xff, 0xff, - 0xab, 0xf3, 0x70, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xac, 0xbe, 0x69, 0xe0, - 0xff, 0xff, 0xff, 0xff, 0xad, 0xd3, 0x52, 0xf0, 0xff, 0xff, 0xff, 0xff, - 0xae, 0x9e, 0x4b, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xaf, 0xb3, 0x34, 0xf0, - 0xff, 0xff, 0xff, 0xff, 0xb0, 0x7e, 0x2d, 0xe0, 0xff, 0xff, 0xff, 0xff, - 0xb1, 0x9c, 0x51, 0x70, 0xff, 0xff, 0xff, 0xff, 0xb2, 0x67, 0x4a, 0x60, - 0xff, 0xff, 0xff, 0xff, 0xb3, 0x7c, 0x33, 0x70, 0xff, 0xff, 0xff, 0xff, - 0xb4, 0x47, 0x2c, 0x60, 0xff, 0xff, 0xff, 0xff, 0xb5, 0x5c, 0x15, 0x70, - 0xff, 0xff, 0xff, 0xff, 0xb6, 0x27, 0x0e, 0x60, 0xff, 0xff, 0xff, 0xff, - 0xb7, 0x3b, 0xf7, 0x70, 0xff, 0xff, 0xff, 0xff, 0xb8, 0x06, 0xf0, 0x60, - 0xff, 0xff, 0xff, 0xff, 0xb9, 0x1b, 0xd9, 0x70, 0xff, 0xff, 0xff, 0xff, - 0xb9, 0xe6, 0xd2, 0x60, 0xff, 0xff, 0xff, 0xff, 0xbb, 0x04, 0xf5, 0xf0, - 0xff, 0xff, 0xff, 0xff, 0xbb, 0xc6, 0xb4, 0x60, 0xff, 0xff, 0xff, 0xff, - 0xbc, 0xe4, 0xd7, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xbd, 0xaf, 0xd0, 0xe0, - 0xff, 0xff, 0xff, 0xff, 0xbe, 0xc4, 0xb9, 0xf0, 0xff, 0xff, 0xff, 0xff, - 0xbf, 0x8f, 0xb2, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xa4, 0x9b, 0xf0, - 0xff, 0xff, 0xff, 0xff, 0xc1, 0x6f, 0x94, 0xe0, 0xff, 0xff, 0xff, 0xff, - 0xc2, 0x84, 0x7d, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xc3, 0x4f, 0x76, 0xe0, - 0xff, 0xff, 0xff, 0xff, 0xc4, 0x64, 0x5f, 0xf0, 0xff, 0xff, 0xff, 0xff, - 0xc5, 0x2f, 0x58, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xc6, 0x4d, 0x7c, 0x70, - 0xff, 0xff, 0xff, 0xff, 0xc7, 0x0f, 0x3a, 0xe0, 0xff, 0xff, 0xff, 0xff, - 0xc8, 0x2d, 0x5e, 0x70, 0xff, 0xff, 0xff, 0xff, 0xc8, 0xf8, 0x57, 0x60, - 0xff, 0xff, 0xff, 0xff, 0xca, 0x0d, 0x40, 0x70, 0xff, 0xff, 0xff, 0xff, - 0xca, 0xd8, 0x39, 0x60, 0xff, 0xff, 0xff, 0xff, 0xcb, 0x88, 0xf0, 0x70, - 0xff, 0xff, 0xff, 0xff, 0xd2, 0x23, 0xf4, 0x70, 0xff, 0xff, 0xff, 0xff, - 0xd2, 0x60, 0xfb, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xd3, 0x75, 0xe4, 0xf0, - 0xff, 0xff, 0xff, 0xff, 0xd4, 0x40, 0xdd, 0xe0, 0xff, 0xff, 0xff, 0xff, - 0xd5, 0x55, 0xc6, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xd6, 0x20, 0xbf, 0xe0, - 0xff, 0xff, 0xff, 0xff, 0xd7, 0x35, 0xa8, 0xf0, 0xff, 0xff, 0xff, 0xff, - 0xd8, 0x00, 0xa1, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xd9, 0x15, 0x8a, 0xf0, - 0xff, 0xff, 0xff, 0xff, 0xd9, 0xe0, 0x83, 0xe0, 0xff, 0xff, 0xff, 0xff, - 0xda, 0xfe, 0xa7, 0x70, 0xff, 0xff, 0xff, 0xff, 0xdb, 0xc0, 0x65, 0xe0, - 0xff, 0xff, 0xff, 0xff, 0xdc, 0xde, 0x89, 0x70, 0xff, 0xff, 0xff, 0xff, - 0xdd, 0xa9, 0x82, 0x60, 0xff, 0xff, 0xff, 0xff, 0xde, 0xbe, 0x6b, 0x70, - 0xff, 0xff, 0xff, 0xff, 0xdf, 0x89, 0x64, 0x60, 0xff, 0xff, 0xff, 0xff, - 0xe0, 0x9e, 0x4d, 0x70, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x69, 0x46, 0x60, - 0xff, 0xff, 0xff, 0xff, 0xe2, 0x7e, 0x2f, 0x70, 0xff, 0xff, 0xff, 0xff, - 0xe3, 0x49, 0x28, 0x60, 0xff, 0xff, 0xff, 0xff, 0xe4, 0x5e, 0x11, 0x70, - 0xff, 0xff, 0xff, 0xff, 0xe5, 0x57, 0x2e, 0xe0, 0xff, 0xff, 0xff, 0xff, - 0xe6, 0x47, 0x2d, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xe7, 0x37, 0x10, 0xe0, - 0xff, 0xff, 0xff, 0xff, 0xe8, 0x27, 0x0f, 0xf0, 0xff, 0xff, 0xff, 0xff, - 0xe9, 0x16, 0xf2, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xea, 0x06, 0xf1, 0xf0, - 0xff, 0xff, 0xff, 0xff, 0xea, 0xf6, 0xd4, 0xe0, 0xff, 0xff, 0xff, 0xff, - 0xeb, 0xe6, 0xd3, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xec, 0xd6, 0xb6, 0xe0, - 0xff, 0xff, 0xff, 0xff, 0xed, 0xc6, 0xb5, 0xf0, 0xff, 0xff, 0xff, 0xff, - 0xee, 0xbf, 0xd3, 0x60, 0xff, 0xff, 0xff, 0xff, 0xef, 0xaf, 0xd2, 0x70, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x9f, 0xb5, 0x60, 0xff, 0xff, 0xff, 0xff, - 0xf1, 0x8f, 0xb4, 0x70, 0xff, 0xff, 0xff, 0xff, 0xf2, 0x7f, 0x97, 0x60, - 0xff, 0xff, 0xff, 0xff, 0xf3, 0x6f, 0x96, 0x70, 0xff, 0xff, 0xff, 0xff, - 0xf4, 0x5f, 0x79, 0x60, 0xff, 0xff, 0xff, 0xff, 0xf5, 0x4f, 0x78, 0x70, - 0xff, 0xff, 0xff, 0xff, 0xf6, 0x3f, 0x5b, 0x60, 0xff, 0xff, 0xff, 0xff, - 0xf7, 0x2f, 0x5a, 0x70, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x28, 0x77, 0xe0, - 0xff, 0xff, 0xff, 0xff, 0xf9, 0x0f, 0x3c, 0x70, 0xff, 0xff, 0xff, 0xff, - 0xfa, 0x08, 0x59, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xfa, 0xf8, 0x58, 0xf0, - 0xff, 0xff, 0xff, 0xff, 0xfb, 0xe8, 0x3b, 0xe0, 0xff, 0xff, 0xff, 0xff, - 0xfc, 0xd8, 0x3a, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xc8, 0x1d, 0xe0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0xb8, 0x1c, 0xf0, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xa7, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0xfe, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x87, 0xe1, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x77, 0xe0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x03, 0x70, 0xfe, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x60, 0xfd, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x05, 0x50, 0xe0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x06, 0x40, 0xdf, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x07, 0x30, 0xc2, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x07, 0x8d, 0x19, 0x70, 0x00, 0x00, 0x00, 0x00, 0x09, 0x10, 0xa4, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x09, 0xad, 0x94, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x0a, 0xf0, 0x86, 0x60, 0x00, 0x00, 0x00, 0x00, 0x0b, 0xe0, 0x85, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x0c, 0xd9, 0xa2, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x0d, 0xc0, 0x67, 0x70, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xb9, 0x84, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x0f, 0xa9, 0x83, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x10, 0x99, 0x66, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x11, 0x89, 0x65, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x12, 0x79, 0x48, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x13, 0x69, 0x47, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x14, 0x59, 0x2a, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x15, 0x49, 0x29, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x16, 0x39, 0x0c, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x17, 0x29, 0x0b, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x18, 0x22, 0x29, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x19, 0x08, 0xed, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x02, 0x0b, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x1a, 0xf2, 0x0a, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x1b, 0xe1, 0xed, 0x60, 0x00, 0x00, 0x00, 0x00, 0x1c, 0xd1, 0xec, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x1d, 0xc1, 0xcf, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x1e, 0xb1, 0xce, 0x70, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xa1, 0xb1, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x20, 0x76, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x21, 0x81, 0x93, 0x60, 0x00, 0x00, 0x00, 0x00, 0x22, 0x55, 0xe2, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x23, 0x6a, 0xaf, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x24, 0x35, 0xc4, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x25, 0x4a, 0x91, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x26, 0x15, 0xa6, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x27, 0x2a, 0x73, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x27, 0xfe, 0xc3, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x29, 0x0a, 0x55, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x29, 0xde, 0xa5, 0x70, 0x00, 0x00, 0x00, 0x00, 0x2a, 0xea, 0x37, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x2b, 0xbe, 0x87, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x2c, 0xd3, 0x54, 0x60, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x9e, 0x69, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x2e, 0xb3, 0x36, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x2f, 0x7e, 0x4b, 0x70, 0x00, 0x00, 0x00, 0x00, 0x30, 0x93, 0x18, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x31, 0x67, 0x67, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x32, 0x72, 0xfa, 0x60, 0x00, 0x00, 0x00, 0x00, 0x33, 0x47, 0x49, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x34, 0x52, 0xdc, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x35, 0x27, 0x2b, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x36, 0x32, 0xbe, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x37, 0x07, 0x0d, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x38, 0x1b, 0xda, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x38, 0xe6, 0xef, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x39, 0xfb, 0xbc, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x3a, 0xc6, 0xd1, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x3b, 0xdb, 0x9e, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x3c, 0xaf, 0xee, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x3d, 0xbb, 0x80, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x8f, 0xd0, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x3f, 0x9b, 0x62, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x40, 0x6f, 0xb2, 0x70, 0x00, 0x00, 0x00, 0x00, 0x41, 0x84, 0x7f, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x42, 0x4f, 0x94, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x43, 0x64, 0x61, 0x60, 0x00, 0x00, 0x00, 0x00, 0x44, 0x2f, 0x76, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x45, 0x44, 0x43, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x45, 0xf3, 0xa8, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x47, 0x2d, 0x5f, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x47, 0xd3, 0x8a, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x49, 0x0d, 0x41, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x49, 0xb3, 0x6c, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x4a, 0xed, 0x23, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x4b, 0x9c, 0x89, 0x70, 0x00, 0x00, 0x00, 0x00, 0x4c, 0xd6, 0x40, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x4d, 0x7c, 0x6b, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x4e, 0xb6, 0x22, 0x60, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x5c, 0x4d, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x50, 0x96, 0x04, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x51, 0x3c, 0x2f, 0x70, 0x00, 0x00, 0x00, 0x00, 0x52, 0x75, 0xe6, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x53, 0x1c, 0x11, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x54, 0x55, 0xc8, 0x60, 0x00, 0x00, 0x00, 0x00, 0x54, 0xfb, 0xf3, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x56, 0x35, 0xaa, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x56, 0xe5, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x58, 0x1e, 0xc6, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x58, 0xc4, 0xf1, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x59, 0xfe, 0xa8, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x5a, 0xa4, 0xd3, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x5b, 0xde, 0x8a, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x5c, 0x84, 0xb5, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x5d, 0xbe, 0x6c, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x5e, 0x64, 0x97, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x5f, 0x9e, 0x4e, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x60, 0x4d, 0xb4, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x61, 0x87, 0x6b, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x62, 0x2d, 0x96, 0x70, 0x00, 0x00, 0x00, 0x00, 0x63, 0x67, 0x4d, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x64, 0x0d, 0x78, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x65, 0x47, 0x2f, 0x60, 0x00, 0x00, 0x00, 0x00, 0x65, 0xed, 0x5a, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x67, 0x27, 0x11, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x67, 0xcd, 0x3c, 0x70, 0x00, 0x00, 0x00, 0x00, 0x69, 0x06, 0xf3, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x69, 0xad, 0x1e, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x6a, 0xe6, 0xd5, 0x60, 0x00, 0x00, 0x00, 0x00, 0x6b, 0x96, 0x3a, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x6c, 0xcf, 0xf1, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x6d, 0x76, 0x1c, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x6e, 0xaf, 0xd3, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x6f, 0x55, 0xfe, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x70, 0x8f, 0xb5, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x71, 0x35, 0xe0, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x72, 0x6f, 0x97, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x73, 0x15, 0xc2, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x74, 0x4f, 0x79, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x74, 0xfe, 0xdf, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x76, 0x38, 0x96, 0x60, 0x00, 0x00, 0x00, 0x00, 0x76, 0xde, 0xc1, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x78, 0x18, 0x78, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x78, 0xbe, 0xa3, 0x70, 0x00, 0x00, 0x00, 0x00, 0x79, 0xf8, 0x5a, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x7a, 0x9e, 0x85, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x7b, 0xd8, 0x3c, 0x60, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x7e, 0x67, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x7d, 0xb8, 0x1e, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x7e, 0x5e, 0x49, 0x70, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x98, 0x00, 0x60, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x03, 0x04, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0xff, 0xff, 0xba, 0x9e, - 0x00, 0x00, 0xff, 0xff, 0xc7, 0xc0, 0x01, 0x04, 0xff, 0xff, 0xb9, 0xb0, - 0x00, 0x08, 0xff, 0xff, 0xc7, 0xc0, 0x01, 0x0c, 0xff, 0xff, 0xc7, 0xc0, - 0x01, 0x10, 0x4c, 0x4d, 0x54, 0x00, 0x45, 0x44, 0x54, 0x00, 0x45, 0x53, - 0x54, 0x00, 0x45, 0x57, 0x54, 0x00, 0x45, 0x50, 0x54, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x45, 0x53, 0x54, - 0x35, 0x45, 0x44, 0x54, 0x2c, 0x4d, 0x33, 0x2e, 0x32, 0x2e, 0x30, 0x2c, - 0x4d, 0x31, 0x31, 0x2e, 0x31, 0x2e, 0x30, 0x0a -}; -unsigned int America_New_York_len = 3536; -unsigned char Australia_Sydney[] = { - 0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8e, - 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0e, 0x80, 0x00, 0x00, 0x00, - 0x9c, 0x4e, 0xc2, 0x80, 0x9c, 0xbc, 0x2f, 0x00, 0xcb, 0x54, 0xb3, 0x00, - 0xcb, 0xc7, 0x65, 0x80, 0xcc, 0xb7, 0x56, 0x80, 0xcd, 0xa7, 0x47, 0x80, - 0xce, 0xa0, 0x73, 0x00, 0xcf, 0x87, 0x29, 0x80, 0x03, 0x70, 0x39, 0x80, - 0x04, 0x0d, 0x1c, 0x00, 0x05, 0x50, 0x1b, 0x80, 0x05, 0xf6, 0x38, 0x80, - 0x07, 0x2f, 0xfd, 0x80, 0x07, 0xd6, 0x1a, 0x80, 0x09, 0x0f, 0xdf, 0x80, - 0x09, 0xb5, 0xfc, 0x80, 0x0a, 0xef, 0xc1, 0x80, 0x0b, 0x9f, 0x19, 0x00, - 0x0c, 0xd8, 0xde, 0x00, 0x0d, 0x7e, 0xfb, 0x00, 0x0e, 0xb8, 0xc0, 0x00, - 0x0f, 0x5e, 0xdd, 0x00, 0x10, 0x98, 0xa2, 0x00, 0x11, 0x3e, 0xbf, 0x00, - 0x12, 0x78, 0x84, 0x00, 0x13, 0x1e, 0xa1, 0x00, 0x14, 0x58, 0x66, 0x00, - 0x14, 0xfe, 0x83, 0x00, 0x16, 0x38, 0x48, 0x00, 0x17, 0x0c, 0x89, 0x80, - 0x18, 0x21, 0x64, 0x80, 0x18, 0xc7, 0x81, 0x80, 0x1a, 0x01, 0x46, 0x80, - 0x1a, 0xa7, 0x63, 0x80, 0x1b, 0xe1, 0x28, 0x80, 0x1c, 0x87, 0x45, 0x80, - 0x1d, 0xc1, 0x0a, 0x80, 0x1e, 0x79, 0x9c, 0x80, 0x1f, 0x97, 0xb2, 0x00, - 0x20, 0x59, 0x7e, 0x80, 0x21, 0x80, 0xce, 0x80, 0x22, 0x42, 0x9b, 0x00, - 0x23, 0x69, 0xeb, 0x00, 0x24, 0x22, 0x7d, 0x00, 0x25, 0x49, 0xcd, 0x00, - 0x25, 0xef, 0xea, 0x00, 0x27, 0x29, 0xaf, 0x00, 0x27, 0xcf, 0xcc, 0x00, - 0x29, 0x09, 0x91, 0x00, 0x29, 0xaf, 0xae, 0x00, 0x2a, 0xe9, 0x73, 0x00, - 0x2b, 0x98, 0xca, 0x80, 0x2c, 0xd2, 0x8f, 0x80, 0x2d, 0x78, 0xac, 0x80, - 0x2e, 0xb2, 0x71, 0x80, 0x2f, 0x58, 0x8e, 0x80, 0x30, 0x92, 0x53, 0x80, - 0x31, 0x5d, 0x5a, 0x80, 0x32, 0x72, 0x35, 0x80, 0x33, 0x3d, 0x3c, 0x80, - 0x34, 0x52, 0x17, 0x80, 0x35, 0x1d, 0x1e, 0x80, 0x36, 0x31, 0xf9, 0x80, - 0x36, 0xfd, 0x00, 0x80, 0x38, 0x1b, 0x16, 0x00, 0x38, 0xdc, 0xe2, 0x80, - 0x39, 0xa7, 0xe9, 0x80, 0x3a, 0xbc, 0xc4, 0x80, 0x3b, 0xda, 0xda, 0x00, - 0x3c, 0xa5, 0xe1, 0x00, 0x3d, 0xba, 0xbc, 0x00, 0x3e, 0x85, 0xc3, 0x00, - 0x3f, 0x9a, 0x9e, 0x00, 0x40, 0x65, 0xa5, 0x00, 0x41, 0x83, 0xba, 0x80, - 0x42, 0x45, 0x87, 0x00, 0x43, 0x63, 0x9c, 0x80, 0x44, 0x2e, 0xa3, 0x80, - 0x45, 0x43, 0x7e, 0x80, 0x46, 0x05, 0x4b, 0x00, 0x47, 0x23, 0x60, 0x80, - 0x47, 0xf7, 0xa2, 0x00, 0x48, 0xe7, 0x93, 0x00, 0x49, 0xd7, 0x84, 0x00, - 0x4a, 0xc7, 0x75, 0x00, 0x4b, 0xb7, 0x66, 0x00, 0x4c, 0xa7, 0x57, 0x00, - 0x4d, 0x97, 0x48, 0x00, 0x4e, 0x87, 0x39, 0x00, 0x4f, 0x77, 0x2a, 0x00, - 0x50, 0x70, 0x55, 0x80, 0x51, 0x60, 0x46, 0x80, 0x52, 0x50, 0x37, 0x80, - 0x53, 0x40, 0x28, 0x80, 0x54, 0x30, 0x19, 0x80, 0x55, 0x20, 0x0a, 0x80, - 0x56, 0x0f, 0xfb, 0x80, 0x56, 0xff, 0xec, 0x80, 0x57, 0xef, 0xdd, 0x80, - 0x58, 0xdf, 0xce, 0x80, 0x59, 0xcf, 0xbf, 0x80, 0x5a, 0xbf, 0xb0, 0x80, - 0x5b, 0xb8, 0xdc, 0x00, 0x5c, 0xa8, 0xcd, 0x00, 0x5d, 0x98, 0xbe, 0x00, - 0x5e, 0x88, 0xaf, 0x00, 0x5f, 0x78, 0xa0, 0x00, 0x60, 0x68, 0x91, 0x00, - 0x61, 0x58, 0x82, 0x00, 0x62, 0x48, 0x73, 0x00, 0x63, 0x38, 0x64, 0x00, - 0x64, 0x28, 0x55, 0x00, 0x65, 0x18, 0x46, 0x00, 0x66, 0x11, 0x71, 0x80, - 0x67, 0x01, 0x62, 0x80, 0x67, 0xf1, 0x53, 0x80, 0x68, 0xe1, 0x44, 0x80, - 0x69, 0xd1, 0x35, 0x80, 0x6a, 0xc1, 0x26, 0x80, 0x6b, 0xb1, 0x17, 0x80, - 0x6c, 0xa1, 0x08, 0x80, 0x6d, 0x90, 0xf9, 0x80, 0x6e, 0x80, 0xea, 0x80, - 0x6f, 0x70, 0xdb, 0x80, 0x70, 0x6a, 0x07, 0x00, 0x71, 0x59, 0xf8, 0x00, - 0x72, 0x49, 0xe9, 0x00, 0x73, 0x39, 0xda, 0x00, 0x74, 0x29, 0xcb, 0x00, - 0x75, 0x19, 0xbc, 0x00, 0x76, 0x09, 0xad, 0x00, 0x76, 0xf9, 0x9e, 0x00, - 0x77, 0xe9, 0x8f, 0x00, 0x78, 0xd9, 0x80, 0x00, 0x79, 0xc9, 0x71, 0x00, - 0x7a, 0xb9, 0x62, 0x00, 0x7b, 0xb2, 0x8d, 0x80, 0x7c, 0xa2, 0x7e, 0x80, - 0x7d, 0x92, 0x6f, 0x80, 0x7e, 0x82, 0x60, 0x80, 0x7f, 0x72, 0x51, 0x80, - 0x03, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x00, 0x00, - 0x8d, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x9a, 0xb0, 0x01, 0x04, 0x00, 0x00, - 0x8c, 0xa0, 0x00, 0x09, 0x00, 0x00, 0x8c, 0xa0, 0x00, 0x09, 0x4c, 0x4d, - 0x54, 0x00, 0x41, 0x45, 0x44, 0x54, 0x00, 0x41, 0x45, 0x53, 0x54, 0x00, - 0x00, 0x01, 0x01, 0x00, 0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0e, - 0xff, 0xff, 0xff, 0xff, 0x73, 0x16, 0x7f, 0x3c, 0xff, 0xff, 0xff, 0xff, - 0x9c, 0x4e, 0xc2, 0x80, 0xff, 0xff, 0xff, 0xff, 0x9c, 0xbc, 0x2f, 0x00, - 0xff, 0xff, 0xff, 0xff, 0xcb, 0x54, 0xb3, 0x00, 0xff, 0xff, 0xff, 0xff, - 0xcb, 0xc7, 0x65, 0x80, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xb7, 0x56, 0x80, - 0xff, 0xff, 0xff, 0xff, 0xcd, 0xa7, 0x47, 0x80, 0xff, 0xff, 0xff, 0xff, - 0xce, 0xa0, 0x73, 0x00, 0xff, 0xff, 0xff, 0xff, 0xcf, 0x87, 0x29, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x03, 0x70, 0x39, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x0d, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x50, 0x1b, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x05, 0xf6, 0x38, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x07, 0x2f, 0xfd, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0xd6, 0x1a, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x09, 0x0f, 0xdf, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x09, 0xb5, 0xfc, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xef, 0xc1, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x0b, 0x9f, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0c, 0xd8, 0xde, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x7e, 0xfb, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x0e, 0xb8, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0f, 0x5e, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x98, 0xa2, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x11, 0x3e, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x12, 0x78, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x1e, 0xa1, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x14, 0x58, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x14, 0xfe, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x38, 0x48, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x17, 0x0c, 0x89, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x18, 0x21, 0x64, 0x80, 0x00, 0x00, 0x00, 0x00, 0x18, 0xc7, 0x81, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x1a, 0x01, 0x46, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x1a, 0xa7, 0x63, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1b, 0xe1, 0x28, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x1c, 0x87, 0x45, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x1d, 0xc1, 0x0a, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x79, 0x9c, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x1f, 0x97, 0xb2, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x20, 0x59, 0x7e, 0x80, 0x00, 0x00, 0x00, 0x00, 0x21, 0x80, 0xce, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x22, 0x42, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x23, 0x69, 0xeb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x22, 0x7d, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x25, 0x49, 0xcd, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x25, 0xef, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x29, 0xaf, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x27, 0xcf, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x29, 0x09, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0xaf, 0xae, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x2a, 0xe9, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x2b, 0x98, 0xca, 0x80, 0x00, 0x00, 0x00, 0x00, 0x2c, 0xd2, 0x8f, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x2d, 0x78, 0xac, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x2e, 0xb2, 0x71, 0x80, 0x00, 0x00, 0x00, 0x00, 0x2f, 0x58, 0x8e, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x30, 0x92, 0x53, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x31, 0x5d, 0x5a, 0x80, 0x00, 0x00, 0x00, 0x00, 0x32, 0x72, 0x35, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x33, 0x3d, 0x3c, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x34, 0x52, 0x17, 0x80, 0x00, 0x00, 0x00, 0x00, 0x35, 0x1d, 0x1e, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x36, 0x31, 0xf9, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x36, 0xfd, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x38, 0x1b, 0x16, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x38, 0xdc, 0xe2, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x39, 0xa7, 0xe9, 0x80, 0x00, 0x00, 0x00, 0x00, 0x3a, 0xbc, 0xc4, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x3b, 0xda, 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3c, 0xa5, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, 0xba, 0xbc, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x3e, 0x85, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3f, 0x9a, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x65, 0xa5, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x41, 0x83, 0xba, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x42, 0x45, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x63, 0x9c, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x44, 0x2e, 0xa3, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x45, 0x43, 0x7e, 0x80, 0x00, 0x00, 0x00, 0x00, 0x46, 0x05, 0x4b, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x47, 0x23, 0x60, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x47, 0xf7, 0xa2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0xe7, 0x93, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x49, 0xd7, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x4a, 0xc7, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4b, 0xb7, 0x66, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x4c, 0xa7, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x4d, 0x97, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x87, 0x39, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x4f, 0x77, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x50, 0x70, 0x55, 0x80, 0x00, 0x00, 0x00, 0x00, 0x51, 0x60, 0x46, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x52, 0x50, 0x37, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x53, 0x40, 0x28, 0x80, 0x00, 0x00, 0x00, 0x00, 0x54, 0x30, 0x19, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x55, 0x20, 0x0a, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x56, 0x0f, 0xfb, 0x80, 0x00, 0x00, 0x00, 0x00, 0x56, 0xff, 0xec, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x57, 0xef, 0xdd, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x58, 0xdf, 0xce, 0x80, 0x00, 0x00, 0x00, 0x00, 0x59, 0xcf, 0xbf, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x5a, 0xbf, 0xb0, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x5b, 0xb8, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0xa8, 0xcd, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x5d, 0x98, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x5e, 0x88, 0xaf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x78, 0xa0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x60, 0x68, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x61, 0x58, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x48, 0x73, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x63, 0x38, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x64, 0x28, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x18, 0x46, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x66, 0x11, 0x71, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x67, 0x01, 0x62, 0x80, 0x00, 0x00, 0x00, 0x00, 0x67, 0xf1, 0x53, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x68, 0xe1, 0x44, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x69, 0xd1, 0x35, 0x80, 0x00, 0x00, 0x00, 0x00, 0x6a, 0xc1, 0x26, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x6b, 0xb1, 0x17, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x6c, 0xa1, 0x08, 0x80, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x90, 0xf9, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x6e, 0x80, 0xea, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x6f, 0x70, 0xdb, 0x80, 0x00, 0x00, 0x00, 0x00, 0x70, 0x6a, 0x07, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x71, 0x59, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x72, 0x49, 0xe9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x39, 0xda, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x74, 0x29, 0xcb, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x75, 0x19, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x09, 0xad, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x76, 0xf9, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x77, 0xe9, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xd9, 0x80, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x79, 0xc9, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x7a, 0xb9, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0xb2, 0x8d, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x7c, 0xa2, 0x7e, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x7d, 0x92, 0x6f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x82, 0x60, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x7f, 0x72, 0x51, 0x80, 0x03, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x00, 0x00, 0x8d, 0xc4, 0x00, 0x00, - 0x00, 0x00, 0x9a, 0xb0, 0x01, 0x04, 0x00, 0x00, 0x8c, 0xa0, 0x00, 0x09, - 0x00, 0x00, 0x8c, 0xa0, 0x00, 0x09, 0x4c, 0x4d, 0x54, 0x00, 0x41, 0x45, - 0x44, 0x54, 0x00, 0x41, 0x45, 0x53, 0x54, 0x00, 0x00, 0x01, 0x01, 0x00, - 0x0a, 0x41, 0x45, 0x53, 0x54, 0x2d, 0x31, 0x30, 0x41, 0x45, 0x44, 0x54, - 0x2c, 0x4d, 0x31, 0x30, 0x2e, 0x31, 0x2e, 0x30, 0x2c, 0x4d, 0x34, 0x2e, - 0x31, 0x2e, 0x30, 0x2f, 0x33, 0x0a -}; -unsigned int Australia_Sydney_len = 2190; diff --git a/absl/types/internal/span.h b/absl/types/internal/span.h index 1920a89e..d653bb2c 100644 --- a/absl/types/internal/span.h +++ b/absl/types/internal/span.h @@ -32,9 +32,6 @@ template <typename T> class Span; namespace span_internal { -// A constexpr min function -constexpr size_t Min(size_t a, size_t b) noexcept { return a < b ? a : b; } - // Wrappers for access to container data pointers. template <typename C> constexpr auto GetDataImpl(C& c, char) noexcept // NOLINT(runtime/references) diff --git a/absl/types/span.h b/absl/types/span.h index cd863a95..d7bdbb1f 100644 --- a/absl/types/span.h +++ b/absl/types/span.h @@ -420,7 +420,7 @@ class Span { // absl::MakeSpan(vec).subspan(5); // throws std::out_of_range constexpr Span subspan(size_type pos = 0, size_type len = npos) const { return (pos <= size()) - ? Span(data() + pos, span_internal::Min(size() - pos, len)) + ? Span(data() + pos, (std::min)(size() - pos, len)) : (base_internal::ThrowStdOutOfRange("pos > size()"), Span()); } diff --git a/ci/linux_clang-latest_libcxx_tsan_bazel.sh b/ci/linux_clang-latest_libcxx_tsan_bazel.sh index 5652ebe1..34d7940e 100755 --- a/ci/linux_clang-latest_libcxx_tsan_bazel.sh +++ b/ci/linux_clang-latest_libcxx_tsan_bazel.sh @@ -87,7 +87,6 @@ for std in ${STD}; do --keep_going \ --linkopt="-fsanitize=thread" \ --show_timestamps \ - --test_env="TSAN_OPTIONS=report_atomic_races=0" \ --test_env="TSAN_SYMBOLIZER_PATH=/opt/llvm/clang/bin/llvm-symbolizer" \ --test_env="TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo" \ --test_output=errors \ diff --git a/ci/linux_clang-latest_libstdcxx_bazel.sh b/ci/linux_clang-latest_libstdcxx_bazel.sh index 720e776d..13d56fc9 100755 --- a/ci/linux_clang-latest_libstdcxx_bazel.sh +++ b/ci/linux_clang-latest_libstdcxx_bazel.sh @@ -77,6 +77,7 @@ for std in ${STD}; do --copt="--gcc-toolchain=/usr/local" \ --copt="-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1" \ --copt="${exceptions_mode}" \ + --copt="-march=haswell" \ --copt=-Werror \ --define="absl=1" \ --distdir="/bazel-distdir" \ diff --git a/ci/linux_gcc-latest_libstdcxx_cmake.sh b/ci/linux_gcc-latest_libstdcxx_cmake.sh index ba65853f..1f721236 100755 --- a/ci/linux_gcc-latest_libstdcxx_cmake.sh +++ b/ci/linux_gcc-latest_libstdcxx_cmake.sh @@ -59,6 +59,7 @@ for std in ${ABSL_CMAKE_CXX_STANDARDS}; do -DCMAKE_CXX_STANDARD=${std} \ -DCMAKE_MODULE_LINKER_FLAGS=\"-Wl,--no-undefined\" && \ make -j$(nproc) && \ + TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo \ ctest -j$(nproc) --output-on-failure" done done diff --git a/ci/linux_gcc_alpine_cmake.sh b/ci/linux_gcc_alpine_cmake.sh index 10540130..b784456f 100755 --- a/ci/linux_gcc_alpine_cmake.sh +++ b/ci/linux_gcc_alpine_cmake.sh @@ -58,6 +58,7 @@ for std in ${ABSL_CMAKE_CXX_STANDARDS}; do -DCMAKE_CXX_STANDARD=${std} \ -DCMAKE_MODULE_LINKER_FLAGS=\"-Wl,--no-undefined\" && \ make -j$(nproc) && \ + TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo \ ctest -j$(nproc) --output-on-failure" done done diff --git a/ci/macos_xcode_cmake.sh b/ci/macos_xcode_cmake.sh index 97988631..690f86b8 100755 --- a/ci/macos_xcode_cmake.sh +++ b/ci/macos_xcode_cmake.sh @@ -51,6 +51,7 @@ for compilation_mode in ${ABSL_CMAKE_BUILD_TYPES}; do -DCMAKE_MODULE_LINKER_FLAGS="-Wl,--no-undefined" \ -DABSL_GOOGLETEST_DOWNLOAD_URL="${ABSL_GOOGLETEST_DOWNLOAD_URL}" time cmake --build . - time ctest -C ${compilation_mode} --output-on-failure + time TZDIR=${ABSEIL_ROOT}/absl/time/internal/cctz/testdata/zoneinfo \ + ctest -C ${compilation_mode} --output-on-failure done done |