diff --git a/.circleci/config.yml b/.circleci/config.yml index 0a6027dc4..ec2e98349 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,44 +1,45 @@ version: 2.1 jobs: - focal_gcc9_docs: + # focal_gcc9_docs: + # docker: + # - image: circleci/buildpack-deps:focal + # steps: + # - checkout + # - run: + # name: Build and test + # command: bash ./tools/circleci_focal_gcc9_docs.sh + # focal_gcc9_coverage: + # docker: + # - image: circleci/buildpack-deps:focal + # steps: + # - checkout + # - run: + # name: Build and test + # command: bash ./tools/circleci_focal_gcc9_coverage.sh + # focal_gcc9_ubsan: + # docker: + # - image: circleci/buildpack-deps:focal + # steps: + # - checkout + # - run: + # name: Build and test + # command: bash ./tools/circleci_focal_gcc9_ubsan.sh + conda_asan: docker: - - image: circleci/buildpack-deps:focal + - image: cimg/base:current + resource_class: large steps: - checkout - run: name: Build and test - command: bash ./tools/circleci_focal_gcc9_docs.sh - focal_gcc9_coverage: - docker: - - image: circleci/buildpack-deps:focal - steps: - - checkout - - run: - name: Build and test - command: bash ./tools/circleci_focal_gcc9_coverage.sh - focal_gcc9_ubsan: - docker: - - image: circleci/buildpack-deps:focal - steps: - - checkout - - run: - name: Build and test - command: bash ./tools/circleci_focal_gcc9_ubsan.sh - focal_gcc9_asan: - docker: - - image: circleci/buildpack-deps:focal - steps: - - checkout - - run: - name: Build and test - command: bash ./tools/circleci_focal_gcc9_asan.sh + command: bash ./tools/conda_asan.sh workflows: version: 2.1 all_builds: jobs: - - focal_gcc9_docs - - focal_gcc9_coverage - - focal_gcc9_ubsan - - focal_gcc9_asan + # - focal_gcc9_docs + # - focal_gcc9_coverage + # - focal_gcc9_ubsan + - conda_asan diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index faaa9554f..000000000 --- a/.travis.yml +++ /dev/null @@ -1,25 +0,0 @@ -language: cpp - -sudo: false - -# Do not build branches of the form "pr/*". By prefixing pull requests coming -# from branches inside the repository with pr/, this avoids building both the -# branch push _and_ the pull request. -branches: - except: /pr\/.*/ - -matrix: - include: - - env: OBAKE_BUILD_TYPE="Debug" - os: osx - osx_image: xcode11.6 - - env: OBAKE_BUILD_TYPE="Release" - os: osx - osx_image: xcode11.6 -script: - - mkdir build - - cd build - - ../tools/travis_osx.sh - -notifications: - email: false diff --git a/CMakeLists.txt b/CMakeLists.txt index 49a665ab7..bce8e828c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ endif() # enable C here, on some configurations (e.g., Ubuntu # + clang) the threading detection/setup seems to break # down. -project(obake VERSION 0.8.0 LANGUAGES CXX C) +project(obake VERSION 0.9.0 LANGUAGES CXX C) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/yacma") @@ -160,11 +160,21 @@ if(${mp++_VERSION} VERSION_LESS ${_OBAKE_MIN_MPPP_VERSION}) message(FATAL_ERROR "The minimum mp++ version required by obake is ${_OBAKE_MIN_MPPP_VERSION}, but version ${mp++_VERSION} was found instead.") endif() -# abseil. -find_package(absl REQUIRED) - # Boost setup. -include(ObakeFindBoost) +# NOTE: need 1.81 for unordered_flat_set. +set(_OBAKE_MIN_BOOST_VERSION "1.81") +# NOTE: we look for Boost in CONFIG mode first, as that has become the official supported way +# of locating Boost in recent Boost/CMake versions. If we fail, we try again in +# MODULE mode as last resort. +find_package(Boost ${_OBAKE_MIN_BOOST_VERSION} QUIET COMPONENTS serialization CONFIG) +if(NOT ${Boost_FOUND}) + message(STATUS "Boost not found in CONFIG mode, retrying in MODULE mode.") + find_package(Boost ${_OBAKE_MIN_BOOST_VERSION} QUIET MODULE COMPONENTS serialization) +endif() +if(NOT ${Boost_FOUND}) + message(FATAL_ERROR "Could not locate Boost in either CONFIG or MODULE mode.") +endif() +message(STATUS "Found Boost version ${Boost_VERSION}.") # libbacktrace. if(OBAKE_WITH_LIBBACKTRACE) @@ -294,7 +304,6 @@ if(YACMA_COMPILER_IS_MSVC) "${CMAKE_CURRENT_LIST_DIR}/include/obake/key/key_tex_stream_insert.hpp" "${CMAKE_CURRENT_LIST_DIR}/include/obake/key/key_trim.hpp" "${CMAKE_CURRENT_LIST_DIR}/include/obake/key/key_trim_identify.hpp" - "${CMAKE_CURRENT_LIST_DIR}/include/obake/detail/abseil.hpp" "${CMAKE_CURRENT_LIST_DIR}/include/obake/detail/atomic_flag_array.hpp" "${CMAKE_CURRENT_LIST_DIR}/include/obake/detail/atomic_lock_guard.hpp" "${CMAKE_CURRENT_LIST_DIR}/include/obake/detail/fcast.hpp" @@ -329,8 +338,8 @@ if(OBAKE_BUILD_STATIC_LIBRARY) else() # Setup of the obake shared library. add_library(obake SHARED "${OBAKE_SRC_FILES}") - set_property(TARGET obake PROPERTY VERSION "8.0") - set_property(TARGET obake PROPERTY SOVERSION 8) + set_property(TARGET obake PROPERTY VERSION "9.0") + set_property(TARGET obake PROPERTY SOVERSION 9) set_target_properties(obake PROPERTIES CXX_VISIBILITY_PRESET hidden) set_target_properties(obake PROPERTIES VISIBILITY_INLINES_HIDDEN TRUE) endif() @@ -347,13 +356,6 @@ target_compile_options(obake PRIVATE target_compile_features(obake PUBLIC cxx_std_20) # Ensure vanilla C++ is being used. set_property(TARGET obake PROPERTY CXX_EXTENSIONS NO) -if(YACMA_COMPILER_IS_MSVC) - # NOTE: older abseil versions have a problem on MSVC - # when using C++20: - # https://github.com/abseil/abseil-cpp/issues/649 - # Hopefully we can eventually remove this. - target_compile_definitions(obake PUBLIC -D_HAS_DEPRECATED_RESULT_OF=1) -endif() if(YACMA_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "10") # GCC < 10 still needs the -fconcepts flag, even if C++20 is in use. message(STATUS "Activating the '-fconcepts' flag for GCC < 10.") @@ -370,8 +372,6 @@ target_include_directories(obake PUBLIC target_link_libraries(obake PUBLIC Threads::Threads mp++::mp++ - absl::flat_hash_map - absl::flat_hash_set Boost::boost Boost::serialization Boost::disable_autolinking @@ -421,7 +421,6 @@ install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindDbgEng.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindTBB.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Findlibbacktrace.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/cmake/ObakeFindBoost.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/ObakeFindDl.cmake" DESTINATION "${OBAKE_INSTALL_LIBDIR}/cmake/obake") install(EXPORT obake_export NAMESPACE obake:: DESTINATION "${OBAKE_INSTALL_LIBDIR}/cmake/obake") diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index f7173f515..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,53 +0,0 @@ -version: '{build}' - -branches: - except: - - /pr\/.+/ - -environment: - matrix: - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - BUILD_TYPE: MSVC17_clang_64 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 Preview - BUILD_TYPE: MSVC19_64 - -install: -- call "C:\\Miniconda37-x64\\Scripts\\activate.bat" -- conda config --set always_yes yes -- conda update -n base conda -- conda config --add channels conda-forge -- conda config --set channel_priority strict -- if [%BUILD_TYPE%]==[MSVC19_64] conda create --name obake cmake boost-cpp mppp tbb tbb-devel abseil-cpp fmt -- if [%BUILD_TYPE%]==[MSVC17_clang_64] conda create --name obake cmake boost-cpp mppp tbb tbb-devel clang clangxx ninja abseil-cpp fmt -- call activate obake -- if [%BUILD_TYPE%]==[MSVC17_clang_64] clang-cl -v -- if [%BUILD_TYPE%]==[MSVC17_clang_64] call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" - -build_script: -- set CONDA_PREFIX_PATH=C:\Miniconda37-x64\envs\obake\Library -- mkdir build -- cd build - -# obake -- if [%BUILD_TYPE%]==[MSVC19_64] cmake .. -G "Visual Studio 16 2019" -A x64 -DOBAKE_BUILD_TESTS=yes -DBoost_NO_BOOST_CMAKE=ON -- if [%BUILD_TYPE%]==[MSVC19_64] cmake --build . --config RelWithDebInfo - -- if [%BUILD_TYPE%]==[MSVC17_clang_64] cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_PREFIX_PATH=%CONDA_PREFIX_PATH% -DCMAKE_INSTALL_PREFIX=%CONDA_PREFIX_PATH% -DOBAKE_BUILD_TESTS=ON -DBoost_NO_BOOST_CMAKE=ON -- if [%BUILD_TYPE%]==[MSVC17_clang_64] cmake --build . -- -v -- if [%BUILD_TYPE%]==[MSVC17_clang_64] cmake --build . --target install - - -test_script: -# NOTE: ensure the PATH variable contains the path to the obake dll, -# otherwise the tests will fail to run. -- if [%BUILD_TYPE%]==[MSVC19_64] set PATH=%PATH%;%CD%\RelWithDebInfo -- if [%BUILD_TYPE%]==[MSVC19_64] ctest -j4 -V -C RelWithDebInfo - -- if [%BUILD_TYPE%]==[MSVC17_clang_64] set PATH=%PATH%;%CD% -- if [%BUILD_TYPE%]==[MSVC17_clang_64] ctest -j4 -V . - -# Enable this to be able to login to the build worker. You can use the -# `remmina` program in Ubuntu, use the login information that the line below -# prints into the log. -# on_finish: -# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) diff --git a/cmake/ObakeFindBoost.cmake b/cmake/ObakeFindBoost.cmake deleted file mode 100644 index adf995e28..000000000 --- a/cmake/ObakeFindBoost.cmake +++ /dev/null @@ -1,35 +0,0 @@ -# NOTE: stacktrace available since 1.65.0. -set(_OBAKE_BOOST_MINIMUM_VERSION 1.65.0) -find_package(Boost ${_OBAKE_BOOST_MINIMUM_VERSION} REQUIRED COMPONENTS serialization) - -message(STATUS "Detected Boost version: ${Boost_VERSION}") -message(STATUS "Boost include dirs: ${Boost_INCLUDE_DIRS}") - -# Might need to recreate targets if they are missing (e.g., older CMake versions). -if(NOT TARGET Boost::boost) - message(STATUS "The 'Boost::boost' target is missing, creating it.") - add_library(Boost::boost INTERFACE IMPORTED) - set_target_properties(Boost::boost PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${Boost_INCLUDE_DIRS}") -endif() - -if(NOT TARGET Boost::serialization) - message(STATUS "The 'Boost::serialization' imported target is missing, creating it.") - add_library(Boost::serialization UNKNOWN IMPORTED) - set_target_properties(Boost::serialization PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${Boost_INCLUDE_DIRS}" - ) - set_target_properties(Boost::serialization PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" - IMPORTED_LOCATION "${Boost_SERIALIZATION_LIBRARY}" - ) -endif() - -if(NOT TARGET Boost::disable_autolinking) - message(STATUS "The 'Boost::disable_autolinking' target is missing, creating it.") - add_library(Boost::disable_autolinking INTERFACE IMPORTED) - if(WIN32) - set_target_properties(Boost::disable_autolinking PROPERTIES INTERFACE_COMPILE_DEFINITIONS "BOOST_ALL_NO_LIB") - endif() -endif() - -unset(_OBAKE_BOOST_MINIMUM_VERSION) diff --git a/cmake/yacma/YACMACompilerLinkerSettings.cmake b/cmake/yacma/YACMACompilerLinkerSettings.cmake index edafe6d4d..e56321846 100644 --- a/cmake/yacma/YACMACompilerLinkerSettings.cmake +++ b/cmake/yacma/YACMACompilerLinkerSettings.cmake @@ -82,6 +82,7 @@ if(NOT _YACMACompilerLinkerSettingsRun) # Configuration bits specific for GCC. if(YACMA_COMPILER_IS_GNUCXX) _YACMA_CHECK_ENABLE_CXX_FLAG(-fdiagnostics-color=auto) + _YACMA_CHECK_ENABLE_CXX_FLAG(-Woverloaded-virtual) # New in GCC 9. _YACMA_CHECK_ENABLE_DEBUG_CXX_FLAG(-Waddress-of-packed-member) endif() @@ -109,7 +110,6 @@ if(NOT _YACMACompilerLinkerSettingsRun) _YACMA_CHECK_ENABLE_DEBUG_CXX_FLAG(-Wc99-designator) _YACMA_CHECK_ENABLE_DEBUG_CXX_FLAG(-Wreorder-init-list) _YACMA_CHECK_ENABLE_DEBUG_CXX_FLAG(-Wsizeof-pointer-div) - _YACMA_CHECK_ENABLE_DEBUG_CXX_FLAG(-Wsizeof-array-div) _YACMA_CHECK_ENABLE_DEBUG_CXX_FLAG(-Wxor-used-as-pow) _YACMA_CHECK_ENABLE_DEBUG_CXX_FLAG(-Wfinal-dtor-non-final-class) # New warnings in clang 11. @@ -122,13 +122,31 @@ if(NOT _YACMACompilerLinkerSettingsRun) # NOTE: this is a new flag in Clang 13 which seems to give # incorrect warnings for UDLs. _YACMA_CHECK_ENABLE_DEBUG_CXX_FLAG(-Wno-reserved-identifier) + _YACMA_CHECK_ENABLE_DEBUG_CXX_FLAG(-Warray-bounds-pointer-arithmetic) + # New warnings in clang 14. + _YACMA_CHECK_ENABLE_DEBUG_CXX_FLAG(-Warray-parameter) + # NOTE: clang 17 enables by default a new compiler flag called "-fassume-unique-vtables": + # + # https://releases.llvm.org/17.0.1/tools/clang/docs/ReleaseNotes.html#c-language-changes + # + # This flag however seems to be buggy: + # + # https://github.com/llvm/llvm-project/issues/71196 + # + # On our part, in several projects we are experiencing Boost.serialization failures when + # (de)serialising derived objects through pointers to bases. Thus, we forcibly disable + # this new flag. + _YACMA_CHECK_ENABLE_CXX_FLAG(-fno-assume-unique-vtables) endif() # Common configuration for GCC, clang and Intel. if(YACMA_COMPILER_IS_CLANGXX OR YACMA_COMPILER_IS_INTELXX OR YACMA_COMPILER_IS_GNUCXX) _YACMA_CHECK_ENABLE_DEBUG_CXX_FLAG(-Wall) _YACMA_CHECK_ENABLE_DEBUG_CXX_FLAG(-Wextra) - _YACMA_CHECK_ENABLE_DEBUG_CXX_FLAG(-Wnon-virtual-dtor) + # NOTE: this flag has been superseded by "-Wdelete-non-virtual-dtor" + # (enabled by "-Wall"). See: + # https://gcc.gnu.org/pipermail/gcc-cvs/2022-November/374730.html + # _YACMA_CHECK_ENABLE_DEBUG_CXX_FLAG(-Wnon-virtual-dtor) # NOTE: this flag is a bit too chatty, let's disable it for the moment. #_YACMA_CHECK_ENABLE_DEBUG_CXX_FLAG(-Wnoexcept) _YACMA_CHECK_ENABLE_DEBUG_CXX_FLAG(-Wlogical-op) diff --git a/doc/install.rst b/doc/install.rst index 5fb3ad90a..33c14a61a 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -11,8 +11,7 @@ Requirements Currently, obake has the following mandatory dependencies: * the `mp++ `_ multiprecision library (at least version 0.27), -* the `Boost `_ C++ libraries (at least version 1.65), -* the `Abseil `_ C++ libraries, +* the `Boost `_ C++ libraries (at least version 1.81), * the `Intel TBB `__ library, * the `{fmt} `__ library. diff --git a/include/obake/detail/abseil.hpp b/include/obake/detail/abseil.hpp deleted file mode 100644 index 09d3fa55d..000000000 --- a/include/obake/detail/abseil.hpp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2019-2020 Francesco Biscani (bluescarni@gmail.com) -// -// This file is part of the obake library. -// -// This Source Code Form is subject to the terms of the Mozilla -// Public License v. 2.0. If a copy of the MPL was not distributed -// with this file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#ifndef OBAKE_DETAIL_ABSEIL_HPP -#define OBAKE_DETAIL_ABSEIL_HPP - -// NOTE: on MSVC, some abseil headers give -// warnings in debug mode, which make -// obake's debug builds fail. Suppress -// those warnings. -#if defined(_MSC_VER) && !defined(__clang__) - -#pragma warning(push) -#pragma warning(disable : 4245) -#pragma warning(disable : 4127) -#pragma warning(disable : 4996) -#pragma warning(disable : 4324) - -#endif - -#include -#include -#include -#include - -#if defined(_MSC_VER) && !defined(__clang__) - -#pragma warning(pop) - -#endif - -#endif diff --git a/include/obake/detail/fmt_compat.hpp b/include/obake/detail/fmt_compat.hpp index 5e5282aa7..d33ac0e14 100644 --- a/include/obake/detail/fmt_compat.hpp +++ b/include/obake/detail/fmt_compat.hpp @@ -32,7 +32,7 @@ struct ostream_formatter { } template - auto format(const T &x, FormatContext &ctx) + auto format(const T &x, FormatContext &ctx) const { std::ostringstream oss; oss << x; diff --git a/include/obake/polynomials/d_packed_monomial.hpp b/include/obake/polynomials/d_packed_monomial.hpp index b3d391600..b0e0c693d 100644 --- a/include/obake/polynomials/d_packed_monomial.hpp +++ b/include/obake/polynomials/d_packed_monomial.hpp @@ -433,7 +433,7 @@ inline void key_tex_stream_insert(::std::ostream &os, const d_packed_monomial #include #include +#include #include #include @@ -41,7 +42,6 @@ #include #include -#include #include #include #include @@ -120,111 +120,111 @@ concept Polynomial = is_polynomial_v; namespace detail { - // Enabler for make_polynomials(): - // - need at least 1 Arg, - // - T must be a polynomial, - // - std::string can be constructed from each input Args, - // - poly key can be constructed from a const int * range, - // - poly cf can be constructed from an integral literal. - template - using make_polynomials_supported - = ::std::conjunction<::std::integral_constant 0u)>, is_polynomial, - ::std::is_constructible<::std::string, const Args &>..., - ::std::is_constructible, const int *, const int *>, - ::std::is_constructible, int>>; - - template - using make_polynomials_enabler = ::std::enable_if_t::value, int>; - - // Overload with a symbol set. - template = 0> - inline auto make_polynomials_impl(const symbol_set &ss, const Args &...names) - { - // Create a temp vector of ints which we will use to - // init the keys. - ::std::vector tmp(::obake::safe_cast<::std::vector::size_type>(ss.size())); - - // Create the fw version of the symbol set. - const detail::ss_fw ss_fw(ss); - - auto make_poly = [&ss_fw, &ss, &tmp](const auto &n) { - using str_t = remove_cvref_t; - - // Fetch a const reference to either the original - // std::string object n, or to a string temporary - // created from it. - const auto &s = [&n]() -> decltype(auto) { - if constexpr (::std::is_same_v) { - return n; - } else { - return ::std::string(n); - } - }(); +// Enabler for make_polynomials(): +// - need at least 1 Arg, +// - T must be a polynomial, +// - std::string can be constructed from each input Args, +// - poly key can be constructed from a const int * range, +// - poly cf can be constructed from an integral literal. +template +using make_polynomials_supported + = ::std::conjunction<::std::integral_constant 0u)>, is_polynomial, + ::std::is_constructible<::std::string, const Args &>..., + ::std::is_constructible, const int *, const int *>, + ::std::is_constructible, int>>; + +template +using make_polynomials_enabler = ::std::enable_if_t::value, int>; + +// Overload with a symbol set. +template = 0> +inline auto make_polynomials_impl(const symbol_set &ss, const Args &...names) +{ + // Create a temp vector of ints which we will use to + // init the keys. + ::std::vector tmp(::obake::safe_cast<::std::vector::size_type>(ss.size())); - // Init the retval, assign the symbol set. - T retval; - retval.set_symbol_set_fw(ss_fw); + // Create the fw version of the symbol set. + const detail::ss_fw ss_fw(ss); - // Try to locate s within the symbol set. - const auto it = ss.find(s); - if (obake_unlikely(it == ss.end() || *it != s)) { - obake_throw(::std::invalid_argument, "Cannot create a polynomial with symbol set " - + detail::to_string(ss) + " from the generator '" + s - + "': the generator is not in the symbol set"); + auto make_poly = [&ss_fw, &ss, &tmp](const auto &n) { + using str_t = remove_cvref_t; + + // Fetch a const reference to either the original + // std::string object n, or to a string temporary + // created from it. + const auto &s = [&n]() -> decltype(auto) { + if constexpr (::std::is_same_v) { + return n; + } else { + return ::std::string(n); } + }(); - // Set to 1 the exponent of the corresponding generator. - tmp[static_cast<::std::vector::size_type>(ss.index_of(it))] = 1; + // Init the retval, assign the symbol set. + T retval; + retval.set_symbol_set_fw(ss_fw); - // Create and add a new term. - // NOTE: at least for some monomial types (e.g., packed monomial), - // we will be computing the iterator difference when constructing from - // a range. Make sure we can safely represent the size of tmp via - // iterator difference. - ::obake::detail::it_diff_check(tmp.size()); - retval.add_term(series_key_t(::std::as_const(tmp).data(), ::std::as_const(tmp).data() + tmp.size()), 1); + // Try to locate s within the symbol set. + const auto it = ss.find(s); + if (obake_unlikely(it == ss.end() || *it != s)) { + obake_throw(::std::invalid_argument, "Cannot create a polynomial with symbol set " + detail::to_string(ss) + + " from the generator '" + s + + "': the generator is not in the symbol set"); + } - // Set back to zero the exponent that was previously set to 1. - tmp[static_cast<::std::vector::size_type>(ss.index_of(it))] = 0; + // Set to 1 the exponent of the corresponding generator. + tmp[static_cast<::std::vector::size_type>(ss.index_of(it))] = 1; - return retval; - }; + // Create and add a new term. + // NOTE: at least for some monomial types (e.g., packed monomial), + // we will be computing the iterator difference when constructing from + // a range. Make sure we can safely represent the size of tmp via + // iterator difference. + ::obake::detail::it_diff_check(tmp.size()); + retval.add_term(series_key_t(::std::as_const(tmp).data(), ::std::as_const(tmp).data() + tmp.size()), 1); - return detail::make_array(make_poly(names)...); - } + // Set back to zero the exponent that was previously set to 1. + tmp[static_cast<::std::vector::size_type>(ss.index_of(it))] = 0; - // Overload without a symbol set. - template = 0> - inline auto make_polynomials_impl(const Args &...names) - { - auto make_poly = [](const auto &n) { - using str_t = remove_cvref_t; + return retval; + }; - // Init the retval, assign a symbol set containing only n. - T retval; - if constexpr (::std::is_same_v) { - retval.set_symbol_set(symbol_set{n}); - } else { - retval.set_symbol_set(symbol_set{::std::string(n)}); - } + return detail::make_array(make_poly(names)...); +} + +// Overload without a symbol set. +template = 0> +inline auto make_polynomials_impl(const Args &...names) +{ + auto make_poly = [](const auto &n) { + using str_t = remove_cvref_t; + + // Init the retval, assign a symbol set containing only n. + T retval; + if constexpr (::std::is_same_v) { + retval.set_symbol_set(symbol_set{n}); + } else { + retval.set_symbol_set(symbol_set{::std::string(n)}); + } - static constexpr int arr[] = {1}; + static constexpr int arr[] = {1}; - // Create and add a new term. - retval.add_term(series_key_t(&arr[0], &arr[0] + 1), 1); + // Create and add a new term. + retval.add_term(series_key_t(&arr[0], &arr[0] + 1), 1); - return retval; - }; + return retval; + }; - return detail::make_array(make_poly(names)...); - } + return detail::make_array(make_poly(names)...); +} } // namespace detail // Polynomial creation functor. template -inline constexpr auto make_polynomials - = [](const auto &...args) OBAKE_SS_FORWARD_LAMBDA(detail::make_polynomials_impl(args...)); +inline constexpr auto make_polynomials = + [](const auto &...args) OBAKE_SS_FORWARD_LAMBDA(detail::make_polynomials_impl(args...)); namespace polynomials { @@ -666,8 +666,8 @@ inline auto poly_mul_estimate_product_size(const ::std::vector &x, const ::s // Init the hash set we will be using for the trials. // NOTE: use exactly the same hasher/comparer as in series.hpp, so that // we are sure we are being consistent wrt type requirements, etc. - using local_set = ::absl::flat_hash_set; + using local_set = ::boost::unordered_flat_set; local_set ls; ls.reserve(::obake::safe_cast(vidx1.size())); @@ -1361,15 +1361,8 @@ inline void poly_mul_impl_mt_hm(Ret &retval, const T &x, const U &y, const Args // coefficient. This is wasteful, it would be better to directly // construct the coefficient product only if the insertion actually // takes place (using a lazy multiplication approach). - // Unfortunately, abseil's hash map is not exception safe, - // and if the lazy multiplication throws, the table will be left - // in an inconsistent state. See: - // https://github.com/abseil/abseil-cpp/issues/388 - // See the commit - // 3e334f560d5844f5f2d8face05aa58be21649ff8 + // See the commit 3e334f560d5844f5f2d8face05aa58be21649ff8 // for an implementation of the lazy multiplication approach. - // If they fix the exception safety issue, we can re-enable the - // lazy approach. // NOTE: the coefficient concept demands default constructibility, // thus we can always emplace without arguments for the coefficient. const auto res = table.try_emplace(tmp_key); @@ -1401,11 +1394,10 @@ inline void poly_mul_impl_mt_hm(Ret &retval, const T &x, const U &y, const Args // in the current table. const auto it_f = table.end(); for (auto it = table.begin(); it != it_f;) { - // NOTE: abseil's flat_hash_map returns void on erase(), - // thus we need to increase 'it' before possibly erasing. - // erase() does not cause rehash and thus will not invalidate - // any other iterator apart from the one being erased. if (obake_unlikely(::obake::is_zero(::std::as_const(it->second)))) { + // NOTE: increase 'it' before erasing. + // erase() does not cause rehash and thus will not invalidate + // any other iterator apart from the one being erased. table.erase(it++); } else { ++it; @@ -1504,15 +1496,8 @@ inline void poly_mul_impl_mt_hm(Ret &retval, const T &x, const U &y, const Args // coefficient. This is wasteful, it would be better to directly // construct the coefficient product only if the insertion actually // takes place (using a lazy multiplication approach). - // Unfortunately, abseil's hash map is not exception safe, - // and if the lazy multiplication throws, the table will be left - // in an inconsistent state. See: - // https://github.com/abseil/abseil-cpp/issues/388 - // See the commit - // 3e334f560d5844f5f2d8face05aa58be21649ff8 + // See the commit 3e334f560d5844f5f2d8face05aa58be21649ff8 // for an implementation of the lazy multiplication approach. - // If they fix the exception safety issue, we can re-enable the - // lazy approach. // NOTE: the coefficient concept demands default constructibility, // thus we can always emplace without arguments for the coefficient. const auto res = table.try_emplace(tmp_key); @@ -1544,11 +1529,10 @@ inline void poly_mul_impl_mt_hm(Ret &retval, const T &x, const U &y, const Args // in the current table. const auto it_f = table.end(); for (auto it = table.begin(); it != it_f;) { - // NOTE: abseil's flat_hash_map returns void on erase(), - // thus we need to increase 'it' before possibly erasing. - // erase() does not cause rehash and thus will not invalidate - // any other iterator apart from the one being erased. if (obake_unlikely(::obake::is_zero(::std::as_const(it->second)))) { + // NOTE: increase 'it' before erasing. + // erase() does not cause rehash and thus will not invalidate + // any other iterator apart from the one being erased. table.erase(it++); } else { ++it; @@ -1867,11 +1851,10 @@ inline void poly_mul_impl_simple(Ret &retval, const T &x, const U &y, const Args // in the return value. const auto it_f = tab.end(); for (auto it = tab.begin(); it != it_f;) { - // NOTE: abseil's flat_hash_map returns void on erase(), - // thus we need to increase 'it' before possibly erasing. - // erase() does not cause rehash and thus will not invalidate - // any other iterator apart from the one being erased. if (obake_unlikely(::obake::is_zero(::std::as_const(it->second)))) { + // NOTE: increase 'it' before erasing. + // erase() does not cause rehash and thus will not invalidate + // any other iterator apart from the one being erased. tab.erase(it++); } else { ++it; @@ -2041,8 +2024,9 @@ inline auto poly_mul_impl_switch(const T &x, const U &y, const Args &...args) } // namespace detail template -requires(detail::poly_mul_algo, polynomial> != 0) inline detail::poly_mul_ret_t< - polynomial, polynomial> series_mul(const polynomial &x, const polynomial &y) + requires(detail::poly_mul_algo, polynomial> != 0) +inline detail::poly_mul_ret_t, polynomial> series_mul(const polynomial &x, + const polynomial &y) { return detail::poly_mul_impl_switch(x, y); } @@ -2127,18 +2111,17 @@ inline constexpr auto poly_mul_truncated_p_degree_algo // NOTE: do we need the type traits/concepts as well? // NOTE: should these be function objects? template -requires(detail::poly_mul_truncated_degree_algo, polynomial, V> != 0) inline detail:: - poly_mul_ret_t, polynomial> truncated_mul(const polynomial &x, - const polynomial &y, const V &max_degree) + requires(detail::poly_mul_truncated_degree_algo, polynomial, V> != 0) +inline detail::poly_mul_ret_t, polynomial> +truncated_mul(const polynomial &x, const polynomial &y, const V &max_degree) { return detail::poly_mul_impl_switch(x, y, max_degree); } template -requires(detail::poly_mul_truncated_p_degree_algo, polynomial, V> != 0) inline detail:: - poly_mul_ret_t, polynomial> truncated_mul(const polynomial &x, - const polynomial &y, const V &max_degree, - const symbol_set &s) + requires(detail::poly_mul_truncated_p_degree_algo, polynomial, V> != 0) +inline detail::poly_mul_ret_t, polynomial> +truncated_mul(const polynomial &x, const polynomial &y, const V &max_degree, const symbol_set &s) { return detail::poly_mul_impl_switch(x, y, max_degree, s); } @@ -2193,9 +2176,8 @@ inline auto pow_poly_impl(T &&x, U &&y) // Exponentiation. template -requires Polynomial> &&( - customisation::internal::series_default_pow_algo< - T &&, U &&> != 0) inline customisation::internal::series_default_pow_ret_t pow(T &&x, U &&y) + requires Polynomial> && (customisation::internal::series_default_pow_algo != 0) +inline customisation::internal::series_default_pow_ret_t pow(T &&x, U &&y) { return detail::pow_poly_impl(::std::forward(x), ::std::forward(y)); } @@ -2327,8 +2309,8 @@ inline auto poly_subs_impl(T &&x_, const symbol_map &sm) // Polynomial subs. template -requires Polynomial> &&( - detail::poly_subs_algo != 0) inline detail::poly_subs_ret_t subs(T &&x, const symbol_map &sm) + requires Polynomial> && (detail::poly_subs_algo != 0) +inline detail::poly_subs_ret_t subs(T &&x, const symbol_map &sm) { return detail::poly_subs_impl(::std::forward(x), sm); } @@ -2662,8 +2644,8 @@ inline auto poly_diff_impl(T &&x_, const ::std::string &s) } // namespace detail template -requires Polynomial> &&(detail::poly_diff_algo != 0) inline detail::poly_diff_ret_t diff( - T &&x, const ::std::string &s) + requires Polynomial> && (detail::poly_diff_algo != 0) +inline detail::poly_diff_ret_t diff(T &&x, const ::std::string &s) { return detail::poly_diff_impl(::std::forward(x), s); } @@ -2876,9 +2858,8 @@ inline auto poly_integrate_impl(T &&x_, const ::std::string &s) } // namespace detail template -requires Polynomial> &&( - detail::poly_integrate_algo != 0) inline detail::poly_integrate_ret_t integrate(T &&x, - const ::std::string &s) + requires Polynomial> && (detail::poly_integrate_algo != 0) +inline detail::poly_integrate_ret_t integrate(T &&x, const ::std::string &s) { return detail::poly_integrate_impl(::std::forward(x), s); } diff --git a/include/obake/series.hpp b/include/obake/series.hpp index f03737c90..6c3a890d8 100644 --- a/include/obake/series.hpp +++ b/include/obake/series.hpp @@ -37,6 +37,7 @@ #include #include #include +#include #include @@ -50,7 +51,6 @@ #include #include #include -#include #include #include #include @@ -483,39 +483,12 @@ concept SeriesConvertible = is_series_convertible_v; namespace detail { -// A small hashing wrapper for keys. It accomplishes two tasks: -// - force the evaluation of a key through const reference, -// so that, in the Key requirements, we can request hashability -// through const lvalue ref; -// - provide additional mixing. +// Wrapper to force key hashing via const lvalue refs. struct series_key_hasher { - // NOTE: here we are duplicating a bit of internal - // abseil code for integral hash mixing, with the intent - // of avoiding the per-process seeding that abseil does. - // See here for the original code: - // https://github.com/abseil/abseil-cpp/blob/37dd2562ec830d547a1524bb306be313ac3f2556/absl/hash/internal/hash.h#L754 - // If/when abseil starts supporting DLL builds, we can - // remove this code and switch back to using abseil's - // own hash machinery for mixing. - static constexpr ::std::uint64_t kMul - = sizeof(::std::size_t) == 4u ? ::std::uint64_t{0xcc9e2d51ull} : ::std::uint64_t{0x9ddfea08eb382d69ull}; - ABSL_ATTRIBUTE_ALWAYS_INLINE static ::std::uint64_t Mix(::std::uint64_t state, ::std::uint64_t v) - { - using MultType = ::std::conditional_t; - // We do the addition in 64-bit space to make sure the 128-bit - // multiplication is fast. If we were to do it as MultType the compiler has - // to assume that the high word is non-zero and needs to perform 2 - // multiplications instead of one. - MultType m = state + v; - m *= kMul; - return static_cast<::std::uint64_t>(m ^ (m >> (sizeof(m) * 8 / 2))); - } template ::std::size_t operator()(const K &k) const noexcept(noexcept(::obake::hash(k))) { - // NOTE: mix with a compile-time seed. - return static_cast<::std::size_t>( - series_key_hasher::Mix(15124392053943080205ull, static_cast<::std::uint64_t>(::obake::hash(k)))); + return ::obake::hash(k); } }; @@ -661,7 +634,8 @@ class series public: // Define the table type, and the type holding the set of tables (i.e., the segmented table). - using table_type = ::absl::flat_hash_map; + using table_type + = ::boost::unordered::unordered_flat_map; using s_table_type = ::boost::container::small_vector; // Shortcut for the segmented table size type. @@ -1518,11 +1492,6 @@ class series // - the original table had no incompatible keys, // - the original table did not overflow the max size, // - the original table had only unique keys. - // Note that deserialisation of a series that was saved - // in a previous program execution will result in a term - // order different from the original one due to abseil's salting, - // but the number of terms in a specific table will be the same - // because the first level hash is not salted. // NOTE: this is essentially identical to a straight emplace_back() // on the table, just with some added assertions. detail::series_add_term_table) - (sizeof(k) + sizeof(c))); } - // Add the space occupied by the unused slots. - assert(tab.capacity() >= tab.size()); - ret += (tab.capacity() - tab.size()) * sizeof(series_term_t); - + // NOTE: here we are not accounting for the empty slots in the table. + // Hopefully we do not end up very far from the true memory utilisation. return ret; } template @@ -3346,8 +3313,7 @@ inline series_default_div_ret_t series_default_div_impl(T &&x, U &&y c /= ::std::as_const(y); if (obake_unlikely(::obake::is_zero(::std::as_const(c)))) { - // NOTE: abseil's flat_hash_map returns void on erase(), - // thus we need to increase 'it' before possibly erasing. + // NOTE: increase 'it' before erasing. // erase() does not cause rehash and thus will not invalidate // any other iterator apart from the one being erased. t.erase(it++); @@ -4288,13 +4254,12 @@ inline void filter_impl(series &s, const F &f) const auto it_f = table.end(); for (auto it = table.begin(); it != it_f;) { - // NOTE: abseil's flat_hash_map returns void on erase(), - // thus we need to increase 'it' before possibly erasing. - // erase() does not cause rehash and thus will not invalidate - // any other iterator apart from the one being erased. if (f(::std::as_const(*it))) { ++it; } else { + // NOTE: increase 'it' before erasing. + // erase() does not cause rehash and thus will not invalidate + // any other iterator apart from the one being erased. table.erase(it++); } } diff --git a/obake-config.cmake.in b/obake-config.cmake.in index d34a80b85..9e65db1b4 100644 --- a/obake-config.cmake.in +++ b/obake-config.cmake.in @@ -8,8 +8,6 @@ find_package(mp++ REQUIRED) if(${mp++_VERSION} VERSION_LESS @_OBAKE_MIN_MPPP_VERSION@) message(FATAL_ERROR "The minimum mp++ version required by obake is @_OBAKE_MIN_MPPP_VERSION@, but version ${mp++_VERSION} was found instead.") endif() -find_package(absl REQUIRED) -include(ObakeFindBoost) # TBB. Try to find it first in config mode (supported # since version 2021 after the oneTBB rename), and, if this # fails, fall back to our own FindTBB.cmake. This is of course diff --git a/src/polynomials/packed_monomial.cpp b/src/polynomials/packed_monomial.cpp index 50effc594..19ef71827 100644 --- a/src/polynomials/packed_monomial.cpp +++ b/src/polynomials/packed_monomial.cpp @@ -169,7 +169,7 @@ void packed_monomial_tex_stream_insert(::std::ostream &os, const packed_monomial // Raise to power, if the exponent is not one. if (!tmp_mp.is_one()) { - *cur_oss << fmt::format("^{{{}}}", tmp_mp); + *cur_oss << fmt::format(fmt::runtime("^{{{}}}"), tmp_mp); } } } diff --git a/test/power_series_01.cpp b/test/power_series_01.cpp index 6bd536ba5..2c4ce4376 100644 --- a/test/power_series_01.cpp +++ b/test/power_series_01.cpp @@ -1255,5 +1255,5 @@ TEST_CASE("fmt") std::ostringstream oss; oss << s; - REQUIRE(oss.str() == fmt::format("{}", s)); + REQUIRE(oss.str() == fmt::format(fmt::runtime("{}"), s)); } diff --git a/test/series_04.cpp b/test/series_04.cpp index 9a14fe1be..29966ecbf 100644 --- a/test/series_04.cpp +++ b/test/series_04.cpp @@ -254,5 +254,5 @@ TEST_CASE("series fmt test") std::ostringstream oss; oss << s; - REQUIRE(oss.str() == fmt::format("{}", s)); + REQUIRE(oss.str() == fmt::format(fmt::runtime("{}"), s)); } diff --git a/tools/circleci_focal_gcc9_asan.sh b/tools/circleci_focal_gcc9_asan.sh deleted file mode 100644 index 02bbea1bc..000000000 --- a/tools/circleci_focal_gcc9_asan.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash - -# Echo each command -set -x - -# Exit on error. -set -e - -# Core deps. -sudo apt-get install build-essential - -wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; -export deps_dir=$HOME/local -bash miniconda.sh -b -p $HOME/miniconda -export PATH="$HOME/miniconda/bin:$PATH" -conda config --add channels conda-forge -conda config --set channel_priority strict -conda_pkgs="cmake mppp boost-cpp tbb tbb-devel abseil-cpp backtrace fmt" -conda create -q -p $deps_dir -y $conda_pkgs -source activate $deps_dir - -# Create the build dir and cd into it. -mkdir build -cd build - -cmake ../ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local -DOBAKE_BUILD_TESTS=YES -DCMAKE_CXX_FLAGS="-march=native -fsanitize=address" -DOBAKE_WITH_LIBBACKTRACE=YES -DBoost_NO_BOOST_CMAKE=ON -make -j2 VERBOSE=1 -make install -# Run the tests. Enable the custom suppression file for ASAN -# in order to suppress spurious warnings from TBB code. -LSAN_OPTIONS=suppressions=/home/circleci/project/tools/lsan.supp ctest -j4 -V - -set +e -set +x diff --git a/tools/conda_asan.sh b/tools/conda_asan.sh new file mode 100644 index 000000000..d3381b512 --- /dev/null +++ b/tools/conda_asan.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +# Echo each command +set -x + +# Exit on error. +set -e + +wget https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-x86_64.sh -O miniconda.sh +export deps_dir=$HOME/local +export PATH="$HOME/miniconda/bin:$PATH" +bash miniconda.sh -b -p $HOME/miniconda +conda create -y -p $deps_dir cmake c-compiler cxx-compiler fmt backtrace mppp \ + libboost-devel tbb-devel ninja +source activate $deps_dir + +# Create the build dir and cd into it. +mkdir build +cd build + +# Clear the compilation flags set up by conda. +unset CXXFLAGS +unset CFLAGS + +cmake ../ -G Ninja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_PREFIX_PATH=$deps_dir \ + -DOBAKE_BUILD_TESTS=YES \ + -DCMAKE_CXX_FLAGS="-fsanitize=address" \ + -DOBAKE_WITH_LIBBACKTRACE=YES \ + -DCMAKE_CXX_FLAGS_DEBUG="-g -Og" + +ninja -v -j4 + +ctest -VV -j4 + +set +e +set +x diff --git a/tools/circleci_focal_gcc9_coverage.sh b/tools/conda_coverage.sh similarity index 92% rename from tools/circleci_focal_gcc9_coverage.sh rename to tools/conda_coverage.sh index b26a45bd4..2b78759f8 100644 --- a/tools/circleci_focal_gcc9_coverage.sh +++ b/tools/conda_coverage.sh @@ -15,7 +15,7 @@ bash miniconda.sh -b -p $HOME/miniconda export PATH="$HOME/miniconda/bin:$PATH" conda config --add channels conda-forge conda config --set channel_priority strict -conda_pkgs="cmake mppp boost-cpp tbb tbb-devel abseil-cpp backtrace fmt" +conda_pkgs="cmake mppp boost-cpp tbb tbb-devel backtrace fmt" conda create -q -p $deps_dir -y $conda_pkgs source activate $deps_dir diff --git a/tools/circleci_focal_gcc9_docs.sh b/tools/conda_docs.sh similarity index 95% rename from tools/circleci_focal_gcc9_docs.sh rename to tools/conda_docs.sh index a77b8e2d0..67e624d19 100644 --- a/tools/circleci_focal_gcc9_docs.sh +++ b/tools/conda_docs.sh @@ -15,7 +15,7 @@ bash miniconda.sh -b -p $HOME/miniconda export PATH="$HOME/miniconda/bin:$PATH" conda config --add channels conda-forge conda config --set channel_priority strict -conda_pkgs="cmake mppp boost-cpp tbb tbb-devel abseil-cpp fmt backtrace sphinx pip" +conda_pkgs="cmake mppp boost-cpp tbb tbb-devel fmt backtrace sphinx pip" conda create -q -p $deps_dir -y $conda_pkgs source activate $deps_dir diff --git a/tools/circleci_focal_gcc9_ubsan.sh b/tools/conda_ubsan.sh similarity index 91% rename from tools/circleci_focal_gcc9_ubsan.sh rename to tools/conda_ubsan.sh index 417957dd7..329b56a13 100644 --- a/tools/circleci_focal_gcc9_ubsan.sh +++ b/tools/conda_ubsan.sh @@ -15,7 +15,7 @@ bash miniconda.sh -b -p $HOME/miniconda export PATH="$HOME/miniconda/bin:$PATH" conda config --add channels conda-forge conda config --set channel_priority strict -conda_pkgs="cmake mppp boost-cpp tbb tbb-devel abseil-cpp fmt backtrace" +conda_pkgs="cmake mppp boost-cpp tbb tbb-devel fmt backtrace" conda create -q -p $deps_dir -y $conda_pkgs source activate $deps_dir diff --git a/tools/lsan.supp b/tools/lsan.supp deleted file mode 100644 index d74950c1f..000000000 --- a/tools/lsan.supp +++ /dev/null @@ -1 +0,0 @@ -leak:*libtbb*.so* diff --git a/tools/travis_osx.sh b/tools/travis_osx.sh deleted file mode 100755 index 150c87a36..000000000 --- a/tools/travis_osx.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash - -# Echo each command -set -x - -# Exit on error. -set -e - -wget https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh -O miniconda.sh; -export deps_dir=$HOME/local -bash miniconda.sh -b -p $HOME/miniconda -export PATH="$HOME/miniconda/bin:$PATH" -conda config --add channels conda-forge -conda config --set channel_priority strict -conda_pkgs="cmake mppp boost-cpp tbb tbb-devel clang clangdev abseil-cpp fmt" -conda create -q -p $deps_dir -y $conda_pkgs -source activate $deps_dir - -export CXX=clang++ -export CC=clang - -cmake ../ -DCMAKE_INSTALL_PREFIX=$deps_dir -DCMAKE_PREFIX_PATH=$deps_dir -DCMAKE_BUILD_TYPE=${OBAKE_BUILD_TYPE} -DOBAKE_BUILD_TESTS=yes -DBoost_NO_BOOST_CMAKE=ON -make -j2 VERBOSE=1 -make install -ctest -j4 -V - -set +e -set +x